Orderadmin/assets/css/offer-refund-metabox.css000064400000002724147600245720014473 0ustar00#wpfnl-offer-refund-metabox .inside { padding: 0 0 12px; margin: 0; } .wpfnl-order-refund-wrapper table tr th, .wpfnl-order-refund-wrapper table tr td { padding: 13px; text-align: left; } .wpfnl-order-refund-wrapper table tr th { background: #f8f8f8; } .wpfnl-order-refund-wrapper table tr th.wpfnl-item { text-align: left; } .wpfnl-order-refund-wrapper table tr .thumb { width: 60px; } .wpfnl-order-refund-wrapper table tr td img { max-width: 100%; height: auto; } .wpfnl-order-refund-wrapper table tr td.wpfnl-item { min-width: 200px; } .wpfnl-order-refund-wrapper table tr td .wc-order-item-sku { color: #888; } .wpfnl-order-refund-wrapper table tr td.wpfnl-offer-type { width: 120px; } .wpfnl-order-refund-wrapper table tr td.wpfnl-cost { width: 90px; } .wpfnl-order-refund-wrapper table tr td.wpfnl-quantity { width: 70px; } .wpfnl-order-refund-wrapper table tr td.wpfnl-total { width: 70px; } .wpfnl-order-refund-wrapper table tr td.wpfnl-action .button { width: 80px; text-align: center; } .wpfnl-order-refund-wrapper .no-refund-offer, .wpfnl-order-refund-wrapper .refund-note { background: #FFF9EB; padding: 12px 14px; border-left: 3px solid #FFB802; margin: 0 13px; color: #473D3D; } .wpfnl-refund-notice{ text-align: left; background: #FFF9EB; padding: 12px 14px; border-left: 3px solid #FFB802; margin: 0 13px; color: #473D3D; line-height: 1.5 !important; }admin/assets/css/wpfnl-pro-admin.css000064400000024255147600245720013471 0ustar00.wpfnl-license-page { background: transparent; } .wpfnl-license-wrapper { display: flex; flex-flow: row wrap; margin-top: 24px; padding-right: 24px; gap: 30px; } .wpfnl-license-wrapper .wpfnl-license-filed { background: #FFFFFF; box-shadow: 0px 1px 2px rgba(190, 190, 215, 0.2); border-radius: 10px; width: calc(100% - 390px); padding: 15px 20px 20px; } .wpfnl-license-wrapper .wpfnl-license-filed .field-header .product-title, .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .product-title { width: 390px; position: relative; } .wpfnl-license-wrapper .wpfnl-license-filed .field-header .input-field, .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .input-field { width: calc(100% - 460px); max-width: 420px; padding: 0 40px; } .wpfnl-license-wrapper .wpfnl-license-filed .field-header .btn-area, .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .btn-area { width: 170px; } .wpfnl-license-wrapper .wpfnl-license-filed .field-header { display: flex; flex-flow: row wrap; align-items: center; justify-content: space-between; margin-bottom: 30px; position: relative; padding-bottom: 15px; } .wpfnl-license-wrapper .wpfnl-license-filed .field-header:before { content: ""; position: absolute; left: -20px; bottom: 0; width: calc(100% + 40px); height: 1px; background: #E5E8F3; } .wpfnl-license-wrapper .wpfnl-license-filed .field-header .single-field { text-transform: uppercase; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area { display: flex; flex-flow: row wrap; justify-content: space-between; align-items: center; width: 100%; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .product-title { display: flex; align-items: center; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .product-title .icon-info { width: calc(100% - 50px); padding-left: 15px; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .product-title h3 { font-weight: 500; font-size: 20px; line-height: 23px; color: #363B4E; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .license-status { font-size: 14px; line-height: 16px; color: #7A8B9A; margin-top: 5px; display: block; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .addon-icon { width: 50px; height: 50px; border-radius: 100%; display: flex; align-items: center; justify-content: center; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .input-field input { height: 46px; padding: 6px 15px 5px; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .btn-area { position: relative; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .btn-area .btn-default { padding: 0 22px; height: 46px; min-width: 162px; border-radius: 10px; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .btn-area .lnc-notice { color: green; font-size: 15px; text-transform: capitalize; margin-left: 8px; position: absolute; bottom: -24px; left: -4px; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license { padding: 15px; background: #F7F7FA; margin-top: 30px; border-radius: 0 8px 8px 0; border-left: 2px solid #6E42D3; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .single-addons { margin-bottom: 15px; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .single-addons:last-child { margin-bottom: 0; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area { padding: 12px 15px; background-color: #fff; box-shadow: 0px 1px 2px rgba(190, 190, 215, 0.2); border-radius: 6px; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .product-title { width: 362px; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .addon-icon { background: #F7F7FA; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area h3 { font-weight: 500; font-size: 14px; line-height: 1.1; color: #363B4E; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .input-field input { height: 38px; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .btn-area { width: 145px; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .btn-area .btn-default { padding: 0 18px; font-size: 13px; height: 38px; min-width: auto; margin-left: auto; display: block; border-radius: 6px; width: 100%; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .not-get-addons .input-field-area .product-title { width: calc(100% - 200px); } .wpfnl-license-wrapper .promo-text-area { width: 360px; background: #FFF; border-radius: 10px; box-shadow: 0px 1px 2px rgba(190, 190, 215, 0.2); } .wpfnl-license-wrapper .promo-text-area .single-area { position: relative; padding: 25px 25px 25px 90px; background: #FFFFFF; box-shadow: 0px 1px 2px rgba(190, 190, 215, 0.2); border-radius: 10px; margin-bottom: 30px; } .wpfnl-license-wrapper .promo-text-area .single-area:last-child { margin-bottom: 0; } .wpfnl-license-wrapper .promo-text-area .single-area .icon { background-color: #F7F7FA; width: 50px; height: 50px; display: flex; flex-flow: column; align-items: center; justify-content: center; border-radius: 100%; position: absolute; top: 30px; left: 26px; } .wpfnl-license-wrapper .promo-text-area .single-area h4 { font-size: 18px; margin-bottom: 8px; color: #363B4E; font-weight: 500; } .wpfnl-license-wrapper .promo-text-area .single-area P { font-size: 14px; } .wpfnl-license-wrapper .promo-text-area .manage-license-area { padding: 20px; display: flex; flex-flow: column; align-items: center; justify-content: center; overflow: hidden; height: 100%; } .wpfnl-license-wrapper .promo-text-area .manage-license-area .logo { margin-bottom: 12px; } .wpfnl-license-wrapper .promo-text-area .manage-license-area p { text-align: center; margin-bottom: 20px; } .wpfnl-license-wrapper .promo-text-area .manage-license-area .btn-default { height: 40px; line-height: 40px; position: relative; z-index: 1; } .cl-doc-row { display: flex; flex-flow: row wrap; margin-top: 30px; padding-right: 24px; gap: 30px; } .cl-doc-row .single-col { background: #fff; padding: 30px; width: calc(25% - 23px); border-radius: 8px; display: flex; flex-flow: column; align-items: flex-start; position: relative; } .cl-doc-row .single-col:last-child { margin-right: 0; } .cl-doc-row .single-col .icon { background-color: #f0f1f3; width: 40px; height: 40px; display: flex; flex-flow: column; align-items: center; justify-content: center; border-radius: 100%; position: absolute; top: 24px; left: 30px; } .cl-doc-row .single-col h4 { font-size: 20px; margin-bottom: 22px; font-weight: 500; padding-left: 53px; color: #3c434a; } .cl-doc-row .single-col p { font-size: 14px; margin-bottom: 20px; } .cl-doc-row .single-col .btn-default { margin-top: auto; } .cl-doc-row .single-col.manage-license .icon svg { width: 27px; } @media only screen and (min-width: 1400px) { .cl-doc-row .single-col { width: calc(33.3333333333% - 20px); } .cl-doc-row .single-col.manage-license { display: none; } } @media only screen and (max-width: 1620px) { .wpfnl-license-wrapper .wpfnl-license-filed .field-header .product-title, .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .product-title { width: 280px; } .wpfnl-license-wrapper .wpfnl-license-filed .field-header .input-field, .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .input-field { padding: 0 20px; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .product-title { width: 260px; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .btn-area { width: 150px; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .input-field { width: calc(100% - 410px); } } @media only screen and (max-width: 1399px) { .wpfnl-license-wrapper .wpfnl-license-filed { width: 100%; margin-right: 0; } .wpfnl-license-wrapper .promo-text-area { width: 100%; display: flex; flex-flow: row wrap; justify-content: center; display: none; } .wpfnl-license-wrapper .promo-text-area .single-area { width: calc(33.3333333333% - 20px); margin-left: 30px; margin-bottom: 0; } .wpfnl-license-wrapper .promo-text-area .single-area:first-child { margin-left: 0; } .cl-doc-row .single-col { padding: 25px 20px 20px; } .cl-doc-row .single-col .icon { top: 17px; left: 20px; } } @media only screen and (max-width: 1199px) { .wpfnl-license-wrapper { gap: 20px; } .wpfnl-license-wrapper .promo-text-area .manage-license-area:before { left: -47px; } .wpfnl-license-wrapper .promo-text-area .manage-license-area { padding: 20px; display: flex; flex-flow: column; justify-content: center; } .wpfnl-license-wrapper .promo-text-area .single-area { width: calc(33.3333333333% - 14px); margin-left: 20px; padding: 20px; } .wpfnl-license-wrapper .promo-text-area .single-area .icon { position: relative; top: inherit; left: inherit; margin-bottom: 12px; } .cl-doc-row { margin-top: 20px; gap: 20px; } .cl-doc-row .single-col { width: calc(50% - 10px); } .cl-doc-row .single-col .icon { top: 18px; left: 20px; } } @media only screen and (max-width: 991px) { .wpfnl-license-wrapper .wpfnl-license-filed .field-header { display: none; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .product-title { width: 100%; margin-bottom: 10px; } .wpfnl-license-wrapper .wpfnl-license-filed .input-field-area .input-field { width: calc(100% - 180px); max-width: initial; padding: 0; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .product-title { width: 100%; } .wpfnl-license-wrapper .wpfnl-license-filed .addons-license .input-field-area .input-field { width: calc(100% - 160px); } .cl-doc-row .single-col:nth-child(2) { margin-right: 0; } } /*# sourceMappingURL=wpfnl-pro-admin.css.map */ admin/assets/js/offer-refund-metabox.js000064400000005722147600245720014144 0ustar00(function( $ ) { 'use strict'; /** * All of the code for your admin-facing JavaScript source * should reside in this file. * * Note: It has been assumed you will write jQuery code here, so the * $ function reference has been prepared for usage within the scope * of this function. * * This enables you to define handlers, for when the DOM is ready: * * $(function() { * * }); * * When the window is loaded: * * $( window ).load(function() { * * }); * * ...and/or other possibilities. * * Ideally, it is not considered best practise to attach more than a * single DOM-ready or window-load handler for a particular page. * Although scripts in the WordPress core, Plugins and Themes may be * practising this, we should strive to set a better example in our own work. */ $( '.button.wpfnl-offer-refund' ).on( 'click', function ( e ) { e.preventDefault(); let refund_reason = prompt( 'Enter Refund reason', 'Refund WPFunnels Offer' ); if ( '' === refund_reason ) { return alert( 'Please enter valid refund reason', false ); } else if ( null === refund_reason ) { return false; } $('#wpfnl-offer-refund-metabox').block({ message: null, overlayCSS: { background: '#fff', opacity: 0.6 } }); var button = $(this), order_id = button.attr( 'data-order-id' ), step_id = button.attr( 'data-step-id' ), item_id = button.attr( 'data-item-id' ), product_id = button.attr( 'data-product-id' ), qty = button.attr( 'data-item-quantity' ), amount = button.attr( 'data-item-amount' ), txn_id = button.attr( 'data-txn-id' ); var payload = { order_id: order_id, step_id: step_id, product_id: product_id, item_id: item_id, quantity: qty, amount: amount, api_refund: true, transaction_id: txn_id, wpfnl_refund: true, } wpAjaxHelperRequest("wpfnl-refund-offer", payload) .success(function(response) { alert(response.msg); $('#wpfnl-offer-refund-metabox').unblock(); window.location.reload(); }) .error(function(response) { $('#wpfnl-offer-refund-metabox').unblock(); console.log(response) }); }); $( '.refund-items' ).on( 'click', function ( e ) { $('.wpfnl-refund-notice').remove(); $('.wc-order-refund-items').prepend('

Here, you can only refund the cost for the main product and order bump products.To refund products sold in Upsell & downsell, go to the WPFunnels Offers section.

') }); })( jQuery );admin/assets/js/wpfnl-pro-admin.js000064400000002450147600245720013132 0ustar00(function( $ ) { 'use strict'; /** * All the code for your admin-facing JavaScript source * should reside in this file. * * Note: It has been assumed you will write jQuery code here, so the * $ function reference has been prepared for usage within the scope * of this function. * * This enables you to define handlers, for when the DOM is ready: * * $(function() { * * }); * * When the window is loaded: * * $( window ).load(function() { * * }); * * ...and/or other possibilities. * * Ideally, it is not considered the best practise to attach more than a * single DOM-ready or window-load handler for a particular page. * Although scripts in the WordPress core, Plugins and Themes may be * practising this, we should strive to set a better example in our own work. */ $( document ).on( 'click', 'div[data-notice_id="wpfnl-import-notice"] button.notice-dismiss', dismissFunnelImportNotice ); /** * Create an ajax post request to hide funnel import notice. * * @since 1.9.7 */ function dismissFunnelImportNotice() { const payload = { action: 'wpfnl_hide_import_funnel_notice', security: WPFunnelProVars.admin_nonce }; $.ajax({ type: 'POST', dataType: 'json', url: WPFunnelProVars.ajaxurl, data: payload }); } })( jQuery ); admin/classes/class-wpfnl-order-meta.php000064400000003600147600245720014276 0ustar00get_id(); $is_wpfnl_order = $order->get_meta('_wpfunnels_offer') === 'yes'; if( $is_wpfnl_order ) { $parent_order_id = $order->get_meta('_wpfunnels_offer_parent_id'); if($parent_order_id) { $parent_order_html = '

'.__('Parent Order: ').'

'; $parent_order_html .= sprintf('#%2s', get_edit_post_link($parent_order_id), $parent_order_id); echo $parent_order_html; } } else { $child_order_html = ''; $child_orders = $order->get_meta( '_wpfunnels_offer_child_orders' ); if( $child_orders ) { $child_order_html = '

'.__('Offer orders: ').''; foreach ($child_orders as $order_id => $child_order) { $type = $child_order['type']; $child_order_html .= sprintf('#%2s - %3s', get_edit_post_link($order_id), $order_id, $type); } $child_order_html .= '

'; } echo $child_order_html; } } }admin/classes/class-wpfnl-pro-notices.php000064400000007331147600245720014506 0ustar00 'inactive_license', 'inactive_supported_payment_gateway' => 'inactive_supported_payment_gateway', ); public function __construct() { foreach ( self::$core_notices as $notice ) { add_action( 'admin_notices', array( __CLASS__, self::$core_notices[ $notice ] ) ); } } /** * Inactive license notice */ public static function inactive_license() { $disabled_notice_page = [ 'edit_funnel', 'wp_funnels', ]; if( !isset($_GET['page']) || (isset($_GET['page']) && !in_array( $_GET['page'],$disabled_notice_page )) ) { $active_status = get_option('wpfunnels_pro_license_status'); if( 'active' === $active_status ) { return; } $message = sprintf( __( 'Your WPFunnels Pro License is not activate. Please, go to the WPFunnels > License menu and activate the license to use all the pro features of WPFunnels Pro . Activate now.', 'wpfnl-pro' ), esc_url(admin_url('admin.php?page=wpf-license'))); $output = '
'; $output .= '

' . wp_kses_post( $message ) . '

'; $output .= '
'; echo $output; } } /** * Inactive supported payment gateway notice */ public static function inactive_supported_payment_gateway() { $is_woocommerce = Wpfnl_functions::is_wc_active(); if( $is_woocommerce ){ global $post, $typenow, $current_screen; $offer_settings = Wpfnl_functions::get_offer_settings(); if( is_admin() && 'wp_funnels' === $current_screen->parent_base && $offer_settings['show_supported_payment_gateway'] == 'on' ){ $available_payment_methods = WC()->payment_gateways->get_available_payment_gateways(); $supported_gateways = Payment_Gateways_Factory::getInstance()->get_supported_payment_gateways(); $is_supported_activated = false; foreach($available_payment_methods as $key => $method ){ if( isset($supported_gateways[$key])){ $is_supported_activated = true; } } if( !$is_supported_activated ){ $message = wp_sprintf( '%s - %l. %s %s', __( 'You have selected the option to show only supported payment gateways in the funnel checkouts, but you do not have any supported payment gateways active. Please enable Stripe, Paypal, Mollie, Authorize.net, or Cash On Delivery.', 'wpfnl-pro' ), '', 'Please check the supported gateways', 'https://getwpfunnels.com/docs/connect-payment-gateways/', 'here.' ); $output = '
'; $output .= '

' . wp_kses_post( $message ) . '

'; $output .= '
'; echo $output; }else{ return; } }else{ return; } } return; } }admin/classes/class-wpfnl-refund.php000064400000017674147600245720013542 0ustar00handle('wpfnl-refund-offer') ->with_callback([ $this, 'refund_offer' ]) ->with_validation($this->get_validation_data()); } public function get_validation_data() { return [ 'logged_in' => true, 'user_can' => 'wpf_manage_funnels', ]; } /** * register offer refund meta box */ public function order_refund_meta_box() { global $post; if ( 'shop_order' === get_current_screen()->id ) { $this->order_id = $post->ID; $order = wc_get_order($this->order_id); if( false !== is_a( $order, 'WC_Order' ) ){ $funnel_id = $order->get_meta('_wpfunnels_funnel_id'); if( $funnel_id ) { if($this->should_show_refund_metabox($order)) { add_action( 'admin_enqueue_scripts', array( $this, 'add_offer_refund_scripts' ) ); add_meta_box( 'wpfnl-offer-refund-metabox', __( 'WPFunnels Refund Offer', 'wpfnl-pro' ), array( $this, 'refund_offer_metabox_callback' ), 'shop_order', 'normal' ); add_filter( 'get_user_option_meta-box-order_shop_order', array( $this, 'move_refund_offer_metabox' ),10,1 ); } } } } } /** * move offer refund meta box under shop order * * @param $metabox * @return mixed */ public function move_refund_offer_metabox( $metabox ) { $metabox['normal'] = join( ',', array( 'woocommerce-order-items', 'wpfnl-offer-refund-metabox', ) ); return $metabox; } /** * add scripts for refund meta box */ public function add_offer_refund_scripts() { if ( ! is_admin() ) { return; } wp_enqueue_style( 'wpfnl-pro-offer-refund', WPFNL_PRO_URL . 'admin/assets/css/offer-refund-metabox.css', array(), WPFNL_PRO_VERSION ); wp_enqueue_script( 'wpfnl-pro-offer-refund', WPFNL_PRO_URL . 'admin/assets/js/offer-refund-metabox.js', array( 'jquery' ), WPFNL_PRO_VERSION, true ); } /** * should show refund meta * * @param \WC_Order $order * @return bool */ public function should_show_refund_metabox( \WC_Order $order ) { $is_offer_exists = $this->is_offer_exits_in_order($order); if($is_offer_exists) { $order_gateway = $order->get_payment_method(); $payment_gateway_obj = Wpfnl_Pro::instance()->payment_gateways->build_gateway($order_gateway); $is_refund_supported = $payment_gateway_obj->refund_support; if($is_refund_supported) { return true; } } return false; } /** * check if offer exits in order * * @param \WC_Order $order * @return bool */ private function is_offer_exits_in_order( \WC_Order $order ) { $is_offer = false; $line_items = $order->get_items(); foreach ($line_items as $item_id => $item) { $is_upsell_offer = wc_get_order_item_meta( $item_id, '_wpfunnels_upsell', true ); $is_downsell_offer = wc_get_order_item_meta( $item_id, '_wpfunnels_downsell', true ); $txn_id = wc_get_order_item_meta( $item_id, '_wpfunnels_offer_txn_id', true ); if ( 'yes' == $is_upsell_offer || 'yes' == $is_downsell_offer && ! empty( $txn_id ) ) { $is_offer = true; break; } } return $is_offer; } public function refund_offer_metabox_callback() { include WPFNL_PRO_DIR . 'admin/partials/refund-metabox.php'; } /** * refund offer ajax action * * @param $payload * @return array * @throws \Exception */ public function refund_offer($payload) { $order_id = isset( $payload['order_id'] ) ? intval( $payload['order_id'] ) : 0; $step_id = isset( $payload['step_id'] ) ? intval( $payload['step_id'] ) : 0; $product_id = isset( $payload['product_id'] ) ? intval( $payload['product_id'] ) : 0; $quantity = isset( $payload['quantity'] ) ? $payload['quantity'] : 0; $amount = isset( $payload['amount'] ) ? $payload['amount'] : 0; $api_refund = isset( $payload['api_refund'] ) ? $payload['api_refund'] : false; $transaction_id = isset( $payload['transaction_id'] ) ? $payload['transaction_id'] : ''; $item_id = isset( $payload['item_id'] ) ? $payload['item_id'] : ''; $wpfnl_refund = isset( $payload['wpfnl_refund'] ) ? $payload['wpfnl_refund'] : ''; $refund_reason = isset( $payload['refund_reason'] ) ? $payload['refund_reason'] : ''; $data = array( 'offer_id' => $product_id, 'transaction_id' => $transaction_id, 'amount' => $amount, 'step_id' => $step_id, 'reason' => '', ); $result = array( 'success' => false, 'msg' => __( 'Refund unsuccessful', 'wpfnl-pro' ), ); if( $order_id ) { $order = wc_get_order($order_id); $order_gateway = $order->get_payment_method(); $payment_gateway_obj = Wpfnl_Pro::instance()->payment_gateways->build_gateway($order_gateway); if( $payment_gateway_obj->refund_support ) { $refunded_txn_id = $payment_gateway_obj->process_refund_offer( $order, $data ); } if ( false !== $refunded_txn_id ) { $offer_item = WC_Order_Factory::get_order_item( $item_id ); $line_items[ $item_id ] = array( 'qty' => max( $offer_item->get_quantity(), 0 ), 'refund_total' => wc_format_decimal( $offer_item->get_total() ), 'refund_tax' => array(), ); $order_taxes = $order->get_taxes(); $tax_data = $offer_item->get_taxes(); $tax_item_total = []; foreach ( $order_taxes as $tax_item ) { $tax_item_id = $tax_item->get_rate_id(); $tax_item_total[ $tax_item_id ] = isset( $tax_data['total'][ $tax_item_id ] ) ? $tax_data['total'][ $tax_item_id ] : 0; } $line_items[ $item_id ]['refund_tax'] = array_filter( array_map( 'wc_format_decimal', $tax_item_total ) ); $refund = wc_create_refund( array( 'amount' => $amount, 'reason' => $refund_reason, 'order_id' => $order_id, 'refund_payment' => false, 'line_items' => $line_items, 'restock_items' => true, ) ); if ( is_wp_error( $refund ) ) { $result['success'] = true; $result['msg'] = __( 'Refund Unsuccessful', 'wpfnl-pro' ); } else { wc_update_order_item_meta( $item_id, '_wpfunnels_offer_refunded', 'yes' ); $result['success'] = true; $result['msg'] = __( 'Refund Successful', 'wpfnl-pro' ); } } } return $result; } }admin/modules/ab-testing/class-wpfnl-ab-testing-hook-backup.php000064400000042466147600245720020562 0ustar00$step){ $node_data = isset($step['data']) ? $step['data'] : []; if( isset($node_data['step_id']) ){ $ab_testings_settings = Backup_Ab_Testing::get_formatted_settings( $node_data['step_id'] ); $class = $step['class']; $triggers = Wpfnl_functions::get_mint_triggers(); $actions = Wpfnl_functions::get_mint_actions(); if( in_array( $node_data['step_type'], $triggers ) ){ $class = $class.' mint-trigger-action mint-trigger'; $step_data[$key]['class'] = $class; } if( in_array( $node_data['step_type'], $actions ) ){ $class = $class.' mint-trigger-action mint-action'; $step_data[$key]['class'] = $class; } if( isset($ab_testings_settings['data']['start_settings']['variations']) && count($ab_testings_settings['data']['start_settings']['variations']) > 1 ) { $needle = 'has-ab-variation'; if (strpos($class, $needle) === false) { $step_data[$key]['class'] = $class.' has-ab-variation'; } }else{ $needle = 'has-ab-variation'; if (strpos($class, $needle) !== false) { $step_data[$key]['class'] = trim(str_replace('has-ab-variation','',$class)); } } } } } return $step_data; } /** * set ab testing winner * @param Int $funnel_id * @param Int $step_id * * @return void * @since 1.7.1 */ public function update_ab_testing_winner( $funnel_id, $step_id ){ $parent_step_id = Backup_Ab_Testing::get_parent_step_id( $step_id ) ? Backup_Ab_Testing::get_parent_step_id( $step_id ) : $step_id; $is_enable = Backup_Ab_Testing::maybe_ab_testing( $parent_step_id ); if( $is_enable ){ $all_conditions = Backup_Ab_Testing::get_all_conditions( $parent_step_id ); $is_matched = Backup_Ab_Testing::match_condition( $all_conditions, $funnel_id, $parent_step_id ); $is_winner = Backup_Ab_Testing::get_winner( $parent_step_id ); if( !$is_winner && $is_matched ){ Backup_Ab_Testing::set_winner( $parent_step_id, $step_id ); // stop AB testing after winner matched $data = get_post_meta( $parent_step_id, '_wpfnl_ab_testing_start_settings' , true ); $data['start_date'] = date( 'Y-m-d H:i:s' ); $data['is_started'] = ''; Backup_Ab_Testing::update_start_settings( $parent_step_id, $data ); } } } /** * Update funnel view link when ab testings is activated * @param String $link * @param Int $step_id * @param Int $funnel_id */ public function modify_funnel_view_link( $link, $step_id, $funnel_id ){ if (class_exists('\WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing')) { $instance = new \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing; $function_exist = is_callable(array($instance, 'maybe_ab_testing')); if( $function_exist ){ $is_enabled = \WPFunnelsPro\AbTesting\Backup_Ab_Testing::maybe_ab_testing( $step_id ); $variations = \WPFunnelsPro\AbTesting\Backup_Ab_Testing::get_all_variations( $step_id ); if( 'yes' == $is_enabled && count($variations) > 1 ){ $link = get_the_permalink($funnel_id).'?wpfnl-step-id='.$step_id; } } } return $link; } /** * Update the meta information for a step in a funnel. * * This function updates the meta information, such as the post title, post name (slug), * step title, and step view link for a specific step in a funnel. It utilizes the * Wpfnl_Ab_Testing class from the WPFunnelsPro\AbTesting namespace to handle the update. * * @param int $step_id The ID of the step to update. * @param int $funnel_id The ID of the funnel to which the step belongs. * @param array $settings An array of settings containing the title and slug for the step. * @return void * @since 1.7.1 */ public function update_step_meta( $step_id, $funnel_id, $settings ){ $instance = new \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing; $function_exist = is_callable(array($instance, 'get_formatted_settings')); if( $function_exist ){ $parent_step_id = \WPFunnelsPro\AbTesting\Backup_Ab_Testing::get_parent_step_id( $step_id ); $parent_step_id = $parent_step_id ? $parent_step_id : $step_id; $ab_settings = \WPFunnelsPro\AbTesting\Backup_Ab_Testing::get_formatted_settings( $parent_step_id ); if( is_array($ab_settings) && isset($ab_settings['data']['start_settings']['variations']) ){ update_post_meta( $step_id, '_wpf_step_title', $settings['title'] ); update_post_meta( $step_id, '_wpf_step_slug', $settings['slug'] ); wp_update_post([ "ID" => $step_id, "post_title" => wp_strip_all_tags( $settings['title'] ), "post_name" => sanitize_title($settings['slug']), ]); foreach( $ab_settings['data']['start_settings']['variations'] as $key=>$variation ){ if( $variation['id'] == $step_id ){ $ab_settings['data']['start_settings']['variations'][$key]['step_title'] = htmlspecialchars_decode(get_the_title($step_id)); $ab_settings['data']['start_settings']['variations'][$key]['step_view_link'] = rtrim( get_the_permalink($step_id), '/' ); } } $ab_settings = $ab_settings['data']['start_settings']; \WPFunnelsPro\AbTesting\Backup_Ab_Testing::update_start_settings( $parent_step_id, $ab_settings ); } } } /** * Update the meta information for a step in a funnel on funnel name change. * * * @param int $step_id The ID of the step to update. * @param int $funnel_id The ID of the funnel to which the step belongs. * @param array $settings An array of settings containing the title and slug for the step. * @return void * @since 1.7.1 */ public function update_step_meta_on_funnel_name_change( $step_id, $funnel_id, $settings ){ $instance = new \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing; $function_exist = is_callable(array($instance, 'get_formatted_settings')); if( $function_exist ){ $parent_step_id = \WPFunnelsPro\AbTesting\Backup_Ab_Testing::get_parent_step_id( $step_id ); $parent_step_id = $parent_step_id ? $parent_step_id : $step_id; $ab_settings = \WPFunnelsPro\AbTesting\Backup_Ab_Testing::get_formatted_settings( $parent_step_id ); if( is_array($ab_settings) && isset($ab_settings['data']['start_settings']['variations']) ){ wp_update_post([ "ID" => $parent_step_id, "post_title" => wp_strip_all_tags( $settings['title'] ), "post_name" => sanitize_title($settings['slug']), ]); foreach( $ab_settings['data']['start_settings']['variations'] as $key=>$variation ){ if( $variation['id'] == $step_id ){ $ab_settings['data']['start_settings']['variations'][$key]['step_title'] = htmlspecialchars_decode(get_the_title($parent_step_id)); $ab_settings['data']['start_settings']['variations'][$key]['step_view_link'] = rtrim( get_the_permalink($parent_step_id), '/' ); } } $ab_settings = $ab_settings['data']['start_settings']; \WPFunnelsPro\AbTesting\Backup_Ab_Testing::update_start_settings( $parent_step_id, $ab_settings ); } } } /** * update funnel data response * * @param Array $response * * @since 1.7.2 * @return Array $response */ public function update_funnel_data_response( $response ){ $ab_data = []; if( is_array($response) && !empty($response['funnel_data']['drawflow']['Home']['data']) ){ $steps_order = $response['funnel_data']['drawflow']['Home']['data']; if( is_array($steps_order) ){ foreach( $steps_order as $key=>$step ){ if( isset( $step['data']['step_id'] ) ){ $step_id = $step['data']['step_id']; $default_settings = Backup_Ab_Testing::get_default_start_setting( $step_id ); //check A/B testing is enable or not $result = Backup_Ab_Testing::maybe_ab_testing( $step_id ); $default_settings['is_ab_enabled'] = $result ? $result : ''; // get start settings $result = Backup_Ab_Testing::get_start_settings( $step_id ); $default_settings['start_settings'] = $result ? $result : $default_settings['start_settings']; if( isset($default_settings['start_settings']['variations']) && is_array($default_settings['start_settings']['variations']) ){ foreach( $default_settings['start_settings']['variations'] as $key => $variation ){ if( isset($variation['id'], $variation['step_type']) && ( 'checkout' == $variation['step_type'] || 'upsell' == $variation['step_type'] || 'downsell' == $variation['step_type'])){ $step_products = get_post_meta( $variation['id'], '_wpfnl_'.$variation['step_type'].'_products', true ); if( is_array($step_products) && count($step_products) ){ $default_settings['start_settings']['variations'][$key]['is_product'] = 'yes'; } if( 'checkout' == $variation['step_type'] ){ $ob_products = get_post_meta( $variation['id'], 'order-bump-settings', true ); if( is_array($ob_products) && count($ob_products) ){ $default_settings['start_settings']['variations'][$key]['is_ob'] = 'yes'; } } } if( count($default_settings['start_settings']['variations']) > 1 ){ $funnel_id = get_post_meta( $variation['id'], '_funnel_id', true ); $stats = Backup_Ab_Testing::get_stats_of_a_step( $funnel_id, $variation['id'] ); if( isset($stats['total_visit'], $stats['conversion'] ) ){ $default_settings['start_settings']['variations'][$key]['visit'] = $stats['total_visit']; $default_settings['start_settings']['variations'][$key]['conversion'] = $stats['conversion']; } }else{ foreach( $response['steps_order'] as $step_order ){ if( isset( $step_order['id'], $variation['id'] ) && $variation['id'] == $step_order['id'] ){ $default_settings['start_settings']['variations'][$key]['visit'] = $step_order['visit']; $default_settings['start_settings']['variations'][$key]['conversion'] = $step_order['conversion']; } } } } } $ab_data['step_'.$step_id]['data'] = $default_settings; $ab_data['step_'.$step_id]['step_type'] = get_post_meta( $step_id, '_step_type', true ); } } } } $response['ab_data'] = $ab_data; return $response; } /** * Update funnel link * * @param Array $response * * @return Array $response * @since 1.7.4 */ public function update_funnel_link( $response ){ if (class_exists('\WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing')) { $instance = new \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing; $function_exist = is_callable(array($instance, 'maybe_ab_testing')); if( $function_exist ){ if( isset($response['funnel_id'],$response['step_id']) ){ $is_enabled = \WPFunnelsPro\AbTesting\Backup_Ab_Testing::maybe_ab_testing( $response['step_id'] ); $variations = \WPFunnelsPro\AbTesting\Backup_Ab_Testing::get_all_variations( $response['step_id'] ); if( 'yes' == $is_enabled && count($variations) > 1 ){ $utm_settings = Wpfnl_functions::get_funnel_utm_settings( $response['funnel_id'] ); if($utm_settings['utm_enable'] == 'on') { unset($utm_settings['utm_enable']); $view_link = add_query_arg($utm_settings,get_the_permalink($response['funnel_id'])); $view_link = strtolower($view_link); $response['link'] = $view_link; } $args = [ 'wpfnl-step-id' => $response['step_id'], ]; $response['link'] = add_query_arg($args,$response['link']); } } } } return $response; } } admin/modules/ab-testing/class-wpfnl-ab-testing-hook.php000064400000034661147600245720017315 0ustar00 $parent_step_id, "post_title" => wp_strip_all_tags( $settings['title'] ), "post_name" => sanitize_title($settings['slug']), ]); foreach( $ab_settings['data']['start_settings']['variations'] as $key=>$variation ){ if( $variation['id'] == $step_id ){ $ab_settings['data']['start_settings']['variations'][$key]['step_title'] = htmlspecialchars_decode(get_the_title($parent_step_id)); $ab_settings['data']['start_settings']['variations'][$key]['step_view_link'] = rtrim( get_the_permalink($parent_step_id), '/' ); } } $ab_settings = $ab_settings['data']['start_settings']; \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing::update_start_settings( $parent_step_id, $ab_settings ); } } } /** * update funnel data response * * @param Array $response * * @since 1.7.2 * @return Array $response */ public function update_funnel_data_response( $response ){ if( is_array($response) && !empty($response['funnel_data']['drawflow']['Home']['data']) ){ $steps_order = $response['funnel_data']['drawflow']['Home']['data']; if( is_array($steps_order) ){ foreach( $steps_order as $key=>$step ){ if( isset( $step['data']['step_id'], $response['funnel_data']['drawflow']['Home']['data'][$key]['data']['step_view_link'] ) ){ $step_id = $step['data']['step_id']; if( Wpfnl_Ab_Testing::maybe_ab_testing_running( $step_id ) ){ $url = base64_decode($response['funnel_data']['drawflow']['Home']['data'][$key]['data']['step_view_link']); if( false === strpos($url, '?wpfnl-step-id') ){ $response['funnel_data']['drawflow']['Home']['data'][$key]['data']['step_view_link'] = base64_encode(base64_decode($step['data']['step_view_link']).'?wpfnl-step-id='.$step_id); } }else{ $url = base64_decode($response['funnel_data']['drawflow']['Home']['data'][$key]['data']['step_view_link']); if( false !== strpos($url, '?wpfnl-step-id') ){ $response['funnel_data']['drawflow']['Home']['data'][$key]['data']['step_view_link'] = base64_encode($this->remove_query_param( $url, 'wpfnl-step-id' )); } } } } } } return $response; } private function remove_query_param($url, $paramToRemove) { // Parse the URL into components $urlParts = parse_url($url); if(isset($urlParts['query'])) { // Parse the query string into an array parse_str($urlParts['query'], $queryParams); // Remove the specified parameter if(isset($queryParams[$paramToRemove])) { unset($queryParams[$paramToRemove]); } // Rebuild the query string $newQuery = http_build_query($queryParams); // Reconstruct the URL $newUrl = $urlParts['scheme'] . '://' . $urlParts['host'] . $urlParts['path']; if (!empty($newQuery)) { $newUrl .= '?' . $newQuery; } if(isset($urlParts['fragment'])) { $newUrl .= '#' . $urlParts['fragment']; } return $newUrl; } else { // No query parameters, return the original URL return $url; } } /** * Update funnel link * * @param Array $response * * @return Array $response * @since 1.7.4 */ public function update_funnel_link( $response ){ if (class_exists('\WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing')) { $instance = new \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing; $function_exist = is_callable(array($instance, 'maybe_ab_testing')); if( $function_exist ){ if( isset($response['funnel_id'],$response['step_id']) ){ $is_enabled = \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing::maybe_ab_testing( $response['step_id'] ); $variations = \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing::get_all_variations( $response['step_id'] ); if( 'yes' == $is_enabled && count($variations) > 1 ){ $utm_settings = Wpfnl_functions::get_funnel_utm_settings( $response['funnel_id'] ); if($utm_settings['utm_enable'] == 'on') { unset($utm_settings['utm_enable']); $view_link = add_query_arg($utm_settings,get_the_permalink($response['funnel_id'])); $view_link = strtolower($view_link); $response['link'] = $view_link; } $args = [ 'wpfnl-step-id' => $response['step_id'], ]; $response['link'] = add_query_arg($args,$response['link']); } } } } return $response; } /** * update step data if ab testings enable * * @param array $step_data * @param int $step_id * * @return array $step_data * @since 2.0.0 */ public function update_step_data( $step_data, $step_id ){ if( !$step_id ){ return $step_data; } $maybe_ab_testing = Wpfnl_Ab_Testing::maybe_ab_testing_running($step_id); $step_data['maybe_ab_testing'] = $maybe_ab_testing ? 'yes' : 'no'; return $step_data; } /** * Update the meta information for a step in a funnel. * * This function updates the meta information, such as the post title, post name (slug), * step title, and step view link for a specific step in a funnel. It utilizes the * Wpfnl_Ab_Testing class from the WPFunnelsPro\AbTesting namespace to handle the update. * * @param int $step_id The ID of the step to update. * @param int $funnel_id The ID of the funnel to which the step belongs. * @param array $settings An array of settings containing the title and slug for the step. * @return void * @since 1.7.1 */ public function update_step_meta( $step_id, $funnel_id, $settings ){ $parent_id = get_post_meta( $step_id, '_parent_step_id', true ); $ab_settings = get_post_meta( $parent_id, '_wpfnl_ab_testing_start_settings', true ); if( isset($ab_settings['variations']) && is_array($ab_settings['variations']) ){ foreach( $ab_settings['variations'] as $key=>$variation ){ if( $variation['stepId'] == $step_id ){ $ab_settings['variations'][$key]['stepName'] = htmlspecialchars_decode(get_the_title($step_id)); $ab_settings['variations'][$key]['stepViewLink'] = rtrim( get_the_permalink($step_id), '/' ); } } update_post_meta( $parent_id, '_wpfnl_ab_testing_start_settings', $ab_settings ); } } /** * Runs the scheduler for A/B testing. * * @param int $step_id The ID of the step. * @param array $settings The settings for the A/B testing. * * @since 2.2.6 */ public function run_scheduler($step_id,$settings){ if( !$step_id || !isset($settings['autoEnd']) || 'yes' != $settings['autoEnd'] || !isset($settings['endDate']) || !$settings['endDate']){ return; } $data['data'] = $step_id; $time = strtotime($settings['endDate']); $group = 'wpfnl-ab-testing-'.$step_id; Wpfnl_Ab_Testing::delete_as_actions($group); if ( function_exists('as_has_scheduled_action') ) { $data['data'] = $step_id; as_schedule_single_action( $time, 'wpfnl_auto_end_ab_testing', $data, $group); }elseif( function_exists('as_next_scheduled_action') ){ if ( false === as_next_scheduled_action( 'wpfnl_auto_end_ab_testing' ) ) { $data['data'] = $step_id; as_schedule_single_action( $time, 'wpfnl_auto_end_ab_testing', $data, $group ); } } } /** * Process automation data from Cookie and initiate triggers * * @param $data * @retun null * * @since 2.2.6 */ public function wpfnl_auto_end_ab_testing( $step_id ) { $settings = get_post_meta( $step_id, 'wpfnl_ab_testing_general_settings', true ); if( isset( $settings['autoEndSettings']['autoEnd'] )){ $settings['autoEndSettings']['autoEnd'] = 'no'; update_post_meta( $step_id, 'wpfnl_ab_testing_general_settings', $settings ); } Wpfnl_Ab_Testing::update_running_status( $step_id,'no' ); } } admin/modules/mint/class-wpfnl-mint-hook-backup.php000064400000021457147600245720016403 0ustar00$step ){ if( isset( $step['data']['step_id'] ) ){ $step_id = $step['data']['step_id']; if( Wpfnl_functions::is_mint_mrm_active() ){ $automation = get_post_meta( $step_id, '_wpfnl_automation_steps', true ); if( is_array($automation) ){ $key = array_search('sendMail', array_column($automation, 'key')); if( false !== $key ){ if( isset($automation[$key]['settings']['settings']['message_data']['body']) ){ unset($automation[$key]['settings']['settings']['message_data']['body']); } } } $automation_steps['step_'.$step_id]['data'] = $automation ? $automation : []; } } } } } $response['automationSteps'] = $automation_steps; return $response; } /** * Save mint automation settings * * @param int $funnel_id * @param array $automation_steps */ public function save_mint_automation( $funnel_id, $automation_steps ){ if( $automation_steps && is_array($automation_steps) ){ foreach( $automation_steps as $key=>$step ){ if( !empty($step['stepID']) ){ $data = isset($step['value']) ? $step['value'] : []; $step_data = get_post_meta( $step['stepID'], '_wpfnl_automation_steps', true ); $key = array_search('sendMail', array_column($data, 'key')); if( false !== $key && isset($data[$key]['settings']['settings']['message_data']['body']) && isset($step_data[$key]['settings']['settings']['message_data']['body']) ){ $data[$key]['settings']['settings']['message_data']['body'] = $step_data[$key]['settings']['settings']['message_data']['body']; } update_post_meta( $step['stepID'], '_wpfnl_automation_steps', $data ); } } } } /** * Delete funnel automation * * @param int $funnel_id */ public function delete_funnel_automation( $funnel_id ){ if( Wpfnl_functions::is_mint_mrm_active() ){ Wpfnl_Pro_functions::delete_automation_by_funnel_id( $funnel_id ); } } /** * Delete funnel automation * * @param int $funnel_id */ public function maybe_automation_exist_for_a_funnel( $response, $funnel_id ){ if( Wpfnl_functions::is_mint_mrm_active() && class_exists("Mint\\MRM\\DataBase\\Tables\\AutomationMetaSchema") && class_exists("MintMail\\App\\Internal\\Automation\\AutomationModel") && class_exists("Mint\\MRM\\DataBase\\Tables\\AutomationSchema") ) { $automationSchema = "Mint\\MRM\\DataBase\\Tables\\AutomationSchema"; $automationMetaSchema = "Mint\\MRM\\DataBase\\Tables\\AutomationMetaSchema"; global $wpdb; $automation_table = $wpdb->prefix . $automationSchema::$table_name; $automation_meta_table = $wpdb->prefix . $automationMetaSchema::$table_name; $automations = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id as id FROM $automation_table as automation INNER JOIN $automation_meta_table as automation_meta ON automation.id = automation_meta.automation_id WHERE automation_meta.meta_key = %s AND automation_meta.meta_value = %s", array( 'funnel_id', $funnel_id ) ), ARRAY_A ); // db call ok. ; no-cache ok. if( is_array($automations) && count($automations) ){ $response = true; } } return $response; } /** * Create a MainMint contact if conditions are met. * * This function checks if certain conditions are met and if the required classes * exist. If the conditions are satisfied, it creates a new contact in the MainMint * system with the provided email address and automation ID. This process may involve * sending a double-opt-in email if configured. * * @since 1.9.6 * * @param int $automation_id The ID of the automation. * @param string $email The email address of the contact to be created. * * @return void */ public function maybe_create_mainmint_contact( $automation_id, $email ) { if ( $automation_id && $email && class_exists( 'Mint\\MRM\\DataBase\\Tables\\AutomationMetaSchema' ) && class_exists( 'MintMail\\App\\Internal\\Automation\\AutomationModel' ) && class_exists( 'Mint\\MRM\\DataBase\\Tables\\AutomationSchema' ) ) { $is_exist = $this->maybe_automation_exist( $automation_id ); if ( $is_exist ) { if ( !HelperFunctions::maybe_user( $email ) ) { $is_double_optin = MRMCommon::is_double_optin_enable(); $parms = array( 'status' => $is_double_optin ? 'pending' : 'subscribed', ); $contact = new ContactData( $email, $parms ); $contact_id = ContactModel::insert( $contact ); if ( $contact_id && $is_double_optin ) { MessageController::get_instance()->send_double_opt_in( $contact_id ); } } } } } /** * Check if a specific automation exists. * * This private function checks whether a given automation ID exists in the MainMint * system by querying the appropriate tables. It uses the provided automation ID to * look for matching automations based on predefined conditions. * * @since 1.6.0 * * @param int $automation_id The ID of the automation to check. * * @return bool True if the automation exists, false otherwise. */ private function maybe_automation_exist( $automation_id ) { if ( ! $automation_id ) { return false; } global $wpdb; $automation_table = $wpdb->prefix . 'mint_automations'; $automation_meta_table = $wpdb->prefix . 'mint_automation_meta'; // Query to retrieve matching automations. $automations = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id as id FROM $automation_table as automation INNER JOIN $automation_meta_table as automation_meta ON automation.id = automation_meta.automation_id WHERE automation_meta.automation_id = %d AND automation_meta.meta_key = %s AND automation_meta.meta_value = %s", array( $automation_id, 'source', 'wpf' ) ), ARRAY_A ); if ( ! is_array( $automations ) || ! count( $automations ) ) { return false; } return true; } }admin/modules/mint/class-wpfnl-mint-hook.php000064400000015374147600245720015141 0ustar00prefix . $automationSchema::$table_name; $automation_meta_table = $wpdb->prefix . $automationMetaSchema::$table_name; $automations = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id as id FROM $automation_table as automation INNER JOIN $automation_meta_table as automation_meta ON automation.id = automation_meta.automation_id WHERE automation_meta.meta_key = %s AND automation_meta.meta_value = %s", array( 'funnel_id', $funnel_id ) ), ARRAY_A ); // db call ok. ; no-cache ok. if( is_array($automations) && count($automations) ){ $response = true; } } return $response; } /** * update step data if mint automation enable * * @param array $step_data * @param int $step_id * * @return array $step_data * @since 2.0.0 */ public function update_step_data( $step_data, $step_id ){ if( !$step_id ){ return $step_data; } $trigger = get_post_meta( $step_id, '_wpfnl_automation_trigger', true ); $step_data['maybe_mint_automation'] = $trigger ? 'yes' : 'no'; return $step_data; } /** * Create a MainMint contact if conditions are met. * * This function checks if certain conditions are met and if the required classes * exist. If the conditions are satisfied, it creates a new contact in the MainMint * system with the provided email address and automation ID. This process may involve * sending a double-opt-in email if configured. * * @since 1.9.6 * * @param int $automation_id The ID of the automation. * @param string $email The email address of the contact to be created. * * @return void */ public function maybe_create_mainmint_contact( $automation_id, $email ) { if ( $automation_id && $email && class_exists( 'Mint\\MRM\\DataBase\\Tables\\AutomationMetaSchema' ) && class_exists( 'MintMail\\App\\Internal\\Automation\\AutomationModel' ) && class_exists( 'Mint\\MRM\\DataBase\\Tables\\AutomationSchema' ) ) { $is_exist = $this->maybe_automation_exist( $automation_id ); if ( $is_exist ) { if ( !HelperFunctions::maybe_user( $email ) ) { $is_double_optin = MRMCommon::is_double_optin_enable(); $parms = array( 'status' => $is_double_optin ? 'pending' : 'subscribed', ); $contact = new ContactData( $email, $parms ); $contact_id = ContactModel::insert( $contact ); if ( $contact_id && $is_double_optin ) { MessageController::get_instance()->send_double_opt_in( $contact_id ); } } } } } /** * Check if a specific automation exists. * * This private function checks whether a given automation ID exists in the MainMint * system by querying the appropriate tables. It uses the provided automation ID to * look for matching automations based on predefined conditions. * * @since 1.6.0 * * @param int $automation_id The ID of the automation to check. * * @return bool True if the automation exists, false otherwise. */ private function maybe_automation_exist( $automation_id ) { if ( ! $automation_id ) { return false; } global $wpdb; $automation_table = $wpdb->prefix . 'mint_automations'; $automation_meta_table = $wpdb->prefix . 'mint_automation_meta'; // Query to retrieve matching automations. $automations = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id as id FROM $automation_table as automation INNER JOIN $automation_meta_table as automation_meta ON automation.id = automation_meta.automation_id WHERE automation_meta.automation_id = %d AND automation_meta.meta_key = %s AND automation_meta.meta_value = %s", array( $automation_id, 'source', 'wpf' ) ), ARRAY_A ); if ( ! is_array( $automations ) || ! count( $automations ) ) { return false; } return true; } /** * Save automation after step duplicate * * @param int $funnel_id * @param int $step_id * * @since 2.1.2 */ public function save_automation_after_step_duplicate( $funnel_id, $step_id ){ if( $funnel_id && $step_id ){ $mint_automation = new Automation(); $automation_data = $mint_automation->prepare_automation_duplicate_data( $funnel_id, $step_id ); if( $automation_data ){ $mint_automation->save_or_update_automation( $automation_data, $funnel_id, $step_id ); } } } }admin/modules/steps/checkout/asset/js/main.js000064400000047623147600245720015307 0ustar00(function($) { 'use strict'; /** * All of the code for your admin-facing JavaScript source * should reside in this file. * * Note: It has been assumed you will write jQuery code here, so the * $ function reference has been prepared for usage within the scope * of this function. * * This enables you to define handlers, for when the DOM is ready: * * $(function() { * * }); * * When the window is loaded: * * $( window ).load(function() { * * }); * * ...and/or other possibilities. * * Ideally, it is not considered best practise to attach more than a * single DOM-ready or window-load handler for a particular page. * Although scripts in the WordPress core, Plugins and Themes may be * practising this, we should strive to set a better example in our own work. */ jQuery(document).ready(function() { /** * edit-field settings tab * * @since 1.0.0 */ $('.edit-field-settings__single-tab-content:first-child').show(); $('.edit-field-settings__tab-nav a').on('click', function(e) { e.preventDefault(); var dataID = $(this).attr('href'); $(this).parent('li').addClass('active').siblings().removeClass('active'); $(this).parents('.checkout-edit-field-tab__content-wrapper').find(dataID).show(); $(this).parents('.checkout-edit-field-tab__content-wrapper').find(dataID).siblings().hide(); }); /** * add new checkout field drawer * * @since 1.0.0 */ $('.add-new-checkout-field-btn').on('click', function(e) { e.preventDefault(); $(this).parents('.checkout-edit-field-tab__content-wrapper').find('.add-checkout-field-wrapper').addClass('show-drawer'); }); $('.add-checkout-field-close').on('click', function(e) { e.preventDefault(); $(this).parents('.add-checkout-field-wrapper').removeClass('show-drawer'); }); /** * edit checkout field drawer * * @since 1.0.0 */ // $('button.edit-field').on('click', function(e) { $(document).on("click", "button.edit-field", function(e) { e.preventDefault(); $(this).parents('.checkout-edit-field-tab__content-wrapper').find('.edit-checkout-field-wrapper').addClass('show-drawer'); }); $('.add-checkout-field-close').on('click', function(e) { e.preventDefault(); $(this).parents('.edit-checkout-field-wrapper').removeClass('show-drawer'); }); /** * show edit field type options * * @since 1.0.0 */ $('.wpfnl-edit-field-type').on('change', function(e) { e.preventDefault(); var thisVal = $(this).val(); if( thisVal == 'select' ){ $(this).parents('.field-body').find('.field-type-options').show(); } else { $(this).parents('.field-body').find('.field-type-options').hide(); } }); }); jQuery(document).ready(function() { var selectedType = $('.wpfnl-edit-field-type').val(); if( selectedType == 'select' ){ $('.wpfnl-edit-field-type').parents('.field-body').find('.field-type-options').show(); } else { $('.wpfnl-edit-field-type').parents('.field-body').find('.field-type-options').hide(); } }); jQuery(document).ready(function() { $('.wpfnl_muliple_select').select2(); /** * get and show billing fields * @since 1.0.1 */ var stepId = window.CheckoutEditor.stepId; var billingFields = window.CheckoutEditor.billingFields; var shippingFields = window.CheckoutEditor.shippingFields; var additionalFields = window.CheckoutEditor.additionalFields; var unique_id =''; // show_additional_field(); /** * add option field as you need when add checkout field * @since 1.0.0 */ var i = 2; $('.wpfnl_add_option').click(function(e) { e.preventDefault(); i++; $('.dynamic_option').append('
'); }); /** * remove option field as you need */ $(document).on('click', '.wpfnl_btn_remove', function(e) { e.preventDefault(); var button_id = $(this).attr("id"); $("#row" + button_id + "").remove(); }); /** * add checkout field * @since 1.0.0 */ $('.custom_checkout_add_field').click(function() { var id = $(this).data('id'); if (id == 'additional') { $("#wpfnl_name").val("additional_") $(".wpfnl_id").val("additional_") $("#hidden_field_add").val(id) } if (id == 'billing') { $("#wpfnl_name").val("billing_") $(".wpfnl_id").val("billing_") $("#hidden_field_add").val(id) } if (id == 'shipping') { $("#wpfnl_name").val("shipping_") $(".wpfnl_id").val("shipping_") $("#hidden_field_add").val(id) } }); /** * edit checkout field * @since 1.0.0 */ // $(document).on("click", ".wpfnl_edit_row", function() { // var id = $(this).data('id'); // var type = $(this).data('type'); // var parent_id = $(this).parent(); // unique_id = $(parent_id).parent().attr("id"); // var payload = { // type: type, // data: id, // billing_fields: billingFields, // shipping_fields: shippingFields, // additional_fields: additionalFields, // }; // wpAjaxHelperRequest("wpfnl-edit-row", payload) // .success(function(data) { // var jsonData = JSON.parse(data); // var index; // if (jsonData.index != 'custom') { // $(".wpfnl_edit_type").prop("disabled", true); // $("#wpfnl_edit_name").prop("disabled", true); // $(".wpfnl_add_edit_option").hide(); // index = 'default'; // } else { // $(".wpfnl_edit_type").prop("disabled", false); // $(".wpfnl_edit_name").prop("disabled", false); // index = 'custom'; // } // $('.wpfnl_edit_type').val(jsonData.data.type); // $('#wpfnl_edit_name').val(id); // $('#wpfnl_hidden_array_index').val(id); // $('#wpfnl_edit_label').val(jsonData.data.label); // $('#wpfnl_edit_placeholder').val(jsonData.data.placeholder); // $('#wpfnl_edit_default').val(jsonData.data.default); // $('#wpfnl_edit_id').val(jsonData.data.id); // $('#wpfnl_hidden_value').val(index); // $('#wpfnl_hidden_index').val(id); // $('#wpfnl_hidden_type').val(type); // if(jsonData.data.validate){ // var validate_arr = jsonData.data.validate; // for(var i=0 ;i 0 && option_value[0] != '') { // $('.edit_field_option').remove(); // for (var i = 0; i < option_text.length; i++) { // var field = '
' // $('.dynamic_option_edit').append(field); // } // } // } // }) // .error(function(response) { // }); // }) /** * Checkout Edit Field delete alert * * @since 1.0.0 */ // $(document).on("click", ".delete-checkout-field", function() { // $('.wpfnl-delete-alert-wrapper').css('display', 'flex'); // var index = $(this).data('id'); // var type = $(this).data('type'); // var parent_id = $(this).parent(); // var parents_id = $(parent_id).parent(); // var payload = { // data: index, // type: type, // billing_fields:billingFields, // shipping_fields:shippingFields, // additional_fields:additionalFields, // step_id:stepId // }; // $(document).on("click", ".wpfnl-delete-confirm-btn .yes", function() { // wpAjaxHelperRequest("wpfnl-delete-row", payload) // .success(function(data) { // $('.wpfnl-delete-alert-wrapper').hide(); // $(parents_id).remove(); // }) // }); // }); $(document).on("click", ".wpfnl-delete-confirm-btn .cancel", function() { $('.wpfnl-delete-alert-wrapper').hide(); }); /** * Restore to deafult checkout field for each section * @since 1.0.0 */ // $('.wpfnl_restore_btn').click(function() { // var payload = { // data: $(this).data('id'), // billing_fields:billingFields, // shipping_fields:shippingFields, // additional_fields:additionalFields, // step_id:stepId // }; // var thisAlert = $(this).parents('.restore-btn').find('.wpfnl-alert'); // var thisLoader = $(this).parents('.restore-btn').find('.wpfnl-loader'); // thisLoader.css('display', 'inline-block'); // wpAjaxHelperRequest("wpfnl-restore-default", payload) // .success(function(data) { // thisAlert.text('Successfully Restored').addClass('wpfnl-success').fadeIn(); // thisLoader.hide(); // if('billing_success' == data){ // show_billing_field(); // setTimeout(function() { // thisAlert.fadeOut().text('').removeClass('wpfnl-success'); // }, 2000); // } // if('shipping_success' == data){ // show_shipping_field(); // setTimeout(function() { // thisAlert.fadeOut().text('').removeClass('wpfnl-success'); // }, 2000); // } // if('additional_success' == data){ // show_additional_field(); // setTimeout(function() { // thisAlert.fadeOut().text('').removeClass('wpfnl-success'); // }, 2000); // } // }) // }); /** * wpfnl_change_required * change required option of each checkout field * @since 1.0.0 */ $(document).on("click", ".wpfnl_change_required", function() { var id = $(this).data('id'); var type = $(this).data('type'); var payload = { id:id, type:type, billing_fields:billingFields, shipping_fields:shippingFields, additional_fields:window.CheckoutEditor.additionalFields, step_id:stepId } wpAjaxHelperRequest("wpfnl-change-required-edit-field", payload) .success(function() { }) }) /** * wpfnl_change_enable * change enable option of each checkout field * @since 1.0.0 */ /** * wpfnl_submit * Add field for each section * @since 1.0.0 */ // $(document).on("click", ".wpfnl_submit", function(e) { // e.preventDefault(); // e.stopPropagation(); // var thisAlert = $(this).parents('.field-footer').find('.wpfnl-alert'); // var thisLoader = $(this).parents('.field-footer').find('.wpfnl-loader'); // thisLoader.css('display', 'inline-block'); // var fields = $('.add_field_form').serialize(); // var payload = // { // fields: fields, // billing_fields: billingFields, // shipping_fields: shippingFields, // additional_fields: additionalFields, // step_id:stepId // } // wpAjaxHelperRequest("wpfnl-add-field", payload) // .success(function(data) { // $('.wpfnl_name').val(''); // $('.wpfnl_label').val(''); // $('.wpfnl_placeholder').val(''); // $('.wpfnl_default').val(''); // $('.wpfnl_validation').val(''); // $('.wpfnl_required').val(''); // $('.wpfnl_enable').val(''); // $('.wpfnl_show').val(''); // $('.wpfnl_edit_type_option').val(''); // thisAlert.text('Successfully Saved').addClass('wpfnl-success').fadeIn(); // thisLoader.hide(); // if('success' == data){ // show_additional_field(); // show_billing_field(); // show_shipping_field(); // } // setTimeout(function() { // thisAlert.fadeOut().text('').removeClass('wpfnl-success'); // }, 2000); // }) // }) /** * wpfnl_edit_submit * Edit field for each section * @since 1.0.0 */ $(document).on("click", ".wpfnl_edit_submit", function(e) { e.preventDefault(); e.stopPropagation(); $('.wpfnl-loader').css('display','inline-block'); var fields = $('.edit_field_form').serialize(); var payload = { fields:fields, billing_fields: billingFields, shipping_fields: shippingFields, additional_fields: additionalFields, step_id:stepId } wpAjaxHelperRequest("wpfnl-edit-field", payload) .success(function(data) { $('#'+unique_id).children('.field-label').text($('#wpfnl_edit_label').val()) $('#'+unique_id).children('.field-type').text($('.wpfnl_edit_type').val()) $('#'+unique_id).children('.field-name').text($('#wpfnl_edit_name').val()) $('#'+unique_id).children('.field-placeholder').text($('#wpfnl_edit_placeholder').val()) if($(".wpfnl_edit_required").prop("checked") == true){ $('#'+unique_id).children('.field-required').children('.wpfnl-switcher').children('.wpfnl_change_required').prop("checked",true) }else{ $('#'+unique_id).children('.field-required').children('.wpfnl-switcher').children('.wpfnl_change_required').prop("checked",false) } if($(".wpfnl_edit_enable").prop("checked") == true){ $('#'+unique_id).children('.field-required').children('.wpfnl-switcher').children('.wpfnl_change_enable').prop("checked",true) }else{ $('#'+unique_id).children('.field-required').children('.wpfnl-switcher').children('.wpfnl_change_enable').prop("checked",false) } var validations = []; $('.edit_validation_select').each(function() { validations.push($(this).val()); }); $('#'+unique_id).children('.field-validation').text(validations.toString()); $('.wpfnl-alert').text('Successfully Updated').addClass('wpfnl-success').fadeIn(); $('.wpfnl-loader').css('display','none'); setTimeout(function() { $('.wpfnl-alert').fadeOut().text('').removeClass('wpfnl-success'); }, 2000); }) }) }) })(jQuery); admin/modules/steps/checkout/checkout-fields/views/partials/add-checkout-field-drawer.php000064400000016554147600245720025725 0ustar00

admin/modules/steps/checkout/checkout-fields/views/partials/checkout-additional-fields-data.php000064400000010637147600245720027111 0ustar00 $bf){ if($bf['delete'] == 0){ $html .= '
'; $html .= '
'; $html .= ''; $html .= '
'; $html .= '
'.$a.'
'; $html .= '
'.$bf['type'].'
'; $html .= '
'.$bf['label'].'
'; $html .= '
'.$bf['placeholder'].'
'; if(isset($bf['validate']) && is_array($bf['validate'])){ $validation_array = $bf['validate']; $validation_str = implode(",",$validation_array); }else{ $validation_str = ''; } $html .= '
'.$validation_str.'
'; $html .= '
'; $html .= '
'; if($bf['required'] == true){ $html .= ''; }else{ $html .= ''; } $html .= ''; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; if($bf['enable'] == 1){ $html .= ''; }else{ $html .= ''; } $html .= ''; $html .= '
'; $html .= '
'; $html .= '
'; $html .= ''; $html .= ''; $html .= '
'; $html .= '
'; } }admin/modules/steps/checkout/checkout-fields/views/partials/checkout-billing-fields-data.php000064400000010567147600245720026423 0ustar00 $bf){ if($bf['delete'] == 0){ $html .= '
'; $html .= '
'; $html .= ''; $html .= '
'; $html .= '
'.$a.'
'; $html .= '
'.$bf['type'].'
'; $html .= '
'.$bf['label'].'
'; $html .= '
'.$bf['placeholder'].'
'; if(isset($bf['validate']) && is_array($bf['validate'])){ $validation_array = $bf['validate']; $validation_str = implode(",",$validation_array); }else{ $validation_str = ''; } $html .= '
'.$validation_str.'
'; $html .= '
'; $html .= '
'; if($bf['required'] == true){ $html .= ''; }else{ $html .= ''; } $html .= ''; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; if($bf['enable'] == 1){ $html .= ''; }else{ $html .= ''; } $html .= ''; $html .= '
'; $html .= '
'; $html .= '
'; $html .= ''; $html .= ''; $html .= '
'; $html .= '
'; } }admin/modules/steps/checkout/checkout-fields/views/partials/checkout-shipping-fields-data.php000064400000010566147600245720026623 0ustar00 $bf){ if($bf['delete'] == 0){ $html .= '
'; $html .= '
'; $html .= ''; $html .= '
'; $html .= '
'.$a.'
'; $html .= '
'.$bf['type'].'
'; $html .= '
'.$bf['label'].'
'; $html .= '
'.$bf['placeholder'].'
'; if(isset($bf['validate']) && is_array($bf['validate'])){ $validation_array = $bf['validate']; $validation_str = implode(",",$validation_array); }else{ $validation_str = ''; } $html .= '
'.$validation_str.'
'; $html .= '
'; $html .= '
'; if($bf['required'] == true){ $html .= ''; }else{ $html .= ''; } $html .= ''; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; if($bf['enable'] == 1){ $html .= ''; }else{ $html .= ''; } $html .= ''; $html .= '
'; $html .= '
'; $html .= '
'; $html .= ''; $html .= ''; $html .= '
'; $html .= '
'; } }admin/modules/steps/checkout/checkout-fields/views/partials/checkout-single-additional-field.php000064400000010042147600245720027264 0ustar00
admin/modules/steps/checkout/checkout-fields/views/partials/checkout-single-billing-field.php000064400000000001147600245720026566 0ustar00 admin/modules/steps/checkout/checkout-fields/views/partials/checkout-single-shipping-field.php000064400000010040147600245720026773 0ustar00
admin/modules/steps/checkout/checkout-fields/views/partials/confirmation-alert.php000064400000001240147600245720024606 0ustar00

admin/modules/steps/checkout/checkout-fields/views/partials/edit-checkout-field-drawer.php000064400000016623147600245720026117 0ustar00

admin/modules/steps/checkout/checkout-fields/views/partials/edit-field-additional-tab.php000064400000005227147600245720025702 0ustar00

admin/modules/steps/checkout/checkout-fields/views/partials/edit-field-billing-tab.php000064400000005244147600245720025211 0ustar00

admin/modules/steps/checkout/checkout-fields/views/partials/edit-field-shipping-tab.php000064400000005215147600245720025410 0ustar00

admin/modules/steps/checkout/checkout-fields/views/view.php000064400000010272147600245720020151 0ustar00

admin/modules/steps/checkout/checkout-fields/wpfnl-checkout-fields.php000064400000064413147600245720022245 0ustar00init_hooks(); $this->init_ajax(); $this->type = "checkout-editor"; } public function init_hooks() { add_action('admin_enqueue_scripts', [ $this, 'load_scripts' ]); add_action('wpfunnels/checkout_pro_settings', array($this, 'render_checkout_fields')); // add_action( 'save_post', 'wpfnl_save_all_field_data', 10 ); } public function get_validation_data() { return[ 'logged_in' => true, 'user_can' => 'wpf_manage_funnels', ]; } public function load_scripts($hook) { if( isset($_GET['page']) && $_GET['page'] === 'edit_funnel' && isset($_GET['step_id']) ) { $this->billing_fields = get_post_meta($_GET['step_id'],'wpfnl_wc_billing_fields',true); $this->shipping_fields = get_post_meta($_GET['step_id'],'wpfnl_wc_shipping_fields',true); $this->additional_fields = get_post_meta($_GET['step_id'],'wpfnl_wc_additional_fields',true); wp_enqueue_script($this->type.'-js', WPFNL_PRO_URL . 'admin/modules/steps/checkout/asset/js/main.js', [ 'jquery', 'wp-util'], '1.0.0', true); wp_localize_script( $this->type.'-js','CheckoutEditor', array( 'ajaxurl' => esc_url_raw( admin_url( 'admin-ajax.php' ) ), 'stepId' => $_GET['step_id'], 'billingFields' => $this->billing_fields, 'shippingFields' => $this->shipping_fields, 'additionalFields' => $this->additional_fields, ) ); } } public function init_ajax() { wp_ajax_helper()->handle('wpfn-show-checkout-fields') ->with_callback([ $this, 'wpfn_show_checkout_fields' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-show-billing-fields') ->with_callback([ $this, 'wpfn_show_checkout_billing_fields' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-show-shipping-fields') ->with_callback([ $this, 'wpfn_show_checkout_shipping_fields' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-show-additional-fields') ->with_callback([ $this, 'wpfn_show_checkout_additional_fields' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-add-checkout-field') ->with_callback([ $this, 'wpfn_add_checkout_fields' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-update-checkout-field') ->with_callback([ $this, 'wpfn_update_checkout_fields' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-restore-to-default-fields') ->with_callback([ $this, 'wpfn_restore_to_default_fields' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-update-field-enable-status') ->with_callback([ $this, 'wpfn_update_field_enable_status' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-delete-field') ->with_callback([ $this, 'wpfn_delete_field' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-update-field-required') ->with_callback([ $this, 'wpfn_update_field_required' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfn-update-field-order') ->with_callback([ $this, 'wpfn_update_field_order' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfnl-edit-fields-additional-settings') ->with_callback([ $this, 'wpfnl_update_additional_settings' ]) ->with_validation($this->get_validation_data()); } /** * wpfn_show_checkout_fields * Show the billing, shipping and addotional fields * * @param array $payload * */ public function wpfn_show_checkout_fields($payload){ $data = []; if( isset( $payload['step_id'] )){ $field_type = [ 'billing', 'shipping', 'additional' ]; $settings = get_post_meta( $payload['step_id'], '_wpfunnels_edit_field_additional_settings', true ); $data['settings'] = $settings ? $settings : 'Data not found'; foreach( $field_type as $type ){ $method = 'wpfnl_get_'.$type.'_default_data'; $data[$type.'_data'] = get_post_meta($payload['step_id'],'wpfnl_checkout_'.$type.'_fields', true ) ? get_post_meta($payload['step_id'],'wpfnl_checkout_'.$type.'_fields', true ) : $this->$method(); } $data['success'] = true; } return $data ; } /** * wpfn_show_checkout_billing_fields * Show the billing fields * * @param array $payload * */ public function wpfn_show_checkout_billing_fields($payload){ $get_billing_data = get_post_meta($payload['step_id'],'wpfnl_checkout_billing_fields', true ); if( empty($get_billing_data) ){ $get_billing_data = $this->wpfnl_get_billing_default_data(); // update_post_meta($payload['step_id'],'wpfnl_checkout_billing_fields',$get_billing_data); return [ 'status' => 'success', 'data' => $get_billing_data ]; } return [ 'status' => 'success', 'data' => $get_billing_data ]; } /** * wpfn_show_checkout_shipping_fields * Show the shipping fields * * @param array $payload * */ public function wpfn_show_checkout_shipping_fields($payload){ $get_shipping_data = get_post_meta($payload['step_id'],'wpfnl_checkout_shipping_fields', true ); if( empty($get_shipping_data) || !is_array($get_shipping_data) ){ $get_shipping_data = $this->wpfnl_get_shipping_default_data(); // update_post_meta($payload['step_id'],'wpfnl_checkout_shipping_fields',$get_shipping_data); return [ 'status' => 'success', 'data' => $get_shipping_data ]; } return [ 'status' => 'success', 'data' => $get_shipping_data ]; } /** * wpfn_show_checkout_additional_fields * Show the additional fields * * @param array $payload * */ public function wpfn_show_checkout_additional_fields($payload){ $get_additional_data = get_post_meta($payload['step_id'],'wpfnl_checkout_additional_fields', true ); if( empty($get_additional_data) ){ $get_additional_data = $this->wpfnl_get_additional_default_data(); // update_post_meta($payload['step_id'],'wpfnl_checkout_additional_fields',$get_additional_data); return [ 'status' => 'success', 'data' => $get_additional_data ]; } return [ 'status' => 'success', 'data' => $get_additional_data ]; } /** * wpfn_add_checkout_billing_fields * add checkout field in billing section * * @param Array $payload field data */ public function wpfn_add_checkout_fields( $payload ){ $field_info['stepID'] = $payload['stepID']; $field_info['sectionType'] = $payload['sectionType']; $field_info['type'] = $payload['selectedFieldType']; $field_info['label'] = $payload['label']; $field_info['name'] = $payload['name']; $field_info['placeholder'] = $payload['placeholder']; $field_info['default'] = $payload['defaultValue']; $field_info['delete'] = 0; if(isset($payload['validate'])){ $field_info['validate'] = $payload['validate']; }else{ $field_info['validate'] = ''; } if(isset($payload['options']) && is_array($payload['options']) && !empty($payload['options'])){ $field_info['options'] = $payload['options']; } $boolReq = json_decode($payload['isRequired']); $field_info['required'] = $boolReq; if($payload['isEnabled'] == 'true'){ $field_info['enable'] = true; }else{ $field_info['enable'] = false; } if($payload['isDisplayOrderPage'] == 'true'){ $field_info['show'] = true; }else{ $field_info['show'] = false; } $field_info['class'] = array('my-field-class form-row-wide'); if( $payload['sectionType'] === 'billing' ){ $response = $this->wpfn_add_checkout_billing_fields($field_info); }elseif( $payload['sectionType'] === 'shipping' ){ $response = $this->wpfn_add_checkout_shipping_fields($field_info); }else{ $response = $this->wpfn_add_checkout_additional_fields($field_info); } return $response; } /** * wpfn_update_checkout_billing_fields * update checkout field in billing section * * @param Array $payload field data */ public function wpfn_update_checkout_fields( $payload ){ $field_info['stepID'] = $payload['stepID']; $field_info['sectionType'] = $payload['sectionType']; $field_info['type'] = $payload['selectedFieldType']; $field_info['label'] = $payload['label']; $field_info['name'] = $payload['name']; $field_info['placeholder'] = $payload['placeholder']; $field_info['default'] = $payload['defaultValue']; $field_info['delete'] = 0; if(isset($payload['validate'])){ $field_info['validate'] = $payload['validate']; }else{ $field_info['validate'] = ''; } if(isset($payload['options']) && is_array($payload['options']) && !empty($payload['options'])){ $field_info['options'] = $payload['options']; } if(!empty($payload['isRequired']) && $payload['isRequired'] == 1){ $field_info['required'] = true; }else{ $field_info['required'] = false; } if(!empty($payload['isEnabled']) && $payload['isEnabled'] == 1){ $field_info['enable'] = true; }else{ $field_info['enable'] = false; } if(!empty($payload['isShow']) && $payload['isShow'] == 1){ $field_info['show'] = true; }else{ $field_info['show'] = false; } $field_info['class'] = array('my-field-class form-row-wide'); if( $payload['sectionType'] === 'billing' ){ $response = $this->wpfn_update_checkout_billing_fields($field_info); }elseif( $payload['sectionType'] === 'shipping' ){ $response = $this->wpfn_update_checkout_shipping_fields($field_info); }else{ $response = $this->wpfn_update_checkout_additional_fields($field_info); } return $response; } /** * wpfn_add_checkout_shipping_fields * add checkout field in shipping section * * @param Array $payload field data */ public function wpfn_add_checkout_billing_fields( $payload ){ $billing_fields = get_post_meta( $payload['stepID'],'wpfnl_checkout_billing_fields', true ); if(!empty($billing_fields)){ if(isset($billing_fields[$payload['name']])){ return [ 'status' => 'fail', 'data' => 'Name should be unique.' ]; } if ( preg_match('/\s/',$payload['name']) ){ return [ 'status' => 'fail', 'data' => 'Name should not contain whitespace.' ]; } $billing_fields[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_billing_fields', $billing_fields); }else{ $billing_default_field = $this->wpfnl_get_billing_default_data(); if(isset($billing_default_field[$payload['name']])){ return [ 'status' => 'fail', 'data' => 'Name should be unique.' ]; } if ( preg_match('/\s/',$payload['name']) ){ return [ 'status' => 'fail', 'data' => 'Name should not contain whitespace.' ]; } $billing_default_field[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_billing_fields', $billing_default_field); } return [ 'status' => 'success', 'data' => 'Successfully saved' ]; } /** * wpfn_add_checkout_shipping_fields * add checkout field in shipping section * * @param Array $payload field data */ public function wpfn_add_checkout_shipping_fields( $payload ){ $shipping_fields = get_post_meta($payload['stepID'],'wpfnl_checkout_shipping_fields', true ); if(!empty($shipping_fields)){ if(isset($shipping_fields[$payload['name']])){ return [ 'status' => 'fail', 'data' => 'Name should be unique.' ]; } if ( preg_match('/\s/',$payload['name']) ){ return [ 'status' => 'fail', 'data' => 'Name should not contain whitespace.' ]; } $shipping_fields[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_shipping_fields', $shipping_fields); }else{ $shipping_default_fields = $this->wpfnl_get_shipping_default_data(); if(isset($shipping_default_fields[$payload['name']])){ return [ 'status' => 'fail', 'data' => 'Name should be unique.' ]; } if ( preg_match('/\s/',$payload['name']) ){ return [ 'status' => 'fail', 'data' => 'Name should not contain whitespace.' ]; } $shipping_default_fields[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_shipping_fields', $shipping_default_fields); } return [ 'status' => 'success', 'data' => 'Successfully saved' ]; } /** * wpfn_add_checkout_additional_fields * add checkout field in additional section * * @param Array $payload field data */ public function wpfn_add_checkout_additional_fields( $payload ){ $additional_fields = get_post_meta($payload['stepID'],'wpfnl_checkout_additional_fields', true ); if(!empty($additional_fields)){ if(isset($additional_fields[$payload['name']])){ return [ 'status' => 'fail', 'data' => 'Name should be unique.' ]; } if ( preg_match('/\s/',$payload['name']) ){ return [ 'status' => 'fail', 'data' => 'Name should not contain whitespace.' ]; } $additional_fields[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_additional_fields', $additional_fields); }else{ $additional_default_fields = $this->wpfnl_get_additional_default_data(); if(isset($additional_default_fields[$payload['name']])){ return [ 'status' => 'fail', 'data' => 'Name should be unique.' ]; } if ( preg_match('/\s/',$payload['name']) ){ return [ 'status' => 'fail', 'data' => 'Name should not contain whitespace.' ]; } $additional_default_fields[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_additional_fields', $additional_default_fields); } return [ 'status' => 'success', 'data' => 'Successfully saved' ]; } /** * wpfn_update_checkout_billing_fields * update checkout field in billing section * * @param Array $payload field data */ public function wpfn_update_checkout_billing_fields( $payload ){ $billing_fields = get_post_meta($payload['stepID'],'wpfnl_checkout_billing_fields', true ); if(is_array($billing_fields)){ if( isset($billing_fields[$payload['name']]['priority']) ){ $payload['priority'] = $billing_fields[$payload['name']]['priority']; } $billing_fields[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_billing_fields', $billing_fields); }else{ $billing_default_field = $this->wpfnl_get_billing_default_data(); if( $billing_default_field[$payload['name']]['priority'] ){ $payload['priority'] = $billing_default_field[$payload['name']]['priority']; } $billing_default_field[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_billing_fields', $billing_default_field); } return [ 'status' => 'success', 'data' => 'Successfully updated' ]; } /** * wpfn_update_checkout_shipping_fields * update checkout field in shipping section * * @param Array $payload field data */ public function wpfn_update_checkout_shipping_fields( $payload ){ $shipping_fields = get_post_meta($payload['stepID'],'wpfnl_checkout_shipping_fields', true ); if(is_array($shipping_fields)){ if( $shipping_fields[$payload['name']]['priority'] ){ $payload['priority'] = $shipping_fields[$payload['name']]['priority']; } $shipping_fields[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_shipping_fields', $shipping_fields); }else{ $shipping_default_field = $this->wpfnl_get_shipping_default_data(); if( $shipping_default_field[$payload['name']]['priority'] ){ $payload['priority'] = $shipping_default_field[$payload['name']]['priority']; } $shipping_default_field[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_shipping_fields', $shipping_default_field); } return [ 'status' => 'success', 'data' => 'Successfully updated' ]; } /** * wpfn_update_checkout_additional_fields * update checkout field in additional section * * @param Array $payload field data */ public function wpfn_update_checkout_additional_fields( $payload ){ $additional_fields = get_post_meta($payload['stepID'],'wpfnl_checkout_additional_fields', true ); if(is_array($additional_fields)){ if( $additional_fields[$payload['name']]['priority'] ){ $payload['priority'] = $additional_fields[$payload['name']]['priority']; } $additional_fields[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_additional_fields', $additional_fields); }else{ $additional_default_field = $this->wpfnl_get_additional_default_data(); if( $additional_default_field[$payload['name']]['priority'] ){ $payload['priority'] = $additional_default_field[$payload['name']]['priority']; } $additional_default_field[$payload['name']] = $payload; update_post_meta( $payload['stepID'], 'wpfnl_checkout_additional_fields', $additional_default_field); } return [ 'status' => 'success', 'data' => 'Successfully updated' ]; } /** * wpfn_restore_to_default_fields * Resotre to woocommerce default fields * * @param Array $payload Step id */ public function wpfn_restore_to_default_fields( $payload ){ update_post_meta( $payload['stepID'], 'wpfnl_checkout_billing_fields', $this->wpfnl_get_billing_default_data() ); update_post_meta( $payload['stepID'], 'wpfnl_checkout_additional_fields',$this->wpfnl_get_additional_default_data() ); update_post_meta( $payload['stepID'], 'wpfnl_checkout_shipping_fields',$this->wpfnl_get_shipping_default_data() ); return [ 'status' => 'success', 'data' => 'Successfully restored' ]; } /** * wpfn_update_field_enable_status * */ public function wpfn_update_field_enable_status($payload){ $field_data = get_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['section'].'_fields', true ); if(!empty($field_data)){ if(isset($field_data[$payload['index']])){ if($field_data[$payload['index']]['enable'] == 1){ $field_data[$payload['index']]['enable'] = false; }else{ $field_data[$payload['index']]['enable'] = true; } update_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['section'].'_fields', $field_data); } }else{ if($payload['section'] == 'billing'){ $field_data = $this->wpfnl_get_billing_default_data(); }else if($payload['section'] == 'shipping'){ $field_data = $this->wpfnl_get_shipping_default_data(); }else{ $field_data = $this->wpfnl_get_additional_default_data(); } if(isset($field_data[$payload['index']])){ if($field_data[$payload['index']]['enable'] == 1){ $field_data[$payload['index']]['enable'] = false; }else{ $field_data[$payload['index']]['enable'] = true; } update_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['section'].'_fields', $field_data); } } return [ 'status' => 'success', 'data' => 'Successfully updated' ]; } /** * wpfn_delete_field */ public function wpfn_delete_field($payload){ $field_data = get_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['section'].'_fields', true ); if(!empty($field_data)){ unset($field_data[$payload['index']]); update_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['section'].'_fields',$field_data); }else{ if($payload['section'] == 'billing'){ $field_data = $this->wpfnl_get_billing_default_data(); }else if($payload['section'] == 'shipping'){ $field_data = $this->wpfnl_get_shipping_default_data(); }else{ $field_data = $this->wpfnl_get_additional_default_data(); } unset($field_data[$payload['index']]); update_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['section'].'_fields',$field_data); } return [ 'status' => 'success', 'data' => 'Successfully deleted' ]; } /** * wpfn_update_field_required */ public function wpfn_update_field_required($payload){ $field_data = get_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['section'].'_fields', true ); if(!empty($field_data)){ if(isset($field_data[$payload['index']])){ if($field_data[$payload['index']]['required'] == true){ $field_data[$payload['index']]['required'] = false; }else{ $field_data[$payload['index']]['required'] = true; } update_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['section'].'_fields', $field_data); } }else{ if($payload['section'] == 'billing'){ $field_data = $this->wpfnl_get_billing_default_data(); }else if($payload['section'] == 'shipping'){ $field_data = $this->wpfnl_get_shipping_default_data(); }else{ $field_data = $this->wpfnl_get_additional_default_data(); } if(isset($field_data[$payload['index']])){ if($field_data[$payload['index']]['required'] == true){ $field_data[$payload['index']]['required'] = false; }else{ $field_data[$payload['index']]['required'] = true; } update_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['section'].'_fields', $field_data); } } return [ 'status' => 'success', 'data' => 'Successfully updated required fields' ]; } /** * wpfnl_get_billing_default_data * get all default billing fields and return all fields */ function wpfnl_get_billing_default_data(){ $checkout_instance = new \WC_Checkout(); $fields = $checkout_instance->get_checkout_fields(); $billing_default_field = isset( $fields['billing'] ) ? $fields['billing'] : []; if( $billing_default_field && !empty($billing_default_field) ){ foreach($billing_default_field as $key => $sf){ $billing_default_field[$key]['enable'] = 1; $billing_default_field[$key]['show'] = 1; $billing_default_field[$key]['delete'] = 0; if(!(isset($sf['type']))){ $billing_default_field[$key]['type'] = null; } if(!(isset($sf['placeholder']))){ $billing_default_field[$key]['placeholder'] = null; } if(!(isset($sf['label']))){ $billing_default_field[$key]['label'] = null; } if(!(isset($sf['validate']))){ $billing_default_field[$key]['validate'] = null; } if(!(isset($sf['default']))){ $billing_default_field[$key]['default'] = null; } } } return $billing_default_field; } /** * wpfnl_get_shipping_default_data * get all default shipping fields and return all fields */ function wpfnl_get_shipping_default_data(){ $checkout_instance = new \WC_Checkout(); $fields = $checkout_instance->get_checkout_fields(); $shipping_default_fields = isset( $fields['shipping'] ) ? $fields['shipping'] : []; // $shipping_default_fields = $countries->get_address_fields( $countries->get_base_country(),'shipping_'); foreach($shipping_default_fields as $key => $sf){ $shipping_default_fields[$key]['enable'] = 1; $shipping_default_fields[$key]['show'] = 1; $shipping_default_fields[$key]['delete'] = 0; if(!(isset($sf['type']))){ $shipping_default_fields[$key]['type'] = null; } if(!(isset($sf['placeholder']))){ $shipping_default_fields[$key]['placeholder'] = null; } if(!(isset($sf['label']))){ $shipping_default_fields[$key]['label'] = null; } if(!(isset($sf['validate']))){ $shipping_default_fields[$key]['validate'] = null; } if(!(isset($sf['default']))){ $shipping_default_fields[$key]['default'] = null; } } return $shipping_default_fields; } /** * wpfnl_get_additional_default_data * get all default additional fields and return all fields */ function wpfnl_get_additional_default_data(){ $checkout_instance = new \WC_Checkout(); $fields = $checkout_instance->get_checkout_fields(); $additional_default_fields = isset( $fields['order'] ) ? $fields['order'] : []; foreach( $additional_default_fields as $key=>$field ){ $additional_default_fields[$key]['required'] = false; $additional_default_fields[$key]['enable'] = 1; $additional_default_fields[$key]['show'] = 1; $additional_default_fields[$key]['default'] = null; $additional_default_fields[$key]['validate'] = null; $additional_default_fields[$key]['delete'] = 0; } return $additional_default_fields; } /** * Update edit fields order * * @param $payload * */ public function wpfn_update_field_order($payload){ $field_data = get_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['index'].'_fields', true ); if( empty($field_data) ){ $get_deafult_field = 'wpfnl_get_'.$payload['index'].'_default_data'; $field_data = $this->$get_deafult_field(); } $orders = $payload['order']; $updatedOrder = array(); $i = 1; foreach($orders as $order){ $updatedOrder[$order] = $field_data[$order]; $updatedOrder[$order]['priority'] = $i; $i++; } update_post_meta( $payload['stepID'], 'wpfnl_checkout_'.$payload['index'].'_fields', $updatedOrder); return [ 'status' => 'success', 'data' => 'Successfully updated' ]; } /** * Update edit field additional settings * * @param Array $payload * */ public function wpfnl_update_additional_settings( $payload ){ if( $payload['step_id'] ){ $step_id = $payload['step_id']; unset($payload['step_id']); update_post_meta( $step_id, '_wpfunnels_edit_field_additional_settings', $payload ); return [ 'status' => 'success', 'message' => 'Saved successful', ]; } return [ 'status' => 'fail', 'message' => 'Saved fail', ]; } } admin/modules/steps/checkout/views/edit-field/add-checkout-field-drawer.php000064400000016554147600245720023043 0ustar00

admin/modules/steps/checkout/views/edit-field/checkout-single-additional-field.php000064400000000057147600245720024407 0ustar00
admin/modules/steps/checkout/views/edit-field/checkout-single-billing-field.php000064400000000053147600245720023713 0ustar00
admin/modules/steps/checkout/views/edit-field/checkout-single-field.php000064400000002542147600245720022302 0ustar00
shipping_first_name
country
First name
Apartment, suite, unit, etc. (optional)
admin/modules/steps/checkout/views/edit-field/checkout-single-shipping-field.php000064400000000054147600245720024115 0ustar00
admin/modules/steps/checkout/views/edit-field/confirmation-alert.php000064400000001240147600245720021724 0ustar00

admin/modules/steps/checkout/views/edit-field/edit-checkout-field-drawer.php000064400000016467147600245720023243 0ustar00

admin/modules/steps/checkout/views/edit-field/edit-field-additional-tab.php000064400000005312147600245720023013 0ustar00

admin/modules/steps/checkout/views/edit-field/edit-field-billing-tab.php000064400000005327147600245720022331 0ustar00

admin/modules/steps/checkout/views/edit-field/edit-field-shipping-tab.php000064400000005300147600245720022521 0ustar00

admin/modules/steps/checkout/views/edit-field/edit-field-tab.php000064400000006511147600245720020707 0ustar00
module_manager->get_admin_modules('product'); $product_array = $this->get_internal_metas_by_key('_wpfnl_downsell_product'); if (count($product_array)) { $quantity = $product_array[0]['quantity']; } else { $quantity = '1'; } $product_module->set_products($product_array); $product_module->get_view(); ?>
WPFNL_EDIT_FUNNEL_SLUG, 'id' => $this->step->get_funnel_id(), 'step_id' => $this->get_id(), ], admin_url('admin.php') ); ?> admin/modules/steps/downsell/views/view.php000064400000007641147600245720015130 0ustar00 WPFNL_EDIT_FUNNEL_SLUG, 'id' => $this->step->get_funnel_id(), 'step_id' => $this->step->get_id(), 'show_settings' => 1, ], admin_url('admin.php') ); $id = $this->get_id(); $link = get_the_permalink($id); ?>
step->get_internal_metas_by_key('_wpfnl_downsell_product'); if (!$products) { ?>
admin/modules/steps/downsell/class-wpfnl-downsell.php000064400000015026147600245720017073 0ustar00validations = [ 'logged_in' => true, 'user_can' => 'wpf_manage_funnels', ]; wp_ajax_helper()->handle('add-downsell-product') ->with_callback([ $this, 'add_product' ]) ->with_validation($this->validations); wp_ajax_helper()->handle('update-offer-settings') ->with_callback([ $this, 'update_settings' ]) ->with_validation($this->validations); } /** * get view of the downsell * * @since 1.0.0 */ public function get_view() { // TODO: Implement get_view() method. $show_settings = filter_input(INPUT_GET, 'show_settings', FILTER_SANITIZE_STRING); if ($show_settings == 1) { $this->_internal_keys = Wpfnl_Step_Meta_keys::get_meta_keys($this->type); $this->set_internal_meta_value(); $prev_next_options = $this->get_prev_next_link_options(); require_once WPFNL_PRO_DIR . '/admin/modules/steps/downsell/views/settings.php'; } else { require_once WPFNL_PRO_DIR . '/admin/modules/steps/downsell/views/view.php'; } } /** * save upsell product tab * data * * @param $payload * @return array */ public function add_product( $payload ) { $step_id = sanitize_text_field($payload['step_id']); $id = sanitize_text_field( $payload['product_id'] ); if(!$step_id) { return [ 'success' => false, ]; } $data = array( array( 'id' => sanitize_text_field($payload['product_id']), 'quantity' => sanitize_text_field($payload['quantity']) ) ); $type = ''; if( $payload['isLms'] == 'true' ){ $type = 'lms'; }else{ $type = 'wc'; } $class_object = Wpfnl_Pro_OfferProduct_Factory::build( $type ); if( $class_object ){ $response = $class_object->add_downsell_items( $id, $data, $step_id ); if( $response ){ return $response; } } return array( 'success' => false, 'message' => __('Product Not Found', 'wpfnl') ); } /** * save upsell product tab * data * * @param $payload * @return array */ public function update_settings( $payload ) { $step_id = sanitize_text_field($payload['step_id']); $products = isset($payload['products']) ? $payload['products'] : ''; $type = $payload['type']; $discount = $payload['discount']; if($products){ update_post_meta( $step_id, '_wpfnl_'.$type.'_products', $products ); } update_post_meta( $step_id, '_wpfnl_'.$type.'_discount', $discount ); $step_info = array( 'step_id' => $step_id, 'step_type' => $type, ); if( isset($payload['replaceSettings']) && $payload['replaceSettings'] == 'true' ){ update_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement_settings', 'true' ); $offerReplacement = $payload['offerReplacement']; $replacement_type = isset($offerReplacement['replacement_type']) && $offerReplacement['replacement_type'] ? $offerReplacement['replacement_type'] : ''; $value = isset($offerReplacement['value']) && $offerReplacement['value'] ? $offerReplacement['value'] : ''; OrderReplacement::save_replace_order_condition( $step_info, $replacement_type, $value ); }else{ update_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement_settings', 'false' ); delete_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement' ); } if( isset($payload['timeBoundDiscount'])){ update_post_meta($step_id, '_wpfnl_time_bound_discount_settings', $payload['timeBoundDiscount']); } // update_post_meta( $step_id, '_wpfnl_'.$type.'_replacement', $isOfferReplace ); return array( 'success' => true, 'step_id' => $step_id, 'message' => __('Product Saved Successfully', 'wpfnl') ); } /** * get prev/next links * * @return array * @since 1.0.0 */ public function get_prev_next_link_options() { $associate_funnel_id = get_post_meta($this->get_id(), '_funnel_id', true); $steps_array = [ 'upsell' => 'Upsell', 'downsell' => 'Downsell', 'thankyou' => 'Thankyou' ]; $option_group = []; foreach ($steps_array as $key=>$value) { $args = [ 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC', 'post_type' => WPFNL_STEPS_POST_TYPE, 'post_status' => 'publish', 'post__not_in' => [ $this->get_id() ], 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_step_type', 'value' => $key, 'compare' => '=', ], [ 'key' => '_funnel_id', 'value' => $associate_funnel_id, 'compare' => '=', ], ], ]; $query = new \WP_Query($args); $steps = $query->posts; if ($steps) { foreach ($steps as $s) { $option_group[$key][] = [ 'id' => $s->ID, 'title' => $s->post_title, ]; } } } return $option_group; } } admin/modules/steps/replace-order/class-replace-order.php000064400000001407147600245720017537 0ustar00 $replacement_type, 'value' => $value ); update_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement', $data ); } }admin/modules/steps/upsell/views/settings.php000064400000022323147600245720015465 0ustar00
module_manager->get_admin_modules('product'); $product_array = $this->get_internal_metas_by_key('_wpfnl_upsell_products'); // $product_array = get_post_meta( $this->get_id(), '_wpfnl_upsell_product', true ); if (count($product_array)) { $quantity = $product_array[0]['quantity']; } else { $quantity = '1'; } $product_module->set_products($product_array); $product_module->get_view(); ?>
WPFNL_EDIT_FUNNEL_SLUG, 'id' => $this->step->get_funnel_id(), 'step_id' => $this->get_id(), ], admin_url('admin.php') ); ?>
admin/modules/steps/upsell/views/view.php000064400000007632147600245720014605 0ustar00 WPFNL_EDIT_FUNNEL_SLUG, 'id' => $this->step->get_funnel_id(), 'step_id' => $this->step->get_id(), 'show_settings' => 1, ], admin_url('admin.php') ); $id = $this->get_id(); $link = get_the_permalink($id); ?>
step->get_internal_metas_by_key('_wpfnl_upsell_products'); if (!$products) { ?>
admin/modules/steps/upsell/class-wpfnl-upsell.php000064400000026037147600245720016231 0ustar00validations = [ 'logged_in' => true, 'user_can' => 'wpf_manage_funnels', ]; wp_ajax_helper()->handle('add-upsell-product') ->with_callback([ $this, 'add_product' ]) ->with_validation($this->validations); wp_ajax_helper()->handle('update-offer-settings') ->with_callback([ $this, 'update_settings' ]) ->with_validation($this->validations); add_action('wp_ajax_offer_product_search', array($this, 'fetch_products') ); } /** * get view of the upsell * * @since 1.0.0 */ public function get_view() { // TODO: Implement get_view() method. $show_settings = filter_input(INPUT_GET, 'show_settings', FILTER_SANITIZE_STRING); if ($show_settings == 1) { $this->_internal_keys = Wpfnl_Step_Meta_keys::get_meta_keys($this->type); $this->set_internal_meta_value(); $prev_next_options = $this->get_prev_next_link_options(); require_once WPFNL_PRO_DIR . '/admin/modules/steps/upsell/views/settings.php'; } else { require_once WPFNL_PRO_DIR . '/admin/modules/steps/upsell/views/view.php'; } } /** * save upsell product tab * data * * @param $payload * @return array */ public function add_product( $payload ) { $step_id = sanitize_text_field($payload['step_id']); $id = sanitize_text_field( $payload['product_id'] ); if(!$step_id) { return [ 'success' => false, ]; } $data = array( array( 'id' => sanitize_text_field($payload['product_id']), 'quantity' => sanitize_text_field($payload['quantity']) ) ); $type = ''; if( $payload['isLms'] == 'true' ){ $type = 'lms'; }else{ $type = 'wc'; } $class_object = Wpfnl_Pro_OfferProduct_Factory::build( $type ); if( $class_object ){ $response = $class_object->add_upsell_items( $id, $data, $step_id ); if( $response ){ return $response; } } return array( 'success' => false, 'message' => __('Product Not Found', 'wpfnl') ); } /** * save upsell settings * * @param $payload * @return array * * @since 1.0.0 */ public function update_settings($payload) { $step_id = sanitize_text_field($payload['step_id']); $products = isset($payload['products']) ? $payload['products'] : ''; $type = $payload['type']; $discount = $payload['discount']; if($products){ update_post_meta( $step_id, '_wpfnl_'.$type.'_products', $products ); } update_post_meta( $step_id, '_wpfnl_'.$type.'_discount', $discount ); $step_info = array( 'step_id' => $step_id, 'step_type' => $type, ); if( isset($payload['replaceSettings']) && $payload['replaceSettings'] == 'true' ){ update_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement_settings', 'true' ); $offerReplacement = $payload['offerReplacement']; $replacement_type = isset($offerReplacement['replacement_type']) && $offerReplacement['replacement_type'] ? $offerReplacement['replacement_type'] : ''; $value = isset($offerReplacement['value']) && $offerReplacement['value'] ? $offerReplacement['value'] : ''; OrderReplacement::save_replace_order_condition( $step_info, $replacement_type, $value ); }else{ update_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement_settings', 'false' ); delete_post_meta( $step_info['step_id'], '_wpfnl_'.$step_info['step_type'].'_replacement' ); } if( isset($payload['timeBoundDiscount'])){ update_post_meta($step_id, '_wpfnl_time_bound_discount_settings', $payload['timeBoundDiscount']); } return array( 'success' => true, 'step_id' => $step_id, 'message' => __('Product Saved Successfully', 'wpfnl') ); } public function get_prev_next_link_options() { $associate_funnel_id = get_post_meta($this->get_id(), '_funnel_id', true); $steps_array = [ 'upsell' => 'Upsell', 'downsell' => 'Downsell', 'thankyou' => 'Thankyou' ]; $option_group = []; foreach ($steps_array as $key=>$value) { $args = [ 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC', 'post_type' => WPFNL_STEPS_POST_TYPE, 'post_status' => 'publish', 'post__not_in' => [ $this->get_id() ], 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_step_type', 'value' => $key, 'compare' => '=', ], [ 'key' => '_funnel_id', 'value' => $associate_funnel_id, 'compare' => '=', ], ], ]; $query = new \WP_Query($args); $steps = $query->posts; if ($steps) { foreach ($steps as $s) { $option_group[$key][] = [ 'id' => $s->ID, 'title' => $s->post_title, ]; } } } return $option_group; } /** * fetch product from WC data store * * @throws \Exception * * @since 1.0.0 */ public function fetch_products() { check_ajax_referer('wpfnl-admin', 'security'); if (isset($_GET['term'])) { $term = (string) sanitize_text_field(wp_unslash($_GET['term'])); } if (empty($term)) { wp_die(); } $products = []; $data_store = \WC_Data_Store::load('product'); $ids = $data_store->search_products($term, '', false, false, 10); $product_objects = array_filter(array_map('wc_get_product', $ids), 'wc_products_array_filter_readable'); if( is_array($product_objects) ){ foreach ($product_objects as $product_object) { if( $product_object ){ $formatted_name = $product_object->get_formatted_name(); if ($product_object->get_type() == 'variable') { $variations = $product_object->get_available_variations(); foreach ($variations as $variation) { $product_image_id = $product_object->get_image_id(); $product_image_src = $product_image_id ? wp_get_attachment_image_src($product_image_id, 'large')[0] : ''; $products[$variation['variation_id']] = [ 'name' => $formatted_name .'('. $variation['sku'].')', 'price' => wc_price($variation['display_price']), 'sale_price' => wc_price($variation['display_regular_price']), 'html_price' => $product_object->get_price_html(), 'title' => $product_object->get_title(), 'img' => array( 'id' => $product_image_id, 'url' => $product_image_src, ), ]; } } elseif ($product_object->get_type() == 'variable-subscription') { $sale_price = $product_object->get_sale_price(); if ($sale_price != "") { $sale_price = wc_price($product_object->get_sale_price()); } $product_image_id = $product_object->get_image_id(); $product_image_src = $product_image_id ? wp_get_attachment_image_src($product_image_id, 'large')[0] : ''; $products[$product_object->get_id()] = [ 'name' => rawurldecode($formatted_name), 'price' => $product_object->get_price_html(), 'sale_price' => $sale_price, 'html_price' => $product_object->get_price_html(), 'title' => $product_object->get_title(), 'img' => array( 'id' => $product_image_id, 'url' => $product_image_src, ), 'product_type' => $product_object->get_type(), ]; } else { $sale_price = $product_object->get_sale_price(); if ($sale_price != "") { $sale_price = wc_price($product_object->get_sale_price()); } $product_image_id = $product_object->get_image_id(); $product_image_src = $product_image_id ? wp_get_attachment_image_src($product_image_id, 'large')[0] : ''; $products[$product_object->get_id()] = [ 'name' => rawurldecode($formatted_name), 'price' => wc_price($product_object->get_regular_price()), 'sale_price' => $sale_price, 'html_price' => $product_object->get_price_html(), 'title' => $product_object->get_title(), 'img' => array( 'id' => $product_image_id, 'url' => $product_image_src, ), ]; } } } } wp_send_json($products); } } admin/modules/type/sales-funnel/class-wpfnl-admin-woocommerce.php000064400000066400147600245720021243 0ustar00get_type() == 'variation' ? Wpfnl_functions::get_formated_product_name( $product ) : $product->get_name(); $price = $product->get_price(); $signUpFee = 0; $sale_price = $product->get_sale_price(); if($product->get_type() == 'variable' || $product->get_type() == 'variable-subscription') { $regular_price = $product->get_variation_regular_price( 'min' ) ? $product->get_variation_regular_price( 'min' ) : $product->get_price(); }else{ $regular_price = $product->get_regular_price() ? $product->get_regular_price() : $product->get_price(); } if ($sale_price != "") { $sale_price = $product->get_sale_price() + floatval($signUpFee); } if ( is_a( $product, 'WC_Product_Bundle' ) ) { $price = $product->get_bundle_price('min'); $sale_price = $product->get_bundle_price('min'); $regular_price = $product->get_bundle_regular_price( 'min' ) ? $product->get_bundle_regular_price( 'min' ) : $product->get_bundle_price( 'min' ); } if( 'composite' === $product->get_type() ){ $price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); $sale_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); $regular_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), true ); } if( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ){ if( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ){ $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $price = floatval($price) + floatval($signUpFee); $sale_price = floatval($sale_price) + floatval($signUpFee); $regular_price = floatval($regular_price) + floatval($signUpFee); } } $pr_image = wp_get_attachment_image_src($product->get_image_id(), 'single-post-thumbnail'); update_post_meta( $step_id, $prefix.'products', $data ); return array( 'success' => true, 'step_id' => $step_id, 'products' => array( 'id' => $id, 'title' => $title, 'price' => wc_price($price), 'numeric_price' => $price, 'currency' => get_woocommerce_currency_symbol(), 'quantity' => 1, 'image' => $pr_image ? $pr_image[0] : '', 'sale_price' => $sale_price ? $sale_price : $price, 'html_price' => $product->get_price_html(), 'regular_price' => $regular_price, 'product_edit_link' => in_array($product->get_type(), array( 'variation', 'subscription_variation' )) ? get_edit_post_link($product->get_parent_id()) : get_edit_post_link($product->get_id()), 'product_view_link' => in_array($product->get_type(), array( 'variation', 'subscription_variation' )) ? get_permalink($product->get_parent_id()) : get_permalink($product->get_id()), 'is_trash' => 'trash' === $product->get_status() ? 'yes' : 'no', 'is_deleted' => 'no', ), ); } } /** * add upsell item to post meta * * @param String $id * @return Array * @since 2.4.6 */ public function get_upsell_items( $products, $step_id ){ if( $products && count($products)) { $product_obj = $products[0]; $product = wc_get_product($product_obj['id']); if( $product instanceof \WC_Product ) { $title = $product->get_type() == 'variation' ? Wpfnl_functions::get_formated_product_name( $product ) : $product->get_name(); $image = wp_get_attachment_image_src( $product->get_image_id(), 'single-post-thumbnail' ); $price = $product->get_price(); if($product->get_type() == 'variable' || $product->get_type() == 'variable-subscription') { $regular_price = $product->get_variation_regular_price( 'min' ) ? $product->get_variation_regular_price( 'min' ) : $product->get_price(); }else{ $regular_price = $product->get_regular_price() ? $product->get_regular_price() : $product->get_price(); } $signUpFee = 0; $sale_price = $product->get_type() == 'variable' ? $price : $product->get_sale_price(); if ($sale_price !== "") { $sale_price = $sale_price + floatval($signUpFee); } if ( is_a( $product, 'WC_Product_Bundle' ) ) { $price = $product->get_bundle_price('min'); $sale_price = $product->get_bundle_price('min'); $regular_price = $product->get_bundle_regular_price( 'min' ) ? $product->get_bundle_regular_price( 'min' ) : $product->get_bundle_price( 'min' ); } if( 'composite' === $product->get_type() ){ $price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); $sale_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); $regular_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), true ); } if( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) { if( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ) { $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $price = floatval($price) + floatval($signUpFee); $regular_price = floatval($regular_price) + floatval($signUpFee); $sale_price = floatval($sale_price) + floatval($signUpFee); } } $response[ 'products' ][] = array( 'id' => $product_obj[ 'id' ], 'title' => $title, 'price' => wc_price( $price ), 'numeric_price' => $price, 'currency' => get_woocommerce_currency_symbol(), 'quantity' => $product_obj[ 'quantity' ], 'image' => $image ? $image[ 0 ] : '', 'regular_price' => $regular_price, 'sale_price' => $sale_price ? $sale_price : $price, 'html_price' => $product->get_price_html(), 'product_edit_link' => in_array( $product->get_type(), array( 'variation', 'subscription_variation' ) ) ? get_edit_post_link( $product->get_parent_id() ) : get_edit_post_link( $product->get_id() ), 'product_view_link' => in_array( $product->get_type(), array( 'variation', 'subscription_variation' ) ) ? get_permalink( $product->get_parent_id() ) : get_permalink( $product->get_id() ), 'is_trash' => 'trash' === $product->get_status() ? 'yes' : 'no', 'is_deleted' => 'no', ); $discount = get_post_meta( $step_id, '_wpfnl_upsell_discount', true ); /** if offer product has discount */ if( $discount ) { $discount_type = $discount[ 'discountType' ]; $discount_apply_to = $discount[ 'discountApplyTo' ]; $discount_value = $discount[ 'discountValue' ]; if( 'discount-percentage' === $discount[ 'discountType' ] || 'discount-price' === $discount[ 'discountType' ] ) { $regular_price = $product->get_regular_price(); if( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) { $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $regular_price = floatval($signUpFee) + floatval($regular_price); } $product_price = $discount_apply_to === 'sale' ? ( $product->get_sale_price() ? $product->get_sale_price() : $product->get_price() ) : $regular_price; $product_price = $product_price ? $product_price : $product->get_price(); $product_price = Wpfnl_functions::calculate_discount_price( $discount_type, $discount_value, floatval($product_price) * intval($product_obj[ 'quantity' ]) ); $product_price = preg_replace( '/[^\d.]/', '', $product_price ); $discount[ 'discountPrice' ] = $product_price; $discount[ 'discountPriceHtml' ] = wc_format_sale_price( floatval($product_price) * intval($product_obj[ 'quantity' ]), ( floatval($product->get_sale_price()) ? floatval($product->get_sale_price()) : floatval($product->get_price()) ) * intval($product_obj[ 'quantity' ] )); } else { $discount[ 'discountPrice' ] = floatval($price) * intval($product_obj[ 'quantity' ]); $discount[ 'discountPriceHtml' ] = wc_format_sale_price( floatval($price) * intval($product_obj[ 'quantity' ]), ( $product->get_sale_price() ? floatval($product->get_sale_price()) : floatval($product->get_price()) ) * floatval($product_obj[ 'quantity' ]) ); } } $response[ 'discount' ] = $discount ? $discount : array( 'discountType' => 'original', 'discountApplyTo' => 'regular', 'discountValue' => '0', 'discountPrice' => floatval($price) * intval($product_obj[ 'quantity' ]), 'discountPriceHtml' => wc_format_sale_price( floatval($price) * floatval($product_obj[ 'quantity' ]), ( $product->get_sale_price() ? $product->get_sale_price() : floatval($product->get_price()) ) * floatval($product_obj[ 'quantity' ]) ), ); } else { $response[ 'products' ][] = array( 'is_trash' => 'no', 'is_deleted' => 'yes', ); $response[ 'discount' ] = array( 'discountType' => 'original', 'discountApplyTo' => 'regular', 'discountValue' => '0', 'discountPrice' => '0', 'discountPriceHtml' => '0', ); } } else { $response[ 'products' ] = array(); $response[ 'discount' ] = array( 'discountType' => 'original', 'discountApplyTo' => 'regular', 'discountValue' => '0', 'discountPrice' => '0', 'discountPriceHtml' => '0', ); } $replaceSettings = get_post_meta( $step_id, '_wpfnl_upsell_replacement_settings', true ); if( $replaceSettings == 'true' ) { $response[ 'replaceSettings' ] = $replaceSettings; $isOfferReplace = get_post_meta( $step_id, '_wpfnl_upsell_replacement', true ); $response[ 'isOfferReplace' ] = array( 'replacement_type' => $isOfferReplace[ 'replacement_type' ], 'value' => $isOfferReplace[ 'value' ], ); } else { $response[ 'replaceSettings' ] = $replaceSettings; $response[ 'isOfferReplace' ] = array( 'replacement_type' => '', 'value' => '', ); } $offer_settings = Wpfnl_functions::get_offer_settings(); $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); $prev_step = Wpfnl_functions::get_prev_step( $funnel_id, $step_id ); $response[ 'prevStep' ] = isset( $prev_step[ 'step_type' ] ) && $prev_step[ 'step_type' ] ? $prev_step[ 'step_type' ] : ''; $response[ 'success' ] = true; $response[ 'isChildOrder' ] = $offer_settings[ 'offer_orders' ] == 'child-order' ? true : false; $response['columns'] = Wpfnl_functions::get_checkout_columns( $step_id ); $time_bound_discount_settings = get_post_meta($step_id, '_wpfnl_time_bound_discount_settings', true); if( !$time_bound_discount_settings ){ $dateTime = new \DateTime(); $time_bound_discount_settings = [ 'isEnabled' => 'no', 'fromDate' => $dateTime->format('M d, Y'), 'toDate' => $dateTime->add(new \DateInterval('P1D'))->format('M d, Y') ]; } $response['time_bound_discount_settings'] = $time_bound_discount_settings; return $response; } /** * Add upsell product * * @param String $id * @return Array * */ public function add_downsell_items( $id, $data, $step_id ){ $product = wc_get_product($id); if ( !empty($product) ) { $title = $product->get_type() == 'variation' ? Wpfnl_functions::get_formated_product_name( $product ) : $product->get_name(); $price = $product->get_price(); if($product->get_type() == 'variable' || $product->get_type() == 'variable-subscription') { $regular_price = $product->get_variation_regular_price( 'min' ) ? $product->get_variation_regular_price( 'min' ) : $product->get_price(); }else{ $regular_price = $product->get_regular_price() ? $product->get_regular_price() : $product->get_price(); } $signUpFee=0; $pr_image = wp_get_attachment_image_src($product->get_image_id(), 'single-post-thumbnail'); $prefix = '_wpfnl_downsell_'; update_post_meta( $step_id, $prefix.'products', $data ); $sale_price = $product->get_sale_price(); if ($sale_price != "") { $sale_price = $product->get_sale_price() + floatval($signUpFee); } if ( is_a( $product, 'WC_Product_Bundle' ) ) { $price = $product->get_bundle_price('min'); $sale_price = $product->get_bundle_price('min'); $regular_price = $product->get_bundle_regular_price( 'min' ) ? $product->get_bundle_regular_price( 'min' ) : $product->get_bundle_price( 'min' ); } if( 'composite' === $product->get_type() ){ $price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); $sale_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); $regular_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), true ); } if( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ){ $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $price = floatval($price) + floatval($signUpFee); $regular_price = floatval($regular_price) + floatval($signUpFee); $sale_price = floatval($sale_price) + floatval($signUpFee); } return array( 'success' => true, 'step_id' => $step_id, 'products' => array( 'id' => $id, 'title' => $title, 'price' => wc_price($price), 'numeric_price' => $price, 'currency' => get_woocommerce_currency_symbol(), 'quantity' => 1, 'image' => $pr_image ? $pr_image[0] : '', 'sale_price' => $sale_price ? $sale_price : $price, 'html_price' => $product->get_price_html(), 'regular_price' => $regular_price, 'product_edit_link' => in_array($product->get_type(), array( 'variation', 'subscription_variation' )) ? get_edit_post_link($product->get_parent_id()) : get_edit_post_link($product->get_id()), 'product_view_link' => in_array($product->get_type(), array( 'variation', 'subscription_variation' )) ? get_permalink($product->get_parent_id()) : get_permalink($product->get_id()), 'is_trash' => 'trash' === $product->get_status() ? 'yes' : 'no', 'is_deleted' => 'no', ), ); } } /** * add downsell item to post meta * * @param String $id * @return Array * @since 2.4.6 */ public function get_downsell_items( $products, $step_id ){ if( $products && count($products)) { $product_obj = $products[0]; $product = function_exists( 'wc_get_product' ) ? wc_get_product($product_obj['id']) : []; if( $product instanceof \WC_Product ) { $title = $product->get_type() == 'variation' ? Wpfnl_functions::get_formated_product_name( $product ) : $product->get_name(); $image = wp_get_attachment_image_src($product->get_image_id(), 'single-post-thumbnail'); $price = $product->get_price(); if($product->get_type() == 'variable' || $product->get_type() == 'variable-subscription') { $regular_price = $product->get_variation_regular_price( 'min' ) ? $product->get_variation_regular_price( 'min' ) : $product->get_price(); }else{ $regular_price = $product->get_regular_price() ? $product->get_regular_price() : $product->get_price(); } $signUpFee=0; $sale_price = $product->get_type() == 'variable' ? $price : $product->get_sale_price(); if ($sale_price !== "") { $sale_price = $sale_price + floatval($signUpFee); } if ( is_a( $product, 'WC_Product_Bundle' ) ) { $price = $product->get_bundle_price('min'); $sale_price = $product->get_bundle_price('min'); $regular_price = $product->get_bundle_regular_price( 'min' ) ? $product->get_bundle_regular_price( 'min' ) : $product->get_bundle_price( 'min' ); } if( 'composite' === $product->get_type() ){ $price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); $sale_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); $regular_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), true ); } if( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ){ if( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ){ $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $price = floatval($price) + floatval($signUpFee); $sale_price = floatval($sale_price) + floatval($signUpFee); $regular_price = floatval($regular_price) + floatval($signUpFee); } } $response['products'][] = array( 'id' => $product_obj['id'], 'title' => $title, 'price' => wc_price($price), 'numeric_price' => $price, 'currency' => get_woocommerce_currency_symbol(), 'quantity' => $product_obj['quantity'], 'image' => $image ? $image[0] : '', 'regular_price' => $regular_price, 'sale_price' => $sale_price ? $sale_price : $price, 'html_price' => $product->get_price_html(), 'product_edit_link' => in_array($product->get_type(), array( 'variation', 'subscription_variation' )) ? get_edit_post_link($product->get_parent_id()) : get_edit_post_link($product->get_id()), 'product_view_link' => in_array($product->get_type(), array( 'variation', 'subscription_variation' )) ? get_permalink($product->get_parent_id()) : get_permalink($product->get_id()), 'is_trash' => 'trash' === $product->get_status() ? 'yes' : 'no', 'is_deleted' => 'no', ); $discount = get_post_meta( $step_id, '_wpfnl_downsell_discount', true ); /** if offer product has discount */ if( $discount ) { $discount_type = $discount['discountType']; $discount_apply_to = $discount['discountApplyTo']; $discount_value = $discount['discountValue']; if( 'discount-percentage' === $discount['discountType'] || 'discount-price' === $discount['discountType'] ) { $regular_price = $product->get_regular_price(); if( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ){ $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $regular_price = floatval($signUpFee) + floatval($regular_price); } $product_price = $discount_apply_to === 'sale' ? ( $product->get_sale_price() ? $product->get_sale_price() : $product->get_price() ) : $regular_price; $product_price = $product_price ? $product_price : $product->get_price(); $product_price = Wpfnl_functions::calculate_discount_price( $discount_type , $discount_value, floatval($product_price) * intval($product_obj['quantity']) ); $product_price = preg_replace('/[^\d.]/', '', $product_price ); $discount['discountPrice'] = $product_price; $discount['discountPriceHtml'] = wc_format_sale_price( $product_price, ($product->get_sale_price() ? $product->get_sale_price() : $product->get_price() ) * intval($product_obj['quantity']) ); } else { $discount['discountPrice'] = floatval($price) * intval($product_obj['quantity']); $discount['discountPriceHtml'] = wc_format_sale_price( floatval($price) * intval($product_obj['quantity']), ($product->get_sale_price() ? $product->get_sale_price() : $product->get_price() ) * intval($product_obj['quantity']) ); } } $response['discount'] = $discount ? $discount : array( 'discountType' => 'original', 'discountApplyTo' => 'regular', 'discountValue' => '0', 'discountPrice' => floatval($price) * intval($product_obj['quantity']), 'discountPriceHtml' => wc_format_sale_price( floatval($price) * intval($product_obj['quantity']), ($product->get_sale_price() ? $product->get_sale_price() : $product->get_price() ) * intval($product_obj['quantity']) ), ); } else { $response[ 'products' ][] = array( 'is_trash' => 'no', 'is_deleted' => 'yes', ); $response['discount'] = array( 'discountType' => 'original', 'discountApplyTo' => 'regular', 'discountValue' => '0', 'discountPrice' => '0', 'discountPriceHtml' => '0', ); } } else { $response['products'] = array(); $response['discount'] = array( 'discountType' => 'original', 'discountApplyTo' => 'regular', 'discountValue' => '0', 'discountPrice' => '0', 'discountPriceHtml' => '0', ); } $response['success'] = true; $replaceSettings = get_post_meta( $step_id, '_wpfnl_downsell_replacement_settings', true ); if( $replaceSettings == 'true' ){ $response['replaceSettings'] = $replaceSettings; $isOfferReplace = get_post_meta( $step_id, '_wpfnl_downsell_replacement', true ); $response['isOfferReplace'] = array( 'replacement_type' => $isOfferReplace['replacement_type'], 'value' => $isOfferReplace['value'], ); }else{ $response['replaceSettings'] = $replaceSettings; $response['isOfferReplace'] = array( 'replacement_type' => '', 'value' => '', ); } $offer_settings = Wpfnl_functions::get_offer_settings(); $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); $prev_step = Wpfnl_functions::get_prev_step( $funnel_id, $step_id ); $response['prevStep'] = isset($prev_step['step_type']) && $prev_step['step_type'] ? $prev_step['step_type'] : ''; $response['isChildOrder'] = $offer_settings['offer_orders'] == 'child-order' ? true : false; $response['columns'] = Wpfnl_functions::get_checkout_columns( $step_id ); $time_bound_discount_settings = get_post_meta($step_id, '_wpfnl_time_bound_discount_settings', true); if( !$time_bound_discount_settings ){ $dateTime = new \DateTime(); $time_bound_discount_settings = [ 'isEnabled' => 'no', 'fromDate' => $dateTime->format('M d, Y'), 'toDate' => $dateTime->add(new \DateInterval('P1D'))->format('M d, Y') ]; } $response['time_bound_discount_settings'] = $time_bound_discount_settings; return $response; } }admin/modules/type/sales-funnel/class-wpfnl-type-factory.php000064400000001005147600245720020252 0ustar00 admin/partials/icons/funnel-icon.php000064400000000565147600245720013531 0ustar00 admin/partials/icons/global-funnel-icon.php000064400000001052147600245720014757 0ustar00 admin/partials/icons/heart-icon.php000064400000003535147600245720013345 0ustar00 admin/partials/icons/instawp-logo.png000064400000002677147600245720013742 0ustar00PNG  IHDR("-e sRGB, pHYs  dIDATx͘mh[Uo]ӏ)LC(sUP%M,kӏ6InN۴M?(` *ం(*`"(hsfI{q>}9-EO7Uځo†ޮhDMϯ gZ*#lg6y]vğlHf aX8Vi1]f!}U)%XBfJE18 *3^BKpp!6 J{#oz@ᅸ×5{^B$.g*tAH$VF_58Jnc%~LbaNw֪; ~DZpB܅qp$\e'ƛPusoKV#!j,/FSS&N{m+ q; %N+TY7K*TbJC-ݓ!墝9\ϲ׊dng-C"wۏ~';ɂ潭qcoo,cт C6}XPb_# SR*~RYq(^oHGJ Ϝ)kv#O"=TI r ,] FHXلϲBꫲ.T9eXL.3f|9=fȥ  L:0OrH*]*2ZYLSufpZzPajqAo5J69|Y8QZ2z F,C:e6?(~OhVT~/&!OuQu4w [b% d&uyU<ȂpY>%u^{…STP)u}BQw@٩^??tzG98FK<ݕ4)jKl֟yLo|HP;*'"DGWe6aVuw96s?~43<z;AJx\^wEvŕb-#:>5ԀΝlA63] lfT H*`J!/&{Xڞ𹅓 0F`Sp^{OSh8 t蕦 no@5|Y*w9݆{+*P7!oRqq{n,yJHCpJtVDA#;lo㘛8uvY8/9!`SF Q}Jk{͹nqܿ\!dr'9Hdн[QT!VYJW*[JARߒ\mz{>HSIENDB`admin/partials/icons/manage-license-icon.php000064400000002040147600245720015100 0ustar00 admin/partials/icons/repeat-icon.php000064400000005633147600245720013523 0ustar00 admin/partials/icons/star-icon.php000064400000001514147600245720013206 0ustar00 admin/partials/icons/support-icon.php000064400000000740147600245720013751 0ustar00 admin/partials/icons/webhook-icon.php000064400000025656147600245720013710 0ustar00 admin/partials/license.php000064400000037077147600245720011633 0ustar00get_addons(); ?>

$addon ) { ?>

'.$addon['description'].''; } ?>
}

admin/partials/refund-metabox.php000064400000017677147600245720013135 0ustar00ID; $order = wc_get_order( $order_id ); $products = array(); defined( 'ABSPATH' ) || exit; $order_items = $order->get_items( 'line_item' ); foreach ( $order_items as $item_id => $value ) { $is_upsell = wc_get_order_item_meta( $item_id, '_wpfunnels_upsell', true ); $is_downsell = wc_get_order_item_meta( $item_id, '_wpfunnels_downsell', true ); $step_id = wc_get_order_item_meta( $item_id, '_wpfunnels_step_id', true ); $is_refunded = wc_get_order_item_meta( $item_id, '_wpfunnels_offer_refunded', true ); if ( 'yes' == $is_upsell || 'yes' == $is_downsell ) { if( 'yes' == $is_upsell ) { $offer_type = 'Upsell'; } elseif ( 'yes' == $is_downsell ) { $offer_type = 'Downsell'; } $transaction_id = wc_get_order_item_meta( $item_id, '_wpfunnels_offer_txn_id', true ); $product = new WC_Product($value['product_id']); $products[ $item_id ] = array( 'order_id' => $order_id, 'product_id' => $value['product_id'], 'item' => $value, 'step_id' => $step_id, 'offer_type' => $offer_type, 'order_item_id' => $item_id, 'product_name' => get_the_title( $value['product_id'] ), 'sku' => $product->get_sku(), 'total' => $value->get_total(), 'cost' => $product->get_price(), 'quantity' => $value->get_quantity(), 'item_total' => 0, 'item_tax' => 0, 'transaction_id' => $transaction_id, 'is_refunded' => $is_refunded ? true : false, ); if ( get_option( 'woocommerce_calc_taxes' ) ) { $products[ $item_id ]['total'] = $products[ $item_id ]['total'] + $value->get_total_tax(); $products[ $item_id ]['item_total'] = $value->get_total(); $products[ $item_id ]['item_tax'] = $products[ $item_id ]['item_tax'] + $value->get_total_tax(); } } } ?>
0 && count( $products ) > 0 ) { ?> $product_details ) { $offer_type = $product_details['offer_type']; $product_id = $product_details['product_id']; $item_id = $key; $item = $product_details['item']; $product_name = $product_details['product_name']; $step_id = $product_details['step_id']; $offer_type = $product_details['offer_type']; $product_qty = $product_details['quantity']; $cost = $product_details['cost']; $is_refunded = $product_details['is_refunded']; $sku = $product_details['sku']; $product_amount = wc_price( $product_details['total'] ); $product = wc_get_product( $product_id ); $thumbnail = $product ? ($product->get_image( 'thumbnail' )) : ''; ?>
get_item_subtotal( $item, false, true ), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
%8s", $order_id, $step_id, $item_id, $product_qty, $product_details['total'], $product_id, $product_details['transaction_id'], __( 'Refund', 'wpfnl-pro' ) ); } else { $button_markup = '' . __( 'Refunded', 'wpfnl-pro' ) . ''; } echo $button_markup; ?>

" . __( 'Refunds are not available for any offer(s) against this order.', 'wpfnl-pro' ) . '
'; } ?> admin/partials/wpfnl-pro-admin-display.php000064400000000527147600245720014654 0ustar00 admin/class-wpfnl-db.php000064400000006112147600245720011170 0ustar00db_version = $db_version; } /** * Creating Tables for WPFunnels */ public function create_wpfnl_pro_tables() { if ( is_multisite() ) { global $wpdb; $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ); foreach ( $blog_ids as $blog_id ) { switch_to_blog( $blog_id ); $this->create_analytics_table(); $this->create_analytics_meta_table(); update_option( 'wpfunnels_pro_tables_created', 'yes' ); restore_current_blog(); } } else { $this->create_analytics_table(); $this->create_analytics_meta_table(); update_option( 'wpfunnels_pro_tables_created', 'yes' ); } } /** * WPFunnels Analytics TABLE * *@param NULL *@return NULL */ public function create_analytics_table() { global $wpdb; $table_name = $wpdb->prefix . "wpfnl_analytics"; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id BIGINT(20) NOT NULL AUTO_INCREMENT, funnel_id BIGINT(20), step_id BIGINT(20), user_id BIGINT(20) DEFAULT 0, user_ip VARCHAR(225) DEFAULT NULL, visitor_type ENUM( 'new','returning' ) NOT NULL DEFAULT 'new', analytics_data LONGTEXT NULL, date_created DATETIME DEFAULT '0000-00-00 00:00:00', date_created_gmt DATETIME DEFAULT '0000-00-00 00:00:00', date_modified DATETIME DEFAULT '0000-00-00 00:00:00', date_modified_gmt DATETIME DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (id) ) $charset_collate; "; require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); dbDelta($sql); } /** * WPFunnels Analytics Meta TABLE * *@param NULL *@return NULL */ public function create_analytics_meta_table() { global $wpdb; $analytics_table = $wpdb->prefix .WPFNL_PRO_ANALYTICS_TABLE; $analytics_meta_table = $wpdb->prefix . WPFNL_PRO_ANALYTICS_META_TABLE; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $analytics_meta_table ( id BIGINT(20) NOT NULL AUTO_INCREMENT, analytics_id BIGINT(20), funnel_id BIGINT(20) DEFAULT 0, step_id BIGINT(20) DEFAULT 0, meta_key VARCHAR(255) NULL, meta_value LONGTEXT NULL, PRIMARY KEY (id), FOREIGN KEY (`analytics_id`) REFERENCES $analytics_table(`id`) ON DELETE CASCADE ) $charset_collate; "; require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); dbDelta($sql); } } admin/class-wpfnl-pro-admin.php000064400000006700147600245720012474 0ustar00 */ class Wpfnl_Pro_Admin { /** * The ID of this plugin. * * @since 1.0.0 * @access private * @var string $plugin_name The ID of this plugin. */ private $plugin_name; /** * The version of this plugin. * * @since 1.0.0 * @access private * @var string $version The current version of this plugin. */ private $version; /** * Page hooks * * @var array */ private $page_hooks = [ 'toplevel_page_wp_funnels', 'wp-funnels_page_wp_funnel_settings', 'wp-funnels_page_edit_funnel', 'wp-funnels_page_create_funnel', 'wp-funnels_page_wpfnl_settings', 'wp-funnels_page_wpf-license', 'wp-funnels_page_email-builder', ]; /** * Initialize the class and set its properties. * * @since 1.0.0 * @param string $plugin_name The name of this plugin. * @param string $version The version of this plugin. */ public function __construct( $plugin_name, $version ) { $this->plugin_name = $plugin_name; $this->version = $version; add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } /** * Register the stylesheets for the admin area. * * @since 1.0.0 */ public function enqueue_styles($hook) { if (in_array($hook, $this->page_hooks)) { /** * This function is provided for demonstration purposes only. * * An instance of this class should be passed to the run() function * defined in Wpfnl_Pro_Loader as all of the hooks are defined * in that particular class. * * The Wpfnl_Pro_Loader will then create the relationship * between the defined hooks and the functions defined in this * class. */ wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'assets/css/wpfnl-pro-admin.css', array(), $this->version, 'all' ); } } /** * Register the JavaScript for the admin area. * * @since 1.0.0 */ public function enqueue_scripts($hook) { if (in_array($hook, $this->page_hooks)) { /** * This function is provided for demonstration purposes only. * * An instance of this class should be passed to the run() function * defined in Wpfnl_Pro_Loader as all the hooks are defined * in that particular class. * * The Wpfnl_Pro_Loader will then create the relationship * between the defined hooks and the functions defined in this * class. */ wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'assets/js/wpfnl-pro-admin.js', array( 'jquery' ), $this->version, false ); //wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/wpfnl-pro-checkout.js', array( 'jquery' ), $this->version, false ); wp_localize_script( $this->plugin_name, 'WPFunnelProVars', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'rest_api_url' => get_rest_url(), 'admin_nonce' => wp_create_nonce( 'wpfnl-admin' ), ) ); } } } admin/index.php000064400000000032147600245720007456 0ustar00init(); } return self::$instance; } /** * Initializes the Reporting class by registering actions and filters. * * @return void * @since 2.4.1 */ public function init() { add_action( 'wpfunnels/offer_accepted', array( $this, 'update_offer_data' ), 10, 2 ); add_filter( 'wpfunnels/funnels-overview-data', array($this, 'get_upsell_data'), 10, 3 ); add_filter( 'wpfunnels/stat-interval-data', array($this, 'get_upsell_stat_data'), 10, 3 ); add_filter( 'wpfunnels/top-performing-funnels-data', array($this, 'get_conversion_data'), 10 ); } /** * Update reporting data from order * * @param $order * @param $offer_product * * @since 2.4.1 */ public function update_offer_data( \WC_Order $order, $offer_product ) { if ( !$order ) { return; } global $wpdb; $table = $wpdb->prefix . 'wpfnl_stats'; $step_id = $offer_product['step_id']; $step_type = get_post_meta($step_id, '_step_type', true); $column_key = 'upsell' === $step_type ? 'upsell_sales' : 'downsell_sales'; $offer_settings = Wpfnl_functions::get_offer_settings(); if ( $offer_settings['offer_orders'] == 'main-order' ) { $order_id = $order->get_id(); $status = $order->get_status(); } else { $order_id = $order->get_meta('_wpfunnels_offer_parent_id'); $status = $order->get_status(); } $paid_date_time = current_time( 'mysql' ); $offer_total = $this->get_offer_sales( $order, $offer_product ); $total = $wpdb->get_var( $wpdb->prepare("SELECT total_sales FROM {$table} WHERE order_id = %d", $order_id )); $wpdb->update( $table, array( $column_key => $offer_total, 'total_sales' => round(floatval( $total ) + $offer_total, 2 ), 'status' => $order->get_status(), 'paid_date' => $paid_date_time, ), array( 'order_id' => $order_id, ) ); } /** * Get offer total sales data * * @param $order * @param $offer_product * @return int|string|null * * @since 2.4.1 */ public function get_offer_sales ( $order, $offer_product ) { global $wpdb; $table = $wpdb->prefix . 'wpfnl_stats'; $step_id = $offer_product['step_id']; $step_type = get_post_meta($step_id, '_step_type', true); $offer_product_data = 'upsell' === $step_type ? get_post_meta($step_id, '_wpfnl_upsell_products', true) : get_post_meta($step_id, '_wpfnl_downsell_products', true); $column_key = 'upsell' === $step_type ? 'upsell_sales' : 'downsell_sales'; $offer_products = array(); $total = 0; foreach ( $offer_product_data as $data ) { $offer_products[] = $data['id']; } foreach ( $order->get_items() as $item ) { $product_id = $item->get_product_id(); // If the product is a variation, get its variation ID if ( $item->get_variation_id() ) { $product_id = $item->get_variation_id(); } if ( in_array( $product_id, $offer_products ) ) { $total += $item->get_total(); } } $_total = $wpdb->get_var( $wpdb->prepare("SELECT %s FROM {$table} WHERE order_id = %s", $column_key, $order->get_id() ) ); $total += floatval($_total); return round( $total, 2 ); } /** * Attach offer overview data with report data * * @param $result * @param $start_date * @param $end_date * @return mixed * @since 2.4.1 */ public function get_upsell_data( $result, $start_date, $end_date ) { $total_upsell_revenue = $this->get_total_offer_sales( $start_date, $end_date ); $result['total_upsell_revenue'] = floatval(number_format(floatval($total_upsell_revenue), 2, '.', '' )); return $result; } /** * Get offer sale data * * @param $start_date * @param $end_date * @return mixed * @since 2.4.1 */ public function get_total_offer_sales( $start_date, $end_date ) { global $wpdb; $table = $wpdb->prefix. 'wpfnl_stats' ; $sql = "SELECT SUM(upsell_sales + downsell_sales) as offer_sales FROM $table"; $sql = $this->include_where_clause($sql); $result = $wpdb->get_var( $wpdb->prepare( $sql, $start_date, $end_date ) ); return $result; } /** * Get upsell stat datt * * @param $result * @param $start_date * @param $end_date * @return mixed * * @since 2.4.1 */ public function get_upsell_stat_data( $result, $start_date, $end_date ) { $total_upsell_revenue = $this->get_total_offer_sales($start_date, $end_date); $result['total_upsell_revenue'] = floatval(number_format(floatval($total_upsell_revenue), 2, '.', '' )); return $result; } /** * Get conversion data * * @param $funnel_data * @param $funnel_id * @return mixed * * @since 2.4.1 */ public function get_conversion_data( $funnel_data ) { foreach ( $funnel_data as $key => $data ) { $funnel_id = $data['id']; $views = $this->get_views($funnel_id); $conversion = $this->get_conversion($funnel_id); $conversion_rate = $this->get_conversion_rate( $views, $conversion ); $funnel_data[$key]['views'] = $views; $funnel_data[$key]['conversion'] = $conversion; $funnel_data[$key]['conversion_rate'] = $conversion_rate; } return $funnel_data; } /** * Retrieves the views for a specific funnel. * * @param int $funnel_id The ID of the funnel. * @return array An array containing the views for the specified funnel. * * @since 2.4.1 */ public function get_views( $funnel_id ) { global $wpdb; $table = $wpdb->prefix.'wpfnl_analytics'; $sql = "SELECT count(id) as total from $table WHERE funnel_id=%d AND visitor_type='new'"; $count = $wpdb->get_var($wpdb->prepare($sql, $funnel_id)); return $count; } /** * Get total number of conversion * * @param $funnel_id * @return mixed * * @since 2.4.1 */ public function get_conversion($funnel_id) { global $wpdb; $table = $wpdb->prefix.'wpfnl_stats'; $sql = "SELECT COUNT(DISTINCT o1.order_id) AS total_conversion FROM $table o1 LEFT JOIN $table o2 ON o1.parent_id = o2.order_id AND o1.funnel_id = o2.funnel_id WHERE o2.order_id IS NULL AND o1.status='completed' AND o1.funnel_id='%d'"; $count = $wpdb->get_var($wpdb->prepare($sql, $funnel_id)); if ( !$count ) { $optin_entries_table = $wpdb->prefix.'wpfnl_optin_entries'; $optin_conversion_sql = "SELECT COUNT(DISTINCT email) AS total_distinct_emails FROM $optin_entries_table WHERE funnel_id = %d;"; $count = $wpdb->get_var($wpdb->prepare($optin_conversion_sql, $funnel_id)); } return $count; } /** * Get conversion rate * * @param $views * @param $conversion * @return float|int * * @since 2.4.1 */ public function get_conversion_rate( $views, $conversion ) { if ( $views > 0 ) { return round( $conversion / ( $views / 100 ), 2 ); } else { return 0; } } /** * Include where clause * * @param $sql * @return string * * @since 2.4.1 */ public function include_where_clause( $sql ) { return $sql." WHERE paid_date >= %s AND paid_date <= %s AND status = 'completed' "; } }includes/core/ab-testing/class-wpfnl-pro-ab-testing-backup.php000064400000140554147600245720020415 0ustar00 '', 'start_settings' => [ 'auto_winner' => [ 'is_enabled' => '', 'conditions' => [ 'index' => 'trafiic', 'value' => 70 ] ], 'winner' => '', 'is_started' => '', 'start_date' => date( 'Y-m-d H:i:s' ), 'variations' => [ [ 'id' => $step_id, 'traffic' => 100, 'step_type'=> get_post_meta($step_id,'_step_type',true), 'variation_type' => 'original', 'step_edit_link' => $step_edit_link, 'step_view_link' => $step_permalink, 'step_title' => $step_title, 'conversion' => 0, 'visit' => 0, 'shouldShowAnalytics' => false, 'is_product' => 'no', 'is_ob' => 'no', ], ], 'archived_variations' => [], ] ]; return $default_settings; } return false; } public static function get_default_data( $step_id ) { $step_edit_link = get_edit_post_link($step_id,'wpfunnel_steps'); if( !$step_edit_link ){ $step = get_post( $step_id ); $post_type_object = get_post_type_object( $step->post_type ); if ( 'revision' === $post->post_type ) { $action = ''; } elseif ( 'display' === $context ) { $action = '&action=edit'; } else { $action = '&action=edit'; } if ( 'wp_template' === $step->post_type || 'wp_template_part' === $step->post_type ) { $slug = urlencode( get_stylesheet() . '//' . $step->post_name ); $step_edit_link = admin_url( sprintf( $post_type_object->_edit_link, $step->post_type, $slug ) ); } elseif ( 'wp_navigation' === $step->post_type ) { $step_edit_link = admin_url( sprintf( $post_type_object->_edit_link, (string) $step->ID ) ); } elseif ( $post_type_object->_edit_link ) { $step_edit_link = admin_url( sprintf( $post_type_object->_edit_link . $action, $step->ID ) ); } } if( 'elementor' == Wpfnl_functions::get_builder_type() ){ $step_edit_link = str_replace('&','&',$step_edit_link); $step_edit_link = str_replace('edit','elementor',$step_edit_link); } $data = [ 'auto_winner' => [ 'is_enabled' => '', 'conditions' => [ 'index' => 'trafiic', 'value' => 70 ] ], 'winner' => '', 'is_started' => '', 'start_date' => date( 'Y-m-d H:i:s' ), 'variations' => [ [ 'id' => $step_id, 'traffic' => 100, 'step_type'=> get_post_meta($step_id,'_step_type',true), 'variation_type' => 'original', 'step_edit_link' => $step_edit_link, 'step_view_link' => get_permalink($step_id), 'step_title' => get_the_title($step_id), 'conversion' => 0, 'visit' => 0, 'shouldShowAnalytics' => false, ], ], 'archived_variations' => [] ]; return $data; } /** * Update start settings of A/B testing * * @param Number $step_id * @param Array $data * * @return Bool * @since 1.6.21 */ public static function update_start_settings( $step_id, $data = [] ){ if( $step_id && !empty($data) ){ update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings' , $data ); return true; } return false; } /** * @desc Get start settings of A/B testing * @since 1.6.21 * * @param $step_id * @param $key * @return false|mixed */ public static function get_start_settings( $step_id, $key = '' ) { if( $step_id ) { $response = get_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', true ); if( $response ) { if( $key && isset( $response[ $key ] ) ) { return $response[ $key ]; } return $response; } } return false; } /** * @desc Get statistics for A/B testing. * * @param $funnel_id * @param $step_id * @return array */ public static function get_stats( $funnel_id, $step_id ) { $stats = []; if( $step_id && $funnel_id ) { global $wpdb; $analytics_table = $wpdb->prefix . WPFNL_PRO_ANALYTICS_TABLE; $analytics_meta_table = $wpdb->prefix . WPFNL_PRO_ANALYTICS_META_TABLE; $start_settings = self::get_start_settings( $step_id ); $variations = isset($start_settings['variations']) ? $start_settings['variations'] : []; $variation_ids = array_column( $variations, 'id' ); $str_variation_ids = implode( ', ', $variation_ids ); $variation_ids = explode( ', ', $str_variation_ids ); if( isset($start_settings['is_started']) && 'yes' == $start_settings['is_started'] ){ if( $variations ) { $query = "SELECT wpfnlt1.step_id AS step_id, "; $query .= "COUNT( wpfnlt1.id ) AS total_visits, "; $query .= "COUNT( DISTINCT( CASE WHEN wpfnlt1.visitor_type = 'new' THEN wpfnlt1.id ELSE NULL END ) ) AS unique_visits, "; $query .= "COUNT( CASE WHEN wpfnlt2.meta_key = 'conversion' AND wpfnlt2.meta_value = %s "; $query .= "THEN wpfnlt1.step_id ELSE NULL END ) AS conversions "; $query .= "FROM {$analytics_table} AS wpfnlt1 "; $query .= "INNER JOIN {$analytics_meta_table} AS wpfnlt2 "; $query .= "ON wpfnlt1.id = wpfnlt2.analytics_id "; $query .= "WHERE wpfnlt1.step_id IN (%s) "; $query .= "AND wpfnlt2.meta_key = %s "; $query .= "AND wpfnlt1.date_created >= %s "; $query .= "GROUP BY wpfnlt1.step_id"; $query = $wpdb->prepare( $query, 'yes', $str_variation_ids, 'conversion', self::get_start_settings( $step_id, 'start_date' ) ); $query = str_replace( '(\'', '(', $query ); $query = str_replace( '\')', ')', $query ); $visits_data = $wpdb->get_results( $query, ARRAY_A ); if( !empty( $visits_data ) ) { $funnel_type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ); $funnel_type = !$funnel_type ? 'wc' : $funnel_type; $param_type = wpfnl_pro_analytics_get_param_type( $funnel_type ); $funnel_orders = $param_type->get_orders_by_funnel( $funnel_id, '', '' ); foreach( $visits_data as $data ) { $variation_id = isset( $data[ 'step_id' ] ) ? $data[ 'step_id' ] : ''; $total_visit = isset( $data[ 'total_visits' ] ) ? $data[ 'total_visits' ] : ''; $unique_visits = isset( $data[ 'unique_visits' ] ) ? $data[ 'unique_visits' ] : ''; $conversion = isset( $data[ 'conversions' ] ) ? $data[ 'conversions' ] : ''; $earnings = $param_type->get_earnings( $funnel_id, $funnel_orders, '', '', 'step_revenue', $variation_id ); if ( ( $key = array_search( $variation_id, $variation_ids ) ) !== false ) { unset( $variation_ids[ $key ] ); } $variation_key = array_search($variation_id, array_column($variations, 'id')); $variation_type = 'original'; $is_winner = ''; if( false !== $variation_key ){ $variation_type = $variations[$variation_key]['variation_type']; $is_winner = $variation_id == $start_settings['winner'] ? 'yes' : ''; } $stats[] = [ 'variation_id' => $variation_id, 'variation_name' => $variation_id ? get_the_title( $variation_id ) : '', 'total_visit' => $total_visit, 'unique_visits' => $unique_visits, 'conversion' => $conversion, 'variation_type' => $variation_type, 'is_winner' => $is_winner, 'conversion_rate' => self::calculate_conversion_rate( $total_visit, $conversion ), 'revenue' => isset( $earnings[ 'gross_sale_with_html' ] ) ? $earnings[ 'gross_sale_with_html' ] : '', 'float_revenue' => isset( $earnings[ 'revenue' ] ) ? $earnings[ 'revenue' ] : '', 'currency' => isset( $earnings[ 'currency' ] ) ? $earnings[ 'currency' ] : '', ]; } } } } if( $variation_ids ) { foreach( $variation_ids as $variation_id ) { $is_winner = ''; $variation_key = array_search($variation_id, array_column($variations, 'id')); $variation_type = 'original'; if( false !== $variation_key ){ $variation_type = $variations[$variation_key]['variation_type']; $is_winner = $variation_id == $start_settings['winner'] ? 'yes' : ''; } $stats[] = [ 'variation_id' => $variation_id, 'variation_name' => $variation_id ? get_the_title( $variation_id ) : '', 'total_visit' => 0, 'unique_visits' => 0, 'conversion' => 0, 'variation_type' => $variation_type, 'conversion_rate' => '0.00', 'is_winner' => $is_winner, 'revenue' => number_format( (float) 0, 2, '.', '' ), 'float_revenue' => number_format( (float) 0, 2, '.', '' ), 'currency' => '', ]; } } } return $stats; } /** * @desc Get statistics for A/B testing. * * @param $funnel_id * @param $step_id * @return array */ public static function get_stats_of_a_step( $funnel_id, $step_id ) { $stats = [ 'total_visit' => 0, 'unique_visits' => 0, 'conversion' => 0, ]; if( $step_id && $funnel_id ) { global $wpdb; $analytics_table = $wpdb->prefix . WPFNL_PRO_ANALYTICS_TABLE; $analytics_meta_table = $wpdb->prefix . WPFNL_PRO_ANALYTICS_META_TABLE; $query = "SELECT wpfnlt1.step_id AS step_id, "; $query .= "COUNT( wpfnlt1.id ) AS total_visits, "; $query .= "COUNT( DISTINCT( CASE WHEN wpfnlt1.visitor_type = 'new' THEN wpfnlt1.id ELSE NULL END ) ) AS unique_visits, "; $query .= "COUNT( CASE WHEN wpfnlt2.meta_key = 'conversion' AND wpfnlt2.meta_value = %s "; $query .= "THEN wpfnlt1.step_id ELSE NULL END ) AS conversions "; $query .= "FROM {$analytics_table} AS wpfnlt1 "; $query .= "INNER JOIN {$analytics_meta_table} AS wpfnlt2 "; $query .= "ON wpfnlt1.id = wpfnlt2.analytics_id "; $query .= "WHERE wpfnlt1.step_id = %d "; $query .= "AND wpfnlt2.meta_key = %s "; $query .= "GROUP BY wpfnlt1.step_id"; $query = $wpdb->prepare( $query, 'yes', $step_id, 'conversion' ); $query = str_replace( '(\'', '(', $query ); $query = str_replace( '\')', ')', $query ); $visits_data = $wpdb->get_results( $query, ARRAY_A ); if( !empty( $visits_data ) ) { foreach( $visits_data as $data ) { $variation_id = isset( $data[ 'step_id' ] ) ? $data[ 'step_id' ] : ''; $total_visit = isset( $data[ 'total_visits' ] ) ? $data[ 'total_visits' ] : ''; $unique_visits = isset( $data[ 'unique_visits' ] ) ? $data[ 'unique_visits' ] : ''; $conversion = isset( $data[ 'conversions' ] ) ? $data[ 'conversions' ] : ''; $stats = [ 'total_visit' => $total_visit, 'unique_visits' => $unique_visits, 'conversion' => $conversion, ]; } } } return $stats; } /** * @desc Calculate and get conversion rate * * @param $total_visit * @param $conversion * @return float|int */ public static function calculate_conversion_rate( $total_visit, $conversion ) { return number_format((float)( $conversion * 100 ) / $total_visit, 2, '.', ''); } /** * Declear winner * * @param Number $step_id * @return Bool * */ public static function set_winner( $step_id, $variation_id ){ if( $step_id ){ $settings = get_post_meta( $step_id, '_wpfnl_ab_testing_start_settings' , true ); if( $settings ){ $settings['winner'] = $variation_id; update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings' , $settings ); return true; } } return false; } /** * Declear winner * * @param Number $step_id * @return Bool * */ public static function get_winner( $step_id ){ if( $step_id ){ $settings = get_post_meta( $step_id, '_wpfnl_ab_testing_start_settings' , true ); if( isset($settings['winner']) && $settings['winner'] ){ return $settings['winner']; } } return false; } /** * Get variation setp Id * Choose varaition step url if AB testing is enabled * * @return Int * @since 1.6.17 */ public static function get_displayable_variation_id( $all_variations, $base_step_id ){ $rand = function_exists( 'mt_rand' ) ? mt_rand( 0, 100 ) : rand( 0, 100 ); $measurement = 0; foreach ( $all_variations as $variation ) { $traffic = intval( $variation['traffic'] ); if ( ( $rand >= $measurement ) && ( $rand <= ( $measurement + $traffic ) ) ) { return $variation['id']; } $measurement += $traffic; } return $base_step_id; } /** * Get all ab-testing variations for each step * * @param Int * @return Array * @since 1.6.17 * */ public static function get_all_variations( $step_id ){ $start_setting = self::get_start_settings( $step_id ); $variations = []; if( isset($start_setting['variations'] ) ){ $variations = isset($start_setting['variations']) ? $start_setting['variations'] : []; } return $variations; } /** * Create or update varient of a step * @param Int @step_id * @param Int @varient_id * */ public static function update_variations( $step_id, $varient_id ){ $start_setting = self::get_start_settings( $step_id ); $variations = []; $step_edit_link = get_edit_post_link($varient_id); if( 'elementor' == Wpfnl_functions::get_builder_type() ){ $step_edit_link = str_replace('&','&',$step_edit_link); $step_edit_link = str_replace('edit','elementor',$step_edit_link); } if( $start_setting ){ $variation =[ 'id' => $varient_id, 'step_type' => get_post_meta($varient_id,'_step_type',true), 'traffic' => 0, 'locked' => false, 'variation_type' => 'variation', 'step_edit_link' => $step_edit_link, 'step_view_link' => get_post_permalink($varient_id), 'step_title' => get_the_title($varient_id), 'conversion' => 0, 'visit' => 0, 'shouldShowAnalytics' => false, ]; array_push( $start_setting['variations'], $variation); update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', $start_setting); $variations = $start_setting['variations']; }else{ $variation = [ 'id' => $varient_id, 'step_type' => get_post_meta($varient_id,'_step_type',true), 'traffic' => 0, 'locked' => false, 'variation_type' => 'variation', 'step_edit_link' => $step_edit_link, 'step_view_link' => get_post_permalink($varient_id), 'step_title' => get_the_title($varient_id), 'conversion' => 0, 'visit' => 0, 'shouldShowAnalytics' => false, ]; $settings = self::get_default_data($step_id); array_push( $settings['variations'], $variation); update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', $settings); update_post_meta( $step_id, '_wpfnl_is_ab_testing' , 'yes' ); $variations = $settings['variations']; } return $variations; } /** * Get all ab-testing variations for each step * * @param Int * @param Int * @return Array * @since 1.6.17 * */ public static function get_start_date( $funnel_id, $step_id ){ $steps = get_post_meta( $funnel_id, '_steps_order', true ); $key = array_search($step_id, array_column($steps, 'id')); if( false !== $key ){ if( isset($steps[$key]['ab_test_start_time']) ){ $date = strtotime($steps[$key]['ab_test_start_time']); $saved_date = date('d/M/Y h:i:s', $date); return $saved_date; } } return []; } /** * Get all ab-testing conditions for each step * * @param Int * @param Int * @return Bool * @since 1.6.17 * */ public static function get_all_conditions( $step_id ){ $start_setting = self::get_start_settings( $step_id ); $conditions = []; if( isset($start_setting['auto_winner'] ) ){ if( isset($start_setting['auto_winner']['is_enabled']) && 'yes' === $start_setting['auto_winner']['is_enabled'] ){ $conditions = isset($start_setting['auto_winner']['conditions']) ? $start_setting['auto_winner']['conditions'] : []; } } return $conditions; } /** * match condition * * @param Array * @param Int * @param Int * * @since 1.6.17 */ public static function match_condition( $all_conditions, $funnel_id, $step_id ){ if(!empty($all_conditions) ){ // foreach( $all_conditions as $key=>$condition ){ if( 'conversion' === $all_conditions['index'] ){ global $wpdb; $table_name = $wpdb->prefix.'wpfnl_analytics_meta'; $results = $wpdb->get_results( "SELECT * FROM ".$table_name." WHERE funnel_id = ".$funnel_id." AND step_id = ".$step_id." AND meta_key = 'conversion' AND meta_value = 'no'"); if( count($results) == $all_conditions['value'] ){ return true; } } elseif( 'trafiic' === $all_conditions['index'] ){ global $wpdb; $table_name = $wpdb->prefix.'wpfnl_analytics_meta'; $results = $wpdb->get_results( "SELECT * FROM ".$table_name." WHERE funnel_id = ".$funnel_id." AND step_id = ".$step_id." AND meta_key = 'conversion'"); if( count($results) >= $all_conditions['value'] ){ return true; } } elseif( 'date' === $all_conditions['index'] ){ $current_date = date('d/M/Y'); $date = strtotime($all_conditions['value']); $saved_date = date('d/M/Y', $date); if( $current_date == $saved_date ){ return true; } } // } } return false; } /** * @desc Get all ab-testing archived step ids for each step * * @param $funnel_id * @param $step_id * @return array|mixed */ public static function get_all_archived_variations( $funnel_id, $step_id ){ $steps = get_post_meta( $funnel_id, '_steps_order', true ); $key = array_search($step_id, array_column($steps, 'id')); if( false !== $key ){ if( isset($steps[$key]['archived_variations']) ){ return $steps[$key]['archived_variations']; } } return []; } /** * Set ab-testings cookie * * @param Int * * @since 1.6.17 * @return void */ public static function set_cookie( $funnel_id, $step_id, $show_variation_id ){ $cookie_name = WPFNL_AB_TESTING_COOKIE_KEY . $step_id; $cookiepath = self::get_cookiepath(); $expire_time = time() + ( 24 * 60 * MINUTE_IN_SECONDS ); $value = $show_variation_id; setcookie( $cookie_name, $value, $expire_time, $cookiepath, COOKIE_DOMAIN ); } /** * Get ab-testings cookie * * @param Int * * @since 1.6.17 * @return Mix Int or boolean */ public static function get_cookie( $step_id ) { $cookie_name = WPFNL_AB_TESTING_COOKIE_KEY . $step_id; if ( isset( $_COOKIE[ $cookie_name ] ) ) { return intval( $_COOKIE[ $cookie_name ] ); } return false; } /** * get wp cookie path * * @return string|string[]|null */ public static function get_cookiepath() { return COOKIEPATH ? COOKIEPATH : '/'; } /** * Get displayable variation ID for redirect * * @param Int * * @return Mix * @since 1.6.17 * */ public static function get_ab_testing_variation_id( $funnel_id, $step_id ){ $is_enable = self::maybe_ab_testing( $step_id ); if( $is_enable ){ $is_winner = self::get_winner( $step_id ); if( $is_winner ){ return $is_winner; } $cookie = self::get_cookie( $step_id ); if( $cookie ){ return $cookie; }else{ $all_variations = self::get_all_variations( $step_id ); $displayable_variation_id = self::get_displayable_variation_id( $all_variations, $step_id ); self::set_cookie( $funnel_id, $step_id, $displayable_variation_id ); return $displayable_variation_id; } } return false; } /** * Convert AB testing variation to pulish from archive * * @param Int * @param Int * @param Int * * @since 1.6.17 */ public static function archive_to_publish( $funnel_id, $step_id, $archived_step_id ){ $archived_steps = self::get_all_archived_variations( $funnel_id, $step_id ); $key = array_search($archived_step_id, array_column($archived_steps, 'id')); if( false !== $key ){ $steps = get_post_meta( $funnel_id, '_steps_order', true ); $step_key = array_search($step_id, array_column($steps, 'id')); if( false !== $step_key ){ array_push($steps[$step_key]['variations'],$archived_steps[$key]); unset($archived_steps[$key]); $steps[$step_key]['archived_variations'] = $archived_steps; update_post_meta( $funnel_id, '_steps_order', $steps ); } } } /** * Convert AB testing variation to pulish from archive * * @param Int * @param Int * @param Int * * @since 1.6.17 */ public static function publish_to_archive( $funnel_id, $step_id, $variation_step_id ){ $variations = self::get_all_variations( $step_id ); $key = array_search($variation_step_id, array_column($variations, 'id')); if( false !== $key ){ unset($variations[$key]); $steps = get_post_meta( $funnel_id, '_steps_order', true ); $step_key = array_search($step_id, array_column($steps, 'id')); if( false !== $step_key ){ array_push($steps[$step_key]['archived_variations'],$variations[$key]); unset($variations[$key]); $steps[$step_key]['variations'] = $variations; update_post_meta( $funnel_id, '_steps_order', $steps ); } } } /** * @desc Reset the stats for the current step * and all of its variations * * @param $step_id * @return bool */ public static function reset_stats( $step_id ) { if( $step_id ){ $ab_start_settings = self::get_start_settings( $step_id ); if( isset( $ab_start_settings[ 'start_date' ] ) ) { $ab_start_settings[ 'start_date' ] = date( 'Y-m-d H:i:s' ); } return self::update_start_settings( $step_id, $ab_start_settings ); } return false; } /** * @desc Reset the stats for the current step * and all of its variations * * @param $step_id * @return bool */ public static function reset_settings( $step_id ) { if( $step_id ){ $ab_start_settings = self::get_start_settings( $step_id ); if( isset( $ab_start_settings[ 'variations' ], $ab_start_settings['auto_winner'] ) ) { $count = count($ab_start_settings['variations']); $avg = floor(100/$count); $total = 0; $ab_start_settings['auto_winner'] = [ 'is_enabled' => '', 'conditions' => [ 'index' => 'trafiic', 'value' => 70 ] ]; foreach( $ab_start_settings[ 'variations' ] as $key=>$variation ){ $total = $total + $avg; $ab_start_settings['variations'][$key]['traffic'] = $avg; } if( $total != 100 ){ $ab_start_settings[ 'variations' ][0]['traffic'] = $ab_start_settings[ 'variations' ][0]['traffic'] + ( 100 - $total ); } } return self::update_start_settings( $step_id, $ab_start_settings ); } return false; } /** * duplicate all meta key and values * * * @param $parent_id * @param $post_id * @param $step_type */ public static function duplicate_ab_testing_meta( $parent_id, $post_id, $exclude_meta = array(), $raw = false ) { global $wpdb; $exclude_sql = ''; if( !empty($exclude_meta) ) { $metas = implode("', '",$exclude_meta ); $exclude_sql = "AND meta_key NOT IN ('".$metas."')"; } $post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE (post_id=$parent_id {$exclude_sql})"); if (count($post_meta_infos)!=0) { $insert_sql_query = "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value) VALUES"; $sql_query_arr = []; foreach ($post_meta_infos as $meta_info) { $meta_key = $meta_info->meta_key; if( $meta_key == '_wp_old_slug' ) continue; if( $meta_key == 'funnel_automation_data' ) continue; if ( $raw ) { $meta_value = get_post_meta( $parent_id, $meta_key,true ); update_post_meta($post_id, $meta_key, $meta_value); } $sql_query_arr[] = $wpdb->prepare( '( %d, %s, %s )', $post_id, $meta_key, $meta_info->meta_value ); } if (!$raw) { $insert_sql_query .= implode( ',', $sql_query_arr ); $wpdb->query( $insert_sql_query ); } } } /** * Get Formatted ab testing settings * @param Integer $step_id * @return Array * @since 1.7.1 */ public static function get_formatted_settings( $step_id ){ if( isset( $step_id ) ){ $default_settings = self::get_default_start_setting( $step_id ); //check A/B testing is enable or not $result = self::maybe_ab_testing( $step_id ); $default_settings['is_ab_enabled'] = $result ? $result : ''; // get start settings $result = self::get_start_settings( $step_id ); $default_settings['start_settings'] = $result ? $result : $default_settings['start_settings']; $response['data'] = $default_settings; }else{ $response['data'] = ''; } return $response; } /** * Get parent step ID by variant ID from postmeta * @param Integrer $variant_id * @return Mix * @since 1.7.1 */ public static function get_parent_step_id( $variant_id ){ if( $variant_id ){ $step_id = get_post_meta( $variant_id, '_parent_step_id', true ); return $step_id; } return false; } /** * Archive all variants * Archive all variants except winner and make winner step as original step * * @param Int $step_id * @param Int $variant_id * * @return Bool * @since 1.7.4 */ public static function archive_all_variant( $step_id, $variant_id, $start_date = '', $stats_data = [] ){ if( $step_id && $variant_id ){ $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); $start_settings = self::get_start_settings( $step_id ); $archived_variations = isset($start_settings['archived_variations']) ? $start_settings['archived_variations'] : []; if( $start_settings && $funnel_id ){ $is_original = $step_id == $variant_id; //Check the parent step id is equal to winner id or not foreach( $start_settings['variations'] as $key => $variations ){ if( $variations['id'] != $variant_id ){ $archived_variation = $variations; $archived_variation['start_date'] = date('d M,Y',strtotime($start_date)); $archived_variation['start_time'] = date('H:i A',strtotime($start_date)); $index = array_search($variations['id'], array_column($stats_data, 'variation_id')); if( false !== $index ){ $archived_variation['conversion_rate'] = $stats_data[$index]['conversion_rate']; $archived_variation['revenue'] = $stats_data[$index]['float_revenue']; $archived_variation['currency'] = $stats_data[$index]['currency']; $archived_variation['visit'] = $stats_data[$index]['total_visit']; $archived_variation['conversion'] = $stats_data[$index]['conversion']; } $archived_variation['end_date'] = date( 'd M,Y' ); $archived_variation['end_time'] = date( 'H:i A' ); array_push( $archived_variations, $archived_variation ); unset($start_settings['variations'][$key]); } } $formatted_variations = []; foreach ( $start_settings['variations'] as $key=> $variation ){ array_push( $formatted_variations, $variation ); } $start_settings['variations'] = $formatted_variations; $response = []; if( !$is_original ){ $payload = [ 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'variant_id' => $variant_id, 'start_settings'=> $start_settings, ]; $response = self::update_winner_variation( $payload ); $start_settings = isset($response['start_settings']) ? $response['start_settings'] : $start_settings; }else{ $response = self::update_funnel_data( $funnel_id, $step_id, $variant_id ); } $start_settings['winner'] = ''; $start_settings['archived_variations'] = $archived_variations; update_post_meta( $variant_id, '_wpfnl_ab_testing_start_settings', $start_settings ); $ab_start_settings = Wpfnl_Ab_Testing::get_formatted_settings( $variant_id ); $result = [ 'funnel_data' => isset($response['funnel_data']) ? $response['funnel_data'] : [], 'ab_start_settings' => $ab_start_settings, 'node_id' => isset($response['node_id']) ? $response['node_id'] : '', 'node_data' => isset($response['node_data']) ? $response['node_data'] : [], ]; return $result; } } return false; } /** * Archive single variant * * @param Int $step_id * @param Int $variant_id * * @return Mix * @since 1.7.5 */ public static function single_archive( $step_id, $variant_id, $start_date = '', $stats_data = []){ if( $step_id && $variant_id ){ $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); $start_settings = self::get_start_settings( $step_id ); $archived_variations = isset($start_settings['archived_variations']) ? $start_settings['archived_variations'] : []; if( $start_settings && $funnel_id ){ $is_original = $step_id == $variant_id; //Check the parent step id is equal to winner id or not foreach( $start_settings['variations'] as $key => $variations ){ if( $variations['id'] == $variant_id ){ $archived_variation = $variations; $archived_variation['start_date'] = date('d M,Y',strtotime($start_date)); $archived_variation['start_time'] = date('H:i A',strtotime($start_date)); $index = array_search($variations['id'], array_column($stats_data, 'variation_id')); if( false !== $index ){ $archived_variation['conversion_rate'] = $stats_data[$index]['conversion_rate']; $archived_variation['revenue'] = $stats_data[$index]['float_revenue']; $archived_variation['currency'] = $stats_data[$index]['currency']; $archived_variation['visit'] = $stats_data[$index]['total_visit']; $archived_variation['conversion'] = $stats_data[$index]['conversion']; } $archived_variation['end_date'] = date( 'd M,Y' ); $archived_variation['end_time'] = date( 'H:i A' ); array_push( $archived_variations, $archived_variation ); unset($start_settings['variations'][$key]); } } $formatted_variations = []; foreach ( $start_settings['variations'] as $key=> $variation ){ array_push( $formatted_variations, $variation ); } $is_multiple = count($formatted_variations) > 1 ? true : false; $start_settings['variations'] = $formatted_variations; $response = []; if( $is_original ){ $maybe_original_variant = isset( $start_settings['variations'][0]['id'] ) ? $start_settings['variations'][0]['id'] : ''; if( $maybe_original_variant ){ $response = self::update_funnel_data( $funnel_id, $step_id, $maybe_original_variant, $is_multiple ); $step_id = $maybe_original_variant; } }else{ $response = self::update_funnel_data( $funnel_id, $step_id, $step_id, $is_multiple ); } $start_settings['archived_variations'] = $archived_variations; update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', $start_settings ); $ab_start_settings = Wpfnl_Ab_Testing::get_formatted_settings( $step_id ); $result = [ 'funnel_data' => isset($response['funnel_data']) ? $response['funnel_data'] : [], 'ab_start_settings' => $ab_start_settings, 'step_id' => $step_id, 'node_id' => isset($response['node_id']) ? $response['node_id'] : '', 'node_data' => isset($response['node_data']) ? $response['node_data'] : [], 'is_multiple_variant' => $is_multiple ? 'yes' : 'no', ]; return $result; } } return false; } /** * Restore archive * * @param Int $step_id * @param Int $variant_id * * @return Bool * @since 1.7.5 */ public static function restore_archive_variant( $step_id, $variant_id, $is_permanent_delete = true ){ if( $step_id && $variant_id ){ $start_settings = self::get_start_settings( $step_id ); if( isset( $start_settings['archived_variations'] ) ){ foreach( $start_settings['archived_variations'] as $key => $variations ){ if( $variant_id == $variations['id'] ){ array_splice($start_settings['archived_variations'], $key, 1); } } update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', $start_settings ); if( !$is_permanent_delete ){ self::update_variations( $step_id, $variant_id ); } return true; } } return false; } /** * Delete archive variant permanently * * @param Int $step_id * @param Int $variant_id * * @return Array * @since 1.7.5 */ public static function delete_archive( $step_id, $variant_id ){ if( $step_id && $variant_id ){ $response = self::restore_archive_variant( $step_id, $variant_id, true ); if( $response ){ wp_delete_post( $variant_id ); return true; } } return false; } /** * Modify ab testing start settings * * @param Obj $parent_post * @param Int $variant_id * @param Array $start_settings * * @return Array * @since 1.7.4 */ public static function modify_start_settings( $parent_post, $variant_id, $start_settings ){ wp_update_post([ "ID" => $variant_id, "post_title" => wp_strip_all_tags( $parent_post->post_title ), "post_name" => $parent_post->post_name, ]); $step_edit_link = get_edit_post_link($variant_id); if( 'elementor' == Wpfnl_functions::get_builder_type() ){ $step_edit_link = str_replace('&','&',$step_edit_link); $step_edit_link = str_replace('edit','elementor',$step_edit_link); } $variation_key = array_search($variant_id, array_column($start_settings['variations'], 'id')); if( false !== $variation_key ){ $start_settings['variations'][$variation_key]['variation_type'] = 'original'; $start_settings['variations'][$variation_key]['step_title'] = wp_strip_all_tags( $parent_post->post_title ); $start_settings['variations'][$variation_key]['step_view_link'] = get_the_permalink($variant_id); $start_settings['variations'][$variation_key]['step_edit_link'] = $step_edit_link; } return $start_settings; } /** * Update winner variation as original step * @param Array $payload * @return Array * @since 1.7.4 */ public static function update_winner_variation( $payload ){ $funnel_id = isset($payload['funnel_id']) ? $payload['funnel_id'] : ''; $step_id = isset($payload['step_id']) ? $payload['step_id'] : ''; $variant_id = isset($payload['variant_id']) ? $payload['variant_id'] : ''; $start_settings = isset($payload['start_settings']) ? $payload['start_settings'] : []; $parent_post = get_post( $step_id ); $start_settings = self::modify_start_settings( $parent_post, $variant_id, $start_settings ); if( $funnel_id ){ $response = self::update_funnel_data( $funnel_id, $step_id, $variant_id ); $response['start_settings'] = $start_settings; update_post_meta($variant_id, '_funnel_id', $funnel_id); delete_post_meta( $step_id, '_wpfnl_ab_testing_start_settings' ); // wp_delete_post( $step_id ); } return $response; } /** * Update funnel data * @param Int $funnel_id * @param Int $step_id * @param Int $variant_id * * @return Array * @since 1.7.4 */ public static function update_funnel_data( $funnel_id, $step_id, $variant_id, $is_multiple = false ){ $funnel_data = get_post_meta( $funnel_id, 'funnel_data', true ); $steps = isset($funnel_data['drawflow']['Home']['data']) ? $funnel_data['drawflow']['Home']['data'] : []; $node_data = []; $node_id = ''; if( is_array($steps) && count($steps) ){ foreach( $steps as $key=>$step ){ $step_data = isset($step['data']) ? $step['data'] : []; if( isset($step_data['step_id']) && $step_id == $step_data['step_id'] ){ $node_id = $step['id']; $steps[$key]['class'] = $is_multiple ? $steps[$key]['class'] : trim(str_replace('has-ab-variation', '', $steps[$key]['class'] )); $steps[$key]['html'] = trim(str_replace($step_id, $variant_id, $steps[$key]['html'] )); $step_data['step_id'] = $variant_id; $step_data['step_edit_link'] = base64_encode( get_edit_post_link( $variant_id ) ); $step_data['step_view_link'] = base64_encode( rtrim( get_the_permalink( $variant_id ), '/' ) ); $steps[$key]['data'] = $step_data; $node_data = [ 'class' => $is_multiple ? $steps[$key]['class'] : trim(str_replace('has-ab-variation', '', $steps[$key]['class'] )), 'html' => trim(str_replace($step_id, $variant_id, $steps[$key]['html'] )), 'pos_x' => $steps[$key]['pos_x'], 'pos_y' => $steps[$key]['pos_y'], 'data' => [ 'step_id' => $variant_id, 'step_edit_link' => base64_encode( get_edit_post_link( $variant_id ) ), 'step_view_link' => base64_encode( rtrim( get_the_permalink( $variant_id ), '/' ) ), 'step_name' => get_the_title( $variant_id ), 'step_type' => $steps[$key]['data']['step_type'], ], ]; } } $funnel_data['drawflow']['Home']['data'] = $steps; update_post_meta( $funnel_id, 'funnel_data', $funnel_data ); } $step_order = get_post_meta( $funnel_id, '_steps_order', true ); $step_order = self::update_step_order( $step_order, $funnel_id, $step_id, $variant_id ); return [ 'funnel_data' => $funnel_data, 'step_order' => $step_order, 'node_data' => $node_data, 'node_id' => $node_id, ]; } /** * Update step order for funnel * * @param Array $step_order * @param Int $funnel_id * @param Int $step_id * @param Int $variant_id * * @return Array * @since 1.7.4 */ public static function update_step_order( $step_order, $funnel_id, $step_id, $variant_id ){ if( $step_id != $variant_id ){ if( is_array($step_order && !empty($step_order)) ){ foreach($step_order as $key=>$step ){ if( $step['id'] == $step_id ){ $step_order[$key]['id'] = $variant_id; } } delete_post_meta($step_id, '_funnel_id' ); update_post_meta( $variant_id, '_funnel_id', $funnel_id ); update_post_meta( $funnel_id, '_steps_order', $step_order ); } } return $step_order; } }includes/core/ab-testing/class-wpfnl-pro-ab-testing.php000064400000203272147600245720017147 0ustar00 'no', 'startDate' => date('Y-m-d H:i:s'), 'endDate' => date('Y-m-d H:i:s'), 'variations' => [ self::make_variation_array($step_id, true), ], 'archived_variations' => [], ]; return $default_settings; } return false; } public static function get_default_data($step_id) { $step_edit_link = get_edit_post_link($step_id); $step_permalink = get_the_permalink($step_id); $step_title = get_the_title($step_id); $step_edit_link = str_replace('&', '&', $step_edit_link); if ('elementor' == Wpfnl_functions::get_builder_type()) { $step_edit_link = str_replace('edit', 'elementor', $step_edit_link); }else{ $step_edit_link = str_replace('elementor', 'edit', $step_edit_link); } $default_settings = [ 'isStart' => 'yes', 'startDate' => date('Y-m-d H:i:s'), 'endDate' => date('Y-m-d H:i:s'), 'variations' => [ [ self::make_variation_array($step_id, true) ], ], 'archived_variations' => [], ]; return $default_settings; } /** * Update start settings of A/B testing * * @param Number $step_id * @param Array $data * * @return Bool * @since 1.6.21 */ public static function update_start_settings($step_id, $data = []) { if ($step_id && !empty($data)) { update_post_meta($step_id, '_wpfnl_ab_testing_start_settings', $data); return true; } return false; } /** * @desc Get start settings of A/B testing * @since 1.6.21 * * @param $step_id * @param $key * @return false|mixed */ public static function get_start_settings($step_id, $key = '') { if ($step_id) { $response = get_post_meta($step_id, '_wpfnl_ab_testing_start_settings', true); if ($response) { if ($key && isset($response[$key])) { return $response[$key]; } return $response; } } return false; } /** * @desc Get statistics for A/B testing. * * @param $funnel_id * @param $step_id * @return array */ public static function get_settings_with_stats($step_id, $start_settings ) { if (!$step_id || empty($start_settings['variations'])) { return $start_settings; } $funnel_id = get_post_meta($step_id, '_funnel_id', true); if (!$funnel_id) { return $start_settings; } global $wpdb; $analytics_table = $wpdb->prefix . WPFNL_PRO_ANALYTICS_TABLE; $analytics_meta_table = $wpdb->prefix . WPFNL_PRO_ANALYTICS_META_TABLE; $variations = $start_settings['variations']; $variation_ids = array_column($variations, 'stepId'); $str_variation_ids = implode(', ', $variation_ids); $variation_ids = explode(', ', $str_variation_ids); if (isset($start_settings['isStart']) && 'yes' == $start_settings['isStart']) { if ($variations) { $query = "SELECT wpfnlt1.step_id AS step_id, "; $query .= "COUNT( wpfnlt1.id ) AS total_visits, "; $query .= "COUNT( DISTINCT( CASE WHEN wpfnlt1.visitor_type = 'new' THEN wpfnlt1.id ELSE NULL END ) ) AS unique_visits, "; $query .= "COUNT( CASE WHEN wpfnlt2.meta_key = 'conversion' AND wpfnlt2.meta_value = %s "; $query .= "THEN wpfnlt1.step_id ELSE NULL END ) AS conversions "; $query .= "FROM {$analytics_table} AS wpfnlt1 "; $query .= "INNER JOIN {$analytics_meta_table} AS wpfnlt2 "; $query .= "ON wpfnlt1.id = wpfnlt2.analytics_id "; $query .= "WHERE wpfnlt1.step_id IN (%s) "; $query .= "AND wpfnlt2.meta_key = %s "; $query .= "AND wpfnlt1.date_created >= %s "; $query .= "GROUP BY wpfnlt1.step_id"; $query = $wpdb->prepare($query, 'yes', $str_variation_ids, 'conversion', isset($start_settings['startDate']) ? date('Y-m-d H:i:s', strtotime($start_settings['startDate'])) : date('Y-m-d H:i:s', current_time('timestamp'))); $query = str_replace('(\'', '(', $query); $query = str_replace('\')', ')', $query); $visits_data = $wpdb->get_results($query, ARRAY_A); if (!empty($visits_data)) { $funnel_type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true); $funnel_type = !$funnel_type ? 'wc' : $funnel_type; $controllerInstance = new \Wpfnl_Analytics_Factory(); $param_type = $controllerInstance->build(ucfirst($funnel_type)); $funnel_orders = $param_type->get_orders_by_funnel($funnel_id, '', ''); foreach ($visits_data as $data) { $variation_id = isset($data['step_id']) ? $data['step_id'] : ''; $total_visit = isset($data['total_visits']) ? $data['total_visits'] : ''; $conversion = isset($data['conversions']) ? $data['conversions'] : ''; $earnings = $param_type->get_earnings($funnel_id, $funnel_orders, '', '', 'step_revenue', $variation_id); $variation_key = array_search($variation_id, array_column($variations, 'stepId')); if (false === $variation_key) { continue; } $start_settings['variations'][$variation_key]['conversion'] = self::calculate_conversion_rate($total_visit, $conversion); $start_settings['variations'][$variation_key]['revenue'] = isset($earnings['gross_sale_with_html']) ? $earnings['gross_sale_with_html'] : ''; $start_settings['variations'][$variation_key]['currency'] = isset($earnings['currency']) ? $earnings['currency'] : ''; $start_settings['variations'][$variation_key]['visit'] = $total_visit; } } else { if (isset($start_settings['variations']) && is_array($start_settings['variations'])) { foreach ($start_settings['variations'] as $key => $variation) { $start_settings['variations'][$key]['conversion'] = 0; $start_settings['variations'][$key]['revenue'] = 0; $start_settings['variations'][$key]['visit'] = 0; } } } } } if( get_post_meta($step_id, '_wpfnl_reset_stats', true) == 'yes' && isset($start_settings['isStart']) && 'yes' === $start_settings['isStart']){ if (isset($start_settings['variations']) && is_array($start_settings['variations'])) { update_post_meta($step_id, '_wpfnl_reset_stats', 'no'); foreach ($start_settings['variations'] as $key => $variation) { $start_settings['variations'][$key]['conversion'] = 0; $start_settings['variations'][$key]['revenue'] = 0; $start_settings['variations'][$key]['visit'] = 0; } } } if ($variation_ids) { foreach ($variation_ids as $variation_id) { $is_winner = ''; $variation_key = array_search($variation_id, array_column($variations, 'id')); if (false !== $variation_key) { $start_settings['variations'][$variation_key]['conversion'] = 0; $start_settings['variations'][$variation_key]['revenue'] = 0; $start_settings['variations'][$variation_key]['currency'] = ''; $start_settings['variations'][$variation_key]['visit'] = 0; } } } return $start_settings; } /** * @desc Get statistics for A/B testing. * * @param $funnel_id * @param $step_id * @return array */ public static function get_stats_of_a_step($funnel_id, $step_id) { $stats = [ 'total_visit' => 0, 'unique_visits' => 0, 'conversion' => 0, ]; if ($step_id && $funnel_id) { global $wpdb; $analytics_table = $wpdb->prefix . WPFNL_PRO_ANALYTICS_TABLE; $analytics_meta_table = $wpdb->prefix . WPFNL_PRO_ANALYTICS_META_TABLE; $query = "SELECT wpfnlt1.step_id AS step_id, "; $query .= "COUNT( wpfnlt1.id ) AS total_visits, "; $query .= "COUNT( DISTINCT( CASE WHEN wpfnlt1.visitor_type = 'new' THEN wpfnlt1.id ELSE NULL END ) ) AS unique_visits, "; $query .= "COUNT( CASE WHEN wpfnlt2.meta_key = 'conversion' AND wpfnlt2.meta_value = %s "; $query .= "THEN wpfnlt1.step_id ELSE NULL END ) AS conversions "; $query .= "FROM {$analytics_table} AS wpfnlt1 "; $query .= "INNER JOIN {$analytics_meta_table} AS wpfnlt2 "; $query .= "ON wpfnlt1.id = wpfnlt2.analytics_id "; $query .= "WHERE wpfnlt1.step_id = %d "; $query .= "AND wpfnlt2.meta_key = %s "; $query .= "GROUP BY wpfnlt1.step_id"; $query = $wpdb->prepare($query, 'yes', $step_id, 'conversion'); $query = str_replace('(\'', '(', $query); $query = str_replace('\')', ')', $query); $visits_data = $wpdb->get_results($query, ARRAY_A); if (!empty($visits_data)) { foreach ($visits_data as $data) { $variation_id = isset($data['step_id']) ? $data['step_id'] : ''; $total_visit = isset($data['total_visits']) ? $data['total_visits'] : ''; $unique_visits = isset($data['unique_visits']) ? $data['unique_visits'] : ''; $conversion = isset($data['conversions']) ? $data['conversions'] : ''; $stats = [ 'total_visit' => $total_visit, 'unique_visits' => $unique_visits, 'conversion' => $conversion, ]; } } } return $stats; } /** * @desc Calculate and get conversion rate * * @param $total_visit * @param $conversion * @return float|int */ public static function calculate_conversion_rate($total_visit, $conversion) { return number_format((float)($conversion * 100) / $total_visit, 2, '.', ''); } /** * Declear winner * * @param Number $step_id * @return Bool * */ public static function set_winner($step_id, $variation_id = '') { if (!$step_id) { return false; } $settings = get_post_meta($step_id, '_wpfnl_ab_testing_start_settings', true); $settings = maybe_unserialize($settings); if (!is_array($settings) || !isset($settings['variations'])) { return false; } foreach ($settings['variations'] as $key => $variation) { if (intval($variation_id) === intval($variation['stepId'])) { $settings['variations'][$key]['isWinner'] = 'yes'; } else { $settings['variations'][$key]['isWinner'] = 'no'; } } update_post_meta($step_id, '_wpfnl_ab_testing_start_settings', $settings); return true; } /** * Get the variation ID of the winning variation. * * @param Number $step_id The ID of the step. * * @return Bool|string The variation ID of the winner or false if not found. * @since 1.6.17 */ public static function get_winner($step_id) { if (!$step_id) { return false; } // Get the A/B testing start settings for the step. $settings = self::get_start_settings($step_id); if (!$settings || !isset($settings['variations']) || !is_array($settings['variations'])) { return false; } // Find the index of the winning variation (where 'isWinner' is 'yes'). $key = array_search('yes', array_column($settings['variations'], 'isWinner')); // Check if a winner was found. if (false === $key) { return false; } // Return the variation ID of the winning variation. return $settings['variations'][$key]['stepId']; } /** * Get variation step ID based on traffic distribution. * * @param Array $all_variations An array containing all variations and their traffic distribution. * @param Int $base_step_id The base step ID in case no variation is selected. * * @return Int The chosen variation step ID. * @since 1.6.17 */ public static function get_displayable_variation_id($all_variations, $base_step_id) { // Generate a random number between 0 and 100. $rand = function_exists('mt_rand') ? mt_rand(0, 100) : rand(0, 100); // Initialize a variable to measure traffic distribution. $measurement = 0; // Iterate through each variation to determine which one to display. foreach ($all_variations as $variation) { $traffic = intval($variation['traffic']); // Check if the random number falls within the current variation's traffic range. if (($rand >= $measurement) && ($rand <= ($measurement + $traffic))) { return $variation['stepId']; // Return the variation's step ID. } // Increment the measurement for the next iteration. $measurement += $traffic; } // If no variation was selected based on the random number, return the base step ID. return $base_step_id; } /** * Get all A/B testing variations for a specific step. * * @param Int $step_id The ID of the step. * @return Array An array containing all A/B testing variations for the step. * @since 1.6.17 */ public static function get_all_variations($step_id) { // Get the A/B testing start settings for the specified step. $start_setting = self::get_start_settings($step_id); // Initialize an empty array to store variations. $variations = []; // Check if the 'variations' key is set in the start settings. if (!isset($start_setting['variations'])) { return $variations; } // If variations exist, assign them to the $variations array. $variations = $start_setting['variations']; // Return the array of variations. return $variations; } /** * Create or update varient of a step * @param Int @step_id * @param Int @varient_id * */ public static function update_variations($step_id, $varient_id) { $start_setting = self::get_start_settings($step_id); $variations = []; if ($start_setting) { $variation = self::make_variation_array($varient_id, false); if ($variation) { array_push($start_setting['variations'], $variation); update_post_meta($step_id, '_wpfnl_ab_testing_start_settings', $start_setting); $variations = $start_setting['variations']; } } else { $variation = self::make_variation_array($varient_id, false); if ($variation) { $settings = self::get_default_start_setting($step_id); array_push($settings['variations'], $variation); update_post_meta($step_id, '_wpfnl_ab_testing_start_settings', $settings); update_post_meta($step_id, '_wpfnl_is_ab_testing', 'yes'); $variations = $settings['variations']; } } return $variations; } /** * Make variation array * * @param int $step_id * * @return array * @since 2.0.0 */ public static function make_variation_array($step_id, $is_default) { if (!$step_id) { return false; } $step_edit_link = get_edit_post_link($step_id); $step_edit_link = str_replace('&', '&', $step_edit_link); if ('elementor' == Wpfnl_functions::get_builder_type()) { $step_edit_link = str_replace('edit', 'elementor', $step_edit_link); }else{ $step_edit_link = str_replace('elementor', 'edit', $step_edit_link); } $variation = [ 'stepId' => $step_id, 'stepName' => get_the_title($step_id), 'stepType' => get_post_meta($step_id, '_step_type', true), 'variationType' => $is_default ? 'original' : 'variant', 'stepEditLink' => $step_edit_link, 'stepViewLink' => get_the_permalink($step_id), 'isTrafficSet' => 'no', 'isWinner' => 'no', 'isLocked' => 'no', 'traffic' => $is_default ? 100 : 0, 'conversion' => 0, 'visit' => 0, 'revenue' => 0, ]; return $variation; } /** * Get all ab-testing variations for each step * * @param Int * @param Int * @return Array * @since 1.6.17 * */ public static function get_start_date($funnel_id, $step_id) { $steps = get_post_meta($funnel_id, '_steps_order', true); $key = array_search($step_id, array_column($steps, 'id')); if (false !== $key) { if (isset($steps[$key]['ab_test_start_time'])) { $date = strtotime($steps[$key]['ab_test_start_time']); $saved_date = date('d/M/Y h:i:s', $date); return $saved_date; } } return []; } /** * Get all ab-testing conditions for each step * * @param Int * @param Int * @return Bool * @since 1.6.17 * */ public static function get_all_conditions($step_id) { $start_setting = self::get_start_settings($step_id); $conditions = []; if (isset($start_setting['auto_winner'])) { if (isset($start_setting['auto_winner']['is_enabled']) && 'yes' === $start_setting['auto_winner']['is_enabled']) { $conditions = isset($start_setting['auto_winner']['conditions']) ? $start_setting['auto_winner']['conditions'] : []; } } return $conditions; } /** * match condition * * @param Array * @param Int * @param Int * * @since 1.6.17 */ public static function match_condition($all_conditions, $funnel_id, $step_id) { if (!empty($all_conditions)) { // foreach( $all_conditions as $key=>$condition ){ if ('conversion' === $all_conditions['index']) { global $wpdb; $table_name = $wpdb->prefix . 'wpfnl_analytics_meta'; $results = $wpdb->get_results("SELECT * FROM " . $table_name . " WHERE funnel_id = " . $funnel_id . " AND step_id = " . $step_id . " AND meta_key = 'conversion' AND meta_value = 'no'"); if (count($results) == $all_conditions['value']) { return true; } } elseif ('trafiic' === $all_conditions['index']) { global $wpdb; $table_name = $wpdb->prefix . 'wpfnl_analytics_meta'; $results = $wpdb->get_results("SELECT * FROM " . $table_name . " WHERE funnel_id = " . $funnel_id . " AND step_id = " . $step_id . " AND meta_key = 'conversion'"); if (count($results) >= $all_conditions['value']) { return true; } } elseif ('date' === $all_conditions['index']) { $current_date = date('d/M/Y'); $date = strtotime($all_conditions['value']); $saved_date = date('d/M/Y', $date); if ($current_date == $saved_date) { return true; } } // } } return false; } /** * @desc Get all ab-testing archived step ids for each step * * @param $funnel_id * @param $step_id * @return array|mixed */ public static function get_all_archived_variations($funnel_id, $step_id) { $steps = get_post_meta($funnel_id, '_steps_order', true); $key = array_search($step_id, array_column($steps, 'id')); if (false !== $key) { if (isset($steps[$key]['archived_variations'])) { return $steps[$key]['archived_variations']; } } return []; } /** * Set ab-testings cookie * * @param Int * * @since 1.6.17 * @return void */ public static function set_cookie($funnel_id, $step_id, $show_variation_id) { $cookie_name = WPFNL_AB_TESTING_COOKIE_KEY . $step_id; $cookiepath = self::get_cookiepath(); $expire_time = time() + (24 * 60 * MINUTE_IN_SECONDS); $value = $show_variation_id; setcookie($cookie_name, $value, $expire_time, $cookiepath, COOKIE_DOMAIN); } /** * Get ab-testings cookie * * @param Int * * @since 1.6.17 * @return Mix Int or boolean */ public static function get_cookie($step_id) { $cookie_name = WPFNL_AB_TESTING_COOKIE_KEY . $step_id; if (isset($_COOKIE[$cookie_name])) { return intval($_COOKIE[$cookie_name]); } return false; } /** * get wp cookie path * * @return string|string[]|null */ public static function get_cookiepath() { return COOKIEPATH ? COOKIEPATH : '/'; } /** * Get a displayable variation ID for redirection in A/B testing. * * @param Int $funnel_id The ID of the funnel. * @param Int $step_id The ID of the step. * * @return Mixed The displayable variation ID or false. * @since 1.6.17 */ public static function get_ab_testing_variation_id($funnel_id, $step_id) { if (!$step_id) { return false; } $is_winner = self::get_winner($step_id); if ($is_winner) { return $is_winner; } $is_enable = self::maybe_ab_testing($step_id); if (!$is_enable) { return false; } $step_id_from_cookie = self::get_cookie($step_id); if ($step_id_from_cookie) { return $step_id_from_cookie; } $all_variations = self::get_all_variations($step_id); $displayable_variation_id = self::get_displayable_variation_id($all_variations, $step_id); self::set_cookie($funnel_id, $step_id, $displayable_variation_id); return $displayable_variation_id; } /** * Convert AB testing variation to pulish from archive * * @param Int * @param Int * @param Int * * @since 1.6.17 */ public static function archive_to_publish($funnel_id, $step_id, $archived_step_id) { $archived_steps = self::get_all_archived_variations($funnel_id, $step_id); $key = array_search($archived_step_id, array_column($archived_steps, 'id')); if (false !== $key) { $steps = get_post_meta($funnel_id, '_steps_order', true); $step_key = array_search($step_id, array_column($steps, 'id')); if (false !== $step_key) { array_push($steps[$step_key]['variations'], $archived_steps[$key]); unset($archived_steps[$key]); $steps[$step_key]['archived_variations'] = $archived_steps; update_post_meta($funnel_id, '_steps_order', $steps); } } } /** * Convert AB testing variation to pulish from archive * * @param Int * @param Int * @param Int * * @since 1.6.17 */ public static function publish_to_archive($funnel_id, $step_id, $variation_step_id) { $variations = self::get_all_variations($step_id); $key = array_search($variation_step_id, array_column($variations, 'id')); if (false !== $key) { unset($variations[$key]); $steps = get_post_meta($funnel_id, '_steps_order', true); $step_key = array_search($step_id, array_column($steps, 'id')); if (false !== $step_key) { array_push($steps[$step_key]['archived_variations'], $variations[$key]); unset($variations[$key]); $steps[$step_key]['variations'] = $variations; update_post_meta($funnel_id, '_steps_order', $steps); } } } /** * @desc Reset the stats for the current step * and all of its variations * * @param $step_id * @return bool */ public static function reset_stats($step_id) { if ($step_id) { $ab_start_settings = self::get_start_settings($step_id); if (isset($ab_start_settings['start_date'])) { $ab_start_settings['start_date'] = date('Y-m-d H:i:s'); } return self::update_start_settings($step_id, $ab_start_settings); } return false; } /** * @desc Reset the stats for the current step * and all of its variations * * @param $step_id * @return bool */ public static function reset_settings($step_id) { if ($step_id) { $ab_start_settings = self::get_start_settings($step_id); if (isset($ab_start_settings['variations'], $ab_start_settings['auto_winner'])) { $count = count($ab_start_settings['variations']); $avg = floor(100 / $count); $total = 0; $ab_start_settings['auto_winner'] = [ 'is_enabled' => '', 'conditions' => [ 'index' => 'trafiic', 'value' => 70 ] ]; foreach ($ab_start_settings['variations'] as $key => $variation) { $total = $total + $avg; $ab_start_settings['variations'][$key]['traffic'] = $avg; } if ($total != 100) { $ab_start_settings['variations'][0]['traffic'] = $ab_start_settings['variations'][0]['traffic'] + (100 - $total); } } return self::update_start_settings($step_id, $ab_start_settings); } return false; } /** * duplicate all meta key and values * * * @param $parent_id * @param $post_id * @param $step_type */ public static function duplicate_ab_testing_meta($parent_id, $post_id, $exclude_meta = array(), $raw = false) { global $wpdb; $exclude_sql = ''; if (!empty($exclude_meta)) { $metas = implode("', '", $exclude_meta); $exclude_sql = "AND meta_key NOT IN ('" . $metas . "')"; } $post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE (post_id=$parent_id {$exclude_sql})"); if (count($post_meta_infos) != 0) { $insert_sql_query = "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value) VALUES"; $sql_query_arr = []; foreach ($post_meta_infos as $meta_info) { $meta_key = $meta_info->meta_key; if ($meta_key == '_wp_old_slug') continue; if ($meta_key == 'funnel_automation_data') continue; if ($raw) { $meta_value = get_post_meta($parent_id, $meta_key, true); update_post_meta($post_id, $meta_key, $meta_value); } $sql_query_arr[] = $wpdb->prepare('( %d, %s, %s )', $post_id, $meta_key, $meta_info->meta_value); } if (!$raw) { $insert_sql_query .= implode(',', $sql_query_arr); $wpdb->query($insert_sql_query); } } } /** * Get Formatted ab testing settings * @param Integer $step_id * @return Array * @since 1.7.1 */ public static function get_formatted_settings($step_id) { if (isset($step_id)) { $default_settings = self::get_default_start_setting($step_id); //check A/B testing is enable or not $result = self::maybe_ab_testing($step_id); $default_settings['is_ab_enabled'] = $result ? $result : ''; // get start settings $result = self::get_start_settings($step_id); $default_settings['start_settings'] = $result ? $result : $default_settings['start_settings']; $response['data'] = $default_settings; } else { $response['data'] = ''; } return $response; } /** * Get parent step ID by variant ID from postmeta * @param Integrer $variant_id * @return Mix * @since 1.7.1 */ public static function get_parent_step_id($variant_id) { if ($variant_id) { $step_id = get_post_meta($variant_id, '_parent_step_id', true); return $step_id; } return false; } /** * Archive all variants * Archive all variants except winner and make winner step as original step * * @param Int $step_id * @param Int $variant_id * * @return Bool * @since 1.7.4 */ public static function archive_all_variant($step_id, $winner_id) { if (!$step_id || !$winner_id) { return false; } $settings = self::get_start_settings($step_id); if (!$settings || !is_array($settings) || !isset($settings['variations'])) { return false; } $archived_steps = []; $variations = []; foreach ($settings['variations'] as $key => $variation) { if (isset($variation['stepId']) && intval($winner_id) === intval($variation['stepId'])) { $variation['variationType'] = 'original'; array_push($variations, $variation); continue; } $variation['variationType'] = 'variant'; array_push($archived_steps, $variation); } $settings['variations'] = $variations; $settings['archived_variations'] = $archived_steps; update_post_meta($step_id, '_wpfnl_ab_testing_start_settings', $settings); return true; } /** * Archive single variant * * @param Int $step_id * @param Int $variant_id * * @return Mix * @since 1.7.5 */ public static function single_archive($step_id, $variant_id, $start_date = '', $stats_data = []) { if ($step_id && $variant_id) { $funnel_id = Wpfnl_functions::get_funnel_id_from_step($step_id); $start_settings = self::get_start_settings($step_id); $archived_variations = isset($start_settings['archived_variations']) ? $start_settings['archived_variations'] : []; if ($start_settings && $funnel_id) { $is_original = $step_id == $variant_id; //Check the parent step id is equal to winner id or not foreach ($start_settings['variations'] as $key => $variations) { if ($variations['id'] == $variant_id) { $archived_variation = $variations; $archived_variation['start_date'] = date('d M,Y', strtotime($start_date)); $archived_variation['start_time'] = date('H:i A', strtotime($start_date)); $index = array_search($variations['id'], array_column($stats_data, 'variation_id')); if (false !== $index) { $archived_variation['conversion_rate'] = $stats_data[$index]['conversion_rate']; $archived_variation['revenue'] = $stats_data[$index]['float_revenue']; $archived_variation['currency'] = $stats_data[$index]['currency']; $archived_variation['visit'] = $stats_data[$index]['total_visit']; $archived_variation['conversion'] = $stats_data[$index]['conversion']; } $archived_variation['end_date'] = date('d M,Y'); $archived_variation['end_time'] = date('H:i A'); array_push($archived_variations, $archived_variation); unset($start_settings['variations'][$key]); } } $formatted_variations = []; foreach ($start_settings['variations'] as $key => $variation) { array_push($formatted_variations, $variation); } $is_multiple = count($formatted_variations) > 1 ? true : false; $start_settings['variations'] = $formatted_variations; $response = []; if ($is_original) { $maybe_original_variant = isset($start_settings['variations'][0]['id']) ? $start_settings['variations'][0]['id'] : ''; if ($maybe_original_variant) { $response = self::update_funnel_data($funnel_id, $step_id, $maybe_original_variant, $is_multiple); $step_id = $maybe_original_variant; } } else { $response = self::update_funnel_data($funnel_id, $step_id, $step_id, $is_multiple); } $start_settings['archived_variations'] = $archived_variations; update_post_meta($step_id, '_wpfnl_ab_testing_start_settings', $start_settings); $ab_start_settings = Wpfnl_Ab_Testing::get_formatted_settings($step_id); $result = [ 'funnel_data' => isset($response['funnel_data']) ? $response['funnel_data'] : [], 'ab_start_settings' => $ab_start_settings, 'step_id' => $step_id, 'node_id' => isset($response['node_id']) ? $response['node_id'] : '', 'node_data' => isset($response['node_data']) ? $response['node_data'] : [], 'is_multiple_variant' => $is_multiple ? 'yes' : 'no', ]; return $result; } } return false; } /** * Restore archive * * @param Int $step_id * @param Int $variant_id * * @return Bool * @since 1.7.5 */ public static function restore_archive_variant($step_id, $variant_id, $is_permanent_delete = true) { if ($step_id && $variant_id) { $start_settings = self::get_start_settings($step_id); if (isset($start_settings['archived_variations'])) { foreach ($start_settings['archived_variations'] as $key => $variations) { if ($variant_id == $variations['id']) { array_splice($start_settings['archived_variations'], $key, 1); } } update_post_meta($step_id, '_wpfnl_ab_testing_start_settings', $start_settings); if (!$is_permanent_delete) { self::update_variations($step_id, $variant_id); } return true; } } return false; } /** * Delete archive variant permanently * * @param Int $step_id * @param Int $variant_id * * @return Array * @since 1.7.5 */ public static function delete_archive($step_id, $variant_id) { if ($step_id && $variant_id) { $response = self::restore_archive_variant($step_id, $variant_id, true); if ($response) { wp_delete_post($variant_id); return true; } } return false; } /** * Modify ab testing start settings * * @param Obj $parent_post * @param Int $variant_id * @param Array $start_settings * * @return Array * @since 1.7.4 */ public static function modify_start_settings($parent_post, $variant_id, $start_settings) { wp_update_post([ "ID" => $variant_id, "post_title" => wp_strip_all_tags($parent_post->post_title), "post_name" => $parent_post->post_name, ]); $step_edit_link = get_edit_post_link($variant_id); $step_edit_link = str_replace('&', '&', $step_edit_link); if ('elementor' == Wpfnl_functions::get_builder_type()) { $step_edit_link = str_replace('edit', 'elementor', $step_edit_link); }else{ $step_edit_link = str_replace('elementor', 'edit', $step_edit_link); } $variation_key = array_search($variant_id, array_column($start_settings['variations'], 'id')); if (false !== $variation_key) { $start_settings['variations'][$variation_key]['variationType'] = 'original'; $start_settings['variations'][$variation_key]['stepName'] = wp_strip_all_tags($parent_post->post_title); $start_settings['variations'][$variation_key]['stepViewLink'] = get_the_permalink($variant_id); $start_settings['variations'][$variation_key]['stepEditLink'] = $step_edit_link; } return $start_settings; } /** * Update winner variation as original step * @param Array $payload * @return Array * @since 1.7.4 */ public static function update_winner_variation($payload) { $funnel_id = isset($payload['funnel_id']) ? $payload['funnel_id'] : ''; $step_id = isset($payload['step_id']) ? $payload['step_id'] : ''; $variant_id = isset($payload['variant_id']) ? $payload['variant_id'] : ''; $start_settings = isset($payload['start_settings']) ? $payload['start_settings'] : []; $parent_post = get_post($step_id); $start_settings = self::modify_start_settings($parent_post, $variant_id, $start_settings); if ($funnel_id) { $response = self::update_funnel_data($funnel_id, $step_id, $variant_id); $response['start_settings'] = $start_settings; update_post_meta($variant_id, '_funnel_id', $funnel_id); delete_post_meta($step_id, '_wpfnl_ab_testing_start_settings'); // wp_delete_post( $step_id ); } return $response; } /** * Update funnel data * @param Int $funnel_id * @param Int $step_id * @param Int $variant_id * * @return Array * @since 1.7.4 */ public static function update_funnel_data($funnel_id, $step_id, $variant_id, $is_multiple = false) { $funnel_data = get_post_meta($funnel_id, '_funnel_data', true); $steps = isset($funnel_data['drawflow']['Home']['data']) ? $funnel_data['drawflow']['Home']['data'] : []; $node_data = []; $node_id = ''; if (is_array($steps) && count($steps)) { foreach ($steps as $key => $step) { $step_data = isset($step['data']) ? $step['data'] : []; if (isset($step_data['step_id']) && $step_id == $step_data['step_id']) { $node_id = $step['id']; $steps[$key]['html'] = trim(str_replace($step_id, $variant_id, $steps[$key]['html'])); $step_data['step_id'] = $variant_id; $step_data['step_edit_link'] = base64_encode(get_edit_post_link($variant_id)); $step_data['step_view_link'] = base64_encode(rtrim(get_the_permalink($variant_id), '/')); $steps[$key]['data'] = $step_data; $node_data = [ 'class' => $steps[$key]['class'], 'html' => trim(str_replace($step_id, $variant_id, $steps[$key]['html'])), 'pos_x' => $steps[$key]['pos_x'], 'pos_y' => $steps[$key]['pos_y'], 'data' => [ 'step_id' => $variant_id, 'step_edit_link' => base64_encode(get_edit_post_link($variant_id)), 'step_view_link' => base64_encode(rtrim(get_the_permalink($variant_id), '/')), 'step_name' => get_the_title($variant_id), 'step_type' => $steps[$key]['data']['step_type'], ], ]; } } $funnel_data['drawflow']['Home']['data'] = $steps; update_post_meta($funnel_id, '_funnel_data', $funnel_data); } $step_order = get_post_meta($funnel_id, '_steps_order', true); $step_order = self::update_step_order($step_order, $funnel_id, $step_id, $variant_id); return [ 'funnel_data' => $funnel_data, 'step_order' => $step_order, 'node_data' => $node_data, 'node_id' => $node_id, ]; } /** * Update step order for funnel * * @param Array $step_order * @param Int $funnel_id * @param Int $step_id * @param Int $variant_id * * @return Array * @since 1.7.4 */ public static function update_step_order($step_order, $funnel_id, $step_id, $variant_id) { if ($step_id != $variant_id) { if (is_array($step_order && !empty($step_order))) { foreach ($step_order as $key => $step) { if ($step['id'] == $step_id) { $step_order[$key]['id'] = $variant_id; } } delete_post_meta($step_id, '_funnel_id'); update_post_meta($variant_id, '_funnel_id', $funnel_id); update_post_meta($funnel_id, '_steps_order', $step_order); } } return $step_order; } /** * Update the status of AB testing (start/pause) for a step. * * @param int $step_id The ID of the step. * * @return array|bool An array indicating success and the updated status, or false on failure. * * @since 2.0.0 */ public static function update_running_status( $step_id, $isDelete = 'yes' ) { if (!$step_id) { return false; } // Get the current AB testing start settings from post meta. $settings = get_post_meta($step_id, '_wpfnl_ab_testing_start_settings', true); // Check if the settings are valid and include 'isStart' key. if (!$settings || !is_array($settings) || !isset($settings['isStart'])) { return false; } // Toggle the AB testing start status. $settings['isStart'] = 'yes' === $settings['isStart'] ? 'no' : 'yes'; if( 'yes' !== $settings['isStart'] ){ $general_settings = get_post_meta( $step_id, 'wpfnl_ab_testing_general_settings', true); if( isset( $general_settings['autoEndSettings']['autoEnd'] )){ $general_settings['autoEndSettings']['autoEnd'] = 'no'; update_post_meta( $step_id, 'wpfnl_ab_testing_general_settings', $general_settings ); } $group = 'wpfnl-ab-testing-'.$step_id; Wpfnl_Ab_Testing::delete_as_actions($group); } $date = date('Y-m-d H:i:s', current_time('timestamp')); if ('yes' === $settings['isStart']) { $settings['startDate'] = 'no' === $isDelete && !empty($settings['startDate']) ? $settings['startDate'] : $date; } else { $settings['endDate'] = $date; } if ('yes' === $settings['isStart']) { foreach ($settings['variations'] as $key => $variation) { if (isset($settings['variations'][$key]['isWinner']) && 'yes' === $settings['variations'][$key]['isWinner']) { $settings['variations'][$key]['isWinner'] = 'no'; } } } $settings = self::get_settings_with_stats( $step_id, $settings ); // Update the post meta with the updated settings. update_post_meta($step_id, '_wpfnl_ab_testing_start_settings', $settings); // Return an array indicating success and the updated status. return [ 'success' => true, 'status' => $settings['isStart'], 'settings' => $settings, ]; } /** * Check if AB testing is currently running for a step. * * @param int $step_id The ID of the step. * * @return bool True if AB testing is running, false otherwise. * * @since 2.0.0 */ public static function maybe_ab_testing_running($step_id) { if (!$step_id) { return false; } // Get the current AB testing start settings from post meta. $settings = get_post_meta($step_id, '_wpfnl_ab_testing_start_settings', true); // Check if the settings are valid and include 'isStart' key. if (!$settings || !is_array($settings) || !isset($settings['isStart'])) { return false; } // Return true if AB testing is running ('isStart' is 'yes'), otherwise false. return 'yes' === $settings['isStart']; } /** * Migrate A/B Testing Start Settings. * * This function is responsible for migrating A/B testing start settings from an older format * to a newer format for a specified step. It takes a step ID as input and processes the * migration, updating the start settings to a new structure. This migration ensures compatibility * with new settings structures and features. * * @param int $step_id The ID of the step to migrate the A/B testing start settings for. * * @return bool True if the migration is successful, false otherwise. * @since 2.0.0 */ public static function maybe_migrate_start_settings($step_id) { if (!$step_id) { return false; } $is_migrated = get_post_meta($step_id, '_wpfnl_is_migrated_ab_settings', true); if ('yes' === $is_migrated) { return false; } $prev_settings = get_post_meta($step_id, '_wpfnl_ab_testing_start_settings', true); if (!is_array($prev_settings) || !isset($prev_settings['variations'], $prev_settings['is_started']) || !is_array($prev_settings['variations'])) { update_post_meta($step_id, '_wpfnl_is_migrated_ab_settings', 'yes'); return false; } $new_settings = []; $new_settings['isStart'] = !empty($prev_settings['is_started']) ? $prev_settings['is_started'] : 'no'; $new_settings['startDate'] = isset($prev_settings['start_date']) ? $prev_settings['start_date'] : date('Y-m-d H:i:s'); $new_settings['endDate'] = ''; $new_settings['variations'] = []; $new_settings['archived_variations'] = []; foreach ($prev_settings['variations'] as $key => $variation) { if (!isset($variation['id'], $variation['variation_type'])) { continue; } $type = 'original' === $variation['variation_type'] ? true : false; $updated_variation = self::make_variation_array($variation['id'], $type); array_push($new_settings['variations'], $updated_variation); } if (isset($prev_settings['archived_variations']) && is_array($prev_settings['archived_variations'])) { foreach ($prev_settings['archived_variations'] as $key => $archived_variation) { if (!isset($archived_variation['id'], $archived_variation['variation_type'])) { continue; } $type = 'original' === $archived_variation['variation_type'] ? true : false; $updated_variation = self::make_variation_array($archived_variation['id'], $type); array_push($new_settings['archived_variations'], $updated_variation); } } update_post_meta($step_id, '_wpfnl_ab_testing_start_settings', $new_settings); update_post_meta($step_id, '_wpfnl_is_migrated_ab_settings', 'yes'); return true; } /** * Updates the variation link for A/B testing. * * @param array $settings The settings for the A/B testing. * @return array The updated settings. * * @since 2.1.0 */ public static function update_variation_link($settings) { if (isset($settings['variations']) && is_array($settings['variations'])) { foreach ($settings['variations'] as $key => $variation) { $step_edit_link = get_edit_post_link($variation['stepId']); $step_edit_link = str_replace('&', '&', $step_edit_link); if ('elementor' == Wpfnl_functions::get_builder_type()) { $step_edit_link = str_replace('edit', 'elementor', $step_edit_link); }else{ $step_edit_link = str_replace('elementor', 'edit', $step_edit_link); } $settings['variations'][$key]['stepEditLink'] = $step_edit_link; $settings['variations'][$key]['stepViewLink'] = get_the_permalink($variation['stepId']); } } if (isset($settings['archived_variations']) && is_array($settings['archived_variations'])) { foreach ($settings['archived_variations'] as $key => $variation) { $step_edit_link = get_edit_post_link($variation['stepId']); $step_edit_link = str_replace('&', '&', $step_edit_link); if ('elementor' == Wpfnl_functions::get_builder_type()) { $step_edit_link = str_replace('edit', 'elementor', $step_edit_link); }else{ $step_edit_link = str_replace('elementor', 'edit', $step_edit_link); } $settings['archived_variations'][$key]['stepEditLink'] = $step_edit_link; $settings['archived_variations'][$key]['stepViewLink'] = get_the_permalink($variation['stepId']); } } return $settings; } /** * Updates the drawflow content for A/B testing. * * @param int $current_step_id The ID of the current step. * @param int $updated_step_id The ID of the updated step. * @return void * * @since 2.2.4 */ public static function update_drawflow_content($current_step_id, $updated_step_id) { if (!$current_step_id || !$updated_step_id) { return false; } $funnel_id = Wpfnl_functions::get_funnel_id_from_step($current_step_id); if( !$funnel_id ){ return false; } $funnel_data = get_post_meta($funnel_id, '_funnel_data', true); if( !$funnel_data ){ return false; } $steps = isset($funnel_data['drawflow']['Home']['data']) ? $funnel_data['drawflow']['Home']['data'] : []; if( !$steps ){ return false; } foreach ($steps as $key => $step) { $step_data = isset($step['data']) ? $step['data'] : []; if (isset($step_data['step_id']) && $current_step_id == $step_data['step_id']) { $steps[$key]['html'] = trim(str_replace($current_step_id, $updated_step_id, $steps[$key]['html'])); $step_data['step_id'] = $updated_step_id; $step_data['step_edit_link'] = base64_encode(get_edit_post_link($updated_step_id)); $step_data['step_view_link'] = base64_encode(rtrim(get_the_permalink($updated_step_id), '/')); $steps[$key]['data'] = $step_data; } } $funnel_data['drawflow']['Home']['data'] = $steps; update_post_meta($funnel_id, '_funnel_data', $funnel_data); $steps = self::get_steps($funnel_data); update_post_meta($funnel_id, '_steps', $steps); Wpfnl_functions::generate_first_step($funnel_id, $steps); $_steps_order = self::get_steps_order($funnel_data); $steps_order = array(); if( is_array($_steps_order) ){ foreach ($_steps_order as $_step) { if (count($_step)) { $steps_order[] = $_step; } } } if (count($steps_order)) { update_post_meta($funnel_id, '_steps_order', $steps_order); } else { delete_post_meta($funnel_id, '_steps_order'); } update_post_meta($updated_step_id, '_parent_step_id', $updated_step_id); self::replace_ab_testing_meta($current_step_id, $updated_step_id); self::replace_conditions_meta($current_step_id, $updated_step_id); self::replace_automation_meta($current_step_id, $updated_step_id); } /** * Get steps order * * @param $funnel_flow_data * * @return array * * @since 2.2.4 */ public static function get_steps_order($funnel_flow_data) { $drawflow = $funnel_flow_data['drawflow']; $step_order = array(); $start_node = array(); if (isset($drawflow['Home']['data'])) { $drawflow_data = $drawflow['Home']['data']; /** * If has only one step, that only step will be the first step, no conditions should be checked. * just return the step order */ if (1 === count($drawflow_data)) { $node_id = array_keys($drawflow_data)[0]; $data = $drawflow_data[$node_id]; $step_data = isset($data['data']) ? $data['data'] : array(); $step_type = isset($step_data['step_type']) ? $step_data['step_type'] : ''; $step_id = isset($step_data['step_id']) ? $step_data['step_id'] : 0; $step_order[] = array( 'id' => $step_id, 'step_type' => $step_type, 'name' => sanitize_text_field(get_the_title($step_id)), ); return $step_order; } /** * First we will find the first node (the node which has only output connection but no input connection will be considered as first node) and the list of nodes array which has the * step information includes output connection and input connection and it will be stored on $nodes */ foreach ($drawflow_data as $key => $data) { $step_data = $data['data']; $step_type = $step_data['step_type']; $step_id = 'conditional' !== $step_type && 'addstep' !== $step_type ? $step_data['step_id'] : 0; if ( (isset($data['outputs']['output_1']['connections']) && count($data['outputs']['output_1']['connections'])) || (isset($data['inputs']['input_1']['connections']) && count($data['inputs']['input_1']['connections'])) ) { if ('conditional' === $step_type || 'addstep' === $step_type) { continue; } /** * A starting node is a node which has only output connection but not any input connection. * if the step is landing, then there should not be any input connection for this step. so we will only consider the output connection for landing only. * for other step types (checkout, offer, thankyou), we will check if the step has any output connection and no input connection. */ if ('landing' === $step_type) { if ( isset($data['outputs']['output_1']['connections']) && count($data['outputs']['output_1']['connections']) && (isset($data['inputs']) && (count($data['inputs']) == 0 || (isset($data['inputs']['input_1']['connections']) && count($data['inputs']['input_1']['connections']) == 0))) ) { $start_node = array( 'id' => $step_id, 'step_type' => $step_type, 'name' => sanitize_text_field(get_the_title($step_id)), ); } } else { if ( isset($data['outputs']['output_1']['connections']) && count($data['outputs']['output_1']['connections']) && (isset($data['inputs']['input_1']['connections']) && count($data['inputs']['input_1']['connections']) === 0) ) { $start_node = array( 'id' => $step_id, 'step_type' => $step_type, 'name' => sanitize_text_field(get_the_title($step_id)), ); } else { $step_order[] = array( 'id' => $step_id, 'step_type' => $step_type, 'name' => sanitize_text_field(get_the_title($step_id)), ); } } } } $step_order = self::array_insert($step_order, $start_node, 0); } return $step_order; } /** * Array insert element on position * * @param $original * @param $inserted * @param int $position * * @return mixed * * @since 2.2.4 */ public static function array_insert(&$original, $inserted, $position) { array_splice($original, $position, 0, array($inserted)); return $original; } /** * Get steps * * @param $funnel_flow_data * * @return array * * @since 2.2.4 */ public static function get_steps($funnel_flow_data) { $drawflow = $funnel_flow_data['drawflow']; $steps = array(); if (isset($drawflow['Home']['data'])) { $drawflow_data = $drawflow['Home']['data']; foreach ($drawflow_data as $key => $data) { $step_data = $data['data']; if ('conditional' !== $step_data['step_type'] && 'addstep' !== $step_data['step_type']) { $step_id = $step_data['step_id']; $step_type = $step_data['step_type']; $step_name = sanitize_text_field(get_the_title($step_data['step_id'])); $steps[] = array( 'id' => $step_id, 'step_type' => $step_type, 'name' => $step_name, ); } } } return $steps; } /** * Replaces the AB testing meta for a given step ID. * * @param int $current_step_id The ID of the current step. * @param int $updated_step_id The ID of the updated step. * @return void * * @since 2.2.4 */ public static function replace_ab_testing_meta ($current_step_id, $updated_step_id) { $current_step_meta = get_post_meta($current_step_id); $updated_step_meta = get_post_meta($updated_step_id); if( $current_step_meta && $updated_step_meta ){ $metas = ['_wpfnl_is_migrated_ab_settings', '_wpfnl_ab_testing_start_settings', '_wpfnl_is_ab_testing']; foreach ($current_step_meta as $key => $value) { if( in_array($key, $metas) ){ delete_post_meta($current_step_id, $key); $value = maybe_unserialize($value[0]); update_post_meta($updated_step_id, $key, $value); } } $ab_testing_data = get_post_meta($updated_step_id, '_wpfnl_ab_testing_start_settings', true); if( is_array($ab_testing_data) && isset($ab_testing_data['variations']) ){ foreach( $ab_testing_data['variations'] as $key => $variation ){ update_post_meta( $variation['stepId'],'_parent_step_id', $updated_step_id ); } } } } /** * Replaces the conditions meta for a given step ID. * * @param int $current_step_id The ID of the current step. * @param int $updated_step_id The ID of the updated step. * @return void * * @since 2.2.4 */ public static function replace_conditions_meta ($current_step_id, $updated_step_id) { $current_step_meta = get_post_meta($current_step_id); $updated_step_meta = get_post_meta($updated_step_id); if( $current_step_meta && $updated_step_meta ){ $metas = ['_wpfnl_step_conditions', '_wpfnl_next_step_after_condition', '_wpfnl_maybe_enable_condition']; foreach ($current_step_meta as $key => $value) { if( in_array($key, $metas) ){ delete_post_meta($current_step_id, $key); $value = maybe_unserialize($value[0]); self::search_and_replace($value, 'optin_', 'optin_'.$updated_step_id); update_post_meta($updated_step_id, $key, $value); } } } } /** * Replaces the automation meta for a given step ID. * * @param int $current_step_id The ID of the current step. * @param int $updated_step_id The ID of the updated step. * @return void * * @since 2.2.4 */ public static function replace_automation_meta ($current_step_id, $updated_step_id) { if( $current_step_id && $updated_step_id ){ $automation_steps = get_post_meta($current_step_id, '_wpfnl_automation_steps', true); $automation_trigger = get_post_meta($current_step_id, '_wpfnl_automation_trigger', true); $automation_id = get_post_meta($current_step_id, 'wpfnl_mint_automation_id', true); if( $automation_trigger ){ update_post_meta($updated_step_id, '_wpfnl_automation_trigger', $automation_trigger); } if( $automation_id ){ update_post_meta($updated_step_id, 'wpfnl_mint_automation_id', $automation_id); } if( is_array($automation_steps) ){ foreach ($automation_steps as $key => $value) { $automation_steps[$key]['settings']['step_id'] = $updated_step_id; } update_post_meta($updated_step_id, '_wpfnl_automation_steps', $automation_steps); } } } /** * Searches and replaces a specific value in an array. * * @param array &$array The array to search and replace values in. * @param mixed $searchString The value to search for in the array. * @param mixed $replaceValue The value to replace the searched value with. * @return void * * @since 2.2.4 */ public static function search_and_replace(&$array, $searchString, $replaceValue) { foreach ($array as &$value) { if (is_array($value)) { self::search_and_replace($value, $searchString, $replaceValue); } elseif (is_string($value) && strpos($value, $searchString) !== false) { $value = $replaceValue; } } } /** * Deletes the A/B testing data for a specific slug. * * @param string $slug The slug of the A/B test. * @param mixed $status The status of the A/B test. Optional. * @return void * * @since 2.2.6 */ public static function delete_as_actions( string $slug, $status = '' ) { if ( empty( $slug ) ) { return false; } $group_id = self::get_as_group_id( $slug ); if ( empty( $group_id ) ) { return false; } global $wpdb; $query = $wpdb->prepare( "DELETE FROM {$wpdb->actionscheduler_actions} WHERE `group_id` = %d", $group_id ); if ( $status ) { $query .= $wpdb->prepare( ' AND `status` = %s', $status ); } return $wpdb->query( $query ); //phpcs:ignore } /** * Retrieves the group ID for a given slug. * * @param string $slug The slug of the group. * @return int|null The group ID if found, null otherwise. * * @since 2.2.6 */ public static function get_as_group_id( string $slug ) { if ( empty( $slug ) ) { return 0; } global $wpdb; return $wpdb->get_var( $wpdb->prepare( "SELECT `group_id` FROM {$wpdb->actionscheduler_groups} WHERE `slug` = %s", $slug ) ); //phpcs:ignore } } includes/core/aes-encryption/class-wpfunnels-aes-counter.php000064400000020366147600245720020340 0ustar00>> operator nor unsigned ints * * @param a number to be shifted (32-bit integer) * @param b number of bits to shift a to the right (0..31) * @return a right-shifted and zero-filled by b bits */ private static function urs($a, $b) { $a = intval($a); $b = intval($b); $a &= 0xffffffff; $b &= 0x1f; // (bounds check) if ($a & 0x80000000 && $b > 0) { // if left-most bit set $a = ($a >> 1) & 0x7fffffff; // right-shift one bit & clear left-most bit $a = $a >> ($b - 1); // remaining right-shifts } else { // otherwise $a = ($a >> $b); // use normal right-shift } return $a; } } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ includes/core/aes-encryption/class-wpfunnels-aes.php000064400000020123147600245720016652 0ustar00 6 && $i % $Nk == 4) { $temp = self::subWord($temp); } for ($t = 0; $t < 4; $t++) $w[$i][$t] = $w[$i - $Nk][$t] ^ $temp[$t]; } return $w; } /** * Apply SBox to 4-byte word w. */ private static function subWord($w) { for ($i = 0; $i < 4; $i++) $w[$i] = self::$sBox[$w[$i]]; return $w; } /** * Rotate 4-byte word w left by one byte. */ private static function rotWord($w) { $tmp = $w[0]; for ($i = 0; $i < 3; $i++) $w[$i] = $w[$i + 1]; $w[3] = $tmp; return $w; } // sBox is pre-computed multiplicative inverse in GF(2^8) used in subBytes and keyExpansion [§5.1.1] private static $sBox = array( 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16); // rCon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2] private static $rCon = array( array(0x00, 0x00, 0x00, 0x00), array(0x01, 0x00, 0x00, 0x00), array(0x02, 0x00, 0x00, 0x00), array(0x04, 0x00, 0x00, 0x00), array(0x08, 0x00, 0x00, 0x00), array(0x10, 0x00, 0x00, 0x00), array(0x20, 0x00, 0x00, 0x00), array(0x40, 0x00, 0x00, 0x00), array(0x80, 0x00, 0x00, 0x00), array(0x1b, 0x00, 0x00, 0x00), array(0x36, 0x00, 0x00, 0x00)); } includes/core/classes/class-wpfnl-pro-offer-product.php000064400000005335147600245720017271 0ustar00ID; } elseif (is_admin() && isset( $_POST['id'] )) { $step_id = $_POST['id']; } return $step_id; } /** * get offer product object * * @return mixed|void */ public function get_offer_product() { $step_id = $this->get_step_id(); $step_type = get_post_meta( $step_id, '_step_type', true ); $offer_product_data = Wpfnl_Pro_functions::get_offer_product( $step_id, $step_type ); $product = null; if( is_array($offer_product_data) ) { foreach ( $offer_product_data as $pr_index => $pr_data ) { $product_id = $pr_data['id']; $product = wc_get_product( $product_id ); break; } } return apply_filters( 'wpfunnels/offer_product', $product, $step_id, $step_type ); } /** * @return string|void */ public function get_offer_product_price() { $step_id = $this->get_step_id(); $offer_product = $this->get_offer_product(); $offer_product_data = Wpfnl_Pro_functions::get_offer_product_data( $step_id ); $output = ''; if( !is_object($offer_product) || null === $offer_product) { return; } $price_args = array( 'decimals' => wc_get_price_decimals(), ); $output .= ''; if( $offer_product_data['discount'] ) { $discount_apply_to = $offer_product_data['discount_apply_to']; if( 'sale' === $discount_apply_to ) { $regular_price = $offer_product_data['sale_price']; } else { $regular_price = $offer_product_data['regular_price']; } $output .= ''.wc_price( $regular_price, $price_args ).''; $output .= ''.wc_price( $offer_product_data['price'], $price_args ).''; } else { $output .= '' . wc_price( $offer_product_data['price'], $price_args ) . ''; } $output .= ''; return $output; } }includes/core/classes/class-wpfnl-pro-orders.php000064400000023315147600245720016006 0ustar00get_status(); $status = [ 'wc-wpfnl-main-order', 'wpfnl-main-order', ]; if ( !in_array($order_status, $status) ) { return; } $this->chanage_normalize_status( $order, $order_status, $normal_status='processing'); } } } /** * Register new order status. * @param string $order_status order status. * @return array */ public function register_new_order_status( $order_status ) { $order_status_title = _x( 'Funnel Order Accepted', 'Order status', 'wpfnl-pro' ); $partial_order_status_title = _x( 'Funnel Partially Refunded', 'Order status', 'wpfnl-pro' ); $order_status[ 'wc-wpfnl-main-order' ] = array( 'label' => $order_status_title, 'public' => false, 'exclude_from_search' => true, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop( 'Funnel Order Accepted (%s)', 'Funnel Order Accepted (%s)', 'wpfnl-pro' ), ); return $order_status; } /** * Update native statuses. * @param string $order_status Order status. * @return array */ public function update_to_new_stauses( $order_status ) { $order_status[ 'wc-wpfnl-main-order' ] = 'Funnel Order Accepted'; return $order_status; } /** * Schedule normalize order status. * @param int $order_id order id. * @param string $before_normal before status. * @param string $normal_status normal status. * @return void */ public function schedule_update_order_status( $order_id, $before_normal, $normal_status ) { $order = wc_get_order( $order_id ); $this->chanage_normalize_status( $order, $before_normal, $normal_status ); } /** * Normalize order status. * @param array $order order. * @param string $before_normal before status. * @param string $normal_status normal status. * @return void */ public function chanage_normalize_status( $order, $before_normal = 'pending', $normal_status = 'processing' ) { if (false === is_a($order, 'WC_Order')) { return; } $current_status = $order->get_status(); $status = [ 'wc-wpfnl-main-order', 'wpfnl-main-order', ]; if ( !in_array($current_status, $status) ) { return; } if( $order ){ $is_course = false; $is_downloadable_product = false; // Set initial state to false foreach( $order->get_items() as $item_id => $item ){ $product = wc_get_product( $item->get_product_id() ); if( $product && ($product->is_downloadable() || $product->is_virtual() ) ){ $is_downloadable_product = true; // Set to true if any product meets the criteria } if( $product->get_type() === 'course' ){ $is_course = true; } } } $payment_method = $order->get_payment_method(); if( apply_filters( 'wpfunnels/maybe_update_order_status', true ) ){ if( $is_course ){ if( 'cod' === $payment_method || 'bacs' === $payment_method ){ $order->update_status($before_normal); $order->update_status($normal_status); }else{ $order->update_status('completed'); } }else{ if( !$is_downloadable_product || 'cod' === $payment_method || 'bacs' === $payment_method ){ $order->update_status($before_normal); $order->update_status($normal_status); }else{ $order->update_status('completed'); } } } do_action( 'wpfunnels/after_update_order_status', $order ); } /** * register new wpfunnel order status to main order * @param $order */ public function assign_wpfnl_status_to_main_order( $order ) { if ( ! is_a( $order, 'WC_Order' ) ) { return; } $payment_method = $order->get_payment_method(); add_filter( 'woocommerce_payment_complete_order_status', array( $this, 'assign_new_order_status' ), 999, 3 ); if ( 'cod' === $payment_method || 'bacs' === $payment_method ) { return; } } /** * Assign new order status 'wc-wpfnl-main-order' * @param $order_status * @param $id * @param $order * @return string */ public function assign_new_order_status( $order_status, $id, $order ) { $new_status = 'wc-wpfnl-main-order'; ob_start(); do_action( 'wpfunnels/update_order_status_processing', $new_status, $order_status, $order ); ob_get_clean(); return 'wc-wpfnl-main-order'; } /** * Run cron every 30 minutes * Update Funnels order status * Funnel order accpected to Processing * @param $new_status * @param $normal_status * @param $order */ public function update_status_funnels_processing($new_status, $normal_status, $order){ if ( false === is_a( $order, 'WC_Order' ) ) { return; } $status = [ 'wc-wpfnl-main-order', 'wpfnl-main-order', ]; if ( !in_array($new_status, $status) ) { return; } $args = array( 'order_id' => $order->get_id(), 'before_normal' => $order->get_status(), 'normal_status' => $normal_status, ); if ( false === wp_next_scheduled( 'wpfnl_cron_update_order_status', $args ) ) { $cron_time = apply_filters( 'wpfnl_order_status_cron_time', 30 ); wp_schedule_single_event( time() + ( $cron_time * MINUTE_IN_SECONDS ), 'wpfnl_cron_update_order_status', $args ); } } /** * Check thank you page ad order status processing * @param $order_id */ public function page_is_thankyou( $order_id ){ $order = wc_get_order($order_id); if ( false === is_a( $order, 'WC_Order' ) ) { return false; } $order_status = $order->get_status(); $status = [ 'wc-wpfnl-main-order', 'wpfnl-main-order', ]; if ( !in_array($order_status, $status) ) { return; } $this->chanage_normalize_status( $order, $order_status, $normal_status='processing'); } /** * Set WP Funner order status in new email hook * @param $actions * @return mixed */ public function set_woocommerce_email_hook_funnel_order($actions) { $actions[] = 'woocommerce_order_status_wpfnl-main-order'; return $actions; } /** * Send Processing email when Order status change * @param $order_id * @param $old_status * @param $new_status * @param $order */ function send_email_funner_order_to_processing( $order_id, $old_status, $new_status, $order ){ $status = [ 'wc-wpfnl-main-order', 'wpfnl-main-order', ]; if ( in_array($old_status, $status) && $new_status == 'processing' ) { $wc_emails = WC()->mailer()->get_emails(); $wc_emails['WC_Email_Customer_Processing_Order']->trigger( $order_id ); $wc_emails['WC_Email_New_Order']->trigger( $order_id ); } if( in_array($old_status, $status) && $new_status == 'completed' ){ $wc_emails = WC()->mailer()->get_emails(); $wc_emails['WC_Email_Customer_Completed_Order']->trigger( $order_id ); } } }includes/core/classes/class-wpfnl-pro-subscription.php000064400000045165147600245720017243 0ustar00get_meta( '_wpfunnels_offer_txn_resp_' . $offer_product['step_id'] ); $offer_product['transaction_id'] = $transaction_id; $subscription = $this->maybe_create_new_subscription( $order, $offer_product ); if ( ! empty( $subscription ) ) { ob_start(); do_action( 'wpfunnels/subscription_created', $subscription, $offer_product, $order ); ob_get_clean(); } } /** * @param $order * @param $offer_product * @param $parent_order * @throws \WC_Data_Exception */ public function create_separate_subscription( $order, $offer_product, $parent_order ) { $subscription = $this->maybe_create_new_subscription( $order, $offer_product ); if ( ! empty( $subscription ) ) { ob_start(); do_action( 'wpfunnels/subscription_created', $subscription, $offer_product, $order ); ob_get_clean(); } } /** * @param \WC_Order $order * @param $offer_product * @return bool|\WC_Order|\WC_Subscription|\WP_Error * @throws \WC_Data_Exception */ public function maybe_create_new_subscription( \WC_Order $order, $offer_product ) { $product_id = $offer_product['id']; $product = wc_get_product( $product_id ); $subscription = false; $subscription_order = $order; $user_created = null; if ( $product && ( 'subscription' === $product->get_type() || 'subscription_variation' === $product->get_type() ) ) { if ( is_user_logged_in() ) { $user_id = $subscription_order->get_user_id(); } else { if( $subscription_order ){ $user_id = ( null === $user_created ) ? $this->create_new_customer( $subscription_order->get_billing_email() ) : $user_created; $user_created = $user_id; $subscription_order->set_customer_id( $user_id ); $subscription_order->save(); } } $args = array( 'product' => $offer_product, 'order' => $subscription_order, 'user_id' => $user_id, ); $subscription = $this->create_new_subscription( $args, $this->get_subscription_status( $subscription_order ) ); if ( false !== $subscription ) { return $subscription; } } return false; } /** * create new WC customer * * @param $email * @return bool|int|\WP_Error */ private function create_new_customer( $email ) { if ( empty( $email ) ) { return false; } $maybe_user = get_user_by( 'email', $email ); if ( $maybe_user instanceof \WP_User ) { return $maybe_user->ID; } $username = sanitize_user( current( explode( '@', $email ) ), true ); /** user has to be unique */ $append = 1; $o_username = $username; while ( username_exists( $username ) ) { $username = $o_username . $append; ++ $append; } $password = wp_generate_password(); $customer_id = wc_create_new_customer( $email, $username, $password ); if ( ! empty( $customer_id ) ) { wp_set_current_user( $customer_id, $username ); wc_set_customer_auth_cookie( $customer_id ); } return $customer_id; } /** * get status of the subscription order * * @param $order * @return string */ private function get_subscription_status( $order ) { $get_payment_method = $order->get_payment_method(); if ( in_array( $get_payment_method, [ 'bacs', 'cheque' ], true ) ) { return 'on-hold'; } return 'completed'; } /** * create new subscription * * @param $args * @param $status * @return bool|\WC_Order|\WC_Subscription|\WP_Error * @throws \WC_Data_Exception */ private function create_new_subscription( $args, $status ) { $offer_product = $args['product']; $order = $args['order']; $user_id = $args['user_id']; $product = wc_get_product($offer_product['id']); if( $product ){ $transaction_id = $offer_product['transaction_id']; $start_date = date( 'Y-m-d H:i:s' ); $period = \WC_Subscriptions_Product::get_period( $product ); $interval = \WC_Subscriptions_Product::get_interval( $product ); $trial_period = \WC_Subscriptions_Product::get_trial_period( $product ); try { /** create subscription */ $subscription = wcs_create_subscription( array( 'start_date' => $start_date, 'order_id' => $order->get_id(), 'billing_period' => $period, 'billing_interval' => $interval, 'customer_note' => $order->get_customer_note(), 'customer_id' => $user_id, ) ); } catch ( \Exception $e ) { return false; } if ( is_wp_error( $subscription ) ) { return false; } if ( ! empty( $subscription ) ) { $subscription_item_id = $subscription->add_product( $product, 1 ); $subscription = wcs_copy_order_address( $order, $subscription ); $trial_end_date = \WC_Subscriptions_Product::get_trial_expiration_date( $product->get_id(), $start_date ); $next_payment_date = \WC_Subscriptions_Product::get_first_renewal_payment_date( $product->get_id(), $start_date ); $end_date = \WC_Subscriptions_Product::get_expiration_date( $product->get_id(), $start_date ); $subscription->update_dates( array( 'trial_end' => $trial_end_date, 'next_payment' => $next_payment_date, 'end' => $end_date, ) ); /** check if the subscription product has trial option */ if ( \WC_Subscriptions_Product::get_trial_length( $product->get_id() ) > 0 ) { wc_add_order_item_meta( $subscription_item_id, '_has_trial', 'true' ); } if ( ! empty( $trial_period ) ) { update_post_meta( $subscription->get_id(), '_trial_period', $trial_period ); } $subscription->set_payment_method( $order->get_payment_method() ); $subscription->set_payment_method_title( $order->get_payment_method_title() ); update_post_meta( $subscription->get_id(), '_customer_user', $user_id ); if ( 'completed' === $status ) { $subscription->payment_complete( $transaction_id ); } else { $subscription->update_status( $status ); } $subscription->calculate_totals(); $subscription->save(); if( is_plugin_active( 'woocommerce-software-license/software-license.php' ) ){ $license_obj = new WOO_SL_functions; $this->order_setup_licensing($order->get_id(), $license_obj); $license_obj->generate_license_keys($order->get_id()); } return $subscription; } } return false; } /** * cancel the main order * * @param $order */ public function may_be_cancel_subscription( $order ) { if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order ) ) { $parent_subscription = wcs_get_subscriptions_for_order( $order->get_id() ); if ( ! empty( $parent_subscription ) ) { $parent_subscription = array_pop( $parent_subscription ); if ( ! empty( $parent_subscription ) ) { $parent_subscription->update_status( 'cancelled', __( 'Subscription replaced by the Offer Product', 'wpfnl-pro' ) ); } } } } /** * Add licence data to meta details for the order * * @param mixed $order_id * @param mixed $license_obj */ public function order_setup_licensing( $order_id, $license_obj) { //check if order contain any licensed product $order_data = new \WC_Order($order_id); $order_products = $order_data->get_items(); $found_licensed_product = FALSE; foreach($order_products as $key => $order_product) { if (WOO_SL_functions::is_product_licensed( $order_product->get_product_id() ) ) { $found_licensed_product = TRUE; break; } } if(!$found_licensed_product) return; $_woo_sl = array(); //get the order items foreach ( $order_products as $key => $order_product ) { if(! $license_obj->is_product_licensed( $order_product->get_product_id() )) continue; $is_licence_extend = FALSE; $_woo_sl_extend = wc_get_order_item_meta($key, '_woo_sl_extend', TRUE); if(!empty($_woo_sl_extend)) $is_licence_extend = TRUE; //no need to process if is an licence extend if ( $is_licence_extend ) continue; //check against the variation, if assigned a licence group if($order_product->get_variation_id() > 0) { $variation_license_group_id = get_post_meta($order_product->get_variation_id(), '_sl_license_group_id', TRUE); if( $variation_license_group_id == '') continue; } //get product licensing details $product_sl_groups = WOO_SL_functions::get_product_licensing_groups( $order_product->get_product_id() ); //if variation, filter out the licence groups if($order_product->get_variation_id() > 0) { if(isset($product_sl_groups[$variation_license_group_id])) { $_product_sl_groups = $product_sl_groups; $product_sl_groups = array(); $product_sl_groups[$variation_license_group_id] = $_product_sl_groups[$variation_license_group_id]; } else $product_sl_groups = array(); } $_group_title = array(); $_licence_prefix = array(); $_max_keys = array(); $_max_instances_per_key = array(); $_use_predefined_keys = array(); $_product_use_expire = array(); $_product_expire_renew_price = array(); $_product_expire_units = array(); $_product_expire_time = array(); $_product_expire_starts_on_activate = array(); $_product_expire_disable_update_link= array(); $_product_expire_limit_api_usage = array(); $_product_expire_notice = array(); foreach($product_sl_groups as $product_sl_group) { $_group_title[] = $product_sl_group['group_title']; $_licence_prefix[] = $product_sl_group['licence_prefix']; //$_max_keys[] = $product_sl_group['max_keys'] * intval($order_product['qty']); $_max_keys[] = $product_sl_group['max_keys']; $_max_instances_per_key[] = $product_sl_group['max_instances_per_key']; $_use_predefined_keys[] = $product_sl_group['use_predefined_keys']; $_product_use_expire[] = $product_sl_group['product_use_expire']; $_product_expire_renew_price[] = $product_sl_group['product_expire_renew_price']; $_product_expire_units[] = $product_sl_group['product_expire_units']; $_product_expire_time[] = $product_sl_group['product_expire_time']; $_product_expire_starts_on_activate[] = $product_sl_group['product_expire_starts_on_activate']; $_product_expire_disable_update_link[]= $product_sl_group['product_expire_disable_update_link']; $_product_expire_limit_api_usage[] = $product_sl_group['product_expire_limit_api_usage']; $_product_expire_notice[] = $product_sl_group['product_expire_notice']; } $data['group_title'] = $_group_title; $data['licence_prefix'] = $_licence_prefix; $data['max_keys'] = $_max_keys; $data['max_instances_per_key'] = $_max_instances_per_key; $data['use_predefined_keys'] = $_use_predefined_keys; $data['product_use_expire'] = $_product_use_expire; $data['product_expire_renew_price'] = $_product_expire_renew_price; $data['product_expire_units'] = $_product_expire_units; $data['product_expire_time'] = $_product_expire_time; $data['product_expire_starts_on_activate'] = $_product_expire_starts_on_activate; $data['product_expire_disable_update_link'] = $_product_expire_disable_update_link; $data['product_expire_limit_api_usage'] = $_product_expire_limit_api_usage; $data['product_expire_notice'] = $_product_expire_notice; $data = apply_filters('woo_sl/order_processed/product_sl', $data, $order_product, $order_id); wc_update_order_item_meta($key, '_woo_sl', $data); //set currently as inactive wc_update_order_item_meta($key, '_woo_sl_licensing_status', 'inactive'); foreach ( $data['product_use_expire'] as $data_key => $data_block_value ) { if ( $data_block_value != 'no' ) { wc_update_order_item_meta($key, '_woo_sl_licensing_using_expire', $data_block_value ); //continue only if expire_starts_on_activate is not set to yes $expire_starts_on_activate = $data['product_expire_starts_on_activate'][$data_key]; if ( $expire_starts_on_activate == 'yes' ) { //set currently as not-activated wc_update_order_item_meta($key, '_woo_sl_licensing_status', 'not-activated'); continue; } if ( $data_block_value == 'yes' ) { $today = date("Y-m-d", current_time( 'timestamp' )); $start_at = strtotime($today); wc_update_order_item_meta($key, '_woo_sl_licensing_start', $start_at); $_sl_product_expire_units = $data['product_expire_units'][$data_key]; $_sl_product_expire_time = $data['product_expire_time'][$data_key]; $expire_at = strtotime( " + " . $_sl_product_expire_units . " " . $_sl_product_expire_time, $start_at); wc_update_order_item_meta($key, '_woo_sl_licensing_expire_at', $expire_at); } } } } } }includes/core/compatibility/class-wpfnl-pro-chained-product.php000064400000012010147600245720020763 0ustar00get_all_chained_product_details( $product_id ); if( !is_array($chained_products) ){ return false; } return $chained_products; } /** * Updates the order items with chained products. * * @param WC_Order $order The order object. * @param Array $chained_products An array of chained products. * @param Int $product_id The ID of the main product. * * @since 1.8.8 * @return Array An associative array with success status, overridden amount, total chained product price and details of added chained items. */ public function update_order_item( $order, $chained_products, $product_id ){ $response = [ 'success' => false ]; if( !$order || empty($chained_products) ){ return $response; } if ($chained_products){ $chain_items = []; $total_chained_product_price = 0; foreach ($chained_products as $chained_product_id => $chianed_product){ $new_product_price = 0; if( !isset($chianed_product['unit']) ) { continue; } $quantity = $chianed_product['unit']; $chained_product = wc_get_product( $chained_product_id ); $chained_product_price = $chained_product->get_price(); if ( ! empty( $chained_product_price ) ) { if( isset($chianed_product['priced_individually']) && 'yes' === $chianed_product['priced_individually'] ){ $total_chained_product_price += $chained_product_price * $chianed_product['unit']; $new_product_price = $chained_product_price * $chianed_product['unit']; $response['override_amount'] = true; } } $chained_product->set_price( $new_product_price ); $chain_item_id = $order->add_product( $chained_product, $quantity); wc_update_order_item_meta($chain_item_id, '_chained_product_of', $product_id); if( isset($chianed_product['priced_individually']) ){ wc_update_order_item_meta($chain_item_id, '_cp_priced_individually', $chianed_product['priced_individually']); } $chain_item_details = [ 'id' => $chain_item_id, 'price' => $new_product_price, ]; array_push( $chain_items, $chain_item_details); } if( isset($response['override_amount']) ){ $response['total_chained_product_price'] = $total_chained_product_price; } $response['success'] = true; $response['chain_item_details'] = $chain_items; } return $response; } /** * Updates the tax amounts and related order item meta for chained items. * * @param Array $chain_items An array containing details of chained items. * * @since 1.8.8 * @return void */ public function update_tax_ammount( $chain_items ){ if( is_array($chain_items) && !empty($chain_items) ){ foreach( $chain_items as $chain_item ){ if( isset($chain_item['id'], $chain_item['price']) ){ $tax = $chain_item['price'] - ( $chain_item['price'] /( ( $tax_rate/100 ) + 1 )); $line_tax = [ 'total' => [ $rate_id => $tax ], 'subtotal' => [ $rate_id => $tax ], ]; $total_without_tax = $chain_item['price'] - $tax; $meta_updates = [ '_line_tax_data' => $line_tax, '_line_subtotal_tax' => $tax, '_line_tax' => $tax, '_line_total' => $total_without_tax, '_line_subtotal' => $total_without_tax, ]; foreach( $meta_updates as $key=>$value ){ wc_update_order_item_meta( $chain_item['id'], $key, $value ); } } } } } }includes/core/compatibility/class-wpfnl-pro-wc-hpos.php000064400000005454147600245720017310 0ustar00= '%s' "; $where .= " AND ( wpft1.date_created_gmt >= '%s' AND wpft1.date_created_gmt <= '%s'))"; $query = $wpdb->prepare( "SELECT wpft1.ID FROM {$wpdb->prefix}wc_orders as wpft1 INNER JOIN {$wpdb->prefix}wc_orders_meta as wpft2 ON wpft1.ID = wpft2.order_id {$where} ", '_wpfunnels_order', 'yes', 'shop_order', $start_date, $end_date ); $orders = $wpdb->get_results( $query ); if( is_array($orders) && !empty($orders) ){ return $orders; } return []; } /** * Get all funnel orders from wc order table based on funnel id, start_date, end_date * * @param String $funnel_id * @param String $start_date * @param String $end_date * * @since 1.8.4 * @return Array */ public function get_hpos_orders_for_a_funnel( $funnel_id, $start_date, $end_date ){ global $wpdb; $where = ''; $where .= "WHERE ( ( wpft2.meta_key = '%s' AND wpft2.meta_value = %s ) OR ( wpft2.meta_key = '%s' AND wpft2.meta_value = %s )"; $where .= " AND wpft1.status IN ( 'wc-completed', 'wc-processing', 'wc-pending', 'wc-on-hold' )"; $where .= " AND wpft1.type = '%s' "; $where .= " AND ( wpft1.date_created_gmt >= '%s' AND wpft1.date_created_gmt <= '%s'))"; $query = $wpdb->prepare( "SELECT wpft1.ID FROM {$wpdb->prefix}wc_orders as wpft1 INNER JOIN {$wpdb->prefix}wc_orders_meta as wpft2 ON wpft1.ID = wpft2.order_id {$where} ",'_wpfunnels_funnel_id', $funnel_id, '_wpfunnels_parent_funnel_id', $funnel_id, 'shop_order', $start_date ,$end_date ); $orders = $wpdb->get_results( $query ); if( is_array($orders) && !empty($orders) ){ return $orders; } return []; } }includes/core/import-export/class-wpfnl-pro-export-funnel.php000064400000024113147600245720020507 0ustar00handle('wpfnl-export-funnel') ->with_callback([ $this, 'export_funnel' ]) ->with_validation($this->get_validation_data()); wp_ajax_helper()->handle('wpfnl-export-all-funnels') ->with_callback([ $this, 'export_all_funnels' ]) ->with_validation($this->get_validation_data()); } /** * Export a single funnel * * @param Array $payload * * @return Array * @since 1.7.5 */ public function export_funnel( $payload ){ $funnel_id = isset( $payload['funnel_id'] ) ? $payload['funnel_id'] : ''; $response = [ 'success' => false, ]; if( $funnel_id ){ $data[] = $this->get_exported_funnel_data( $funnel_id ); if( $data ){ $response = [ 'success' => true, 'title' => get_the_title( $funnel_id ), 'steps' => $data ? $data : [], ]; } } return $response; } /** * Export all funnels * @param Array $payload * * @return Array * @since 1.7.5 */ public function export_all_funnels( $payload ){ $status = isset($payload['status']) ? $payload['status'] : 'publish'; $funnels = Wpfnl_functions::get_all_funnels( $status ); $funnel_data = array(); $response = [ 'success' => false, 'data' => $funnel_data, ]; if( $funnels ){ if ( $funnels && is_array($funnels) ) { foreach ( $funnels as $key => $funnel ) { $funnel_data[] = $this->get_exported_funnel_data( $funnel->ID ); } } $response['success'] = true; $response['data'] = $funnel_data; } return $response; } /** * Export selected funnels * * @param array $payload Funnel ids and status. * * @return array|\WP_Rest_Response|\WP_Error * @since 1.7.5 */ public function bulk_export_funnel ( $payload ){ $status = isset( $payload['status'] ) ? $payload['status'] : 'publish'; $selected_ids = isset($payload['ids']) ? $payload['ids'] : []; $arg = [ "post_type" => WPFNL_FUNNELS_POST_TYPE, "fields" => "ids", "orderby" => "date", "order" => 'ASC', "post__in" => $selected_ids, "numberposts" => -1 ]; if( 'all' !== $status ){ $arg['post_status'] = $status; } $funnels = get_posts( $arg ); $funnel_data = array(); $redirect_link = add_query_arg( [ 'page' => WPFNL_FUNNEL_PAGE_SLUG, ], admin_url('admin.php') ); $response = [ 'success' => false, 'data' => $funnel_data, 'redirectUrl' => $redirect_link ]; if( $funnels ){ if ( $funnels && is_array($funnels) ) { foreach ( $funnels as $key => $funnel_id ) { $funnel_data[] = $this->get_exported_funnel_data( $funnel_id ); } } $response['success'] = true; $response['data'] = $funnel_data; $response['redirectUrl'] = $redirect_link; } return rest_ensure_response ( $response ); } /** * Get exported funnel data by funnel id * * @param Int $funnel_id * @return Array * @since 1.7.5 */ public function get_exported_funnel_data( $funnel_id ){ if( $funnel_id ){ $funnel_id = trim($funnel_id); $funnel_id = (int)$funnel_id; $is_export = apply_filters( 'wpfunnels/allow_to_export', true ); $steps = get_post_meta( $funnel_id, '_steps', true ); $all_funnel_meta = get_post_meta( $funnel_id ); $exclude_funnel_meta = ['funnel_automation_data', 'global_funnel_start_condition' ]; $all_funnel_meta = $this->exclude_meta( $all_funnel_meta, $exclude_funnel_meta ); $funnel_data['funnel_id'] = $funnel_id; $funnel_data['funnel_name'] = get_the_title( $funnel_id ); $funnel_data['funnel_meta'] = $all_funnel_meta; $funnel_data['steps_data'] = []; if ( $steps && is_array($steps) ) { foreach ( $steps as $key => $step ) { if( isset( $step['id'], $step['step_type']) ){ $all_meta = get_post_meta( $step['id'] ); $exclude_step_meta = [ '_wpfnl_checkout_products', '_wpfnl_checkout_discount_main_product', 'order-bump-settings', '_wpfnl_upsell_products', '_wpfnl_upsell_discount', '_wpfnl_downsell_products', '_wpfnl_downsell_discount', 'global_funnel_upsell_rules', 'global_funnel_downsell_rules', ]; $all_meta = $this->exclude_meta( $all_meta, $exclude_step_meta ); $post_step = get_post($step['id']); $step_slug = isset( $post_step->post_name) ? $post_step->post_name : ''; $step_data = array( 'id' => $step['id'], 'title' => get_the_title( $step['id'] ), 'slug' => $step_slug, 'type' => $step['step_type'], 'post_content' => '', 'page_template' => get_post_meta( $step['id'], '_wp_page_template', true), 'meta' => $all_meta ); if ( $is_export ) { $step_obj = get_post( $step['id'] ); $step_data['post_content'] = isset($step_obj->post_content) ? $step_obj->post_content : ''; } $ab_settings = get_post_meta( $step['id'], '_wpfnl_ab_testing_start_settings', true ); if( $ab_settings ){ if( isset( $ab_settings['variations'] ) && is_array( $ab_settings['variations'] ) ){ foreach( $ab_settings['variations'] as $index => $variations ){ if( isset( $variations['stepId'] ) ){ $ab_step_meta = $this->export_ab_step_metas( $variations['stepId'] ); $ab_step_obj = get_post( $variations['stepId'] ); $step_data['ab_step_data'][] = [ 'step_id' => $variations['stepId'], 'title' => get_the_title( $variations['stepId'] ), 'type' => get_post_meta( $variations['stepId'], '_step_type', true), 'post_content' => isset($ab_step_obj->post_content) ? $ab_step_obj->post_content : '', 'page_template' => get_post_meta( $variations['stepId'], '_wp_page_template', true), 'meta' => $ab_step_meta ]; } } } } $funnel_data['steps_data'][] = $step_data; } } return $funnel_data; } } return false; } /** * Export A/B testing steps * * @param Int $step_id * * @return Array * @since 1.7.5 */ public function export_ab_step_metas( $step_id ){ $step_data = []; if( $step_id ){ $is_export = apply_filters( 'wpfunnels/allow_to_export_ab_step', true ); if ( $is_export ) { $all_meta = get_post_meta( $step_id ); $step_data = array( 'id' => $step_id, 'title' => get_the_title( $step_id ), 'type' => get_post_meta( $step_id, '_step_type', true), 'post_content' => '', 'page_template' => get_post_meta( $step_id, '_wp_page_template', true), 'meta' => $all_meta ); $step_obj = get_post( $step_id ); $step_data['post_content'] = isset($step_obj->post_content) ? $step_obj->post_content : ''; } } return $step_data; } /** * Exclude meta * @param array $all_meta * @param array $meta_key Which meta should be excluded * */ private function exclude_meta( $all_meta, $meta_keys ){ if( is_array( $meta_keys ) ){ foreach( $meta_keys as $meta_key ){ if( isset( $all_meta[$meta_key] ) ){ unset( $all_meta[$meta_key] ); } } } return $all_meta; } public function get_validation_data() { return [ 'logged_in' => true, 'user_can' => 'wpf_manage_funnels', ]; } }includes/core/import-export/class-wpfnl-pro-import-funnel.php000064400000057360147600245720020512 0ustar00parent_base || !isset( $_GET[ 'page' ] ) || ( isset( $_GET[ 'page' ] ) && in_array( $_GET[ 'page' ], $disabled_notice_page ) ) ) { $options = array( 'id' => 'wpfnl-import-notice', 'title' => 'wpfunnels-basic', 'description' => "{$import_status}", 'classes' => [ 'notice', 'notice-info', 'wpfnl-import-notice' ], 'type' => 'update-plugin', 'dismissible' => true, 'icon' => '', ); Admin_Notice::print_notice( $options ); } } } /** * Schedule the import of funnels as a background process. * * This method is responsible for scheduling the import of funnels as a background process. It checks if the 'wpfnl_import_funnels' * action has not already been scheduled, and if so, it prepares the necessary file for import and schedules the background process. * The process involves uploading the imported file, updating the import notice, and enqueueing the 'wpfnl_import_funnels' action * to be executed asynchronously using a background processing mechanism (such as WP Async Task). * * @since 1.9.7 */ public function schedule_import_funnels() { if( !as_has_scheduled_action( 'wpfnl_import_funnels' ) ) { $file = $this->upload_imported_file(); if( file_exists( $file ) ) { update_option( 'wpfnl_import_notice', 'Funnels import in-progress...' ); as_enqueue_async_action( 'wpfnl_import_funnels', [ $file ], 'wpfunnels-import' ); } } } /** * Uploads an imported file to the specified directory in the WordPress uploads folder. * * This private method is used to upload an imported file to a specific directory within the WordPress uploads folder. * It ensures that the target directory exists and creates it if not present. The method reads the temporary uploaded * file, extracts its content, and saves it to the target file location. The saved file's name is based on the original * uploaded filename, and if no filename is provided, a default name is used. * * @return string|false The full path to the uploaded file on success, or false on failure. * @since 1.9.7 */ private function upload_imported_file() { $path = wp_upload_dir(); $path = $path[ 'basedir' ] . '/wpfunnels/import/'; // make directory if not exist if( !file_exists( $path ) ) { wp_mkdir_p( $path ); } $temp_file = $_FILES[ 'uploaded_file' ][ 'tmp_name' ] ?? ''; $file_name = $_FILES[ 'uploaded_file' ][ 'name' ] ?? ''; $content = !empty( $temp_file ) ? file_get_contents( $temp_file ) : ''; $file_name = !empty( $file_name ) ? "{$path}$file_name" : "{$path}funnel-import.json"; return file_put_contents( $file_name, $content ) ? $file_name : ''; } /** * Import Funnel data and create * * @param $file * * @return true * @since 1.7.5 */ public function import_funnels( $file ) { $file_data = []; if( file_exists( $file ) ) { $file_data = json_decode( file_get_contents( $file ), true ); unlink( $file ); } $message = __( 'The funnels import process has been successfully completed.', 'wpfnl-pro' ); if( is_array( $file_data ) ) { $funnel = Wpfnl::$instance->funnel_store; $exclude_meta = array( '_is_imported', '_steps_order', '_steps', '_funnel_data'); foreach( $file_data as $data ) { if( empty( $data[ 'funnel_id' ] ) ) { $message = __( 'Invalid funnel(s) data.', 'wpfnl-pro' ); break; } $funnel_data = []; $funnel_name = $data[ 'funnel_name' ] ?? 'New Funnel'; $funnel_id = $funnel->create( $funnel_name ); $funnel->update_meta( $funnel_id, '_is_imported', 'yes' ); foreach( $data[ 'funnel_meta' ] as $meta_key => $meta ) { if( !in_array( $meta_key, $exclude_meta ) ) { $funnel->update_meta( $funnel_id, $meta_key, maybe_unserialize( $data[ 'funnel_meta' ][ $meta_key ][ 0 ] ) ); } else { if( 'funnel_data' === $meta_key ) { $funnel_data = maybe_unserialize( $data[ 'funnel_meta' ][ $meta_key ][ 0 ] ); } if( '_funnel_data' === $meta_key ) { $funnel_data = maybe_unserialize( $data[ 'funnel_meta' ][ $meta_key ][ 0 ] ); } } } $_new_step_ids = []; $_new_step_order = []; foreach( $data[ 'steps_data' ] as $step_data ) { $response = $this->import_step( $funnel_id, $step_data ); if( isset( $response[ 'id' ], $response[ 'title' ], $response[ 'type' ] ) ) { $_new_step_ids[ $step_data[ 'id' ] ] = $response[ 'id' ]; $_new_step_order[] = [ 'id' => $response[ 'id' ], 'step_type' => $response[ 'type' ], 'name' => $response[ 'title' ], ]; } else { $message = __( 'Invalid funnel(s) data.', 'wpfnl-pro' ); break; } } $this->update_funnel_data( $_new_step_ids, $funnel_id, $funnel_data ); update_post_meta( $funnel_id, '_steps_order', $_new_step_order ); update_post_meta( $funnel_id, '_steps', $_new_step_order ); $instance = new Wpfnl_functions(); if( method_exists( $instance, 'generate_first_step' ) ) { Wpfnl_functions::generate_first_step( $funnel_id, [] ); } } } update_option( 'wpfnl_import_notice', $message ); return true; } /** * Import step from json * @param Int $funnel_id * @param Array $step_data * @return * @since 1.7.5 * */ public function import_step($funnel_id, $step_data){ if (!is_array($step_data)) { return false; } $builder_type = Wpfnl_functions::get_builder_type(); $previous_step_id = isset($step_data['id']) ? $step_data['id'] : ''; $title = isset($step_data['title']) ? $step_data['title'] : ''; $slug = isset($step_data['slug']) ? $step_data['slug'] : ''; $page_template = isset($step_data['page_template']) ? $step_data['page_template'] : ''; $post_content = isset($step_data['post_content']) ? $step_data['post_content'] : ''; $step_type = isset($step_data['type']) ? $step_data['type'] : ''; $step = new Wpfnl_Steps_Store_Data(); $step_id = $step->create_step($funnel_id, $title, $step_type, $post_content, true); if ($step_id) { if ($slug) { wp_update_post(array( 'ID' => $step_id, 'post_name' => $slug )); } $step->update_meta($step_id, '_funnel_id', $funnel_id); $step->update_meta($step_id, '_wp_page_template', $page_template); $exclude_meta = array('_wpfnl_ab_testing_start_settings', '_funnel_id'); $sql_query_arr = []; $is_enable_condition_meta_blank = false; $is_condition_exits = false; foreach ($step_data['meta'] as $meta_key => $meta) { if (isset($meta[0])) { if (!in_array($meta_key, $exclude_meta)) { if ('_wpfnl_maybe_enable_condition' === $meta_key) { if ('' === $meta[0]) { $is_enable_condition_meta_blank = true; } } if ('_wpfnl_step_conditions' === $meta_key) { if ($meta[0]) { $is_condition_exits = true; } } $sql_data = [ 'post_id' => $step_id, 'meta_key' => $meta_key, 'meta_value' => $meta[0], ]; $sql_query_arr[] = $sql_data; } elseif ('_wpfnl_ab_testing_start_settings' === $meta_key) { $this->update_ab_testing_settings($funnel_id, $step_id, $meta[0], $step_data['ab_step_data']); } } } $this->update_meta_by_sql($sql_query_arr); // update meta if ($is_enable_condition_meta_blank && $is_condition_exits) { update_post_meta($step_id, '_wpfnl_maybe_enable_condition', 'yes'); } if (('gutenberg' === $builder_type) && class_exists('\WPFunnels\Batch\Gutenberg\Wpfnl_Gutenberg_Source')) { if (ini_get('allow_url_fopen')) { $gutenberg_source = new \WPFunnels\Batch\Gutenberg\Wpfnl_Gutenberg_Source; $gutenberg_source->import_single_template($step_id); } } // Elementor Data. if (('elementor' === $builder_type) && class_exists('\Elementor\Plugin') && class_exists('\WPFunnels\Batch\Elementor\Wpfnl_Elementor_Source')) { if (ini_get('allow_url_fopen')) { $elementor_source = new \WPFunnels\Batch\Elementor\Wpfnl_Elementor_Source; $elementor_source->import_single_template($step_id); Plugin::$instance->files_manager->clear_cache(); } } if (('divi-builder' === $builder_type) && class_exists('\WPFunnels\Batch\Divi\Wpfnl_Divi_Source')) { if (ini_get('allow_url_fopen')) { $divi_source = new \WPFunnels\Batch\Divi\Wpfnl_Divi_Source; $divi_source->import_single_template($step_id); } } if (('oxygen' === $builder_type) && class_exists('\WPFunnels\Batch\Oxygen\Wpfnl_Oxygen_Source')) { if (ini_get('allow_url_fopen')) { $oxygen_source = new \WPFunnels\Batch\Oxygen\Wpfnl_Oxygen_Source; $oxygen_source->import_single_template($step_id); } } delete_post_meta($step_id, '_funnel_id'); update_post_meta($step_id, '_funnel_id', $funnel_id); return [ 'id' => $step_id, 'type' => $step_type, 'title' => $title, ]; } return false; } /** * Update post meta by raw SQL. * * Inserts post meta data into the WordPress database using raw SQL queries. * * @param array $sql_query_arr An array of SQL query values in the format (post_id, meta_key, meta_value). * * @since 1.8.10 * @return boolean */ public function update_meta_by_sql( $sql_query_arr ){ if ( ! is_array( $sql_query_arr ) || empty( $sql_query_arr ) ) { return false; } global $wpdb; $insert_sql_query = "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value) VALUES "; $value_placeholders = array(); $query_args = array(); foreach( $sql_query_arr as $query ) { if( isset($query['post_id'],$query['meta_key'],$query['meta_value']) ){ $value_placeholders[] = '(%d, %s, %s)'; $query_args[] = $query['post_id']; $query_args[] = $query['meta_key']; $query_args[] = $query['meta_value']; } } if( empty( $value_placeholders ) || empty( $query_args )){ return false; } $insert_sql_query = rtrim( $insert_sql_query, ',' ); $insert_sql_query .= ' ' . implode( ',', $value_placeholders ); $prepared_query = $wpdb->prepare( $insert_sql_query, $query_args ); $result = $wpdb->query( $prepared_query ); return false !== $result; } /** * Update A/B testing settings for a funnel step. * This function updates the A/B testing settings for a funnel step with the provided parameters. * @param int $funnel_id The ID of the funnel to which the step belongs. * @param int $step_id The ID of the step for which the A/B testing settings are being updated. * @param mixed $settings The new A/B testing settings for the step. This can be a serialized string or an array. * @param mixed $prev_settings The previous A/B testing settings for the step. This can be a serialized string or an array. * * @since 1.8.8 * @return void */ public function update_ab_testing_settings( $funnel_id, $step_id, $settings, $prev_settings ){ $settings = maybe_unserialize($settings); $prev_settings = maybe_unserialize($prev_settings); if( isset( $settings['variations'] ) ){ $new_settings = []; $new_settings['isStart'] = 'no'; $new_settings['startDate'] = isset($settings['startDate']) ?$settings['startDate'] : date( 'Y-m-d H:i:s' ); $new_settings['endDate'] = isset($settings['endDate']) ?$settings['endDate'] : date( 'Y-m-d H:i:s' ); $new_settings['variations'] = $this->update_ab_variations( $funnel_id, $step_id, $settings['variations'], $prev_settings ); update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings' , $new_settings ); } } /** * Update A/B testing variations for a funnel step. * This function updates the A/B testing variations for a funnel step with the provided parameters. * * @param int $funnel_id The ID of the funnel to which the step belongs. * @param int $step_id The ID of the step for which the A/B testing variations are being updated. * @param array $variations The new A/B testing variations for the step. * @param mixed $prev_settings The previous A/B testing settings for the step. * * @return array The updated A/B testing variations. * @since 1.8.8 */ public function update_ab_variations( $funnel_id, $step_id, $variations, $prev_settings ){ if( count( $variations ) >= 2 ){ $step = new Wpfnl_Steps_Store_Data(); foreach( $variations as $variation_key=>$variation ){ if( !isset($variation['stepId'], $variation['variationType'] ) ){ continue; } $key = array_search($variation['stepId'], array_column($prev_settings, 'step_id')); if( false !== $key ){ if ( 'original' !== $variation['variationType'] && isset($prev_settings[$key]['meta']) ){ $metas = $prev_settings[$key]['meta']; $exclude_meta = array( '_wpfnl_ab_testing_start_settings','_wpfnl_automation_steps', '_parent_step_id' ); $page_template = isset( $metas['page_template'] ) ? $metas['page_template'] : ''; $title = isset( $metas['title'] ) ? $metas['title'] : ''; $post_content = isset( $metas['post_content'] ) ? $metas['post_content'] : ''; $step_type = isset( $metas['type'] ) ? $metas['type'] : ''; if( $step ){ $new_step_id = $step->create_step($funnel_id, $title, $step_type, $post_content, true); if( $new_step_id ){ $step->update_meta($new_step_id, '_funnel_id', $funnel_id); $step->update_meta($new_step_id, '_wp_page_template', $page_template); $sql_query_arr = []; foreach( $metas['meta'] as $meta_key=>$meta ){ if( !in_array( $meta_key, $exclude_meta ) ){ $sql_data = [ 'post_id' => $new_step_id, 'meta_key' => $meta_key, 'meta_value'=> $meta[0], ]; $sql_query_arr[] = $sql_data; } } $this->update_meta_by_sql( $sql_query_arr ); // update meta } } }else{ $new_step_id = $step_id; } update_post_meta( $new_step_id, '_parent_step_id', $step_id ); $variations[$variation_key]['id'] = $new_step_id; $variations[$variation_key]['stepType'] = get_post_meta( $step_id, '_step_type', true ); $step_edit_link = get_edit_post_link($new_step_id); if( 'elementor' === Wpfnl_functions::get_builder_type() ){ $step_edit_link = str_replace('&','&',$step_edit_link); $step_edit_link = str_replace('edit','elementor',$step_edit_link); } $variations[$variation_key]['stepEditLink'] = $step_edit_link; $variations[$variation_key]['stepViewLink'] = get_the_permalink($new_step_id); $variations[$variation_key]['stepName'] = get_the_title($new_step_id); $variations[$variation_key]['stepId'] = $new_step_id; } } }else{ $default_data = Wpfnl_Ab_Testing::get_default_data( $step_id ); $variations = $default_data['variations']; } return $variations; } /** * Update funnel data and steps * * @param Int $funnel_id * @param Array $step_data * * @return void * @since 1.7.5 */ public function update_funnel_data( $new_step_ids, $funnel_id, $funnel_data ){ $funnel_identifier = array(); if ($funnel_data) { $node_data = isset($funnel_data['drawflow']['Home']['data']) ? $funnel_data['drawflow']['Home']['data'] : []; foreach ($node_data as $node_key => $node_value) { if(isset($node_value['data']['step_id'])) { if ( isset($new_step_ids[$node_value['data']['step_id']]) ) { $post_edit_link = base64_encode(get_edit_post_link($new_step_ids[$node_value['data']['step_id']])); $post_view_link = base64_encode(get_post_permalink($new_step_ids[$node_value['data']['step_id']])); $funnel_data['drawflow']['Home']['data'][$node_key]['data']['step_id'] = $new_step_ids[$node_value['data']['step_id']]; $funnel_data['drawflow']['Home']['data'][$node_key]['data']['step_edit_link'] = $post_edit_link; $funnel_data['drawflow']['Home']['data'][$node_key]['data']['step_view_link'] = $post_view_link; $funnel_data['drawflow']['Home']['data'][$node_key]['html'] = $node_value['data']['step_type'] . $new_step_ids[$node_value['data']['step_id']]; $funnel_identifier[$node_value['id']] = $new_step_ids[$node_value['data']['step_id']]; } else { if ($node_value['data']['step_type'] != 'conditional') { $funnel_identifier[$node_value['id']] = $node_value['data']['step_id']; } else { $funnel_identifier[$node_value['id']] = $node_value['data']['node_identifier']; } } } if ($node_value['data']['step_type'] == 'conditional') { $funnel_identifier[$node_value['id']] = $node_value['data']['node_identifier']; } } } update_post_meta($funnel_id, '_funnel_data', $funnel_data); if ($funnel_identifier) { $funnel_identifier_json = json_encode($funnel_identifier, JSON_UNESCAPED_SLASHES); update_post_meta($funnel_id, 'funnel_identifier', $funnel_identifier_json); } } /** * Hide the import funnel notice based on nonce verification. * * This method is responsible for hiding the import funnel notice displayed to users. It receives a nonce value from the * submitted form, verifies the nonce using the 'wp_verify_nonce' function, and if the verification succeeds, it deletes the * 'wpfnl_import_notice' option to hide the notice. It then sends a JSON success response to the client and terminates the script * execution using 'wp_die'. * * @since 1.9.7 */ public function hide_import_funnel_notice() { $nonce = !empty( $_POST[ 'security' ] ) ? htmlspecialchars( trim( $_POST[ 'security' ] ) ) : null; // phpcs:ignore if( wp_verify_nonce( $nonce, 'wpfnl-admin' ) ) { delete_option( 'wpfnl_import_notice' ); } wp_send_json_success(); wp_die(); } public function get_validation_data() { return [ 'logged_in' => true, 'user_can' => 'wpf_manage_funnels', ]; } }includes/core/integrations/affiliate/class-wpfnl-pro-wp-affiliate-integration.php000064400000003100147600245720024364 0ustar00is_wp_affiliate_active() ){ global $wpdb; $offer_settings = Wpfnl_functions::get_offer_settings(); $affilate_woocommerce = new Affiliate_WP_WooCommerce; $offer_order = isset($offer_settings['offer_orders']) ? $offer_settings['offer_orders'] : 'main-order'; if( 'main-order' == $offer_order ){ $table = $wpdb->prefix.'affiliate_wp_referrals'; $wpdb->delete( $table, array( 'reference' => $order->get_id() ) ); $affilate_woocommerce->add_pending_referral($order->get_id()); }else{ $affilate_woocommerce->add_pending_referral($order->get_id()); } } } /** * Checking when Affiliate Plugin is active * @return bool */ public function is_wp_affiliate_active() { if (is_plugin_active('affiliate-wp/affiliate-wp.php')) { return true; } return false; } }includes/core/integrations/class-wpfnl-pro-integration-fluent-forms.php000064400000011622147600245720022521 0ustar00 $redirect_link, 'message' => 'Redirecting to next step', 'action' => 'hide_form', ); return $data; } } return; } public function fluentcrm_funnel_will_process_fluentform_submission_inserted($willProcess, $funnel, $subscriberData, $originalArgs) { $formData = $originalArgs[1]; $page_id = $formData['__fluent_form_embded_post_id']; $trigger = false; if (isset($formData['__fluent_form_embded_post_id'])) { $embedded_post = $formData['__fluent_form_embded_post_id']; $funnel_id = get_post_meta($embedded_post, '_funnel_id', true); $steps_order = get_post_meta($funnel_id, '_steps_order', true); foreach ($steps_order as $step_key => $step_value) { if ($step_value['type'] == 'checkout') { $trigger = true; } } if ($trigger) { $insertId = $originalArgs[0]; $get_crm_data = array(); $data = array( 'funnel' => $funnel, 'subscriber' => $subscriberData, 'insertID' => $insertId, ); $get_crm_data = get_option('wpfnl_fluent_crm_data'); $get_crm_data[] = $data; update_option('wpfnl_fluent_crm_data', $get_crm_data); return false; } return true; } return true; } public function wpfnl_10minute_schedules($schedules) { if (!isset($schedules["10min"])) { $schedules["10min"] = array( 'interval' => 10 * 60, 'display' => __('Once every 10 minutes')); } return $schedules; } public function wpfnl_10_min_cron() { wp_schedule_event(time(), '10min', 'wpfnl_10_min_client_check'); } public function wpfnl_10_min_client_check() { $crm_pending_objects = get_option('wpfnl_fluent_crm_data'); $contactApi = FluentCrmApi('contacts'); if ($crm_pending_objects) { foreach ($crm_pending_objects as $crm_object) { if (isset($crm_object['funnel']) && isset($crm_object['insertID']) && isset($crm_object['subscriber'])) { $funnel = $crm_object['funnel']; $insert_id = $crm_object['insertID']; $subscriberData = $crm_object['subscriber']; $subscriberData['status'] = 'subscriber'; $subscriber = $contactApi->createOrUpdateContact($subscriberData); (new FunnelProcessor())->startFunnelSequence($funnel, $subscriberData, [ 'source_trigger_name' => 'fluentform_submission_inserted', 'source_ref_id' => $insert_id, ], $subscriber); } } delete_option('wpfnl_fluent_crm_data'); } } public function wpfnl_woocommerce_new_order($order_id) { $order = wc_get_order($order_id); $billing_email = $order->get_billing_email(); $crm_pending_objects = get_option('wpfnl_fluent_crm_data'); if ($crm_pending_objects) { foreach ($crm_pending_objects as $crm_key => $crm_object) { $subscriberData = $crm_object['subscriber']; if ($subscriberData['email'] == $billing_email) { unset($crm_pending_objects[$crm_key]); } } update_option('wpfnl_fluent_crm_data', $crm_pending_objects); } } } includes/core/integrations/Manager.php000064400000001004147600245720014075 0ustar00value = $value; $this->targeted_value = $targeted_value; } /** * Checks if the condition is true or false and returns the value * * @return bool * @since 2.3.4 */ public function is(): bool { return ($this->value === $this->targeted_value); } /** * Checks if the condition is true or false and returns the value * * @return bool * @since 2.3.4 */ public function isNot(): bool { return ($this->value !== $this->targeted_value); } } includes/core/mint-automation/conditions/order-bump.php000064400000002613147600245720017376 0ustar00value = $value; $this->targeted_value = $targeted_value; } /** * Checks if the condition is true or false and returns the value * * @return bool * @since 2.3.4 */ public function is(): bool { return ($this->value === $this->targeted_value); } /** * Checks if the condition is true or false and returns the value * * @return bool * @since 2.3.4 */ public function isNot(): bool { return ($this->value !== $this->targeted_value); } } includes/core/mint-automation/conditions/upsell.php000064400000002626147600245720016632 0ustar00value = $value; $this->targeted_value = $targeted_value; } /** * Checks if the condition is true or false and returns the value * * @return bool * @since 2.3.4 */ public function is(): bool { return ($this->value === $this->targeted_value); } /** * Checks if the condition is true or false and returns the value * * @return bool * @since 2.3.4 */ public function isNot(): bool { return ($this->value !== $this->targeted_value); } } includes/core/mint-automation/class-wpfnl-pro-automation.php000064400000065032147600245720020362 0ustar00 !empty($trigger['id']) ? $trigger['id'] : uniqid(), 'key' => $trigger['value'], 'type' => 'trigger', 'next_step_id' => $next_step, 'settings' => $this->prepare_settings($step_id, $funnel_id), ]; $automation_id = get_post_meta($step_id, 'wpfnl_mint_automation_id', true); if ($automation_id) { $automation_data['id'] = $automation_id; $trigger_steps['automation_id'] = $automation_id; } $id = $this->is_automation_step_exist_by_step_id($trigger_steps['step_id']); if( $id ) { $trigger_steps['id'] = $id; } $trigger['id'] = $trigger_steps['step_id']; update_post_meta( $step_id, '_wpfnl_automation_trigger', $trigger ); // Set default values for automation data. $automation_data['name'] = 'automation'; $automation_data['author'] = get_current_user_id(); $automation_data['trigger_name'] = $trigger['value']; $automation_data['status'] = 'active'; $automation_data['steps'] = []; // Add the trigger step to the automation data. $automation_data[ 'steps' ][] = $trigger_steps; $automation_step_id = $next_step; // If there are automation steps, process and add them to the automation data. if (is_array($automation_steps) && count($automation_steps)) { $count = count($automation_steps); foreach ($automation_steps as $key => $automation_step) { if( ! empty( $automation_step['automation_step_id'] ) ){ $automation_step_id = $automation_step['automation_step_id']; } // Format the automation step data. $trigger_settings = $this->formatted_automation_data($automation_step_id, $automation_steps, $key, $count); $automation_step_id = $trigger_settings[ 'next_step_id' ] ?? ''; $automation_steps[ $key ]['automation_step_id'] = $trigger_settings['step_id']; $automation_steps[ $key ]['automation_next_step_id'] = $trigger_settings['next_step_id']; if( isset( $automation_steps[ $key - 1 ]['automation_next_step_id'] ) ){ $automation_steps[ $key - 1 ]['automation_next_step_id'] = $trigger_settings['step_id']; } if( isset($automation_steps[ $key ]['key'],$automation_steps[ $key ]['settings']['settings']['yes'],$automation_steps[ $key ]['settings']['settings']['no'],$trigger_settings['key']) && is_array($automation_steps[ $key ]['settings']['settings']['yes']) && is_array($automation_steps[ $key ]['settings']['settings']['no']) && 'condition' === $automation_steps[ $key ]['key'] && 'condition' === $trigger_settings['key'] ){ foreach($automation_steps[ $key ]['settings']['settings']['yes'] as $yes_key=> $yes_data ){ $automation_steps[ $key ]['settings']['settings']['yes'][$yes_key]['step_id'] = $trigger_settings['node_data']['yes'][$yes_key]['step_id']; } foreach($automation_steps[ $key ]['settings']['settings']['no'] as $no_key=> $no_data ){ $automation_steps[ $key ]['settings']['settings']['no'][$no_key]['step_id'] = $trigger_settings['node_data']['no'][$no_key]['step_id']; } } $id = $this->is_automation_step_exist_by_step_id($trigger_settings['step_id']); if( $id ) { $trigger_settings['id'] = $id; } $prev_index = count($automation_data[ 'steps' ]) - 1; if( isset($automation_data[ 'steps' ][$prev_index]) ){ $automation_data[ 'steps' ][$prev_index]['next_step_id'] = $trigger_settings['step_id']; } // Add the formatted automation step to the automation data. $automation_data[ 'steps' ][] = $trigger_settings; } } update_post_meta($step_id, '_wpfnl_automation_steps', $automation_steps ); // Return the prepared automation data. return $automation_data; } public function prepare_automation_duplicate_data($funnel_id, $step_id){ if (!$funnel_id || !$step_id) { return false; } // Get the trigger information from the step's post meta. $trigger = get_post_meta($step_id, '_wpfnl_automation_trigger', true); // If the trigger value is empty, return false as automation data is not applicable. if (empty($trigger['value'])) { return false; } // Initialize an array to store automation data. $automation_data = []; // Check if an automation ID exists and include it in the data. // Retrieve automation steps from post meta. $automation_steps = get_post_meta($step_id, '_wpfnl_automation_steps', true); $next_step = uniqid(); // Create a trigger step based on the trigger value. $trigger_steps = [ 'step_id' => uniqid(), 'key' => $trigger['value'], 'type' => 'trigger', 'next_step_id' => $next_step, 'settings' => $this->prepare_settings($step_id, $funnel_id), ]; $trigger['id'] = $trigger_steps['step_id']; update_post_meta($step_id, '_wpfnl_automation_trigger', $trigger); // Set default values for automation data. $automation_data['name'] = 'automation'; $automation_data['author'] = get_current_user_id(); $automation_data['trigger_name'] = $trigger['value']; $automation_data['status'] = 'active'; $automation_data['steps'] = array(); // Add the trigger step to the automation data. $automation_data['steps'][] = $trigger_steps; $automation_step_id = $trigger_steps['next_step_id']; // If there are automation steps, process and add them to the automation data. if (is_array($automation_steps) && count($automation_steps)) { $count = count($automation_steps); foreach ($automation_steps as $key => $automation_step) { // Format the automation step data. $trigger_settings = $this->formatted_automation_data($automation_step_id, $automation_steps, $key, $count); $automation_step_id = $trigger_settings['next_step_id'] ?? ''; $automation_steps[$key]['automation_step_id'] = $trigger_settings['step_id']; $automation_steps[$key]['automation_next_step_id'] = $trigger_settings['next_step_id']; if (isset($automation_steps[$key - 1]['automation_next_step_id'])) { $automation_steps[$key - 1]['automation_next_step_id'] = $trigger_settings['step_id']; } if (isset($automation_steps[$key]['key'], $automation_steps[$key]['settings']['settings']['yes'], $automation_steps[$key]['settings']['settings']['no'], $trigger_settings['key']) && is_array($automation_steps[$key]['settings']['settings']['yes']) && is_array($automation_steps[$key]['settings']['settings']['no']) && 'condition' === $automation_steps[$key]['key'] && 'condition' === $trigger_settings['key']) { foreach ($automation_steps[$key]['settings']['settings']['yes'] as $yes_key => $yes_data) { $automation_steps[$key]['settings']['settings']['yes'][$yes_key]['step_id'] = $trigger_settings['node_data']['yes'][$yes_key]['step_id']; } foreach ($automation_steps[$key]['settings']['settings']['no'] as $no_key => $no_data) { $automation_steps[$key]['settings']['settings']['no'][$no_key]['step_id'] = $trigger_settings['node_data']['no'][$no_key]['step_id']; } } $id = $this->is_automation_step_exist_by_step_id($trigger_settings['step_id']); if ($id) { $trigger_settings['id'] = $id; } $prev_index = count($automation_data['steps']) - 1; if (isset($automation_data['steps'][$prev_index])) { $automation_data['steps'][$prev_index]['next_step_id'] = $trigger_settings['step_id']; } // Add the formatted automation step to the automation data. $automation_data['steps'][] = $trigger_settings; } } update_post_meta($step_id, '_wpfnl_automation_steps', $automation_steps); // Return the prepared automation data. return $automation_data; } /** * Check existing automation step on database * * @param mixed $step_id step id. * * @return mix $automation_step_id mixed if successful otherwise false. * @since 2.3.4 */ public function is_automation_step_exist_by_step_id( $step_id ) { if( !$step_id ){ return false; } global $wpdb; $automation_step_table = $wpdb->prefix . AutomationStepSchema::$table_name; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $select_query = $wpdb->prepare( "SELECT id FROM $automation_step_table WHERE step_id = %s", $step_id ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $results = $wpdb->get_results( $select_query ); // db call ok. ; no-cache ok. // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared // Fetch a single row $result = $wpdb->get_row( $select_query ); // db call ok. ; no-cache ok. // Check if a valid result is returned if ( isset( $result->id ) ) { return $result->id; } return false; } /** * Format automation step data. * * This method formats automation step data based on the provided parameters. * * @param string $step_id The ID of the current automation step. * @param array $automation_steps An array of automation steps. * @param int $index The index of the current automation step. * @param int $count The total count of automation steps. * * @return array Formatted automation step data. * @since 2.0.0 */ public function formatted_automation_data($step_id, $automation_steps, $index, $count, $is_conditional = 'no', $parent_next_step = '', $logic = '' ) { // Check if the automation step data at the given index exists. if (!isset($automation_steps[$index]['key'])) { return []; } // Check if the automation step key is "sendMail." if ( 'sendMail' == $automation_steps[ $index ][ 'key' ] ) { // Check if message data exists in the automation step settings. $automation_steps[ $index ][ 'settings' ][ 'settings' ][ 'message_data' ][ 'body' ] = $automation_steps[ $index ][ 'settings' ][ 'settings' ][ 'message_data' ][ 'body' ] ?? ''; } // Initialize an array to store the formatted trigger settings. $trigger_settings = []; if( ! empty( $automation_steps[$index]['next_step_id'] ) ){ $trigger_settings['next_step_id'] = $automation_steps[$index]['next_step_id']; }else{ if( ! empty( $automation_steps[(int)($index) + 1]['step_id'] ) ){ $trigger_settings['next_step_id'] = $automation_steps[(int)($index) + 1]['step_id']; }else{ // Set the next step ID based on the total count and the current index. $trigger_settings['next_step_id'] = $count > ($index + 1) ? uniqid() : ''; } } if( !$trigger_settings['next_step_id'] && 'yes' === $is_conditional && $parent_next_step ){ $trigger_settings['next_step_id'] = $parent_next_step; } if ( 'condition' == $automation_steps[$index]['key'] ) { $parent_next_step = $trigger_settings['next_step_id']; if( isset($automation_steps[$index]['settings']['settings']['yes']) && is_array($automation_steps[$index]['settings']['settings']['yes']) ){ $count = count($automation_steps[$index]['settings']['settings']['yes']); foreach( $automation_steps[$index]['settings']['settings']['yes'] as $key=> $yes_data ){ $condition_step_id = !empty($yes_data['step_id']) ? $yes_data['step_id'] : uniqid(); $_trigger_settings = $this->formatted_automation_data($condition_step_id, $automation_steps[$index]['settings']['settings']['yes'], $key, $count, 'yes', $parent_next_step, 'yes'); $id = $this->is_automation_step_exist_by_step_id( $condition_step_id ); if( $id ){ $_trigger_settings['id'] = $id; } $condition_step_id = $_trigger_settings[ 'next_step_id' ] ?? ''; $automation_steps[$index]['settings']['settings']['yes'][$key] = $_trigger_settings; $automation_steps[$index]['settings']['settings']['yes'][$key]['popover_type'] = 'condition'; $automation_steps[$index]['settings']['settings']['yes'][$key]['condition_type'] = 'yes'; $automation_steps[$index]['settings']['settings']['yes'][$key]['parent_index'] = $index; } } if( isset($automation_steps[$index]['settings']['settings']['no']) && is_array($automation_steps[$index]['settings']['settings']['no']) ){ $condition_step_id = uniqid(); $count = count($automation_steps[$index]['settings']['settings']['no']); foreach( $automation_steps[$index]['settings']['settings']['no'] as $key=> $no_data ){ $condition_step_id = !empty($no_data['step_id']) ? $no_data['step_id'] : uniqid(); $_trigger_settings = $this->formatted_automation_data($condition_step_id, $automation_steps[$index]['settings']['settings']['no'], $key, $count, 'yes', $parent_next_step, 'no'); $id = $this->is_automation_step_exist_by_step_id( $condition_step_id ); if( $id ){ $_trigger_settings['id'] = $id; } $condition_step_id = $_trigger_settings[ 'next_step_id' ] ?? ''; $automation_steps[$index]['settings']['settings']['no'][$key] = $_trigger_settings; $automation_steps[$index]['settings']['settings']['no'][$key]['popover_type'] = 'condition'; $automation_steps[$index]['settings']['settings']['no'][$key]['condition_type'] = 'no'; $automation_steps[$index]['settings']['settings']['no'][$key]['parent_index'] = $index; } } } // Set the step ID, automation key, type, and settings. $trigger_settings['step_id'] = $step_id; $trigger_settings['key'] = $automation_steps[ $index ][ 'key' ]; $trigger_settings['type'] = 'condition' === $automation_steps[$index]['key'] ? 'logical' : 'action'; if( 'condition' == $automation_steps[$index]['key'] ){ $trigger_settings['settings']['rules']['condition'][0] = $automation_steps[ $index ][ 'settings' ][ 'settings' ][ 'rules' ] ?? []; $trigger_settings['node_data']['yes'] = $automation_steps[ $index ][ 'settings' ][ 'settings' ][ 'yes' ] ?? []; $trigger_settings['node_data']['no'] = $automation_steps[ $index ][ 'settings' ][ 'settings' ][ 'no' ] ?? []; $trigger_settings['logical_next_step_id']['yes'] = $automation_steps[ $index ][ 'settings' ][ 'settings' ][ 'yes' ][ 0 ][ 'step_id' ] ?? ''; $trigger_settings['logical_next_step_id']['no'] = $automation_steps[ $index ][ 'settings' ][ 'settings' ][ 'no' ][ 0 ][ 'step_id' ] ?? ''; }else{ $trigger_settings['settings'] = $automation_steps[ $index ][ 'settings' ][ 'settings' ] ?? []; } // Return the formatted automation step data. return $trigger_settings; } /** * Prepare settings for a Mint Automation. * * This method prepares settings for a Mint Automation based on the provided step ID and funnel ID. * * @param int $step_id The ID of the current step. * @param int $funnel_id The ID of the funnel associated with the step. * * @return array An array of prepared settings for the Mint Automation. * @since 2.0.0 */ public function prepare_settings($step_id, $funnel_id) { // Create an array to store the prepared settings. $settings = [ 'type' => 'selected', // Indicates that specific steps and funnel IDs are selected. 'selected_funnel_ids' => [$funnel_id], // An array containing the ID of the associated funnel. 'all_steps' => 'no', // Indicates that not all steps are selected. 'selectedStep' => $step_id, // The ID of the selected step. 'selectedType' => get_post_meta($step_id, '_step_type', true) // The type of the selected step. ]; // Return the prepared settings. return $settings; } /** * Save or update automation data for a step within a funnel. * * This method takes in automation data, funnel ID, and step ID, and then saves or updates the automation * information associated with the step within the funnel. * * @param mixed $data The automation data to be saved or updated. * @param int $funnel_id The ID of the funnel to which the step belongs. * @param int $step_id The ID of the step for which automation data is being saved or updated. * * @return bool Returns true if the automation data is successfully saved or updated, otherwise false. * @since 2.0.0 */ public function save_or_update_automation($data, $funnel_id, $step_id) { // Check if both data and funnel ID are provided. if (!$data || !$funnel_id) { return false; } // Define the class name for the AutomationModel. $class_name = "MintMail\\App\\Internal\\Automation\\AutomationModel"; // Check if the AutomationModel class exists. if (!class_exists($class_name)) { return false; } // Use the AutomationModel class to create or update the automation data. $automation_id = $class_name::get_instance()->create_or_update($data); // Check if the automation ID was obtained. if (!$automation_id) { return false; } // Update the post meta for the step with the automation ID. update_post_meta($step_id, 'wpfnl_mint_automation_id', $automation_id); // Define an array of meta data to associate with the automation. $meta_data = [ [ 'meta_key' => 'source', 'meta_value' => 'wpf', ], [ 'meta_key' => 'funnel_id', 'meta_value' => $funnel_id, ], ]; $this->maybe_automation_exist( $automation_id ); // Iterate through the meta data array and update automation meta accordingly. foreach ($meta_data as $meta) { $this->update_automation_meta($automation_id, $meta['meta_key'], $meta['meta_value']); } // Return true to indicate successful saving or updating of automation data. return true; } /** * Check if a specific automation exists. * * This private function checks whether a given automation ID exists in the MainMint * system by querying the appropriate tables. It uses the provided automation ID to * look for matching automations based on predefined conditions. * * @since 1.6.0 * * @param int $automation_id The ID of the automation to check. * * @return bool True if the automation exists, false otherwise. */ private function maybe_automation_exist( $automation_id ) { if ( ! $automation_id ) { return false; } global $wpdb; $automation_table = $wpdb->prefix . 'mint_automations'; $automation_meta_table = $wpdb->prefix . 'mint_automation_meta'; // Query to retrieve matching automations. $automations = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id as id FROM $automation_table as automation INNER JOIN $automation_meta_table as automation_meta ON automation.id = automation_meta.automation_id WHERE automation_meta.automation_id = %d AND automation_meta.meta_key = %s AND automation_meta.meta_value = %s", array( $automation_id, 'source', 'wpf' ) ), ARRAY_A ); if ( ! is_array( $automations ) || ! count( $automations ) ) { return false; } return true; } /** * Update or insert automation meta data. * * This method allows you to update or insert meta data associated with automation. It takes in an automation ID, * a meta key, and a meta value, and performs the necessary database operations to update or insert the data. * * @param int $automation_id The ID of the automation. * @param string $meta_key The key of the meta data to update or insert. * @param string $meta_value The value of the meta data to update or insert. * * @return bool|int Returns true if the meta data is successfully updated, the inserted ID if data is added, or false on failure. * @since 2.0.0 */ public function update_automation_meta($automation_id, $meta_key, $meta_value) { global $wpdb; // Define the table name for automation meta data. $automation_meta_table = $wpdb->prefix . AutomationMetaSchema::$table_name; // Check if the meta data already exists. $select_query = $wpdb->prepare("SELECT * FROM $automation_meta_table WHERE automation_id = %d AND meta_key = %s", array($automation_id, $meta_key)); $results = $wpdb->get_results($select_query); // If meta data already exists. if (is_array($results) && !empty($results)) { try { // Prepare the payload for updating. $payload = [ 'id' => isset($results[0]->id) ? $results[0]->id : '', 'meta_key' => $meta_key, 'meta_value' => $meta_value, ]; // Update the 'updated_at' timestamp. $payload['updated_at'] = current_time('mysql'); // Perform the update operation. $updated = $wpdb->update( $automation_meta_table, $payload, array('ID' => $payload['id']) ); // Check if the update was successful. if ($updated) { return true; } else { return false; } } catch (\Exception $e) { return false; } } else { // If meta data doesn't exist, insert it. try { $wpdb->insert( $automation_meta_table, array( 'automation_id' => $automation_id, 'meta_key' => $meta_key, 'meta_value' => $meta_value, 'created_at' => current_time('mysql'), 'updated_at' => current_time('mysql'), ) ); // Return the inserted ID. return $wpdb->insert_id; } catch (\Exception $e) { return false; } } } /** * Delete automation by Id */ public function delete_automation( $step_id ){ if( !$step_id ){ return false; } $automation_id = get_post_meta($step_id, 'wpfnl_mint_automation_id', true); if( !$automation_id ){ return false; } $automationModel = "MintMail\\App\\Internal\\Automation\\AutomationModel"; if (!class_exists($automationModel)) { return false; } $function_exist = is_callable(array($automationModel, 'destroy')); if( !$function_exist ){ return false; } delete_post_meta( $step_id, 'wpfnl_mint_automation_id' ); return $automationModel::destroy( $automation_id ); } }includes/core/rest-api/controllers/analytics/Analyticsfactory.php000064400000000537147600245720021427 0ustar00 0, 'gross_sale_with_html' => '', 'gross_sale' => 0, 'order_bump_with_html' => 0, 'order_bump' => 0, 'avg_order_value_with_html' => 0, 'avg_order_value' => 0, 'total_revenue' => 0, 'revenue' => 0, 'offer_revenue' => 0, 'revenue_with_html' => 0, 'currency' => '' ); return $all_earnings; } /** * get orders from funnel id * * @param $funnel_id * @param \WP_REST_Request $request * @return array|object|null */ public function get_orders_by_funnel( $funnel_id, $start_date, $end_date ) { global $wpdb; $start_date = date( 'Y-m-d H:i:s', strtotime( $start_date . '00:00:00' ) ); //phpcs:ignore $end_date = date( 'Y-m-d H:i:s', strtotime( $end_date . '23:59:59' ) ); //phpcs:ignore $where = ''; $where .= " WHERE ( (( wpft2.meta_key = '_wpfunnels_funnel_id' AND wpft2.meta_value = $funnel_id ) OR ( wpft2.meta_key = '_wpfunnels_parent_funnel_id' AND wpft2.meta_value = $funnel_id )) "; $where .= " AND wpft1.post_status IN ( 'wc-completed', 'wc-processing', 'wc-cancelled' ))"; $where .= " AND wpft1.post_date >= '$start_date' "; $where .= " AND wpft1.post_date <= '$end_date' "; $query = 'SELECT wpft1.ID FROM ' . $wpdb->prefix . 'posts wpft1 INNER JOIN ' . $wpdb->prefix . 'postmeta wpft2 ON wpft1.ID = wpft2.post_id ' . $where; return $wpdb->get_results( $query ); } /** * get interval against start and end date * * @param $funnel_id * @param $start_date * @param $end_date * @param bool $db_interval * @return array|object|null */ public function get_intervals( $funnel_id, $start_date, $end_date, $db_interval = false, $type='hour' ) { global $wpdb; // get total order within those dates from funnel $start_date = date( 'Y-m-d H:i:s', strtotime( $start_date . '00:00:00' ) ); //phpcs:ignore $end_date = date( 'Y-m-d H:i:s', strtotime( $end_date . '23:59:59' ) ); //phpcs:ignore $where = ''; $where .= " AND wpft1.post_date >= '$start_date' "; $where .= " AND wpft1.post_date <= '$end_date' "; $where .= " AND ( (( wpft2.meta_key = '_wpfunnels_funnel_id' AND wpft2.meta_value = $funnel_id ) OR ( wpft2.meta_key = '_wpfunnels_parent_funnel_id' AND wpft2.meta_value = $funnel_id ))"; $where .= " AND wpft1.post_status IN ( 'wc-completed', 'wc-processing', 'wc-cancelled' ))"; $group_by = ''; $include_id = 'wpft1.ID,'; $date_format = "'%Y-%m-%d'"; if( $db_interval ) { $group_by = ' GROUP BY time_interval'; $include_id = ''; } if( $type === 'hour' ) { $date_format = "'%Y-%m-%d %h'"; } $query = 'SELECT '.$include_id.' DATE_FORMAT(wpft1.post_date, '.$date_format.') AS time_interval FROM ' . $wpdb->prefix . 'posts wpft1 INNER JOIN ' . $wpdb->prefix . 'postmeta wpft2 ON wpft1.ID = wpft2.post_id ' . $where . $group_by; $orders = $wpdb->get_results($query); $final_results = array(); $_temp_results = array(); if( $orders ) { foreach ($orders as $result) { if($db_interval) { $final_results[] = $result->time_interval; } else { $_temp_results[$result->time_interval][] = (object) array( 'ID' => $result->ID, ); } } } if( $_temp_results ) { foreach ($_temp_results as $key => $temp_result) { $default = array( 'time_interval' => '', 'gross_sale' => 0, 'order_bump' => 0, 'avg_order_value' => 0, 'all_steps' => array(), ); $totals = $this->get_earnings($funnel_id, $temp_result,$start_date, $end_date,'intervals','' ); $totals['time_interval'] = $key; $totals = wp_parse_args( $totals, $default ); $final_results[] = $totals; } } return $final_results; } /** * Fills in interval gaps from DB with 0-filled objects. * * @param array $db_intervals Array of all intervals present in the db. * @param DateTime $start_datetime Start date. * @param DateTime $end_datetime End date. * @param string $time_interval Time interval, e.g. day, week, month. * @param stdClass $data Data with SQL extracted intervals. * @return stdClass */ public function fill_in_missing_intervals( $db_intervals, $start_datetime, $end_datetime, $time_interval, &$data, $funnel_id ) { // @todo This is ugly and messy. $local_tz = new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ); $time_ids = array_flip( wp_list_pluck( $data->intervals, 'time_interval' ) ); // At this point, we don't know when we can stop iterating, as the ordering can be based on any value. $db_intervals = array_flip( $db_intervals ); // Totals object used to get all needed properties. $totals_arr = $data->earnings; foreach ( $totals_arr as $key => $val ) { $totals_arr[ $key ] = 0; } while ( $start_datetime <= $end_datetime ) { $next_start = TimeInterval::iterate( $start_datetime, $time_interval ); $time_id = TimeInterval::time_interval_id( $time_interval, $start_datetime ); // Either create fill-zero interval or use data from db. if ( $next_start > $end_datetime ) { $interval_end = $end_datetime->format( 'Y-m-d H:i:s' ); } else { $prev_end_timestamp = (int) $next_start->format( 'U' ) - 1; $prev_end = new \DateTime(); $prev_end->setTimestamp( $prev_end_timestamp ); $prev_end->setTimezone( $local_tz ); $interval_end = $prev_end->format( 'Y-m-d H:i:s' ); } $start_date = $start_datetime->format( 'Y-m-d' ); $year = date('Y',strtotime($start_date)); $month = date('m',strtotime($start_date)); $day = date('d',strtotime($start_date)); $order_url = admin_url('edit.php?post_type=shop_order&m='.$year.$month.$day.'&id='.$funnel_id); if ( array_key_exists( $time_id, $time_ids ) ) { $record = &$data->intervals[ $time_ids[ $time_id ] ]; $record['date_start'] = $start_datetime->format( 'Y-m-d H:i:s' ); $record['date_end'] = $interval_end; $record['order_url'] = $order_url; $record['steps_data'] = $this->get_steps_conversion_data( $funnel_id, $start_datetime->format('Y-m-d H:i:s'), $next_start->format('Y-m-d H:i:s') ); } elseif ( ! array_key_exists( $time_id, $db_intervals ) ) { $record_arr = array(); $record_arr['time_interval'] = $time_id; $record_arr['date_start'] = $start_datetime->format( 'Y-m-d H:i:s' ); $record_arr['date_end'] = $interval_end; $record_arr['order_url'] = $order_url; $record_arr['steps_data'] = $this->get_steps_conversion_data( $funnel_id, $start_datetime->format('Y-m-d H:i:s'), $next_start->format('Y-m-d H:i:s') ); $totals_arr['steps_data'] = $record_arr['steps_data']; $data->intervals[] = array_merge( $record_arr, $totals_arr ); } $start_datetime = $next_start; } return $data; } /** * @param $funnel_id * @param $start_date * @param $end_date * @return array */ public function get_steps_conversion_data( $funnel_id, $start_date, $end_date ) { global $wpdb; $analytics_db = $wpdb->prefix . WPFNL_PRO_ANALYTICS_TABLE; $analytics_meta_db = $wpdb->prefix . WPFNL_PRO_ANALYTICS_META_TABLE; // get all steps $steps = Wpfnl_functions::get_steps($funnel_id); $all_steps = array(); $conversion = []; $conversion_rate = []; $revenue_array = []; foreach ( $steps as $key => $step ) { $all_steps[] = $step['id']; $variations = Wpfnl_Ab_Testing::get_all_variations( $step['id'] ); if( is_array($variations) && count($variations) > 1 ){ foreach( $variations as $variation ){ if( isset($variation['id']) && !in_array( $variation['id'], $all_steps ) ){ $all_steps[] = $variation['id']; } } } } $step_ids = implode( ', ', $all_steps ); $analytics_columns = array( 'step_id' => "wpft1.step_id", 'total_visits' => "COUNT( DISTINCT( wpft1.id ) ) AS total_visits", 'unique_visits' => "COUNT( DISTINCT( CASE WHEN wpft1.visitor_type = 'new' THEN wpft1.id ELSE NULL END ) ) AS unique_visits", 'conversion' => "COUNT( CASE WHEN wpft2.meta_key = 'conversion' AND wpft2.meta_value = 'yes' AND wpft1.visitor_type = 'returning' THEN wpft1.step_id ELSE NULL END ) AS conversions ", 'new_conversion' => "COUNT( CASE WHEN wpft2.meta_key = 'conversion' AND wpft2.meta_value = 'yes' AND wpft1.visitor_type = 'new' THEN wpft1.step_id ELSE NULL END ) AS new_conversions ", 'optin_submission' => "COUNT( CASE WHEN wpft2.meta_key = 'wpfunnel_optin_submit' AND wpft2.meta_value = 'yes' THEN wpft1.step_id ELSE NULL END ) AS optin_submission ", ); $where = ''; $where .= " AND wpft1.funnel_id = %s "; $where .= " AND wpft1.date_created >= %s "; $where .= " AND wpft1.date_created <= %s "; $query = $wpdb->prepare( "SELECT {$analytics_columns['step_id']}, {$analytics_columns['total_visits']}, {$analytics_columns['unique_visits']}, {$analytics_columns['conversion']}, {$analytics_columns['new_conversion']}, {$analytics_columns['optin_submission']} FROM $analytics_db as wpft1 INNER JOIN $analytics_meta_db as wpft2 ON wpft1.id = wpft2.analytics_id WHERE ( wpft1.step_id IN ( $step_ids ) ) {$where} GROUP BY wpft1.step_id ORDER BY NULL ",$funnel_id,$start_date ,$end_date); $visits_data = $wpdb->get_results( $query ); $total_visit_data = array(); if( $steps ) { foreach ( $steps as $step ) { $step_type = get_post_meta( $step['id'], '_step_type', true ); $revenue = 0; $offer_revenue = 0; $variations = Wpfnl_Ab_Testing::get_all_variations( $step['id'] ); if( is_array($variations) && count($variations) > 1 ){ $variations_revenue = 0.00; $variations_offer_revenue = 0.00; foreach( $variations as $variation ){ if( isset($variation['id']) ){ if( 'checkout' === $step_type || 'upsell' === $step_type || 'downsell' === $step_type ) { $variations_rev = $this->get_step_revenue( $variation['id'], $step_type, $start_date, $end_date ); $variations_revenue = bcadd( $variations_revenue , floatval($variations_rev['revenue']), 2) ; $variations_offer_revenue = bcadd( $variations_offer_revenue , floatval($variations_rev['offer_revenue']), 2) ; } } } if( 'checkout' === $step_type || 'upsell' === $step_type || 'downsell' === $step_type ) { $revenue = $variations_revenue; $offer_revenue = $variations_offer_revenue; } $key = array_search( $step['id'], array_column($visits_data, 'step_id')); $variation_total_visit = 0; $variation_returning_visits = 0; $variation_unique_visits = 0; $variation_conversions = 0; $variation_new_conversions = 0; $variation_optin_submission = 0; $conversion_rate = 0; $new_conversion_rate = 0; foreach( $variations as $variant_key => $variation ){ $variant_key = array_search( $variation['id'], array_column($visits_data, 'step_id')); $isPermittedData = false; if($variant_key!==false){ $variation_total_visit += $visits_data[$variant_key]->total_visits; $variation_returning_visits += ($visits_data[$variant_key]->total_visits-$visits_data[$variant_key]->unique_visits); $variation_unique_visits += $visits_data[$variant_key]->unique_visits; $variation_conversions += $visits_data[$variant_key]->conversions; $variation_new_conversions += $visits_data[$variant_key]->new_conversions; $variation_optin_submission += $visits_data[$variant_key]->optin_submission; if ( $variation_conversions > 0 ) { if($variation_returning_visits > 0){ $conversion_rate = $variation_conversions / intval($variation_returning_visits) * 100; }else{ $conversion_rate = 0; } if($variation_unique_visits > 0){ $new_conversion_rate = $variation_new_conversions / intval($variation_unique_visits) * 100; }else{ $new_conversion_rate = 0; } } } } // if($key !== false){ $total_visit_data[$step['id']] = array( 'step_id' => $step['id'], 'step_name' => $step['name'], 'total_visits' => $variation_total_visit, 'returning_visits' => $variation_returning_visits, 'unique_visits' => $variation_unique_visits, 'conversions' => $variation_conversions, 'new_conversions' => $variation_new_conversions, 'optin_submission' => $variation_optin_submission, 'conversions_rate' => $conversion_rate, 'new_conversions_rate' => $new_conversion_rate, 'revenue' => $revenue, 'offer_revenue' => $offer_revenue, ); // } }else{ if( 'checkout' === $step_type || 'upsell' === $step_type || 'downsell' === $step_type ) { $rev = $this->get_step_revenue( $step['id'], $step_type, $start_date, $end_date ); $revenue = $rev['revenue']; $offer_revenue = $rev['offer_revenue']; } $conversion_rate = 0; if( $visits_data ) { $key = array_search( $step['id'], array_column($visits_data, 'step_id') ); if($key !== false){ if ( $visits_data[$key]->total_visits > 0 ) { $conversion_rate = $visits_data[$key]->conversions / intval($visits_data[$key]->total_visits) * 100; } $total_visit_data[$step['id']] = array( 'step_id' => $visits_data[$key]->step_id, 'step_name' => $step['name'], 'step_type' => $step['step_type'], 'total_visits' => $visits_data[$key]->total_visits, 'returning_visits' => $visits_data[$key]->total_visits-$visits_data[$key]->unique_visits, 'unique_visits' => $visits_data[$key]->unique_visits, 'conversions' => $visits_data[$key]->conversions, 'new_conversions' => $visits_data[$key]->new_conversions, 'optin_submission' => $visits_data[$key]->optin_submission, 'conversion_rate' => $conversion_rate, 'revenue' => $revenue, 'offer_revenue' => $offer_revenue, ); } else{ $total_visit_data[$step['id']] = array( 'step_id' => $step['id'], 'step_name' => $step['name'], 'step_type' => $step['step_type'], 'total_visits' => 0, 'returning_visits' => 0, 'unique_visits' => 0, 'conversions' => 0, 'new_conversions' => 0, 'optin_submission' => 0, 'conversion_rate' => $conversion_rate, 'revenue' => $revenue, 'offer_revenue' => $offer_revenue, ); } } else { $total_visit_data[$step['id']] = array( 'step_id' => $step['id'], 'step_name' => $step['name'], 'step_type' => $step['step_type'], 'total_visits' => 0, 'returning_visits' => 0, 'unique_visits' => 0, 'conversions' => 0, 'new_conversions' => 0, 'optin_submission' => 0, 'conversion_rate' => $conversion_rate, 'revenue' => $revenue, 'offer_revenue' => $offer_revenue, ); } } } } return $total_visit_data; } public function get_step_revenue( $step_id, $step_type, $start_date, $end_date ) { global $wpdb; $where = ''; $meta_key = '_wpfunnels_checkout_id'; switch ($step_type) { case 'checkout': $meta_key = '_wpfunnels_checkout_id'; break; case 'upsell': $meta_key = '_wpfunnels_offer_'.$step_id; break; case 'downsell': $meta_key = '_wpfunnels_offer_'.$step_id; break; } $where .= "WHERE ( ( wpft2.meta_key = '%s' AND wpft2.meta_value = $step_id )"; $where .= " AND wpft1.post_status IN ( 'wc-completed', 'wc-processing', 'wc-cancelled' ))"; $where .= " AND wpft1.post_date >= '$start_date' "; $where .= " AND wpft1.post_date <= '$end_date' "; $query = $wpdb->prepare( "SELECT ID FROM {$wpdb->prefix}posts as wpft1 INNER JOIN {$wpdb->prefix}postmeta as wpft2 ON wpft1.ID = wpft2.post_id {$where} ", $meta_key); $orders = $wpdb->get_results( $query ); $funnel_id = Wpfnl_functions::get_funnel_id_from_step($step_id); $earnings = $this->get_earnings($funnel_id,$orders,$start_date,$end_date, 'step_revenue', $step_id, $step_type); return $earnings; } /** * Reset analytics * * @param $request * @return Array */ public function reset_analytics_data( $request ){ global $wpdb; $funnel_id = $request['funnel_id']; $analytics_table = 'wp_wpfnl_analytics'; $analytics_meta = 'wp_wpfnl_analytics_meta'; $wpdb->delete( $analytics_table, array( 'funnel_id' => $funnel_id ) ); $wpdb->delete( $analytics_meta, array( 'funnel_id' => $funnel_id ) ); $response = array( 'success' => true, 'data' => 'data reset successfully', ); return $response; } }includes/core/rest-api/controllers/analytics/WcController.php000064400000065337147600245720020536 0ustar00ID) || (isset($order->ID) && in_array( $order->ID, $counted_orders )) ){ continue; } $counted_orders[] = $order->ID; // $funnel_id = Wpfnl_functions::get_funnel_id_from_order($order->ID) ? Wpfnl_functions::get_funnel_id_from_order($order->ID) : Wpfnl_functions::get_funnel_id_from_step($step_id); $rowCount = 0; if( $all_data == 'step_revenue'){ $wpdb->get_results(" SELECT * FROM " . $wpdb->prefix . "wpfnl_analytics_meta WHERE funnel_id = '" . $funnel_id . "' AND step_id = '" . $step_id . "' AND meta_key = 'wpfunnel_order_id' AND meta_value = '" . $order->ID . "' "); $rowCount = $wpdb->num_rows; }elseif( $all_data == 'all_earning' || $all_data == 'intervals' ){ $wpdb->get_results(" SELECT * FROM " . $wpdb->prefix . "wpfnl_analytics_meta WHERE funnel_id = '" . $funnel_id . "' AND meta_key = 'wpfunnel_order_id' AND meta_value = '" . $order->ID . "' "); $rowCount = $wpdb->num_rows; } // $wpdb->get_results(" // SELECT * FROM " . $wpdb->prefix . "wpfnl_analytics_meta // WHERE // funnel_id = '" . $funnel_id . "' // AND step_id = '" . $step_id . "' // AND meta_key = 'wpfunnel_order_id' // AND meta_value = '" . $order->ID . "' // "); // $rowCount = $wpdb->num_rows; if( $rowCount == 0 ){ continue; } $order_count++; $order_id = $order->ID; $order = wc_get_order( $order_id ); $total_order = $order->get_total(); $general_settings = Wpfnl_functions::get_general_settings(); $wpdb->get_results(" SELECT * FROM " . $wpdb->prefix . "wpfnl_analytics_meta WHERE funnel_id = '" . $funnel_id . "' AND meta_key = 'user_role' AND meta_value != '" . $order->get_id() . "' "); $rowCount = $wpdb->num_rows; if ( ! $order->has_status( 'cancelled' ) ) { $gross_sale += (float) $total_order; } $order_bump_product_id = $order->get_meta('_wpfunnels_order_bump_product'); foreach ( $order->get_items() as $item_id => $item_data ) { $item_product_id = $item_data->get_product_id(); if ( $item_product_id == $order_bump_product_id ) { $order_bump_earnings += $item_data->get_total() + $item_data->get_subtotal_tax(); } if( 'checkout' === $step_type ){ $step = $order->get_meta('_wpfunnels_offer_'.$step_id); if(empty($step)){ $total_revenue += $item_data->get_total() + $item_data->get_subtotal_tax(); if( 'yes' === $item_data->get_meta('_wpfunnels_upsell') || 'yes' === $item_data->get_meta('_wpfunnels_downsell') ){ $revenue += $item_data->get_total() + $item_data->get_subtotal_tax(); } } } if( 'upsell' === $step_type ){ if( 'yes' === $item_data->get_meta('_wpfunnels_upsell') ){ $step = $order->get_meta('_wpfunnels_offer_'.$step_id); if(!empty($step)){ $offer_revenue += $item_data->get_total() + $item_data->get_subtotal_tax(); $total_offer_revenue += $offer_revenue; } } } if( 'downsell' === $step_type ){ if( 'yes' === $item_data->get_meta('_wpfunnels_downsell') ){ $step = $order->get_meta('_wpfunnels_offer_'.$step_id); if(!empty($step)){ $offer_revenue += $item_data->get_total() + $item_data->get_subtotal_tax(); $total_offer_revenue += $offer_revenue; } } } } $total_revenue = $total_revenue + $order->get_shipping_total() + $order->get_shipping_tax();; } $total_order_bump_earnings = $order_bump_earnings; $gross_sale = $gross_sale + $total_offer_revenue; if ( 0 !== $order_count ) { $average_order_value = $gross_sale / $order_count; } } $revenue = $total_revenue-$revenue; $all_earnings = array( 'order' => (int)$order_count, 'gross_sale_with_html' => wc_price(number_format( (float) $gross_sale, 2, '.', '' )), 'gross_sale' => number_format( (float) $gross_sale, 2, '.', '' ), 'order_bump_with_html' => wc_price(number_format( (float) $total_order_bump_earnings, 2, '.', '' )), 'order_bump' => number_format( (float) $total_order_bump_earnings, 2, '.', '' ), 'avg_order_value_with_html' => wc_price(number_format( (float) $average_order_value, 2, '.', '' )), 'avg_order_value' => number_format( (float) $average_order_value, 2, '.', '' ), 'total_revenue' => number_format( (float) $total_revenue, 2, '.', '' ), 'revenue' => number_format( (float) $revenue, 2, '.', '' ), 'offer_revenue' => number_format( (float) $offer_revenue, 2, '.', '' ), 'revenue_with_html' => wc_price(number_format( (float) $revenue, 2, '.', '' )), 'currency' => get_woocommerce_currency_symbol() ); return $all_earnings; } /** * get orders from funnel id * * @param $funnel_id * @param \WP_REST_Request $request * @return array|object|null */ public function get_orders_by_funnel( $funnel_id, $start_date, $end_date ) { global $wpdb; $start_date = date( 'Y-m-d H:i:s', strtotime( $start_date . '00:00:00' ) ); //phpcs:ignore $end_date = date( 'Y-m-d H:i:s', strtotime( $end_date . '23:59:59' ) ); //phpcs:ignore $where = ''; $where .= " WHERE ( (( wpft2.meta_key = '_wpfunnels_funnel_id' AND wpft2.meta_value = $funnel_id ) OR ( wpft2.meta_key = '_wpfunnels_parent_funnel_id' AND wpft2.meta_value = $funnel_id )) "; $where .= " AND wpft1.post_status IN ( 'wc-completed', 'wc-processing', 'wc-cancelled' ))"; $where .= " AND wpft1.post_date >= '$start_date' "; $where .= " AND wpft1.post_date <= '$end_date' "; $query = 'SELECT wpft1.ID FROM ' . $wpdb->prefix . 'posts wpft1 INNER JOIN ' . $wpdb->prefix . 'postmeta wpft2 ON wpft1.ID = wpft2.post_id ' . $where; return $wpdb->get_results( $query ); } /** * get interval against start and end date * * @param $funnel_id * @param $start_date * @param $end_date * @param bool $db_interval * @return array|object|null */ public function get_intervals( $funnel_id, $start_date, $end_date, $db_interval = false, $type='hour' ) { global $wpdb; // get total order within those dates from funnel $start_date = date( 'Y-m-d H:i:s', strtotime( $start_date . '00:00:00' ) ); //phpcs:ignore $end_date = date( 'Y-m-d H:i:s', strtotime( $end_date . '23:59:59' ) ); //phpcs:ignore $where = ''; $where .= " AND wpft1.post_date >= '$start_date' "; $where .= " AND wpft1.post_date <= '$end_date' "; $where .= " AND ( (( wpft2.meta_key = '_wpfunnels_funnel_id' AND wpft2.meta_value = $funnel_id ) OR ( wpft2.meta_key = '_wpfunnels_parent_funnel_id' AND wpft2.meta_value = $funnel_id ))"; $where .= " AND wpft1.post_status IN ( 'wc-completed', 'wc-processing', 'wc-cancelled' ))"; $group_by = ''; $include_id = 'wpft1.ID,'; $date_format = "'%Y-%m-%d'"; if( $db_interval ) { $group_by = ' GROUP BY time_interval'; $include_id = ''; } if( $type === 'hour' ) { $date_format = "'%Y-%m-%d %h'"; } $query = 'SELECT '.$include_id.' DATE_FORMAT(wpft1.post_date, '.$date_format.') AS time_interval FROM ' . $wpdb->prefix . 'posts wpft1 INNER JOIN ' . $wpdb->prefix . 'postmeta wpft2 ON wpft1.ID = wpft2.post_id ' . $where . $group_by; $orders = $wpdb->get_results($query); if( empty($orders) ){ $wchpos_instance = new WcHpos(); if( $wchpos_instance->maybe_hpos_enable() ){ $where = ''; $where .= " AND wpft1.date_created_gmt >= '$start_date' "; $where .= " AND wpft1.date_created_gmt <= '$end_date' "; $where .= " AND ( (( wpft2.meta_key = '_wpfunnels_funnel_id' AND wpft2.meta_value = $funnel_id ) OR ( wpft2.meta_key = '_wpfunnels_parent_funnel_id' AND wpft2.meta_value = $funnel_id ))"; $where .= " AND wpft1.status IN ( 'wc-completed', 'wc-processing', 'wc-cancelled','wc-pending' ))"; $group_by = ''; $include_id = 'wpft1.ID,'; $date_format = "'%Y-%m-%d'"; if( $db_interval ) { $group_by = ' GROUP BY time_interval'; $include_id = ''; } if( $type === 'hour' ) { $date_format = "'%Y-%m-%d %h'"; } $query = 'SELECT '.$include_id.' DATE_FORMAT(wpft1.date_created_gmt, '.$date_format.') AS time_interval FROM ' . $wpdb->prefix . 'wc_orders wpft1 INNER JOIN ' . $wpdb->prefix . 'wc_orders_meta wpft2 ON wpft1.ID = wpft2.order_id ' . $where . $group_by; $orders = $wpdb->get_results($query); } } $final_results = array(); $_temp_results = array(); if( $orders ) { foreach ($orders as $result) { if($db_interval) { $final_results[] = $result->time_interval; } else { $_temp_results[$result->time_interval][] = (object) array( 'ID' => $result->ID, ); } } } if( $_temp_results ) { foreach ($_temp_results as $key => $temp_result) { $default = array( 'time_interval' => '', 'gross_sale' => 0, 'order_bump' => 0, 'avg_order_value' => 0, 'all_steps' => array(), ); $totals = $this->get_earnings($funnel_id, $temp_result,$start_date, $end_date,'intervals','' ); $totals['time_interval'] = $key; $totals = wp_parse_args( $totals, $default ); $final_results[] = $totals; } } return $final_results; } /** * Fills in interval gaps from DB with 0-filled objects. * * @param array $db_intervals Array of all intervals present in the db. * @param DateTime $start_datetime Start date. * @param DateTime $end_datetime End date. * @param string $time_interval Time interval, e.g. day, week, month. * @param stdClass $data Data with SQL extracted intervals. * @return stdClass */ public function fill_in_missing_intervals( $db_intervals, $start_datetime, $end_datetime, $time_interval, &$data, $funnel_id ) { // @todo This is ugly and messy. $local_tz = new \DateTimeZone( wc_timezone_string() ); $time_ids = array_flip( wp_list_pluck( $data->intervals, 'time_interval' ) ); // At this point, we don't know when we can stop iterating, as the ordering can be based on any value. $db_intervals = array_flip( $db_intervals ); // Totals object used to get all needed properties. $totals_arr = $data->earnings; foreach ( $totals_arr as $key => $val ) { $totals_arr[ $key ] = 0; } while ( $start_datetime <= $end_datetime ) { $next_start = TimeInterval::iterate( $start_datetime, $time_interval ); $time_id = TimeInterval::time_interval_id( $time_interval, $start_datetime ); // Either create fill-zero interval or use data from db. if ( $next_start > $end_datetime ) { $interval_end = $end_datetime->format( 'Y-m-d H:i:s' ); } else { $prev_end_timestamp = (int) $next_start->format( 'U' ) - 1; $prev_end = new \DateTime(); $prev_end->setTimestamp( $prev_end_timestamp ); $prev_end->setTimezone( $local_tz ); $interval_end = $prev_end->format( 'Y-m-d H:i:s' ); } $start_date = $start_datetime->format( 'Y-m-d' ); $year = date('Y',strtotime($start_date)); $month = date('m',strtotime($start_date)); $day = date('d',strtotime($start_date)); $order_url = admin_url('edit.php?post_type=shop_order&m='.$year.$month.$day.'&id='.$funnel_id); if ( array_key_exists( $time_id, $time_ids ) ) { $record = &$data->intervals[ $time_ids[ $time_id ] ]; $record['date_start'] = $start_datetime->format( 'Y-m-d H:i:s' ); $record['date_end'] = $interval_end; $record['order_url'] = $order_url; $record['steps_data'] = $this->get_steps_conversion_data( $funnel_id, $start_datetime->format('Y-m-d H:i:s'), $next_start->format('Y-m-d H:i:s') ); } elseif ( ! array_key_exists( $time_id, $db_intervals ) ) { $record_arr = array(); $record_arr['time_interval'] = $time_id; $record_arr['date_start'] = $start_datetime->format( 'Y-m-d H:i:s' ); $record_arr['date_end'] = $interval_end; $record_arr['order_url'] = $order_url; $record_arr['steps_data'] = $this->get_steps_conversion_data( $funnel_id, $start_datetime->format('Y-m-d H:i:s'), $next_start->format('Y-m-d H:i:s') ); $totals_arr['steps_data'] = $record_arr['steps_data']; $data->intervals[] = array_merge( $record_arr, $totals_arr ); } $start_datetime = $next_start; } return $data; } /** * @param $funnel_id * @param $start_date * @param $end_date * @return array */ public function get_steps_conversion_data( $funnel_id, $start_date, $end_date ) { global $wpdb; $analytics_db = $wpdb->prefix . WPFNL_PRO_ANALYTICS_TABLE; $analytics_meta_db = $wpdb->prefix . WPFNL_PRO_ANALYTICS_META_TABLE; // get all steps $steps = Wpfnl_functions::get_steps($funnel_id); $all_steps = array(); $conversion = []; $conversion_rate = []; $revenue_array = []; foreach ( $steps as $key => $step ) { $all_steps[] = $step['id']; } $step_ids = implode( ', ', $all_steps ); $analytics_columns = array( 'step_id' => "wpft1.step_id", 'total_visits' => "COUNT( DISTINCT( wpft1.id ) ) AS total_visits", 'unique_visits' => "COUNT( DISTINCT( CASE WHEN wpft1.visitor_type = 'new' THEN wpft1.id ELSE NULL END ) ) AS unique_visits", 'conversion' => "COUNT( CASE WHEN wpft2.meta_key = 'conversion' AND wpft2.meta_value = 'yes' AND wpft1.visitor_type = 'returning' THEN wpft1.step_id ELSE NULL END ) AS conversions ", 'new_conversion' => "COUNT( CASE WHEN wpft2.meta_key = 'conversion' AND wpft2.meta_value = 'yes' AND wpft1.visitor_type = 'new' THEN wpft1.step_id ELSE NULL END ) AS new_conversions ", 'optin_submission' => "COUNT( CASE WHEN wpft2.meta_key = 'wpfunnel_optin_submit' AND wpft2.meta_value = 'yes' THEN wpft1.step_id ELSE NULL END ) AS optin_submission ", ); $where = ''; $where .= " AND wpft1.funnel_id = %s "; $where .= " AND wpft1.date_created >= %s "; $where .= " AND wpft1.date_created <= %s "; $query = $wpdb->prepare( "SELECT {$analytics_columns['step_id']}, {$analytics_columns['total_visits']}, {$analytics_columns['unique_visits']}, {$analytics_columns['conversion']}, {$analytics_columns['new_conversion']}, {$analytics_columns['optin_submission']} FROM $analytics_db as wpft1 INNER JOIN $analytics_meta_db as wpft2 ON wpft1.id = wpft2.analytics_id WHERE ( wpft1.step_id IN ( $step_ids ) ) {$where} GROUP BY wpft1.step_id ORDER BY NULL ",$funnel_id,$start_date ,$end_date); $visits_data = $wpdb->get_results( $query ); $total_visit_data = array(); if( $steps ) { foreach ( $steps as $step ) { $step_type = get_post_meta( $step['id'], '_step_type', true ); $revenue = 0; $offer_revenue = 0; $variations = Wpfnl_Ab_Testing::get_all_variations( $step['id'] ); if( is_array($variations) && count($variations) > 1 ){ $variations_revenue = 0.00; $variations_offer_revenue = 0.00; foreach( $variations as $variation ){ if( isset($variation['id']) ){ if( 'checkout' === $step_type || 'upsell' === $step_type || 'downsell' === $step_type ) { $variations_rev = $this->get_step_revenue( $variation['id'], $step_type, $start_date, $end_date ); $variations_revenue = bcadd( $variations_revenue , floatval($variations_rev['revenue']), 2) ; $variations_offer_revenue = bcadd( $variations_offer_revenue , floatval($variations_rev['offer_revenue']), 2) ; } } } if( 'checkout' === $step_type || 'upsell' === $step_type || 'downsell' === $step_type ) { $revenue = $variations_revenue; $offer_revenue = $variations_offer_revenue; } }else{ if( 'checkout' === $step_type || 'upsell' === $step_type || 'downsell' === $step_type ) { $rev = $this->get_step_revenue( $step['id'], $step_type, $start_date, $end_date ); $revenue = $rev['revenue']; $offer_revenue = $rev['offer_revenue']; } } $conversion_rate = 0; if( $visits_data ) { $key = array_search( $step['id'], array_column($visits_data, 'step_id') ); if($key !== false){ if ( $visits_data[$key]->total_visits > 0 ) { $conversion_rate = $visits_data[$key]->conversions / intval($visits_data[$key]->total_visits) * 100; } $total_visit_data[$step['id']] = array( 'step_id' => $visits_data[$key]->step_id, 'step_name' => $step['name'], 'step_type' => $step['step_type'], 'total_visits' => $visits_data[$key]->total_visits, 'returning_visits' => $visits_data[$key]->total_visits-$visits_data[$key]->unique_visits, 'unique_visits' => $visits_data[$key]->unique_visits, 'conversions' => $visits_data[$key]->conversions, 'new_conversions' => $visits_data[$key]->new_conversions, 'optin_submission' => $visits_data[$key]->optin_submission, 'conversion_rate' => $conversion_rate, 'revenue' => $revenue, 'offer_revenue' => $offer_revenue, ); } else{ $total_visit_data[$step['id']] = array( 'step_id' => $step['id'], 'step_name' => $step['name'], 'step_type' => $step['step_type'], 'total_visits' => 0, 'returning_visits' => 0, 'unique_visits' => 0, 'conversions' => 0, 'new_conversions' => 0, 'optin_submission' => 0, 'conversion_rate' => $conversion_rate, 'revenue' => $revenue, 'offer_revenue' => $offer_revenue, ); } } else { $total_visit_data[$step['id']] = array( 'step_id' => $step['id'], 'step_name' => $step['name'], 'step_type' => $step['step_type'], 'total_visits' => 0, 'returning_visits' => 0, 'unique_visits' => 0, 'conversions' => 0, 'new_conversions' => 0, 'optin_submission' => 0, 'conversion_rate' => $conversion_rate, 'revenue' => $revenue, 'offer_revenue' => $offer_revenue, ); } } } return $total_visit_data; } public function get_step_revenue( $step_id, $step_type, $start_date, $end_date ) { global $wpdb; $where = ''; $meta_key = '_wpfunnels_checkout_id'; switch ($step_type) { case 'checkout': $meta_key = '_wpfunnels_checkout_id'; break; case 'upsell': $meta_key = '_wpfunnels_offer_'.$step_id; break; case 'downsell': $meta_key = '_wpfunnels_offer_'.$step_id; break; } $where .= "WHERE ( ( wpft2.meta_key = '%s' AND wpft2.meta_value = $step_id )"; $where .= " AND wpft1.post_status IN ( 'wc-completed', 'wc-processing', 'wc-cancelled' ))"; $where .= " AND wpft1.post_date >= '$start_date' "; $where .= " AND wpft1.post_date <= '$end_date' "; $query = $wpdb->prepare( "SELECT ID FROM {$wpdb->prefix}posts as wpft1 INNER JOIN {$wpdb->prefix}postmeta as wpft2 ON wpft1.ID = wpft2.post_id {$where} ", $meta_key); $orders = $wpdb->get_results( $query ); if( empty($orders) ){ $wchpos_instance = new WcHpos(); if( $wchpos_instance->maybe_hpos_enable() ){ $orders = $wchpos_instance->get_hpos_orders( $start_date, $end_date ); } } $funnel_id = Wpfnl_functions::get_funnel_id_from_step($step_id); $earnings = $this->get_earnings($funnel_id,$orders,$start_date,$end_date, 'step_revenue', $step_id, $step_type); return $earnings; } /** * Reset analytics * * @param $request * @return Array */ public function reset_analytics_data( $request ){ global $wpdb; $funnel_id = $request['funnel_id']; $analytics_table = 'wp_wpfnl_analytics'; $analytics_meta = 'wp_wpfnl_analytics_meta'; $wpdb->delete( $analytics_table, array( 'funnel_id' => $funnel_id ) ); $wpdb->delete( $analytics_meta, array( 'funnel_id' => $funnel_id ) ); $response = array( 'success' => true, 'data' => 'data reset successfully', ); return $response; } }includes/core/rest-api/controllers/AbTestingController.php000064400000130174147600245720020046 0ustar00 rest_authorization_required_code()]); } return true; } /** * Makes sure the current user has access to READ the settings APIs. * * @param WP_REST_Request $request Full data about the request. * @return WP_Error|boolean * @since 3.0.0 */ public function get_items_permissions_check( $request ) { return true; if( !Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'settings' ) ) { return new WP_Error( 'wpfunnels_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'wpfnl' ), [ 'status' => rest_authorization_required_code() ] ); } return true; } /** * register rest routes * * @since 1.0.0 */ public function register_routes() { register_rest_route($this->namespace, '/' . $this->rest_base . '/update-status/(?P[\d]+)', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'update_status' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/update-running-status', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'update_running_status' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , 'args' => array( 'stepId' => array( 'description' => __( 'Funnel step ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), ), ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/create-varient', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'create_varient' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/duplicate-varient', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'duplicate_varient' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/restore-variant', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'restore_variant' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ], 'args' => array( 'stepId' => array( 'description' => __( 'Funnel step ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), 'variationId' => array( 'description' => __( 'AB testing variation ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), ), ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/update-settings', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'update_settings' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ], 'args' => array( 'stepId' => array( 'description' => __( 'Funnel step ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), ), ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/save-general-settings', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'save_general_settings' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ], 'args' => array( 'stepId' => array( 'description' => __( 'Funnel step ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), ), ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/delete-variant', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'delete_variant' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ], 'args' => array( 'stepId' => array( 'description' => __( 'Funnel step ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), 'variationId' => array( 'description' => __( 'AB testing variation ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), ), ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/get-start-settings/(?P[\d]+)', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_start_settings' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/get-general-settings/(?P[\d]+)', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_general_settings' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/get-settings', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_settings' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/get-stats/(?P[\d]+)/(?P[\d]+)', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_stats' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/reset-stats/(?P[\d]+)', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'reset_stats' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/reset-settings/(?P[\d]+)', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'reset_settings' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/declare-winner', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'declare_winner' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ], 'args' => array( 'stepId' => array( 'description' => __( 'Funnel step ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), ), ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/archive-variant', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'archive_variant' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ], 'args' => array( 'stepId' => array( 'description' => __( 'Funnel step ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), 'variationId' => array( 'description' => __( 'AB testing variation ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), ), ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/delete-archive-variant', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'delete_archive_variant' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ], 'args' => array( 'stepId' => array( 'description' => __( 'Funnel step ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), 'variationId' => array( 'description' => __( 'AB testing variation ID.','wpfnl-pro' ), 'type' => 'integer', 'required' => true ), ), ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/get-winner/(?P[\d]+)', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_winner' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/stop-ab-testing/(?P[\d]+)', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'stop_ab_testing' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); } /** * Update A/B testing status * * @param WP_REST_Request $request * @return Array * * @since 1.6.21 */ public function update_status( WP_REST_Request $request ){ if( isset($request['step_id']) ){ $step_id = $request['step_id']; $value = isset($request['value']) ? $request['value'] : ''; $response = Wpfnl_Ab_Testing::update_ab_testing_status( $step_id, $value ); if( $response ){ return rest_ensure_response( $this->get_success_response() ); } } return rest_ensure_response( $this->get_error_response() ); } /** * Update A/B testing running status * * @param WP_REST_Request $request * @return Array * * @since 1.6.21 */ public function update_running_status( WP_REST_Request $request ){ $required_params = array('stepId'); foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } $step_id = sanitize_text_field($request['stepId']); $isDeleteStats = isset($request['isDeleteStats']) ? $request['isDeleteStats'] : 'no'; $response = Wpfnl_Ab_Testing::update_running_status( $step_id,$isDeleteStats ); if( $response ){ if( isset($response['status']) && 'yes' === $response['status'] ){ update_post_meta($step_id, '_wpfnl_reset_stats', $isDeleteStats ); } return rest_ensure_response( $response ); } if( !$response || !is_array( $response ) ){ return rest_ensure_response( $this->get_error_response( __( "AB testing settings is not available", 'wpfnl-pro' ), 400 ) ); } return rest_ensure_response( $response ); } /** * Get setttings * * @param WP_REST_Request $request * @return Array * * @since 1.6.21 */ public function get_settings( WP_REST_Request $request ){ $response = []; if( !empty( $request['stepId'] ) ){ $step_id = $request['stepId']; $is_migrated = Wpfnl_Ab_Testing::maybe_migrate_start_settings( $step_id ); if( $is_migrated ){ $settings = get_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', true ); }else{ $settings = get_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', true ); if( !$settings || !is_array($settings) ){ $settings = Wpfnl_Ab_Testing::get_default_start_setting( $step_id ); update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', $settings ); } } $settings = Wpfnl_Ab_Testing::get_settings_with_stats( $step_id, $settings ); $settings = Wpfnl_Ab_Testing::update_variation_link( $settings ); update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', $settings ); $response = $this->get_success_response(); $response['data'] = $settings; $response['stepName'] = get_the_title( $step_id ); }else{ $response = $this->get_error_response(); $response['data'] = ''; } return rest_ensure_response( $response ); } /** * Update/save start settings * * @param WP_REST_Request $request * @return Array * @since 1.6.21 */ public function update_settings( WP_REST_Request $request ){ $required_params = array('stepId'); foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } $step_id = sanitize_text_field( $request['stepId'] ); $settings = filter_var_array( $request['settings'] ? $request['settings'] : [] ); Wpfnl_Ab_Testing::update_ab_testing_status( $step_id, 'yes' ); $response = Wpfnl_Ab_Testing::update_start_settings( $step_id, $settings ); if( !$response ){ return rest_ensure_response( $this->get_error_response( __( "Failed to save settings", 'wpfnl-pro' ), 400 ) ); } return rest_ensure_response( $this->get_success_response() ); } /** * Save general settings for A/B testing * * @param WP_REST_Request $request * @return Array * @since 2.2.6 */ public function save_general_settings( WP_REST_Request $request ){ $required_params = array('stepId'); foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } $step_id = sanitize_text_field( $request['stepId'] ); $settings = filter_var_array( isset($request['settings']) ? $request['settings'] : []); if( !isset($settings['autoEndSettings']) ){ return rest_ensure_response( $this->get_error_response( __( "Settings is missing", 'wpfnl-pro' ), 400 ) ); } if( isset($settings['autoEndSettings']['autoEnd']) && ($settings['autoEndSettings']['autoEnd'] == 1 || $settings['autoEndSettings']['autoEnd'] == 'true' ) ){ $settings['autoEndSettings']['autoEnd'] = 'yes'; do_action('wpfunnels/ab_testing_auto_end', $step_id, $settings['autoEndSettings']); }else{ $settings['autoEndSettings']['autoEnd'] = 'no'; } update_post_meta( $step_id, 'wpfnl_ab_testing_general_settings', $settings ); return rest_ensure_response( $this->get_success_response() ); } /** * Get general settings for A/B testing * * @param WP_REST_Request $request * @return Array * @since 2.2.6 */ public function get_general_settings( WP_REST_Request $request ){ if( isset($request['step_id']) && !$request['step_id'] ){ return rest_ensure_response( $this->get_error_response( __( "Required parameter step_id is missing.", 'wpfnl-pro' ), 400 ) ); } $step_id = sanitize_text_field( $request['step_id'] ); $settings = get_post_meta( $step_id, 'wpfnl_ab_testing_general_settings', true ); $response = [ 'success' => true, 'settings'=> $settings ]; return rest_ensure_response( $response ); } /** * Update/save start settings * * @param WP_REST_Request $request * @return Array * @since 1.6.21 */ public function stop_ab_testing( WP_REST_Request $request ){ if( isset($request['step_id']) ){ $step_id = $request['step_id']; $data = get_post_meta( $step_id, '_wpfnl_ab_testing_start_settings' , true ); if( isset($data['auto_winner']['is_enabled']) ){ $data['auto_winner']['is_enabled'] = 'true' == $data['auto_winner']['is_enabled'] ? 'yes' : 'no'; } $data['start_date'] = date( 'Y-m-d H:i:s' ); $data['is_started'] = ''; $result = Wpfnl_Ab_Testing::update_start_settings( $step_id, $data ); if( $result ){ return rest_ensure_response( $this->get_success_response() ); } } return rest_ensure_response( $this->get_error_response() ); } /** * Get start settings * * @param WP_REST_Request $request * @return Array * @since 1.6.21 */ public function get_start_settings( WP_REST_Request $request ){ if( isset($request['step_id']) ){ $step_id = $request['step_id']; $result = Wpfnl_Ab_Testing::get_start_settings( $step_id ); if( $result ){ $response = $this->get_success_response(); $response['data'] = $result; $start_date = new \DateTime($result['start_date']); $now = new \DateTime(Date('Y-m-d')); $interval = $start_date->diff($now); $response['running_days']= $interval->d; $response['step_type'] = get_post_meta( $step_id, '_step_type', true ); return rest_ensure_response( $response ); } } return rest_ensure_response( $this->get_error_response() ); } /** * Declear winner * * @param WP_REST_Request $request * * @return Array * @since 1.6.21 */ public function declare_winner( WP_REST_Request $request ){ $required_params = array('stepId'); foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } $step_id = sanitize_text_field( $request['stepId'] ); $variation_id = sanitize_text_field( isset($request['variationId']) ? $request['variationId'] : '' ); $is_archived = sanitize_text_field( isset($request['isArchived']) ? $request['isArchived'] : 'no' ); $result = Wpfnl_Ab_Testing::set_winner( $step_id, $variation_id ); if( !$result ){ return rest_ensure_response( $this->get_error_response( __( "Failed to update winner", 'wpfnl-pro' ), 400 ) ); } $need_reload = 'no'; if( 'yes' === $is_archived ){ Wpfnl_Ab_Testing::archive_all_variant( $step_id, $variation_id ); if( (int)$variation_id !== (int)$step_id ){ Wpfnl_Ab_Testing::update_drawflow_content($step_id, $variation_id); $need_reload = 'yes'; } $settings = get_post_meta( $variation_id, '_wpfnl_ab_testing_start_settings', true ); $settings['isStart'] = 'no'; update_post_meta( $variation_id, '_wpfnl_ab_testing_start_settings', $settings ); $settings = Wpfnl_Ab_Testing::get_settings_with_stats( $variation_id, $settings ); }else{ $settings = get_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', true ); $settings['isStart'] = 'no'; update_post_meta( $step_id, '_wpfnl_ab_testing_start_settings', $settings ); $settings = Wpfnl_Ab_Testing::get_settings_with_stats( $step_id, $settings ); } $response = [ 'success' => true, 'data' => $settings, 'needReload' => $need_reload, ]; return rest_ensure_response( $response ); } /** * Archive a variant in the A/B testing configuration. * * @param WP_REST_Request $request The REST request object. * * @return \WP_REST_Response The REST response containing success or error information. * @since 1.6.21 */ public function archive_variant( WP_REST_Request $request ) { $required_params = array('stepId', 'variationId'); // Check if all required parameters are present in the request. foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } // Get the step ID and variant ID from the request. $step_id = sanitize_text_field( $request['stepId'] ); $variation_id = sanitize_text_field( $request['variationId'] ); // Get the A/B testing start settings for the step. $settings = Wpfnl_Ab_Testing::get_start_settings( $step_id ); // Check if both active variations and archived variations exist in the settings. if ( !isset( $settings['variations'], $settings['archived_variations'] ) ) { return rest_ensure_response( $this->get_error_response( __( "No variation found", "wpfnl-pro" ), 400 ) ); } // Find the index of the variant to be archived within active variations. $key = array_search( $variation_id, array_column( $settings['variations'], 'stepId' ) ); // Check if the variant was found. if ( false === $key ) { return rest_ensure_response( $this->get_error_response( __( "Variation ID is not exist", "wpfnl-pro" ), 400 ) ); } // Retrieve the archived variant's data and remove it from active variations. $archived_variation_data = $settings['variations'][$key]; array_splice( $settings['variations'], $key, 1 ); $updated_step_id = ''; $need_reload = 'no'; if( 'original' === $archived_variation_data['variationType'] ){ if( isset($settings['variations'][0]) && count($settings['variations']) === 1 ){ $settings['isStart'] = 'no'; $settings['variations'][0]['variationType'] = 'original'; $updated_step_id = $settings['variations'][0]['stepId']; } $archived_variation_data['variationType'] = 'variant'; $archived_variation_data['isWinner'] = 'no'; } // Add the archived variant to the list of archived variations. array_push( $settings['archived_variations'], $archived_variation_data ); // Update the A/B testing start settings. $is_updated = Wpfnl_Ab_Testing::update_start_settings( $step_id, $settings ); if( $updated_step_id ){ Wpfnl_Ab_Testing::update_drawflow_content($archived_variation_data['stepId'], $updated_step_id); $need_reload = 'yes'; } // Check if the update was successful. if ( !$is_updated ) { return rest_ensure_response( $this->get_error_response( __( "Please provide correct step id and settings for update", "wpfnl-pro" ), 400 ) ); } // Prepare the response data. $response = [ 'success' => true, 'data' => $settings, 'needReload' => $need_reload ]; // Return the response. return rest_ensure_response( $response ); } /** * Get winner * * @param WP_REST_Request $request * @return Array * @since 1.6.21 */ public function get_winner( WP_REST_Request $request ){ if( isset( $request['step_id'] ) ){ $step_id = $request['step_id']; $result = Wpfnl_Ab_Testing::get_winner( $step_id ); if( $result ){ $response = $this->get_success_response(); $response['data'] = $result; return rest_ensure_response( $response ); } } return rest_ensure_response( $this->get_error_response() ); } /** * Delete a variant from the AB testing configuration. * * @param \WP_REST_Request $request The REST request object. * * @return \WP_REST_Response The REST response containing success or error information. * * @since 1.6.21 */ public function delete_variant( WP_REST_Request $request ) { $required_params = array('stepId', 'variationId'); // Check if all required parameters are present in the request. foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } // Get the step ID and variant ID from the request. $step_id = sanitize_text_field( $request['stepId'] ); $variant_id = sanitize_text_field( $request['variationId'] ); // Get the AB testing start settings for the step. $settings = Wpfnl_Ab_Testing::get_start_settings( $step_id ); // Check if variations exist in the settings. if ( !isset( $settings['variations'] ) ) { return rest_ensure_response( $this->get_error_response( __( "No variation found", "wpfnl-pro" ), 400 ) ); } // Find the index of the variant to be deleted. $key = array_search( $variant_id, array_column( $settings['variations'], 'stepId' ) ); // Check if the variant was found. if ( false === $key ) { return rest_ensure_response( $this->get_error_response( __( "Variation ID is not exist", "wpfnl-pro" ), 400 ) ); } $is_original = false; if( $settings['variations'][$key]['variationType'] === 'original' ){ $is_original = true; } $need_reload = 'no'; // Remove the variant from the settings. array_splice( $settings['variations'], $key, 1 ); // If no variations remain, restore default settings. if ( !count( $settings['variations'] ) ) { $default_settings = Wpfnl_Ab_Testing::get_default_start_setting( $step_id ); $settings = $default_settings; } if( isset($settings['variations']) && count($settings['variations']) == 1 ){ $settings['isStart'] = 'no'; } // Update the AB testing start settings. $settings['variations'][0]['variationType'] = 'original'; $is_updated = Wpfnl_Ab_Testing::update_start_settings( $step_id, $settings ); // Check if the update was successful. if ( !$is_updated ) { return rest_ensure_response( $this->get_error_response( __( "Please provide correct step id and settings for update", "wpfnl-pro" ), 400 ) ); } if( $is_original ){ $updated_step_id = $settings['variations'][0]['stepId']; Wpfnl_Ab_Testing::update_drawflow_content($variant_id, $updated_step_id); $need_reload = 'yes'; } // Prepare the response data. $response = [ 'success' => true, 'data' => $settings, 'needReload' => $need_reload ]; if( intval($step_id) !== intval($variant_id) ){ wp_delete_post($variant_id); } // Return the response. return rest_ensure_response( $response ); } /** * Create varient * */ public function create_varient( WP_REST_Request $request ){ if( isset( $request['step_id'], $request['step_type'], ) && isset( $request['funnel_id'] ) && isset( $request['step_name'] ) ){ $step_id = $request['step_id']; $step_type = $request['step_type']; $funnel_id = $request['funnel_id']; $step_name = $request['step_name']; $funnel = Wpfnl::get_instance()->funnel_store; $step = Wpfnl::get_instance()->step_store; $varient_id = $step->create_step( $funnel_id, $step_name, $step_type , '' , false ); $step->set_id($varient_id); if ($varient_id && ! is_wp_error($varient_id)) { $funnel->set_id($funnel_id); $step_edit_link = get_edit_post_link($varient_id); if( 'elementor' == Wpfnl_functions::get_builder_type() ){ $step_edit_link = str_replace('/&/g','&',$step_edit_link); $step_edit_link = str_replace('edit','elementor',$step_edit_link); } $step_view_link = get_post_permalink($varient_id); Wpfnl_Ab_Testing::update_variations( $step_id, $varient_id ); update_post_meta( $varient_id, '_parent_step_id', $step_id ); Wpfnl_Ab_Testing::update_ab_testing_status( $step_id, 'yes'); return [ 'success' => true, ]; } else { return [ 'success' => false, 'message' => $step_id->get_error_message(), ]; } } return rest_ensure_response( $this->get_error_response() ); } /** * Delete an archived variant from A/B testing configuration. * * @param WP_REST_Request $request The REST request object. * * @return \WP_REST_Response The REST response containing success or error information. * @since 1.6.21 */ public function delete_archive_variant( WP_REST_Request $request ) { $required_params = array('stepId', 'variationId'); // Check if all required parameters are present in the request. foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } // Get the step ID and variant ID from the request. $step_id = sanitize_text_field( $request['stepId'] ); $variant_id = sanitize_text_field( $request['variationId'] ); // Get the A/B testing start settings for the step. $settings = Wpfnl_Ab_Testing::get_start_settings( $step_id ); // Check if archived variations exist in the settings. if ( !isset( $settings['archived_variations'] ) ) { return rest_ensure_response( $this->get_error_response( __( "No variation found", "wpfnl-pro" ), 400 ) ); } // Find the index of the archived variant to be deleted. $key = array_search( $variant_id, array_column( $settings['archived_variations'], 'stepId' ) ); // Check if the variant was found. if ( false === $key ) { return rest_ensure_response( $this->get_error_response( __( "Variation ID is not exist", "wpfnl-pro" ), 400 ) ); } // Remove the archived variant from the list of archived variations. array_splice( $settings['archived_variations'], $key, 1 ); // Update the A/B testing start settings. $is_updated = Wpfnl_Ab_Testing::update_start_settings( $step_id, $settings ); // Check if the update was successful. if ( !$is_updated ) { return rest_ensure_response( $this->get_error_response( __( "Please provide correct step id and settings for update", "wpfnl-pro" ), 400 ) ); } if( intval($step_id) !== intval($variant_id) ){ wp_delete_post($variant_id); } // Prepare the response data. $response = [ 'success' => true, 'data' => $settings ]; // Return the response. return rest_ensure_response( $response ); } /** * Duplicate AB testing varient * @param \WP_REST_Request $request */ public function duplicate_varient( \WP_REST_Request $request ){ if( isset( $request['step_id'], $request['step_type'] ) && isset( $request['funnel_id'] ) && isset( $request['step_name'] ) ){ $step_id = $request['step_id']; $step_type = $request['step_type']; $funnel_id = $request['funnel_id']; $step_name = $request['step_name']; $title = get_the_title($step_id); $page_template = get_post_meta($step_id, '_wp_page_template', true); $post_content = get_post_field('post_content', $step_id); $funnel = Wpfnl::get_instance()->funnel_store; $step = Wpfnl::get_instance()->step_store; $varient_id = $step->create_step($funnel_id, $step_name, $step_type, $post_content, false); $step->set_id($varient_id); if ($varient_id && ! is_wp_error($varient_id)) { $builder = Wpfnl_functions::get_builder_type(); delete_post_meta($varient_id, '_wp_page_template'); $step->update_meta($varient_id, '_wp_page_template', $page_template); Wpfnl_Ab_Testing::duplicate_ab_testing_meta($step_id, $varient_id, array('_funnel_id', '_wpf_step_title', '_wpf_step_slug')); ob_start(); do_action('wpfunnels_after_step_import', $varient_id, $builder); ob_get_clean(); $funnel->set_id($funnel_id); $step_edit_link = get_edit_post_link($varient_id); if( 'elementor' == Wpfnl_functions::get_builder_type() ){ $step_edit_link = str_replace('/&/g','&',$step_edit_link); $step_edit_link = str_replace('edit','elementor',$step_edit_link); } $step_view_link = get_post_permalink($varient_id); Wpfnl_Ab_Testing::update_variations( $step_id, $varient_id ); update_post_meta( $varient_id, '_parent_step_id', $step_id ); Wpfnl_Ab_Testing::update_ab_testing_status( $step_id, 'yes'); $settings = Wpfnl_Ab_Testing::get_start_settings( $step_id ); return [ 'success' => true, 'data' => $settings, ]; } else { return [ 'success' => false, 'message' => $step_id->get_error_message(), ]; } } return rest_ensure_response( $this->get_error_response() ); } /** * Restore an archived variant to the AB testing configuration. * * @param \WP_REST_Request $request The REST request object. * * @return \WP_REST_Response The REST response containing success or error information. * @since 1.6.21 */ public function restore_variant( \WP_REST_Request $request ) { $required_params = array('stepId', 'variationId'); // Check if all required parameters are present in the request. foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } // Get the step ID and variant ID from the request. $step_id = sanitize_text_field( $request['stepId'] ); $variant_id = sanitize_text_field( $request['variationId'] ); // Get the AB testing start settings for the step. $settings = Wpfnl_Ab_Testing::get_start_settings( $step_id ); // Check if archived variations exist in the settings. if ( !isset( $settings['archived_variations'] ) ) { return rest_ensure_response( $this->get_error_response( __( "No archived variation found", "wpfnl-pro" ), 400 ) ); } // Find the index of the archived variant to be restored. $key = array_search( $variant_id, array_column( $settings['archived_variations'], 'stepId' ) ); // Check if the variant was found. if ( false === $key ) { return rest_ensure_response( $this->get_error_response( __( "This archived variation is not exist", "wpfnl-pro" ), 400 ) ); } // Retrieve the restored variant's data and remove it from archived variations. $restore_step_data = $settings['archived_variations'][$key]; array_splice( $settings['archived_variations'], $key, 1 ); // Check if variations exist in the settings. if ( !isset( $settings['variations'] ) ) { return rest_ensure_response( $this->get_error_response( __( "No variation found", "wpfnl-pro" ), 400 ) ); } // Add the restored variant to the list of variations. array_push( $settings['variations'], $restore_step_data ); // Update the AB testing start settings. $is_updated = Wpfnl_Ab_Testing::update_start_settings( $step_id, $settings ); // Check if the update was successful. if ( !$is_updated ) { return rest_ensure_response( $this->get_error_response( __( "Please provide correct step id and settings to restore", "wpfnl-pro" ), 400 ) ); } // Prepare the response data. $response = [ 'success' => true, 'data' => $settings ]; // Return the response. return rest_ensure_response( $response ); } /** * return error reponse * @return Array * */ private function get_error_response(){ return [ 'success' => false, 'data' => '', ]; } /** * return error reponse * @return Array * */ private function get_success_response(){ return [ 'success' => true, 'data' => '', ]; } /** * Prepare a single setting object for response. * * @param object $item Setting object. * @param WP_REST_Request $request Request object. * @return \WP_REST_Response $response Response data. * @since 1.0.0 */ public function prepare_item_for_response($item, $request) { $data = $this->add_additional_fields_to_object($item, $request); return rest_ensure_response($data); } /** * @desc Get statistics data for A/B testing. * * @param WP_REST_Request $data * @return WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_stats( \WP_REST_Request $data ) { if( isset($data['funnel_id'] ) && isset($data['step_id'] ) ){ $funnel_id = $data->get_param( 'funnel_id' ); $step_id = $data->get_param( 'step_id' ); $step_type = get_post_meta( $data->get_param( 'step_id' ), '_step_type', true ); $stats = Wpfnl_Ab_Testing::get_stats( $funnel_id, $step_id ); $response = $this->get_success_response(); $response['data'] = $stats; $response['step_type'] = $step_type; return rest_ensure_response( $response ); } return rest_ensure_response( $this->get_error_response() ); } /** * @desc Reset analytics stats API callback function * * @param WP_REST_Request $data * @return WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function reset_stats( \WP_REST_Request $data ) { $step_id = $data->get_param( 'step_id' ); if( Wpfnl_Ab_Testing::reset_stats( $step_id ) ) { return rest_ensure_response( $this->get_success_response() ); } return rest_ensure_response( $this->get_error_response() ); } /** * Reset settings API callback function * * @param WP_REST_Request $data * @return WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function reset_settings( \WP_REST_Request $data ) { $step_id = $data->get_param( 'step_id' ); if( Wpfnl_Ab_Testing::reset_settings( $step_id ) ) { return rest_ensure_response( $this->get_success_response() ); } return rest_ensure_response( $this->get_error_response() ); } } includes/core/rest-api/controllers/AnalyticsController.php000064400000202032147600245720020106 0ustar00namespace, $this->rest_base . '/(?P\d+)/(?P[a-zA-Z]+)', [ 'methods' => WP_REST_Server::READABLE, 'args' => [ 'funnel_id' => [ 'description' => __( 'Specific Funnel ID.', 'wpfnl-pro' ), 'type' => 'integer', 'required' => true ], 'filter' => [ 'description' => __( 'Filter name (weekly/monthly/yearly)', 'wpfnl-pro' ), 'type' => 'string', 'required' => true ] ], 'callback' => [ $this, 'get_funnel_analytics_data' ], 'permission_callback' => [ $this, 'rest_permissions_check' ] ] ); register_rest_route( $this->namespace, $this->rest_base . '/reset/(?P\d+)', [ 'methods' => WP_REST_Server::EDITABLE, 'args' => [ 'funnel_id' => [ 'description' => __( 'Specific Funnel ID.', 'wpfnl-pro' ), 'type' => 'integer', 'required' => true ] ], 'callback' => [ $this, 'reset_analytics_data' ], 'permission_callback' => [ $this, 'rest_permissions_check' ] ] ); } /** * Makes sure the current user has access to READ the settings APIs. * * @return \WP_Error|bool * * @since 1.9.6 */ public function rest_permissions_check() { if( !Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'settings' ) ) { return new \WP_Error( 'wpfunnels_rest_cannot_get', __( 'Sorry, you cannot list resources.', 'wpfnl' ), [ 'status' => rest_authorization_required_code() ] ); } return true; } /** * Sanitizes and retrieves API parameter values from a WP_REST_Request object. * * This function sanitizes and extracts the parameter values from a WP_REST_Request object, * returning an array of sanitized values. * * @param WP_REST_Request $request The REST request object containing API parameters. * @return array An array of sanitized parameter values retrieved from the request. * @since 1.9.6 */ private function get_api_params_values( WP_REST_Request $request ) { if( $request->sanitize_params() ) { return filter_var_array( $request->get_params() ); } return []; } /** * Controller function for wpfunnels/v1/analytics route * * @param WP_REST_Request $request * * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response * @since 1.9.6 */ public function get_funnel_analytics_data( WP_REST_Request $request ) { $params = $this->get_api_params_values( $request ); $funnel_id = $params[ 'funnel_id' ] ?? null; $filter = $params[ 'filter' ] ?? 'weekly'; $start_date = $params[ 'dateFrom' ] ?? ''; $end_date = $params[ 'dateTo' ] ?? ''; $funnel_order_ids = Wpfnl_Pro_functions::get_order_ids_by_funnel_id( $funnel_id ); $order_function = "get_{$filter}_orders"; $filtered_order_ids = method_exists( __CLASS__, $order_function ) ? $this->$order_function( $funnel_order_ids, $start_date, $end_date ) : []; $funnel_order_ids = !empty( $filtered_order_ids[ 'current' ] ) ? call_user_func_array( 'array_merge', array_filter( array_values( $filtered_order_ids[ 'current' ] ) ) ) : []; $prev_funnel_order_ids = !empty( $filtered_order_ids[ 'previous' ] ) ? call_user_func_array( 'array_merge', array_filter( array_values( $filtered_order_ids[ 'previous' ] ) ) ) : []; $header_section_summery = $this->get_header_summery( $funnel_order_ids, $prev_funnel_order_ids ); $revenue_section_summery = $this->get_revenue_section_summery( $header_section_summery ); $revenue_section_bar_data_cur = !empty( $filtered_order_ids[ 'current' ] ) ? $this->get_revenue_section_bar_data( $filtered_order_ids[ 'current' ] ) : []; $revenue_section_bar_data_prev = !empty( $filtered_order_ids[ 'previous' ] ) ? $this->get_revenue_section_bar_data( $filtered_order_ids[ 'previous' ] ) : []; $optin_function = "get_{$filter}_optin_submission"; $optin_submission = method_exists( __CLASS__, $optin_function ) ? $this->$optin_function( $funnel_id, $start_date, $end_date ) : []; $visitor_function = "get_{$filter}_visitors"; $visitors = method_exists( __CLASS__, $visitor_function ) ? $this->$visitor_function( $funnel_id, $start_date, $end_date ) : []; $visitors = $this->filter_visitor_data( $funnel_id, $visitors ); $header_section_summery[ 'optin_summery' ] = $optin_submission; $format = 'Y-m-d H:i:s'; $analyticsDateText = ''; if( 'weekly' === $filter ) { $current_week = get_weekstartend( date_i18n( $format, strtotime( 'now' ) ), get_option( 'start_of_week', 1 ) ); $start_of_week = date_i18n( 'F d, Y', $current_week[ 'start' ] ); $end_of_week = date_i18n( 'F d, Y', $current_week[ 'end' ] ); $analyticsDateText = 'Date range : '.$start_of_week.' - '.$end_of_week; }elseif( 'monthly' === $filter ){ $analyticsDateText = 'Date range : '.date('F, Y'); }elseif( 'yearly' === $filter ){ $analyticsDateText = 'Date range : '.date('Y'); }elseif( 'custom' === $filter ){ $analyticsDateText = 'Date range : '.$start_date.' - '.$end_date; } return rest_ensure_response( [ 'headerSection' => $header_section_summery, 'revenueSection' => [ 'summery' => $revenue_section_summery, 'bar_data' => [ 'previous' => $revenue_section_bar_data_prev, 'current' => $revenue_section_bar_data_cur ] ], 'visitors' => $visitors, 'analyticsDateText' => $analyticsDateText ] ); } /** * Retrieves a summary of analytics header section information for a specific funnel. * * This function calculates and compiles various header-related statistics for a given funnel, such as * gross sales, average order value, and offer sales. * * @param array $funnel_order_ids An array of order IDs linked to the specific funnel. * @return array An array containing the header summary information including gross sale, average order value, * and additional offer sales data. * @since 1.9.6 */ private function get_header_summery( $funnel_order_ids, $prev_funnel_order_ids = [] ) { // Calculate gross sale amount. $gross_sale_current = $this->get_total_sales( $funnel_order_ids ); $gross_sale_prev = $this->get_total_sales( $prev_funnel_order_ids ); $gross_sale_change_rate = $this->get_data_change_rate( $gross_sale_current, $gross_sale_prev ); // Retrieve offer sales data. $offer_sales_current = $this->get_offer_sales( $funnel_order_ids ); $offer_sales_prev = $this->get_offer_sales( $prev_funnel_order_ids ); $order_bump = [ 'sale' => $offer_sales_current[ 'order_bump_sales' ], 'rate' => $this->get_data_change_rate( $offer_sales_current[ 'order_bump_sales' ], $offer_sales_prev[ 'order_bump_sales' ] ) ]; $upsell_sales = [ 'sale' => $offer_sales_current[ 'upsell_sales' ][ 'total_sale' ] ?? 0, 'rate' => $this->get_data_change_rate( $offer_sales_current[ 'upsell_sales' ][ 'total_sale' ] ?? 0, $offer_sales_prev[ 'upsell_sales' ][ 'total_sale' ] ?? 0 ) ]; $downsell_sales = [ 'sale' => $offer_sales_current[ 'downsell_sales' ][ 'total_sale' ] ?? 0, 'rate' => $this->get_data_change_rate( $offer_sales_current[ 'downsell_sales' ][ 'total_sale' ] ?? 0, $offer_sales_prev[ 'downsell_sales' ][ 'total_sale' ] ?? 0 ) ]; // Calculate average order value. $avg_order_value = count( $funnel_order_ids ) > 0 ? $gross_sale_current / count( $funnel_order_ids ) : 0.00; $avg_order_value_prev = count( $prev_funnel_order_ids ) > 0 ?$gross_sale_prev / count( $prev_funnel_order_ids ) : 0.00; $avg_order_value_rate = $this->get_data_change_rate( $avg_order_value, $avg_order_value_prev ); $checkout_sales_current = $gross_sale_current - ( ( $offer_sales_current[ 'upsell_sales' ][ 'total_sale' ] ?? 0 ) + ( $offer_sales_current[ 'downsell_sales' ][ 'total_sale' ] ?? 0 ) ); $checkout_sales_prev = $gross_sale_prev - ( ( $offer_sales_prev[ 'upsell_sales' ][ 'total_sale' ] ?? 0 ) + ( $offer_sales_prev[ 'downsell_sales' ][ 'total_sale' ] ?? 0 ) ); $checkout_sale_change_rate = $this->get_data_change_rate( $checkout_sales_current, $checkout_sales_prev ); return [ 'gross_sale' => [ 'sale' => $gross_sale_current, 'rate' => $gross_sale_change_rate ], 'avg_order_value' => [ 'value' => $avg_order_value, 'rate' => $avg_order_value_rate ], 'order_bump' => $order_bump, 'upsell_sales' => $upsell_sales, 'downsell_sales' => $downsell_sales, 'checkout' => [ 'sale' => $checkout_sales_current, 'rate' => $checkout_sale_change_rate ] ]; } /** * Calculates the total sales amount for the provided funnel order IDs. * * This function calculates the sum of net total sales amount for the orders associated with the given funnel order IDs. * * @param array $funnel_order_ids An array of order IDs linked to the specific funnel. * @return float The total sales amount for the provided funnel order IDs. * @since 1.9.6 */ private function get_total_sales( $funnel_order_ids ) { if( is_array( $funnel_order_ids ) && !empty( $funnel_order_ids ) ) { global $wpdb; $funnel_order_ids = implode( ', ', $funnel_order_ids ); // Retrieve and calculate the sum of net total sales amount. $total_sales = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(net_total) FROM {$wpdb->prefix}wc_order_stats WHERE order_id IN(%1s) OR parent_id IN(%1s)", [ $funnel_order_ids, $funnel_order_ids ] ) ); } return !empty( $total_sales ) ? (float)$total_sales : 0; // Return 0 if no funnel order IDs are provided. } /** * Calculates and retrieves offer sales amounts for the provided funnel order IDs. * * This function calculates the sales amounts for different types of offers (order bump, upsell, downsell) * within the orders associated with the given funnel order IDs. * * @param array $funnel_order_ids An array of order IDs linked to the specific funnel. * @return array An array containing offer sales amounts for order bump, upsell, and downsell. * @since 1.9.6 */ private function get_offer_sales( $funnel_order_ids ) { $order_bump_sales = 0.00; $upsell_sales = []; $downsell_sales = []; if( is_array( $funnel_order_ids ) && !empty( $funnel_order_ids ) ) { foreach( $funnel_order_ids as $order_id ) { $order = wc_get_order( $order_id ); $items = $order instanceof \WC_Order ? $order->get_items() : []; if( is_array( $items ) && !empty( $items ) ) { foreach( $items as $item ) { if( $item instanceof \WC_Order_Item_Product ) { $item_data = $item->get_data(); $step_id = $item->get_meta( '_wpfunnels_step_id' ); $step_type = get_post_meta( $step_id, '_step_type', true ); if( $step_id && $step_type ) { if( 'yes' === $item->get_meta( '_wpfunnels_upsell' ) ) { $sale_value = $upsell_sales[ $step_id ][ ucfirst( $step_type ) ] ?? 0; $upsell_sales[ $step_id ][ ucfirst( $step_type ) ] = (float)$sale_value + (float)$item_data[ 'total' ]; $total_sale = $upsell_sales[ 'total_sale' ] ?? 0; $upsell_sales[ 'total_sale' ] = (float)$total_sale + (float)$item_data[ 'total' ]; } elseif( 'yes' === $item->get_meta( '_wpfunnels_downsell' ) ) { $sale_value = $downsell_sales[ $step_id ][ ucfirst( $step_type ) ] ?? 0; $downsell_sales[ $step_id ][ ucfirst( $step_type ) ] = (float)$sale_value + (float)$item_data[ 'total' ]; $total_sale = $downsell_sales[ 'total_sale' ] ?? 0; $downsell_sales[ 'total_sale' ] = (float)$total_sale + (float)$item_data[ 'total' ]; } } if( 'yes' === $item->get_meta( '_wpfunnels_order_bump' ) ) { $order_bump_sales += (float)$item_data[ 'total' ]; } } } } } } return [ 'order_bump_sales' => $order_bump_sales, 'upsell_sales' => $upsell_sales, 'downsell_sales' => $downsell_sales ]; } /** * Retrieves revenue section summary based on sale data. * * This function calculates and returns a revenue section summary based on the provided sale data. * It calculates the checkout revenue, upsell revenue, and downsell revenue based on the given * gross sale, upsell sales, and downsell sales values. * * @param array $sale_data An associative array containing sale data with keys 'gross_sale', * 'upsell_sales', and 'downsell_sales'. * @return array An associative array with keys 'checkout', 'upsell', and 'downsell', * containing formatted revenue values for each section. * @since 1.9.6 */ private function get_revenue_section_summery( $sale_data ) { return [ 'checkout' => $sale_data[ 'checkout' ] ?? [], 'upsell' => $sale_data[ 'upsell_sales' ] ?? [], 'downsell' => $sale_data[ 'downsell_sales' ] ?? [] ]; } /** * Retrieves revenue section bar data based on filtered funnel order IDs. * * This function calculates and returns revenue section bar data for each label in the filtered * funnel order IDs array. It calculates the total revenue for each label and formats the data * into an associative array with labels as keys and revenue values as values. * * @param array $filtered_funnel_order_ids An associative array containing filtered funnel order IDs. * The array structure should have labels as keys and arrays of order IDs as values. * @return array An associative array with labels as keys and revenue section summary values as values. * @since 1.9.6 */ private function get_revenue_section_bar_data( $filtered_funnel_order_ids ) { $funnel_order_revenues = []; if( is_array( $filtered_funnel_order_ids ) && !empty( $filtered_funnel_order_ids ) ) { foreach( $filtered_funnel_order_ids as $label => $order_ids ) { if( is_array( $order_ids ) && !empty( $order_ids ) ) { $total_sale_summery = $this->get_header_summery( $order_ids ); $funnel_order_revenues[ $label ] = $this->get_revenue_section_summery( $total_sale_summery ); } else { $funnel_order_revenues[ $label ] = '0.00'; } } } return $funnel_order_revenues; } /** * Retrieve orders based on funnel order IDs and a custom date range. * * This function queries the WooCommerce order stats table to retrieve custom orders. * It filters orders based on the provided funnel order IDs and a date range if specified. * * @param array $funnel_order_ids An array of funnel order IDs to filter orders. * @param string $start_date The start date of the date range (optional). * @param string $end_date The end date of the date range (optional). * * @global object $wpdb WordPress database object. * * @return array An associative array containing 'previous' and 'current' custom orders. * @since 1.9.6 */ private function get_custom_orders( $funnel_order_ids, $start_date = '', $end_date = '' ) { if( !is_array( $funnel_order_ids ) || empty( $funnel_order_ids ) ) { return [ 'previous' => [], 'current' => [] ]; } global $wpdb; // Determine the appropriate order meta table and column based on WooCommerce settings. $order_stats_table = "{$wpdb->prefix}wc_order_stats"; $select_column = 'order_id'; $query = $wpdb->prepare( "SELECT DATE_FORMAT(%1s, '%b %e') AS label", [ 'date_created' ] ); $query .= $wpdb->prepare( ', %i AS order_id ', [ $select_column ] ); //phpcs:ignore $query .= $wpdb->prepare( "FROM %i ", [ $order_stats_table ] ); //phpcs:ignore $query .= $wpdb->prepare( "WHERE %i IN( %1s ) ", [ $select_column, implode( ', ', $funnel_order_ids ) ] ); //phpcs:ignore $custom_where = $this->get_custom_where_query( $start_date, $end_date ); $current_query = $query . $custom_where[ 'current' ] ?? ''; $previous_query = $query . $custom_where[ 'previous' ] ?? ''; $current = $current_query ? $wpdb->get_results( $current_query, ARRAY_A ) : []; //phpcs:ignore $previous = $previous_query ? $wpdb->get_results( $previous_query, ARRAY_A ) : []; //phpcs:ignore $current = $this->prepare_orders_format( $current ); $previous = $this->prepare_orders_format( $previous ); $current_labels = $custom_where[ 'labels' ][ 'current' ] ?? []; $previous_labels = $custom_where[ 'labels' ][ 'previous' ] ?? []; return [ 'previous' => array_merge( $previous_labels, $previous ), 'current' => array_merge( $current_labels, $current ) ]; } /** * Retrieves weekly order data based on funnel order IDs. * * This function retrieves weekly order data for both the current week and the previous week, * based on the provided funnel order IDs. It calculates order counts for each day of the week * and formats the data into an associative array. * * @param array $funnel_order_ids An array containing funnel order IDs. * @return array An associative array with order data for the current and previous week. * The array contains 'current' and 'previous' sub-arrays, each having day labels as keys * and order counts as values. * @since 1.9.6 */ private function get_weekly_orders( $funnel_order_ids ) { if( !is_array( $funnel_order_ids ) || empty( $funnel_order_ids ) ) { return [ 'previous' => [], 'current' => [] ]; } global $wpdb; // Determine the appropriate order meta table and column based on WooCommerce settings. $order_stats_table = "{$wpdb->prefix}wc_order_stats"; $select_column = 'order_id'; $date_column = 'date_created'; $format = 'Y-m-d H:i:s'; $query = $wpdb->prepare( "SELECT DATE_FORMAT(%1s, '%b %e') AS label", [ $date_column ] ); $query .= $wpdb->prepare( ', %i AS order_id ', [ $select_column ] ); //phpcs:ignore $query .= $wpdb->prepare( "FROM %i ", [ $order_stats_table ] ); //phpcs:ignore $query .= $wpdb->prepare( "WHERE %i IN( %1s ) ", [ $select_column, implode( ', ', $funnel_order_ids ) ] ); //phpcs:ignore $current_week = get_weekstartend( date_i18n( $format, strtotime( 'now' ) ), get_option( 'start_of_week', 1 ) ); $current_query = $query . $this->get_weekly_order_query( $date_column, $current_week ); $prev_week = get_weekstartend( date_i18n( $format, strtotime( '-7 day' ) ), get_option( 'start_of_week', 1 ) ); $previous_query = $query . $this->get_weekly_order_query( $date_column, $prev_week ); $current = $current_query ? $wpdb->get_results( $current_query, ARRAY_A ) : []; //phpcs:ignore $previous = $previous_query ? $wpdb->get_results( $previous_query, ARRAY_A ) : []; //phpcs:ignore $current = $this->prepare_orders_format( $current ); $previous = $this->prepare_orders_format( $previous ); $current_week_days = $this->get_week_days_with_label( $current_week ); $previous_week_days = $this->get_week_days_with_label( $prev_week ); return [ 'previous' => array_merge( $previous_week_days, $previous ), 'current' => array_merge( $current_week_days, $current ) ]; } /** * Generates an associative array with week day labels and initializes counts to 0. * * This function generates an associative array representing week day labels and initializes counts to 0. * It uses the provided start date of the week to calculate the labels for each day of the week. * * @param array $week An associative array containing the start date of the week. * * @return array An associative array with week day labels as keys and initialized counts as values. * @since 1.9.6 */ private function get_week_days_with_label( $week ) { if( !empty( $week[ 'start' ] ) ) { $start_of_week = date_i18n( 'Y-m-d', $week[ 'start' ] ); $interval = 0; while( $interval < 7 ) { $label = date_i18n( 'M j', strtotime( $start_of_week . '+' . $interval . 'day' ) ); //phpcs:ignore $week_days[ $label ] = 0; $interval++; } } return $week_days ?? []; } /** * Generates a SQL query to retrieve weekly order data. * * This function generates a SQL query to retrieve order data for a specified week, using the provided parameters. * The query retrieves order data from the specified order table based on the given date column and order IDs. * * @param string $date_column The name of the date column in the order table. * @param array $week An associative array representing the start and end dates of the week. * @return string The generated SQL query to retrieve weekly order data. * @since 1.9.6 */ private function get_weekly_order_query( $date_column, $week ) { global $wpdb; if( !empty( $week[ 'start' ] ) && !empty( $week[ 'end' ] ) ) { $start_of_week = date_i18n( 'Y-m-d', $week[ 'start' ] ); $end_of_week = date_i18n( 'Y-m-d', $week[ 'end' ] ); $query = $wpdb->prepare( "AND DATE_FORMAT(%1s, ", [ $date_column ] ); $query .= "'%Y-%m-%d') >= "; $query .= $wpdb->prepare( '%s ', [ $start_of_week ] ); //phpcs:ignore $query .= $wpdb->prepare( "AND DATE_FORMAT(%1s, ", [ $date_column ] ); $query .= "'%Y-%m-%d') <= "; return $query . $wpdb->prepare( '%s ', [ $end_of_week ] ); //phpcs:ignore } return ''; } /** * Retrieves monthly order data for a set of funnel order IDs. * * This function retrieves monthly order data for a given set of funnel order IDs, * both for the current month and the previous month. It queries the appropriate order meta table * and column based on WooCommerce settings, and prepares the data for presentation. * * @param array $funnel_order_ids An array containing funnel order IDs for which to retrieve order data. * @return array An associative array containing monthly order data for the current and previous months. * The array is structured with 'previous' and 'current' keys, each containing an array of order data. * @since 1.9.6 */ private function get_monthly_orders( $funnel_order_ids ) { if( !is_array( $funnel_order_ids ) || empty( $funnel_order_ids ) ) { return [ 'previous' => [], 'current' => [] ]; } global $wpdb; // Determine the appropriate order meta table and column based on WooCommerce settings. $order_meta_table = "{$wpdb->prefix}wc_order_stats"; $column = 'order_id'; $where_column = 'date_created'; $query = $wpdb->prepare( "SELECT DATE_FORMAT(%1s, '%b %e') AS label", [ $where_column ] ); //phpcs:ignore $query .= $wpdb->prepare( ', %i AS order_id ', [ $column ] ); //phpcs:ignore $query .= $wpdb->prepare( "FROM %i ", [ $order_meta_table ] ); //phpcs:ignore $query .= $wpdb->prepare( "WHERE %i IN( %1s ) ", [ $column, implode( ', ', $funnel_order_ids ) ] ); //phpcs:ignore $query_current = $query . $wpdb->prepare( "AND (EXTRACT(YEAR_MONTH FROM %1s) = EXTRACT(YEAR_MONTH FROM now())) ", [ $where_column ] ); //phpcs:ignore $query_previous = $query . $wpdb->prepare( "AND (EXTRACT(YEAR_MONTH FROM %1s) = EXTRACT(YEAR_MONTH FROM now()) - 1) ", [ $where_column ] ); //phpcs:ignore $current = $wpdb->get_results( $query_current, ARRAY_A ); //phpcs:ignore $previous = $wpdb->get_results( $query_previous, ARRAY_A ); //phpcs:ignore $current = $this->prepare_orders_format( $current ); $previous = $this->prepare_orders_format( $previous ); $current_monthly_days = $this->get_month_label_with_days(); $previous_monthly_days = $this->get_month_label_with_days( '-1 month' ); return [ 'previous' => array_merge( $previous_monthly_days, $previous ), 'current' => array_merge( $current_monthly_days, $current ) ]; } /** * Generates a label array for a specified month with corresponding days. * * This function generates an associative array with day labels as keys and initial values set to 0, * for the specified month. The labels are in the format "Month Day". * * @param string $datetime The date and time for which to generate the label array. Defaults to 'now'. * @return array An associative array containing day labels for the specified month with initial values of 0. * @since 1.9.6 */ private function get_month_label_with_days( $datetime = 'now' ) { $current_datetime = strtotime( $datetime ); $month = (int)date_i18n( 'n', $current_datetime ); $current_month_label = date_i18n( 'M', $current_datetime ); $days = $this->get_month_days( $month ); for( $day = 1; $day <= $days; $day++ ) { $monthly_days[ $current_month_label . ' ' . $day ] = 0; } return $monthly_days ?? []; } /** * Determines the number of days in a given month. * * This function calculates the number of days in a given month based on its numerical value. * February has 28 days, months with odd numbers less than 9 and even numbers greater than 9 have 31 days, * and other months have 30 days. * * @param int $month The numerical value representing the month (1 to 12). * @return int The number of days in the specified month. * @since 1.9.6 */ private function get_month_days( $month ) { if( $month >= 1 && $month <= 12 ) { if( 2 === $month ) { $days = 28; } elseif( 8 === $month || ( 0 !== $month % 2 && 9 > $month ) || ( 0 === $month % 2 && 9 < $month ) ) { $days = 31; } else { $days = 30; } return $days; } return 0; } /** * Retrieves yearly order data for a given list of funnel order IDs. * * This function fetches yearly order data based on the provided funnel order IDs. It groups the order * data by months within the current year and the previous year, calculating the count of orders for * each month. The function also handles WooCommerce's custom order tables if enabled. * * @param array $funnel_order_ids An array of funnel order IDs. * @return array An associative array containing the count of orders for each month in the current * and previous years. * @since 1.9.6 */ private function get_yearly_orders( $funnel_order_ids ) { if( !is_array( $funnel_order_ids ) || empty( $funnel_order_ids ) ) { return [ 'previous' => [], 'current' => [] ]; } global $wpdb; // Determine the appropriate order meta table and column based on WooCommerce settings. $order_meta_table = "{$wpdb->prefix}wc_order_stats"; $column = 'order_id'; $where_column = 'date_created'; $query = $wpdb->prepare( "SELECT DATE_FORMAT(%1s, '%b') AS label", [ $where_column ] ); //phpcs:ignore $query .= $wpdb->prepare( ', %i AS order_id ', [ $column ] ); //phpcs:ignore $query .= $wpdb->prepare( "FROM %i ", [ $order_meta_table ] ); //phpcs:ignore $query .= $wpdb->prepare( "WHERE %i IN( %1s ) ", [ $column, implode( ', ', $funnel_order_ids ) ] ); //phpcs:ignore $query_current = $query . $wpdb->prepare( "AND YEAR(%1s) = YEAR(now()) ", [ $where_column ] ); $query_previous = $query . $wpdb->prepare( "AND YEAR(%1s) = YEAR(now()) - 1 ", [ $where_column ] ); $current = $wpdb->get_results( $query_current, ARRAY_A ); //phpcs:ignore $previous = $wpdb->get_results( $query_previous, ARRAY_A ); //phpcs:ignore $current = $this->prepare_orders_format( $current ); $previous = $this->prepare_orders_format( $previous ); $months = [ 'Jan' => 0, 'Feb' => 0, 'Mar' => 0, 'Apr' => 0, 'May' => 0, 'Jun' => 0, 'Jul' => 0, 'Aug' => 0, 'Sep' => 0, 'Oct' => 0, 'Nov' => 0, 'Dec' => 0 ]; return [ 'previous' => array_merge( $months, $previous ), 'current' => array_merge( $months, $current ) ]; } /** * Prepares formatted order data from a query result. * * This function takes an array of query data and prepares formatted order data * grouped by their labels. It extracts order IDs from the data and groups them * according to their corresponding labels. * * @param array $query_data The query data array to be formatted. * @return array An associative array containing formatted order data. * The array is structured with labels as keys and arrays of order IDs as values. * @since 1.9.6 */ private function prepare_orders_format( $query_data ) { $formatted_data = []; if( is_array( $query_data ) && !empty( $query_data ) ) { foreach( $query_data as $data ) { if( !empty( $data[ 'label' ] ) && !empty( $data[ 'order_id' ] ) ) { $formatted_data[ $data[ 'label' ] ][] = $data[ 'order_id' ]; } } } return $formatted_data; } /** * Calculate the rate of change between two data points as a percentage. * * This function calculates the percentage change between the current data point * and the previous data point, expressing it as a percentage with one decimal place. * * @param float $current_data The current data point. * @param float $previous_data The previous data point. * * @return string A string representing the rate of change as a percentage. * If current_data <= 0 and previous_data > 0, it returns '-100.0'. * If current_data > 0 and previous_data <= 0, it returns '100.0'. * For other cases, it calculates the rate of change as * ((current_data - previous_data) / previous_data) * 100 * and returns it as a string with one decimal place. * * @example * Input: get_data_change_rate(75, 100) * Output: '33.3' // Indicates a 33.3% decrease from 100 to 75 * * Input: get_data_change_rate(125, 100) * Output: '25.0' // Indicates a 25% increase from 100 to 125 * * Input: get_data_change_rate(0, 100) * Output: '-100.0' // Indicates a 100% decrease from 100 to 0 * * Input: get_data_change_rate(100, 0) * Output: '100.0' // Indicates a 100% increase from 0 to 100 * * @return string * @since 1.9.6 */ private function get_data_change_rate( $current_data, $previous_data ) { if( 0 >= $current_data && 0 < $previous_data ) { return '-100.0'; } if( 0 < $current_data && 0 >= $previous_data ) { return '100.0'; } $rate = ( $current_data - $previous_data ) * 100; if( 0 < $previous_data ) { return $rate / $previous_data; } return $rate; } /** * Get opt-in submission statistics for a specific funnel within a custom date range. * * This function queries the WordPress database to retrieve the count of opt-in submissions * for a given funnel within a specified date range. It calculates the submission rate * change between the current and previous periods. * * @param int $funnel_id The ID of the funnel to retrieve opt-in statistics for. * @param string $start_date The start date of the date range (optional). * @param string $end_date The end date of the date range (optional). * * @global object $wpdb WordPress database object. * * @return array An associative array containing 'visitor' (opt-in count) and 'rate' (submission rate change). */ private function get_custom_optin_submission( $funnel_id, $start_date = '', $end_date = '' ) { global $wpdb; $query = "SELECT COUNT(analytics_meta.id) AS optin_submitted FROM {$wpdb->prefix}wpfnl_analytics_meta AS analytics_meta "; $query .= "JOIN {$wpdb->prefix}wpfnl_analytics AS analytics ON analytics_meta.analytics_id = analytics.id "; $query .= $wpdb->prepare( 'WHERE analytics.funnel_id = %d ', $funnel_id ); $query .= $wpdb->prepare( 'AND analytics_meta.meta_key = %s ', 'wpfunnel_optin_submit' ); $query .= $wpdb->prepare( 'AND analytics_meta.meta_value = %s ', 'yes' ); $custom_where = $this->get_custom_where_query( $start_date, $end_date ); $current_query = $query . $custom_where[ 'current' ] ?? ''; $previous_query = $query . $custom_where[ 'previous' ] ?? ''; $current_optin = $current_query ? $wpdb->get_var( $current_query ) : 0; $current_optin = !empty( $current_optin ) ? (float)$current_optin : 0; $previous_optin = $previous_query ? $wpdb->get_var( $previous_query ) : 0; $previous_optin = !empty( $previous_optin ) ? (float)$previous_optin : 0; $rate = $this->get_data_change_rate( $current_optin, $previous_optin ); return [ 'visitor' => $current_optin, 'rate' => $rate ]; } /** * Retrieves weekly opt-in submission counts for a specific funnel. * * This function retrieves weekly opt-in submission counts for a given funnel, * including the current week and the previous week, and prepares the data. * * @param int $funnel_id The ID of the funnel for which to retrieve opt-in submission data. * @return array An array containing weekly opt-in submission counts for the current and previous weeks. * @since 1.9.6 */ private function get_weekly_optin_submission( $funnel_id ) { global $wpdb; $query = "SELECT COUNT(analytics_meta.id) AS optin_submitted FROM {$wpdb->prefix}wpfnl_analytics_meta AS analytics_meta "; $query .= "JOIN {$wpdb->prefix}wpfnl_analytics AS analytics ON analytics_meta.analytics_id = analytics.id "; $query .= $wpdb->prepare( 'WHERE analytics.funnel_id = %d ', $funnel_id ); $current_query = $query . $this->get_optin_submission_weekly_query(); $previous_query = $query . $this->get_optin_submission_weekly_query('-7 day' ); $current_optin = $current_query ? $wpdb->get_var( $current_query ) : 0; $current_optin = !empty( $current_optin ) ? (float)$current_optin : 0; $previous_optin = $previous_query ? $wpdb->get_var( $previous_query ) : 0; $previous_optin = !empty( $previous_optin ) ? (float)$previous_optin : 0; $rate = $this->get_data_change_rate( $current_optin, $previous_optin ); return [ 'visitor' => $current_optin, 'rate' => $rate ]; } /** * Generates a weekly opt-in submission query for a specific funnel and date range. * * This function generates a database query to retrieve weekly opt-in submission counts for a given funnel * and a specified date range (week). * * @param string $date_time The date and time for which to generate the query. Defaults to 'now'. * @return string The generated SQL query to retrieve weekly opt-in submission counts. * Returns an empty string if the date range information is missing. * @since 1.9.6 */ private function get_optin_submission_weekly_query( $date_time = 'now' ) { global $wpdb; $week = get_weekstartend( date_i18n( 'Y-m-d H:i:s', strtotime( $date_time ) ), get_option( 'start_of_week', 1 ) ); if( !empty( $week[ 'start' ] ) && !empty( $week[ 'end' ] ) ) { $start_of_week = date_i18n( 'Y-m-d', $week[ 'start' ] ); $end_of_week = date_i18n( 'Y-m-d', $week[ 'end' ] ); $query = $wpdb->prepare( "AND DATE_FORMAT(%1s, ", [ 'analytics.date_modified' ] ); $query .= "'%Y-%m-%d') >= "; $query .= $wpdb->prepare( '%s ', [ $start_of_week ] ); //phpcs:ignore $query .= $wpdb->prepare( "AND DATE_FORMAT(%1s, ", [ 'analytics.date_modified' ] ); $query .= "'%Y-%m-%d') <= "; $query .= $wpdb->prepare( '%s ', [ $end_of_week ] ); //phpcs:ignore $query .= "AND analytics_meta.meta_key = 'wpfunnel_optin_submit' "; return $query . "AND analytics_meta.meta_value = 'yes'"; } return ''; } /** * Retrieves monthly opt-in submission counts for a specific funnel. * * This function retrieves monthly opt-in submission counts for a given funnel, * including the current month, and prepares the data. * * @param int $funnel_id The ID of the funnel for which to retrieve opt-in submission data. * @return array An array containing monthly opt-in submission counts for the current month. * @since 1.9.6 */ private function get_monthly_optin_submission( $funnel_id ) { global $wpdb; $query = "SELECT COUNT(analytics_meta.id) AS optin_submitted FROM {$wpdb->prefix}wpfnl_analytics_meta AS analytics_meta "; $query .= "JOIN {$wpdb->prefix}wpfnl_analytics AS analytics ON analytics_meta.analytics_id = analytics.id "; $query .= "WHERE analytics.funnel_id = {$funnel_id} "; $query .= "AND analytics_meta.meta_key = 'wpfunnel_optin_submit' "; $query .= "AND analytics_meta.meta_value = 'yes' "; $current_query = $query . 'AND (EXTRACT(YEAR_MONTH FROM analytics.date_modified) = EXTRACT(YEAR_MONTH FROM now()))'; $previous_query = $query . 'AND (EXTRACT(YEAR_MONTH FROM analytics.date_modified) = EXTRACT(YEAR_MONTH FROM now()) - 1)'; $current_optin = $current_query ? $wpdb->get_var( $current_query ) : 0; $current_optin = !empty( $current_optin ) ? (float)$current_optin : 0; $previous_optin = $previous_query ? $wpdb->get_var( $previous_query ) : 0; $previous_optin = !empty( $previous_optin ) ? (float)$previous_optin : 0; $rate = $this->get_data_change_rate( $current_optin, $previous_optin ); return [ 'visitor' => $current_optin, 'rate' => $rate ]; } /** * Retrieves yearly opt-in submission counts for a specific funnel. * * This function retrieves yearly opt-in submission counts for a given funnel, * including the current year, and prepares the data. * * @param int $funnel_id The ID of the funnel for which to retrieve opt-in submission data. * @return array An array containing yearly opt-in submission counts for the current year. * @since 1.9.6 */ private function get_yearly_optin_submission( $funnel_id ) { global $wpdb; $query = "SELECT COUNT(analytics_meta.id) AS optin_submitted FROM {$wpdb->prefix}wpfnl_analytics_meta AS analytics_meta "; $query .= "JOIN {$wpdb->prefix}wpfnl_analytics AS analytics ON analytics_meta.analytics_id = analytics.id "; $query .= "WHERE analytics.funnel_id = {$funnel_id} "; $query .= "AND analytics_meta.meta_key = 'wpfunnel_optin_submit' "; $query .= "AND analytics_meta.meta_value = 'yes' "; $current_query = $query . 'AND YEAR(analytics.date_modified) = YEAR(now())'; $previous_query = $query . 'AND YEAR(analytics.date_modified) = YEAR(now()) - 1'; $current_optin = $current_query ? $wpdb->get_var( $current_query ) : 0; $current_optin = !empty( $current_optin ) ? (float)$current_optin : 0; $previous_optin = $previous_query ? $wpdb->get_var( $previous_query ) : 0; $previous_optin = !empty( $previous_optin ) ? (float)$previous_optin : 0; $rate = $this->get_data_change_rate( $current_optin, $previous_optin ); return [ 'visitor' => $current_optin, 'rate' => $rate ]; } /** * Get custom visitor statistics for a specific funnel within a date range. * * This function queries the WordPress database to retrieve visitor statistics for a given funnel * within a specified date range. It returns an array of visitor data, including date labels, * step IDs, visitor types, and visitor counts. * * @param int $funnel_id The ID of the funnel to retrieve visitor statistics for. * @param string $start_date The start date of the date range (optional). * @param string $end_date The end date of the date range (optional). * * @global object $wpdb WordPress database object. * * @return array An associative array containing visitor data with date labels, step IDs, visitor types, and visitor counts. * @since 1.9.6 */ private function get_custom_visitors( $funnel_id, $start_date = '', $end_date = '' ) { global $wpdb; $custom_where = $this->get_custom_where_query( $start_date, $end_date ); $date_range = $custom_where[ 'current' ] ?? ''; $query = "SELECT DATE_FORMAT(date_created, '%b %e') AS label, step_id, visitor_type, COUNT(id) AS visitors "; $query .= "FROM {$wpdb->prefix}wpfnl_analytics "; $query .= $wpdb->prepare( 'WHERE funnel_id = %d ', $funnel_id ); $query .= $date_range; $query .= ' GROUP BY label, step_id, visitor_type'; $visitors = $wpdb->get_results( $query, ARRAY_A ); $conversions = $this->get_other_visitor_conversion_data( $funnel_id, 'conversion', 'conversions', $date_range, '%b %e' ); $bounced = $this->get_other_visitor_conversion_data( $funnel_id, 'bounced', 'bounced', $date_range, '%b %e' ); return $this->prepare_visitor_data( $visitors, $conversions, $bounced ); } /** * Retrieves weekly visitors data for a specific funnel. * * This function retrieves weekly visitors data for a given funnel, including the current week, * and prepares the data with day labels and visitor statistics. * * @param int $funnel_id The ID of the funnel for which to retrieve visitors data. * @return array An array containing weekly visitors data for the current week. * The array is structured with daily visitor information. * @since 1.9.6 */ private function get_weekly_visitors( $funnel_id ) { global $wpdb; $format = 'Y-m-d H:i:s'; $current_week = get_weekstartend( date_i18n( $format, strtotime( 'now' ) ), get_option( 'start_of_week', 1 ) ); $query = "SELECT DATE_FORMAT(date_created, '%b %e') AS label, step_id, visitor_type, COUNT(id) AS visitors "; $query .= "FROM {$wpdb->prefix}wpfnl_analytics "; $query .= $wpdb->prepare( 'WHERE funnel_id = %d ', $funnel_id ); $query .= $this->get_weekly_visitor_where_query( $current_week ); $visitors = $wpdb->get_results( $query, ARRAY_A ); $conversions = $this->get_weekly_visitor_conversion_data( $funnel_id, 'conversion', 'conversions' ); $bounced = $this->get_weekly_visitor_conversion_data( $funnel_id, 'bounced', 'bounced' ); return $this->prepare_visitor_data( $visitors, $conversions, $bounced ); } /** * Generates a weekly visitor query for a specific funnel and week. * * This function generates a database query to retrieve weekly visitor data for a given funnel and a specified week. * * @param array $week An array containing the start and end timestamps of the week. * @return string The generated SQL query to retrieve weekly visitor data. * Returns an empty string if the week information is missing. * @since 1.9.6 */ private function get_weekly_visitor_where_query( $week ) { global $wpdb; if( !empty( $week[ 'start' ] ) && !empty( $week[ 'end' ] ) ) { $start_of_week = date_i18n( 'Y-m-d', $week[ 'start' ] ); $end_of_week = date_i18n( 'Y-m-d', $week[ 'end' ] ); $query = $wpdb->prepare( "AND DATE_FORMAT(%1s, ", [ 'date_created' ] ); $query .= "'%Y-%m-%d') >= "; $query .= $wpdb->prepare( '%s ', [ $start_of_week ] ); //phpcs:ignore $query .= $wpdb->prepare( "AND DATE_FORMAT(%1s, ", [ 'date_created' ] ); $query .= "'%Y-%m-%d') <= "; return $query . $wpdb->prepare( '%s ', [ $end_of_week ] ) . ' GROUP BY label, step_id, visitor_type'; //phpcs:ignore } return ''; } /** * Retrieves monthly visitors data for a specific funnel. * * This function queries the database to retrieve monthly visitors data for a given funnel, grouped by month, * step ID, visitor type, and step type. It also fetches and merges monthly conversion data if available. * * @param int $funnel_id The ID of the funnel for which to retrieve visitors data. * @return array An array containing monthly visitors data for the current month. * The array is structured with daily visitor information. * @since 1.9.6 */ private function get_monthly_visitors( $funnel_id ) { global $wpdb; $date_range = 'AND (EXTRACT(YEAR_MONTH FROM date_created) = EXTRACT(YEAR_MONTH FROM now()))'; $groupby = "GROUP BY label, step_id, visitor_type"; $query = "SELECT DATE_FORMAT(date_created, '%b %e') AS label, step_id, visitor_type, COUNT(id) AS visitors "; $query .= "FROM {$wpdb->prefix}wpfnl_analytics "; $query .= "WHERE funnel_id = {$funnel_id} "; $current_query = "{$query} {$date_range} {$groupby}"; $current_visitors = $wpdb->get_results( $current_query, ARRAY_A ); $conversions = $this->get_other_visitor_conversion_data( $funnel_id, 'conversion', 'conversions', $date_range, '%b %e' ); $bounced = $this->get_other_visitor_conversion_data( $funnel_id, 'bounced', 'bounced', $date_range, '%b %e' ); return $this->prepare_visitor_data( $current_visitors, $conversions, $bounced ); } /** * Retrieves yearly visitors data for a specific funnel. * * This function queries the database to retrieve yearly visitors data for a given funnel, grouped by month, * step ID, visitor type, and step type. * * @param int $funnel_id The ID of the funnel for which to retrieve visitors data. * @return array An array containing yearly visitors data for the current and previous years. * The array is structured with monthly visitor information. * @since 1.9.6 */ private function get_yearly_visitors( $funnel_id ) { global $wpdb; $date_range = 'AND YEAR(date_created) = YEAR(now())'; $groupby = "GROUP BY label, step_id, visitor_type"; $query = "SELECT DATE_FORMAT(date_created, '%b') AS label, step_id, visitor_type, COUNT(id) AS visitors "; $query .= "FROM {$wpdb->prefix}wpfnl_analytics "; $query .= "WHERE funnel_id = {$funnel_id} "; $current_query = "{$query} {$date_range} {$groupby}"; $current_visitors = $wpdb->get_results( $current_query, ARRAY_A ); $conversions = $this->get_other_visitor_conversion_data( $funnel_id, 'conversion', 'conversions', $date_range, '%b' ); $bounced = $this->get_other_visitor_conversion_data( $funnel_id, 'bounced', 'bounced', $date_range, '%b' ); return $this->prepare_visitor_data( $current_visitors, $conversions, $bounced ); } /** * Get custom visitor conversion statistics for a specific funnel within a date range. * * This function queries the WordPress database to retrieve visitor conversion statistics for a given funnel * within a specified date range. It returns an array of conversion data, including date labels, step IDs, * and the number of conversions. * * @param int $funnel_id The ID of the funnel to retrieve conversion statistics for. * @param string $start_date The start date of the date range (optional). * @param string $end_date The end date of the date range (optional). * * @global object $wpdb WordPress database object. * * @return array An associative array containing conversion data with date labels, step IDs, and conversion counts. * @since 1.9.6 */ private function get_custom_visitor_conversions( $funnel_id, $start_date = '', $end_date = '' ) { global $wpdb; $custom_where = $this->get_custom_where_query( $start_date, $end_date ); $groupby = "GROUP BY label, step_id"; $query = "SELECT DATE_FORMAT(date_created, '%b %e') AS label, analytics_meta.step_id AS step_id, COUNT(analytics_meta.id) AS conversions "; $query .= "FROM {$wpdb->prefix}wpfnl_analytics AS analytics "; $query .= "JOIN {$wpdb->prefix}wpfnl_analytics_meta AS analytics_meta "; $query .= "ON analytics.id = analytics_meta.analytics_id "; $query .= "WHERE analytics.funnel_id = {$funnel_id} "; $query .= "AND analytics_meta.meta_key = 'conversion' "; $query .= "AND analytics_meta.meta_value = 'yes' "; $query .= $custom_where[ 'current' ] ?? ''; $query .= $groupby; return $wpdb->get_results( $query, ARRAY_A ) ?? []; } /** * Get weekly visitor conversion data for a specific funnel. * * This function queries the database to retrieve data related to weekly visitor conversions * within a specific funnel. It calculates the number of conversions for each week and step * within the funnel. * * @param int $funnel_id The ID of the funnel. * @param string $data_field The name of the data field to retrieve (e.g., 'conversion_rate'). * @param string $data_field_label The label for the data field (e.g., 'conversion_rate_label'). * * @return array An array of data containing weekly conversion statistics. * @since 1.9.6 */ private function get_weekly_visitor_conversion_data( $funnel_id, $data_field, $data_field_label ) { $week = get_weekstartend( date_i18n( 'Y-m-d H:i:s', strtotime( 'now' ) ), get_option( 'start_of_week', 1 ) );; if ( !empty( $week[ 'start' ] ) && !empty( $week[ 'end' ] ) ) { $start_of_week = date_i18n( 'Y-m-d', $week[ 'start' ] ); $end_of_week = date_i18n( 'Y-m-d', $week[ 'end' ] ); global $wpdb; $groupby = "GROUP BY label, step_id"; $query = $wpdb->prepare( "SELECT DATE_FORMAT(date_created, '%b %e') AS label, analytics_meta.step_id AS step_id, COUNT(analytics_meta.id) AS %s ", $data_field_label ); $query .= "FROM {$wpdb->prefix}wpfnl_analytics AS analytics "; $query .= "JOIN {$wpdb->prefix}wpfnl_analytics_meta AS analytics_meta "; $query .= "ON analytics.id = analytics_meta.analytics_id "; $query .= $wpdb->prepare( 'WHERE analytics.funnel_id = %d ', $funnel_id ); $query .= $wpdb->prepare( 'AND analytics_meta.meta_key = %s ', $data_field ); $query .= "AND analytics_meta.meta_value = 'yes' "; $query .= $wpdb->prepare( "AND DATE_FORMAT(%1s, ", [ 'date_created' ] ); $query .= "'%Y-%m-%d') >= "; $query .= $wpdb->prepare( '%s ', [ $start_of_week ] ); //phpcs:ignore $query .= $wpdb->prepare( "AND DATE_FORMAT(%1s, ", [ 'date_created' ] ); $query .= "'%Y-%m-%d') <= "; $query .= $wpdb->prepare( '%s ', [ $end_of_week ] ) . $groupby; return $wpdb->get_results( $query, ARRAY_A ) ?? []; } return []; } /** * Get monthly or yearly visitor conversion data for a specific funnel. * * This function queries the database to retrieve data related to monthly or yearly visitor * conversions within a specific funnel. It calculates the number of conversions for each * specified date range and step within the funnel. * * @param int $funnel_id The ID of the funnel. * @param string $data_field The name of the data field to retrieve (e.g., 'conversion_rate'). * @param string $data_field_label The label for the data field (e.g., 'conversion_rate_label'). * @param string $date_range The date range condition for the query (e.g., 'WHERE date_created >= "2023-01-01" AND date_created <= "2023-12-31"'). * @param string $date_label The date format label for grouping (e.g., '%Y-%m' for monthly or '%Y' for yearly). * * @return array An array of data containing monthly or yearly conversion statistics. * @since 1.9.6 */ private function get_other_visitor_conversion_data( $funnel_id, $data_field, $data_field_label, $date_range, $date_label ) { global $wpdb; $groupby = "GROUP BY label, step_id"; $query = $wpdb->prepare( "SELECT DATE_FORMAT(date_created, %s) AS label, analytics_meta.step_id AS step_id, COUNT(analytics_meta.id) AS %s ", $date_label, $data_field_label ); $query .= "FROM {$wpdb->prefix}wpfnl_analytics AS analytics "; $query .= "JOIN {$wpdb->prefix}wpfnl_analytics_meta AS analytics_meta "; $query .= "ON analytics.id = analytics_meta.analytics_id "; $query .= $wpdb->prepare( 'WHERE analytics.funnel_id = %d ', $funnel_id ); $query .= $wpdb->prepare( 'AND analytics_meta.meta_key = %s ', $data_field ); $query .= "AND analytics_meta.meta_value = 'yes'"; return $wpdb->get_results( "{$query} {$date_range} {$groupby}", ARRAY_A ) ?? []; } /** * Prepares and organizes visitor data with conversion and bounce rate calculations. * * This function takes visitor data and associated conversions, calculates conversion rates and bounce rates, * and arranges the data in a structured format. * * @param array $visitor_data An array of visitor data containing information about visitors and their behavior. * @param array $conversions An array of conversion data containing information about conversions. * @param array $bounced An array of bounced data containing information about visitor bouncing. * @return array An organized array of visitor data with calculated conversion rates and bounce rates. * @since 1.9.6 */ private function prepare_visitor_data( $visitor_data, $conversions, $bounced ) { $final_data = []; if( is_array( $visitor_data ) && !empty( $visitor_data ) ) { foreach( $visitor_data as $visitor ) { if ( !empty( $visitor[ 'label' ] ) && !empty( $visitor[ 'step_id' ] ) && !empty( $visitor[ 'visitors' ] ) && !empty( $visitor[ 'visitor_type' ] ) ) { $label = $visitor[ 'label' ]; $step_id = $visitor[ 'step_id' ]; $step_type = get_post_meta( $step_id, '_step_type', true ); $visitors = $visitor[ 'visitors' ]; $visitor_type = $visitor[ 'visitor_type' ]; $final_data[ $step_id ][ $step_type ][ 'step_title' ] = get_the_title( $step_id ); $final_data[ $step_id ][ $step_type ][ $visitor_type ] = $visitors; $total = $final_data[ $step_id ][ $step_type ][ 'total' ] ?? 0; $total = (int)$total + (int)$visitors; $final_data[ $step_id ][ $step_type ][ 'total' ] = $total; $conversion_label_keys = array_keys( array_column( $conversions, 'label' ), $label, true ); $conversion_stepid_keys = array_keys( array_column( $conversions, 'step_id' ), $step_id, true ); $conversion_key = array_values( array_intersect( $conversion_label_keys, $conversion_stepid_keys ) ); $conversion_key = $conversion_key[ 0 ] ?? null; $conversion = is_numeric( $conversion_key ) ? (int)$conversions[ $conversion_key ][ 'conversions' ] : 0; $conversion = ( $conversion / $total ) * 100; $bounce_label_keys = array_keys( array_column( $bounced, 'label' ), $label, true ); $bounce_stepid_keys = array_keys( array_column( $bounced, 'step_id' ), $step_id, true ); $bounce_key = array_values( array_intersect( $bounce_label_keys, $bounce_stepid_keys ) ); $bounce_key = $bounce_key[ 0 ] ?? null; $bounce = is_numeric( $bounce_key ) ? (int)$bounced[ $bounce_key ][ 'bounced' ] : 0; $bounce = ( $bounce / $total ) * 100; $final_data[ $step_id ][ $step_type ][ 'bounce_rate' ] = $bounce; $final_data[ $step_id ][ $step_type ][ 'conversions_rate' ] = 'thankyou' === $step_type ? 0 : $conversion; } } } return $final_data; } /** * Get where query for custom date range * * @param string $start_date Start date. * @param string $end_date End date. * * @return array * @since 1.9.6 */ private function get_custom_where_query( $start_date, $end_date ) { global $wpdb; $start_date = date_format( date_create( $start_date ), 'Y-m-d H:i:s' ); $end_date = date_format( date_create( $end_date ), 'Y-m-d' ) . ' 23:59:59'; $range = strtotime( $end_date ) - strtotime( $start_date ); $time_span = round( $range / ( 60 * 60 * 24 ) ); $prev_start_date = date_create( $start_date ); date_sub( $prev_start_date, date_interval_create_from_date_string( $time_span . ' days' ) ); $prev_start_date = date_format( $prev_start_date, 'Y-m-d H:i:s' ); $prev_end_date = date_create( $end_date ); date_sub( $prev_end_date, date_interval_create_from_date_string( $time_span . ' days' ) ); $prev_end_date = date_format( $prev_end_date, 'Y-m-d' ) . ' 23:59:59'; $conditions1 = $wpdb->prepare( 'AND (date_created BETWEEN %s AND %s) ', $start_date, $end_date ); //phpcs:ignore $conditions2 = $wpdb->prepare( 'AND (date_created BETWEEN %s AND %s) ', $prev_start_date, $prev_end_date ); //phpcs:ignore return [ 'current' => $conditions1, 'previous' => $conditions2, 'labels' => [ 'current' => $this->get_custom_dates_label( $start_date, $end_date ), 'previous' => $this->get_custom_dates_label( $prev_start_date, $prev_end_date ), ] ]; } /** * Generate date labels within a specified date range. * * This function generates date labels within a specified date range, typically for use in charting or data * aggregation. It creates an associative array with date labels as keys and initial values set to zero. * * @param string $start_date The start date of the date range. * @param string $end_date The end date of the date range. * * @return array An associative array with date labels as keys and initial values set to zero. * @since 1.9.6 */ private function get_custom_dates_label( $start_date = '', $end_date = '' ) { $labels = []; $periods = new DatePeriod( new DateTime( $start_date ), new DateInterval( 'P1D' ), new DateTime( $end_date ) ); foreach( $periods as $date ) { $labels[ $date->format( 'M j' ) ] = 0; } return $labels; } /** * Filters visitor data for a specific funnel. * * This function rearranges visitor data for a given funnel in a specific order. * It starts with the first step of the funnel and arranges visitor data for each * subsequent step in the order they appear within the funnel. * * @param int $funnel_id The ID of the funnel. * @param array $visitors An associative array containing visitor data for each step. * * @return array An associative array of rearranged visitor data for the funnel. * @since 1.9.6 */ private function filter_visitor_data( $funnel_id, $visitors ) { $funnel_steps = Wpfnl_functions::get_steps( $funnel_id ); $funnel_step_ids = array_column( $funnel_steps, 'id' ); if ( !empty( $funnel_step_ids ) ) { foreach( $visitors as $step_id => $step_data ) { if ( !in_array( $step_id, $funnel_step_ids ) ) { unset( $visitors[ $step_id ] ); } } } return $visitors_rearranged ?? $visitors; } /** * Delete/Reset all the analytics data for the specified * funnel id * * @param WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response * @since 2.1.1 */ public function reset_analytics_data( \WP_REST_Request $request ) { $params = $this->get_api_params_values( $request ); $funnel_id = $params['funnel_id']; global $wpdb; // Delete analytics meta $meta_table = $wpdb->prefix . 'wpfnl_analytics_meta'; $wpdb->delete( $meta_table, array( 'funnel_id' => $funnel_id ) ); // Delete analytics $post_table = $wpdb->prefix . 'wpfnl_analytics'; $wpdb->delete( $post_table, array( 'funnel_id' => $funnel_id ) ); $response = array( 'message' => 'Analytics with funnel id ' . $funnel_id . ' have been deleted successfully.', 'success' => true ); return rest_ensure_response( $response ); } }includes/core/rest-api/controllers/AutomationController.php000064400000071300147600245720020301 0ustar00 rest_authorization_required_code())); } return true; } /** * register rest routes * * @since 1.0.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_automation_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_data_by_index/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_automation_data_by_index' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_crm_data/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_crm_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/update_status/(?P\d+)', array( array( 'methods' => 'POST', 'callback' => array( $this, 'update_status' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'funnel_id' => array( 'description' => __('Funnel ID.', 'wpfnl-pro'), 'type' => 'string', ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/update_trigger_status/(?P\d+)', array( array( 'methods' => 'POST', 'callback' => array( $this, 'update_trigger_status' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'funnel_id' => array( 'description' => __('Funnel ID.', 'wpfnl-pro'), 'type' => 'string', ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/save_data/(?P\d+)', array( array( 'methods' => 'POST', 'callback' => array( $this, 'save_automation_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'funnel_id' => array( 'description' => __('Funnel ID.', 'wpfnl-pro'), 'type' => 'string', ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/wpfnl-test-zapier/(?P\d+)', array( array( 'methods' => 'POST', 'callback' => array( $this, 'wpfnl_test_zapier' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'funnel_id' => array( 'description' => __('Funnel ID.', 'wpfnl-pro'), 'type' => 'string', ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/save_name/(?P\d+)', array( array( 'methods' => 'POST', 'callback' => array( $this, 'save_automation_name' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'funnel_id' => array( 'description' => __('Funnel ID.', 'wpfnl-pro'), 'type' => 'string', ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/save_crm', array( array( 'methods' => 'POST', 'callback' => array( $this, 'save_crm' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'funnel_id' => array( 'description' => __('Funnel ID.', 'wpfnl-pro'), 'type' => 'string', ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_crm', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_crm' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/delete/(?P\d+)', array( array( 'methods' => 'DELETE', 'callback' => array( $this, 'delete_automation' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'funnel_id' => array( 'description' => __('Funnel ID.', 'wpfnl-pro'), 'type' => 'string', ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/delete/(?P\d+)', array( array( 'methods' => 'POST', 'callback' => array( $this, 'delete_automation' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_mailchimp_tags', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_mailchimp_tags' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_drip_tags', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_drip_tags' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); } /** * Get Automation data from DB by funnel id * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_automation_data( \WP_REST_Request $request ) { $funnel_id = $request['funnel_id']; $is_automation_enabled = get_post_meta( $funnel_id, 'is_automation_enabled', true ); $automation_data = get_post_meta( $funnel_id, 'funnel_automation_data', true ); if( !$automation_data ) { $automation_data = array(); } $response = array( 'status' => 'success', 'is_automation_enabled' => $is_automation_enabled, 'automation_data' => $automation_data, ); return rest_ensure_response( $response ); } /** * Get automation data by funnel id from DB and return data by index * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_automation_data_by_index( \WP_REST_Request $request ) { $funnel_id = $request['funnel_id']; $index = $request['index']; $is_automation_enabled = get_post_meta( $funnel_id, 'is_automation_enabled', true ); $automation_data = get_post_meta( $funnel_id, 'funnel_automation_data', true ); if( !$automation_data ) { $automation_data = array(); }else{ $automation_data = $automation_data[$index]; } $response = array( 'status' => 'success', 'is_automation_enabled' => $is_automation_enabled, 'name' => $automation_data['name'], 'crm' => $automation_data['crm'], 'automation_data' => $automation_data['triggers'], ); return rest_ensure_response( $response ); } /** * get all events for automation from supported CRM * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_crm_data( \WP_REST_Request $request ){ $funnel_id = isset($request['funnel_id']) ? $request['funnel_id'] : ''; $crm_name = isset($request['crm_name']) ? $request['crm_name'] : 'fluent_crm'; $events = $this->get_events( $funnel_id ); $supported_crm = Wpfnl_Pro_functions::get_supported_crm(); $lists = array(); $tags = array(); foreach( $supported_crm as $key => $crm ){ if( $key == $crm_name ){ $crm_class = "WPFunnelsPro\\Integrations\\".$crm['class_name']; if (class_exists($crm_class)) { $crm_object = $crm_class::getInstance(); if($crm_object->is_connected()){ $lists = $crm_object->get_crm_contact_lists(); $tags = $crm_object->get_crm_contact_tags(); } } } } $data = array( 'events' => $events, 'lists' => $lists, 'tags' => $tags, ); return rest_ensure_response( $data ); } /** * get all events for automation from funnel steps * * @param $funnel_id * @return $events * */ private function get_events( $funnel_id ){ // $get_steps = Wpfnl_functions::get_steps( $funnel_id ); $get_steps = get_post_meta( $funnel_id, '_steps', true ); $type = get_post_meta($funnel_id,'_wpfnl_funnel_type',true); $events = array(); if( !empty( $get_steps ) ){ foreach( $get_steps as $key=>$step ){ if( isset( $step['step_type'], $step['name'] ) ){ if( 'landing' === $step['step_type'] || 'custom' === $step['step_type'] ){ $events['cta_clicked_'.$step['step_type']] = array( 'value' => 'CTA Triggered ('.$step['name'].')', 'stepID' => '', 'stepName' => '', 'type' => 'default', ); $events['after_optin_submit_'.$step['step_type']] = array( 'value' => 'After Opt-in Form Submit ('.$step['name'].')', 'stepID' => '', 'stepName' => '', 'type' => 'default', ); } elseif( $step['step_type'] === 'checkout' ) { if( 'lms' === $type ){ $events['main_order_accepted_enrolled'] = array( 'value' =>'Main Order Accepted', 'stepID' => '', 'stepName' => '', 'type' => 'default', ); } $events['main_order_accepted'] = array( 'value' => $type === 'lms' ? 'Main Order Accepted(new enrollment only)' : 'Main Order Accepted', 'stepID' => '', 'stepName' => '', 'type' => 'default', ); $ob_settings = get_post_meta($step['id'], 'order-bump-settings', true); if( !empty($ob_settings) ){ $events['any_orderbump_accepted'] = array( 'value' => 'Any Order Bump Accepted', 'stepID' => '', 'stepName' => '', 'type' => 'default', ); foreach($ob_settings as $index=>$settings){ if( $settings['isEnabled'] || $settings['isEnabled'] === 'yes' ){ if( 'lms' === $type ){ $events[(int)($index+1).'_orderbump_accepted_enrolled'] = array( 'value' => ucwords($settings['name']).'(#'.(int)($index+1).') Accepted', 'stepID' => '', 'stepName' => '', 'type' => 'default', ); } $events[(int)($index+1).'_orderbump_accepted'] = array( 'value' => $type === 'lms' ? ucwords($settings['name']).'(#'.(int)($index+1).') Accepted(new enrollment only)' : ucwords($settings['name']).'(#'.(int)($index+1).') Accepted', 'stepID' => '', 'stepName' => '', 'type' => 'default', ); $events[(int)($index+1).'_orderbump_not_accepted'] = array( 'value' => ucwords($settings['name']).'(#'.(int)($index+1).') Not Accepted', 'stepID' => '', 'stepName' => '', 'type' => 'default', ); } } } } elseif ( $step['step_type'] === 'upsell' || $step['step_type'] === 'downsell' ) { if( 'lms' === $type ){ $events[$step['step_type'].'_accepted_enrolled_'.$step['id']] = array( 'value' => ucfirst($step['step_type']).' Accepted ('.$step['name'].')', 'stepID' => $step['id'], 'stepName' => $step['name'], 'type' => 'offer', ); } $events[$step['step_type'].'_accepted_'.$step['id']] = array( 'value' => $type === 'lms' ? ucfirst($step['step_type']).' Accepted ('.$step['name'].')(new enrollment only)' : ucfirst($step['step_type']).' Accepted ('.$step['name'].')', 'stepID' => $step['id'], 'stepName' => $step['name'], 'type' => 'offer', ); $events[$step['step_type'].'_rejected_'.$step['id']] = array( 'value' => ucfirst($step['step_type']).' Rejected ('.$step['name'].')', 'stepID' => $step['id'], 'stepName' => $step['name'], 'type' => 'offer', ); } } } } return $events; } /** * update funnel automation status * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response * */ public function update_status( \WP_REST_Request $request ){ $funnel_id = $request['funnel_id'] ? $request['funnel_id'] : ''; $data = $request['data']; update_post_meta( $funnel_id, 'is_automation_enabled', $data ); $response = array( 'status' => 'success', 'msg' => 'Update automation status successfully', 'data' => $data, ); return rest_ensure_response( $response ); } /** * update trigger status * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response * */ public function update_trigger_status( \WP_REST_Request $request ){ $funnel_id = $request['funnel_id'] ? $request['funnel_id'] : ''; $index = $request['index'] ? $request['index'] : 0; $data = $request['data']; $automations = get_post_meta( $funnel_id, 'funnel_automation_data', true ); if( !empty($automations) ){ $automations[$index]['status'] = $data; update_post_meta( $funnel_id, 'funnel_automation_data', $automations ); $response = array( 'status' => 'success', 'msg' => 'Update automation status successfully', 'data' => $data ); } else { $response = array( 'status' => 'fail', 'data' => 'Automation not found', ); } return rest_ensure_response( $response ); } /** * Save automation data to postmeta * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function save_automation_data( \WP_REST_Request $request ){ $funnel_id = $request['funnel_id'] ? $request['funnel_id'] : ''; $data = array(); $data = $request['data']; $index = $request['index']; $previous_data = get_post_meta( $funnel_id, 'funnel_automation_data', true ); if( !empty($previous_data ) ) { $previous_data[$index] = $data; update_post_meta( $funnel_id, 'funnel_automation_data', $previous_data ); } else { $previous_data = array(); $previous_data[$index] = $data; update_post_meta( $funnel_id, 'funnel_automation_data', $previous_data ); } $response = array( 'status' => 'success', 'message' => 'Saved Successfully', ); return rest_ensure_response( $response ); } /** * Save automation data to postmeta * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function wpfnl_test_zapier( \WP_REST_Request $request ){ $url = isset ($request['data']['triggers'][0][0]['tag']) ? $request['data']['triggers'][0][0]['tag'] : ''; $client = new \WPFunnelsPro\Integrations\GuzzleHttp\Client(); if ( $url ){ $dummy_data = [ 'ad_tracking' => '', 'email' => '', 'name' => '', 'phone_number' => '', 'billing_email' => '', 'billing_first_name' => '', 'billing_last_name' => '', 'billing_phone' => '', 'product_name_1' => '', 'product_price_1' => '', 'product_name_2' => '', 'product_price_2' => '', ]; $dummy_data = json_encode($dummy_data); $response = $client->request('POST', $url, [ 'headers' => [ 'Content-Type' => 'application/json', 'Accept' => 'application/json' ], 'body' => $dummy_data ]); if( 200 == $response->getStatusCode() ){ $result = json_decode($response->getBody()->getContents()); return $result; } } $response = array( 'status' => 'failed', 'message' => 'Failed to sent data', ); return rest_ensure_response( $response ); } /** * Save automation data to postmeta * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function save_automation_name( \WP_REST_Request $request ){ $funnel_id = $request['funnel_id'] ? $request['funnel_id'] : ''; $data = $request['data']; $index = $request['index']; $previous_data = get_post_meta( $funnel_id, 'funnel_automation_data', true ); if( !empty($previous_data ) ) { if(isset( $previous_data[$index]) ){ $previous_data[$index]['name'] = $data; update_post_meta( $funnel_id, 'funnel_automation_data', $previous_data ); $response = array( 'status' => 'success', 'message' => 'Saved Successfully', ); }else{ $response = array( 'status' => 'fail', 'message' => 'No integrations were created. Please choose a CRM and create integrations.', ); } }else { $response = array( 'status' => 'fail', 'message' => 'No integrations were created. Please choose a CRM and create integrations.', ); } return rest_ensure_response( $response ); } /** * Save CRM to option table */ public function save_crm( \WP_REST_Request $request ){ $crm = array( 'fluent_crm' => 'Fluent CRM' ); if( $crm ) { update_option( '_wpfunnels_supported_crm', $crm, 'yes' ); $response['data'] = 'save crm successfully'; } $response['status'] = 'success'; return rest_ensure_response( $response ); } /** * get crm name from CRM */ public function get_crm( \WP_REST_Request $request ){ $supported_crm = Wpfnl_Pro_functions::get_supported_crm(); $connected_crm = array(); foreach( $supported_crm as $key => $crm ){ $crm_class = "WPFunnelsPro\\Integrations\\".$crm['class_name']; if (class_exists($crm_class)) { $crm_object = $crm_class::getInstance(); if($crm_object->is_connected()){ $connected_crm[$key] = $crm_object->get_name(); } } } return rest_ensure_response( $connected_crm ); } /** * Delete automation * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function delete_automation( \WP_REST_Request $request ){ $funnel_id = isset($request['funnel_id']) ? $request['funnel_id'] : null; $ids = isset($request['ids']) ? $request['ids'] : array(); if( $funnel_id ){ $data = get_post_meta( $funnel_id, 'funnel_automation_data', true ); foreach($ids as $id){ if( isset($data[$id]) ){ unset($data[$id]); } } update_post_meta( $funnel_id, 'funnel_automation_data', $data ); $response = array( 'status' => 'success', 'data' => 'Delete successfully', ); } else { $response = array( 'status' => 'fail', 'data' => 'Data not found', ); } return rest_ensure_response( $response ); } /** * Delete automation * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_mailchimp_tags( \WP_REST_Request $request ){ $response = []; if( isset($request['data']['list']) ){ $list_data = $request['data']; if( Wpfnl_functions::is_integrations_addon_active() ){ $response = \WPFunnelsPro\Integartions\Mailchimp\Wpfnl_Integartion_Mailchimp_functions::get_mailchimp_tags( $list_data ); } } return rest_ensure_response( $response ); } /** * Get Drip CRM tags * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_drip_tags( \WP_REST_Request $request ){ $response = []; if( isset($request['data']['list']) ){ $list_data = $request['data']; if( Wpfnl_functions::is_integrations_addon_active() && class_exists( '\WPFunnelsPro\Integartions\Drip\Wpfnl_Integartion_Drip_functions' ) ){ $response = \WPFunnelsPro\Integartions\Drip\Wpfnl_Integartion_Drip_functions::get_drip_tags( $list_data ); } } return rest_ensure_response( $response ); } }includes/core/rest-api/controllers/ImportExportController.php000064400000004507147600245720020642 0ustar00 rest_authorization_required_code()]); } return true; } /** * register rest routes * * @since 1.9.3 */ public function register_routes() { register_rest_route($this->namespace, '/' . $this->rest_base . '/bulk-export-funnel', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'bulk_export_funnel' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ], ], ]); } /** * Export selected funnels * * @param array $payload Funnel ids and status. * * @return array|\WP_Rest_Response|\WP_Error * @since 1.9.3 */ public function bulk_export_funnel ( $payload ){ $controller_class = Wpfnl_Export::getInstance(); return $controller_class->bulk_export_funnel( $payload ); } /** * Prepare a single setting object for response. * * @param object $item Setting object. * @param WP_REST_Request $request Request object. * @return \WP_REST_Response $response Response data. * @since 1.9.3 */ public function prepare_item_for_response($item, $request) { $data = $this->add_additional_fields_to_object($item, $request); return rest_ensure_response($data); } } includes/core/rest-api/controllers/MintController.php000064400000076356147600245720017110 0ustar00 rest_authorization_required_code())); } return true; } /** * register rest routes * * @since 1.0.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_automations' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_automation/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_automation_by_id' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/save_data', array( array( 'methods' => 'POST', 'callback' => array( $this, 'save_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'funnelId' => array( 'description' => __('Funnel ID.', 'wpfnl-pro'), 'type' => 'integer', 'required' => true ), 'stepId' => array( 'description' => __('Step ID.', 'wpfnl-pro'), 'type' => 'integer', 'required' => true ), 'data' => array( 'description' => __('Automation steps', 'wpfnl-pro'), 'type' => 'array', 'required' => true ), 'trigger' => array( 'description' => __('Automation trigger.', 'wpfnl-pro'), 'required' => true ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/save_mail_data', array( array( 'methods' => 'POST', 'callback' => array( $this, 'save_mail_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'funnel_id' => array( 'description' => __('Funnel ID.', 'wpfnl-pro'), 'type' => 'string', ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_mail_data/(?P\d+)/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_mail_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_mail_template_data/(?P\d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_mail_template_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/render_email_builder', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'render_email_builder' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::READABLE), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_data', array( array( 'methods' => 'GET', 'callback' => array( $this, 'get_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/delete_data', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'delete_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'stepId' => array( 'description' => __('Step ID.', 'wpfnl-pro'), 'required' => true ) ), ) ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/get_analytics_data', array( array( 'methods' => 'GET', 'callback' => array( $this, 'get_analytics_data' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ) ) ) ); } /** * Get Automation data from DB by funnel id * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_automation_data( \WP_REST_Request $request ) { $funnel_id = $request['funnel_id']; $is_automation_enabled = get_post_meta( $funnel_id, 'is_automation_enabled', true ); $automation_data = get_post_meta( $funnel_id, 'funnel_automation_data', true ); if( !$automation_data ) { $automation_data = array(); } $response = array( 'status' => 'success', 'is_automation_enabled' => $is_automation_enabled, 'automation_data' => $automation_data, ); return rest_ensure_response( $response ); } /** * Save automation data to postmeta * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function save_automation_data( \WP_REST_Request $request ){ $funnel_id = $request['funnel_id'] ? $request['funnel_id'] : ''; $step_id = $request['step_id'] ? $request['step_id'] : ''; $params = Wpfnl_functions::get_default_automation(); try { $class_name = "Mint\\MRM\\Automation\AutomationModel"; if( class_exists($class_name) ){ $automation_id = $class_name::get_instance()->create_or_update( $params ); if ( $automation_id ) { $data = array( 'automation_id' => $automation_id, ); update_post_meta( $step_id, '_wpfnl_autoamtion_id', $automation_id ); $this->update_meta( $automation_id, 'source', 'wpf' ); return $this->get_success_response( __( 'Automation has been saved successfully', 'mrm' ), 201, $data ); } return $this->get_error_response( __( 'Failed to save', 'mrm' ), 400 ); } return $this->get_error_response( __( 'Failed to save automation step', 'mrm' ), 400 ); } catch ( Exception $e ) { return $this->get_error_response( __( 'Failed to save automation step', 'mrm' ), 400 ); } } /** * Save automation data to postmeta. * * This method is responsible for saving automation-related data to the postmeta of a specific step within a funnel. * * @param \WP_REST_Request $request The REST request object containing parameters. * * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response The response indicating the status of the operation. * * @since 2.0.0 */ public function save_data( \WP_REST_Request $request ){ $required_params = array('funnelId','stepId','data', 'trigger'); foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } $stepId = isset($request['stepId']) ? sanitize_text_field( $request['stepId'] ) : ''; $funnelId = isset($request['funnelId']) ? sanitize_text_field( $request['funnelId'] ) : ''; $data = isset($request['data']) ? $request['data'] : []; $trigger = isset($request['trigger']) ? $request['trigger'] : [ 'value' => '', 'title' => '', ]; update_post_meta( $stepId, '_wpfnl_automation_steps', $data ); update_post_meta( $stepId, '_wpfnl_automation_trigger', $trigger ); $mint_automation = new Automation(); $automation_data = $mint_automation->prepare_automation_data( $funnelId, $stepId ); $response = [ 'success' => true, 'data' => get_post_meta( $stepId, '_wpfnl_automation_steps', true ) ]; $response['trigger'] = get_post_meta($stepId, '_wpfnl_automation_trigger', true); if( !$automation_data ){ return $this->get_success_response( __( 'Automation has been saved successfully but fail to prepare data for MailMint', 'wpfnl-pro' ), 201, $response ); } $is_saved = $mint_automation->save_or_update_automation( $automation_data, $funnelId, $stepId ); if( !$is_saved ){ return $this->get_success_response( __( 'Automation has been saved successfully but fail to save from MailMint', 'wpfnl-pro' ), 201, $response ); } return $this->get_success_response( __( 'Automation has been saved successfully', 'wpfnl-pro' ), 201, $response ); } /** * Save automation data to postmeta. * * This method is responsible for saving automation-related data to the postmeta of a specific step within a funnel. * * @param \WP_REST_Request $request The REST request object containing parameters. * * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response The response indicating the status of the operation. * * @since 2.0.0 */ public function delete_data( \WP_REST_Request $request ){ $required_params = array('stepId'); foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } $stepId = isset($request['stepId']) ? sanitize_text_field( $request['stepId'] ) : ''; delete_post_meta( $stepId, '_wpfnl_automation_steps' ); delete_post_meta( $stepId, '_wpfnl_automation_trigger' ); $response = [ 'success' => true ]; $mint_automation = new Automation(); $maybe_deleted = $mint_automation->delete_automation( $stepId ); if( !$maybe_deleted ){ return $this->get_success_response( __( 'Automation has been deleted successfully but fail to delete from MailMint', 'wpfnl-pro' ), 201, $response ); } delete_post_meta( $stepId, 'wpfnl_mint_automation_id' ); return $this->get_success_response( __( 'Automation has been deleted successfully', 'wpfnl-pro' ), 201, $response ); } /** * Save automation data to postmeta * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function save_mail_data( \WP_REST_Request $request ){ $step_id = $request['step_id'] ? $request['step_id'] : ''; $index = $request['index'] ? $request['index'] : 0; $logical_index = $request['logicalIndex'] ? $request['logicalIndex'] : 0; $logic = $request['logic'] ? $request['logic'] : ''; try { if( $step_id ){ $settings = []; $step_settings = get_post_meta( $step_id, '_wpfnl_automation_steps', true ); if( !$logic ){ if( isset($step_settings[$index]['settings']['settings']) ){ $settings = $step_settings[$index]['settings']; } if( !$settings ){ $settings = []; } $settings['settings']['message_data']['body'] = $request['email_body'] ?? ''; $settings['settings']['message_data']['json_body'] = $request['json_data'] ?? ''; $step_settings[$index]['settings'] = $settings; update_post_meta( $step_id, '_wpfnl_automation_steps', $step_settings ); }else{ if( isset($step_settings[$index]['settings']['settings'][$logic][$logical_index]['settings']['settings']) ){ $settings = $step_settings[$index]['settings']['settings'][$logic][$logical_index]['settings']; } if( !$settings ){ $settings = []; } $settings['settings']['message_data']['body'] = $request['email_body'] ?? ''; $settings['settings']['message_data']['json_body'] = $request['json_data'] ?? ''; $step_settings[$index]['settings']['settings'][$logic][$logical_index]['settings'] = $settings; update_post_meta( $step_id, '_wpfnl_automation_steps', $step_settings ); } $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); $mint_automation = new Automation(); $automation_data = $mint_automation->prepare_automation_data( $funnel_id, $step_id ); if( $automation_data ){ $mint_automation->save_or_update_automation( $automation_data, $funnel_id, $step_id ); } } return $this->get_success_response( __( 'Automation has been saved successfully', 'wpfnl' ), 201, [] ); } catch ( \Exception $e ) { return $this->get_error_response( __( 'Failed to save automation step', 'wpfnl' ), 400 ); } } /** * Save automation data to postmeta * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_mail_data( \WP_REST_Request $request ){ $step_id = $request['step_id'] ? $request['step_id'] : ''; $index = $request['index'] ? $request['index'] : 0; $logical_index = $request['logicalIndex'] ? $request['logicalIndex'] : 0; $logic = $request['logic'] ? $request['logic'] : ''; try { $step_settings = get_post_meta( $step_id, '_wpfnl_automation_steps', true ); if( !$logic ){ if( isset($step_settings[$index]['settings']['settings']) ){ $settings = $step_settings[$index]['settings']; if( $settings ){ $response = [ 'json_data' => isset($settings['settings']['message_data']['json_body']) ? $settings['settings']['message_data']['json_body'] : [], ]; return $this->get_success_response( __( 'Automation has been saved successfully', 'mrm' ), 201, $response ); } } }else{ if( isset($step_settings[$index]['settings']['settings'][$logic][$logical_index]['settings']['settings']) ){ $settings = $step_settings[$index]['settings']['settings'][$logic][$logical_index]['settings']; if( $settings ){ $response = [ 'json_data' => isset($settings['settings']['message_data']['json_body']) ? $settings['settings']['message_data']['json_body'] : [], ]; return $this->get_success_response( __( 'Automation has been saved successfully', 'mrm' ), 201, $response ); } } } return $this->get_error_response( __( 'Failed to get data', 'wpfnl' ), 400 ); } catch ( \Exception $e ) { return $this->get_error_response( __( 'Failed to get data', 'wpfnl' ), 400 ); } } /** * Save automation data to postmeta * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_mail_template_data( \WP_REST_Request $request ){ $index = $request[ 'index' ] ?? null; $saved_template = $request[ 'saved_template' ] ?? 'no'; try { if ( 'yes' === $saved_template ) { $template_controller = new TemplateController(); $json_data = $template_controller->retrieve_template_value_by_key( $index, 'json_content' ); $json_data = maybe_unserialize( $json_data ); } else { $templates = \Mint\MRM\Internal\Admin\EmailTemplates\DefaultEmailTemplates::get_default_templates(); $json_data = $templates[ $index ][ 'json_content' ] ?? []; } return $this->get_success_response( __( 'Success', 'wpfnl-pro' ), 201, [ 'json_data' => $json_data ] ); } catch ( \Exception $e ) { return $this->get_error_response( __( 'Failed to get data', 'wpfnl-pro' ), 400 ); } } /** * Save automation data to postmeta * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function render_email_builder( \WP_REST_Request $request ){ $url = admin_url().'admin.php?page=email-builder'; $current_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; $response = [ 'url' => $url, 'current_url' => $current_url, ]; return $this->get_success_response( __( 'Automation has been saved successfully', 'mrm' ), 201, $response ); } /** * Get automation data from postmeta * * @param \WP_REST_Request $request * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_data( \WP_REST_Request $request ){ $required_params = array('stepId'); foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } $stepId = isset($request['stepId']) ? $request['stepId'] : ''; $this->migrateAutomationData( $stepId ); $stepTitle = get_the_title( $stepId ); $steps = get_post_meta( $stepId, '_wpfnl_automation_steps', true ); $trigger = get_post_meta( $stepId, '_wpfnl_automation_trigger', true ); if( !$steps || !is_array($steps) ){ $steps = []; } if( isset($trigger['trigger']) ){ if( 'cta' === $trigger['trigger'] ){ $trigger['value'] = 'wpf_cta_trigger'; $trigger['title'] = 'CTA Triggered'; }elseif( 'optin' === $trigger['trigger'] ){ $trigger['value'] = 'wpf_optin_submit'; $trigger['title'] = 'Optin Submitted'; }elseif( 'orderbump' === $trigger['trigger'] ){ $trigger['value'] = 'accepted' == $trigger['condition'] ? 'wpf_orderbump_accepted' : 'wpf_orderbump_rejected'; $trigger['title'] = 'accepted' == $trigger['condition'] ? 'Order Bump Accepted' : 'Order Bump Rejected'; }elseif( 'upsell' === $trigger['trigger'] ){ $trigger['value'] = 'accepted' == $trigger['condition'] ? 'wpf_upsell_accepted' : 'wpf_upsell_rejected'; $trigger['title'] = 'accepted' == $trigger['condition'] ? 'Upsell Accepted' : 'Upsell Rejected'; }elseif( 'downsell' === $trigger['trigger'] ){ $trigger['value'] = 'accepted' == $trigger['condition'] ? 'wpf_downsell_accepted' : 'wpf_downsell_rejected'; $trigger['title'] = 'accepted' == $trigger['condition'] ? 'Downsell Accepted' : 'Downsell Rejected'; }elseif( 'downsell' === $trigger['trigger'] ){ $trigger['value'] = 'accepted' == $trigger['condition'] ? 'wpf_downsell_accepted' : 'wpf_downsell_rejected'; $trigger['title'] = 'accepted' == $trigger['condition'] ? 'Downsell Accepted' : 'Downsell Rejected'; }elseif( 'order' === $trigger['trigger'] ){ $trigger['value'] = 'accepted' == $trigger['condition'] ? 'wpf_order_placed' : ''; $trigger['title'] = 'accepted' == $trigger['condition'] ? 'Checkout Order Accepted' : ''; } update_post_meta( $stepId, '_wpfnl_automation_trigger', $trigger ); } if( !$trigger || !is_array($trigger) ){ $trigger = [ 'value' => '', 'title' => '', ]; } $tags = Wpfnl_functions::get_mint_contact_groups( 'tags' ); $lists = Wpfnl_functions::get_mint_contact_groups( 'lists' ); $response = [ 'success' => true, 'steps' => $steps, 'stepTitle' => $stepTitle, 'trigger' => $trigger, 'tags' => $tags, 'lists' => $lists, ]; return rest_ensure_response( $response ); } /** * Update/insert automation meta * * @param int $automation_id * @param string $meta_key * @param string $meta_value */ public function update_meta( $automation_id,$meta_key,$meta_value ){ global $wpdb; $automation_meta_table = $wpdb->prefix . AutomationMetaSchema::$table_name; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $select_query = $wpdb->prepare( "SELECT * FROM $automation_meta_table WHERE automation_id = %d AND meta_key = %s", array( $automation_id, $meta_key ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $results = $wpdb->get_results( $select_query ); // db call ok. ; no-cache ok. // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared if( $results ){ try{ $payload = [ 'id' => isset($results[0]['id']) ? $results[0]['id'] : '', 'meta_key' => $meta_key, 'meta_value' => $meta_value, ]; $payload['updated_at'] = current_time( 'mysql' ); $updated = $wpdb->update( $automation_meta_table, $payload, array( 'ID' => $payload['id'] ) ); // db call ok. ; no-cache ok. if( $updated ){ return true; }else{ return false; } }catch( \Exception $e ){ return false; } }else{ try{ $wpdb->insert( $automation_meta_table, array( 'automation_id' => $automation_id, 'meta_key' => $meta_key, 'meta_value' => $meta_value, 'created_at' => current_time( 'mysql' ), 'updated_at' => current_time( 'mysql' ), ) ); // db call ok. return $wpdb->insert_id; }catch( \Exception $e ){ return false; } } } /** * Prepare success response for REST API * * @param string $message Response success message. * @param int $code Response success code. * @param mixed $data Response data on success. * * @return array * @since 1.0.0 */ public function get_success_response( $message = '', $code = 0, $data = null ) { $response = array( 'code' => $code, 'message' => $message, 'success' => true, 'data' => $data, ); return rest_ensure_response( $response ); } /** * Prepare error response for REST API * * @param string $message Response error message. * @param int $code Response error code. * @param mixed $wp_error Response data on error. * * @return array * @since 1.0.0 */ public function get_error_response( $message = '', $code = 0, $wp_error = null ) { return array( 'success' => false, ); } /** * Migrate automation data from old format to new format. * * Migrate automation data from old format to new format. * * @param int $step_id The step ID for which the automation data is migrated. * * @return bool True if the data is successfully migrated, otherwise false. * * @since 3.4.15 */ public function migrateAutomationData( $step_id ){ if( !$step_id ){ return false; } $steps = get_post_meta( $step_id, '_wpfnl_automation_steps', true ); if( is_array($steps) ){ foreach( $steps as $key=>$step ){ if( isset($step['automation_step_id']) ){ $response = $this->getStepStats( $step['automation_step_id'] ); } else { $response = [ 'enterance' => 0, 'completed' => 0, 'exited' => 0 ]; } $steps[$key] = array_merge($step, $response); } update_post_meta( $step_id, '_wpfnl_automation_steps', $steps ); update_post_meta( $step_id, '_wpfnl_automation_steps_migrated', 'yes' ); } return true; } /** * Get automation step stats. * * Get automation step stats which includes enterance, completed and exited. * * @param int $automation_step_id The ID of the automation step for which the stats are retrieved. * * @return array An array containing the automation step stats. The array contains the 'enterance', 'completed' and 'exited' keys. * * @since 3.4.15 */ public function getStepStats( $automation_step_id ){ $response = [ 'enterance' => 0, 'completed' => 0, 'exited' => 0 ]; if( !$automation_step_id ){ return $response; } $instance = new \MintMail\App\Internal\Automation\HelperFunctions(); if( method_exists($instance,'count_total_enterance_in_step') ) { $response['enterance'] = \MintMail\App\Internal\Automation\HelperFunctions::count_total_enterance_in_step( $automation_step_id ); } if( method_exists($instance,'count_completed_step') ) { $response['completed'] = \MintMail\App\Internal\Automation\HelperFunctions::count_completed_step( $automation_step_id ); } if( method_exists($instance,'count_exited_step') ) { $response['exited'] = \MintMail\App\Internal\Automation\HelperFunctions::count_exited_step( $automation_step_id ); } return $response; } /** * Get automation analytics data * * Get automation analytics data which includes overall report and performance report. * * @param \WP_REST_Request $request The REST request object containing parameters. * * @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response The response indicating the status of the operation. * * @since 3.4.15 */ public function get_analytics_data( \WP_REST_Request $request ){ $required_params = array('stepId','filter'); foreach ( $required_params as $param ) { if ( !$request->has_param($param) ) { return rest_ensure_response( $this->get_error_response( __( "Required parameter '$param' is missing.", 'wpfnl-pro' ), 400 ) ); } } $stepId = sanitize_text_field( $request['stepId'] ); $filter = sanitize_text_field( $request['filter'] ); $automationId = get_post_meta($stepId, 'wpfnl_mint_automation_id', true); if( !$automationId ){ return rest_ensure_response( $this->get_error_response( __( "Automation Id is missing.", 'wpfnl-pro' ), 400 ) ); } $overallData = \MintMail\App\Internal\Automation\AutomationLogModel::get_automation_overall_analytics( $automationId, $filter ); $performanceData = \MintMail\App\Internal\Automation\AutomationLogModel::get_automation_performance_analytics( $automationId, $filter ); $response = [ 'overallReport' => isset( $overallData['data'] ) ? $overallData['data'] : [], 'performance' => isset( $performanceData['data'] ) ? $performanceData['data'] : [], ]; return $this->get_success_response( __( 'Automation analytics data has been retrieved successfully', 'wpfnl-pro' ), 201, $response ); } }includes/core/rest-api/controllers/OfferController.php000064400000021032147600245720017217 0ustar00 rest_authorization_required_code()]); } return true; } /** * Makes sure the current user has access to READ the settings APIs. * * @param WP_REST_Request $request Full data about the request. * @return WP_Error|boolean * @since 3.0.0 */ public function get_items_permissions_check($request) { if (!Wpfnl_functions::wpfnl_rest_check_manager_permissions('settings')) { return new WP_Error('wpfunnels_rest_cannot_view', __('Sorry, you cannot list resources.', 'wpfnl'), ['status' => rest_authorization_required_code()]); } return true; } /** * register rest routes * * @since 1.0.0 */ public function register_routes() { register_rest_route($this->namespace, '/' . $this->rest_base . '/getUpsellData/', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_upsell_data' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/saveUpsellData/', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'save_upsell_data' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/add-offer-product/', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'add_offer_product' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/getDownsellData/', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_downsell_data' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/saveDownsellData/', [ [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => [ $this, 'save_downsell_data' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); } /** * Save upsell data */ public function save_upsell_data($request) { $step_id = $request['step_id']; $products = array(); $data = json_decode($request['product'], true); $products[] = $data; update_post_meta($step_id, '_wpfnl_upsell_products', $products); return 'success'; } /** * Save upsell data */ public function add_offer_product($request) { $step_id = isset($request['step_id']) ? sanitize_text_field($request['step_id']) : ''; $id = isset($request['product_id']) ? sanitize_text_field( $request['product_id'] ) : ''; $quantity = isset($request['quantity']) ? sanitize_text_field( $request['quantity'] ) : ''; $offer_type = isset($request['type']) ? sanitize_text_field( $request['type'] ) : 'upsell'; if(!$step_id) { return [ 'success' => false, ]; } $data = array( array( 'id' => $id, 'quantity' => $quantity ) ); $type = ''; if( $request['isLms'] == 'true' ){ $type = 'lms'; }else{ $type = 'wc'; } $class_object = Wpfnl_Pro_OfferProduct_Factory::build( $type ); if( $class_object ){ $function = 'add_'.$offer_type.'_items'; $response = $class_object->$function( $id, $data, $step_id ); if( $response ){ return $response; } } return array( 'success' => false, 'message' => __('Product Not Found', 'wpfnl') ); } /** * Get upsell product data * * @param $request * @return WP_Error|\WP_REST_Response * * @since 1.0.0 */ public function get_upsell_data( $request ) { $response = []; $step_id = $request['step_id']; $_products = get_post_meta( $step_id, '_wpfnl_upsell_products', true ); $products = apply_filters( 'wpfunnels/upsell_product', $_products, $step_id ); $funnel_id = Wpfnl_functions::get_funnel_id_from_step($step_id); $type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true); if( 'lms' === $type ){ $_class = 'lms'; }else{ $_class = 'wc'; } $class_object = Wpfnl_Pro_OfferProduct_Factory::build( $_class ); if( $class_object ){ $response = $class_object->get_upsell_items( $products, $step_id ); } $response['priceConfig'] = Wpfnl_functions::get_wc_price_config(); return $this->prepare_item_for_response( $response, $request ); } /** * Save downsell data */ public function save_downsell_data($request) { $step_id = $request['step_id']; $products = array(); $data = json_decode($request['product'], true); $products[] = $data; update_post_meta($step_id, '_wpfnl_downsell_product', $products); return 'success'; } /** * get downsell product data * * @param $request * @return WP_Error|\WP_REST_Response * * @since 1.0.0 */ public function get_downsell_data($request) { $response = []; $step_id = $request['step_id']; $_products = get_post_meta( $step_id, '_wpfnl_downsell_products', true ); $products = apply_filters( 'wpfunnels/downsell_product', $_products, $step_id ); $discount = get_post_meta( $step_id, '_wpfnl_downsell_discount', true ); $offer_settings = \WPFunnels\Wpfnl_functions::get_offer_settings(); $funnel_id = Wpfnl_functions::get_funnel_id_from_step($step_id); $type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true); if( 'lms' === $type ){ $_class = 'lms'; }else{ $_class = 'wc'; } $class_object = Wpfnl_Pro_OfferProduct_Factory::build( $_class ); if( $class_object ){ $response = $class_object->get_downsell_items( $products, $step_id ); } $response['priceConfig'] = Wpfnl_functions::get_wc_price_config(); return $this->prepare_item_for_response( $response, $request ); } /** * Prepare a single setting object for response. * * @param object $item Setting object. * @param WP_REST_Request $request Request object. * @return \WP_REST_Response $response Response data. * @since 1.0.0 */ public function prepare_item_for_response($item, $request) { $data = $this->add_additional_fields_to_object($item, $request); return rest_ensure_response($data); } } includes/core/rest-api/controllers/TimeInterval.php000064400000060276147600245720016532 0ustar00setTimezone( new \DateTimeZone( 'GMT' ) ); return $datetime; } /** * Returns default 'before' parameter for the reports. * * @return DateTime */ public static function default_before() { $datetime = new \Wpfnl_DateTime(); // Set local timezone or offset. if ( get_option( 'timezone_string' ) ) { $datetime->setTimezone( new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ) ); } else { $datetime->set_utc_offset( Wpfnl_functions::wpfnl_timezone_offset() ); } return $datetime; } /** * Returns default 'after' parameter for the reports. * * @return DateTime */ public static function default_after() { $now = time(); $week_back = $now - WEEK_IN_SECONDS; $datetime = new \Wpfnl_DateTime(); $datetime->setTimestamp( $week_back ); // Set local timezone or offset. if ( get_option( 'timezone_string' ) ) { $datetime->setTimezone( new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ) ); } else { $datetime->set_utc_offset( Wpfnl_functions::wpfnl_timezone_offset() ); } return $datetime; } /** * Returns date format to be used as grouping clause in SQL. * * @param string $time_interval Time interval. * @param string $table_name Name of the db table relevant for the date constraint. * @return mixed */ public static function db_datetime_format( $time_interval, $table_name ) { $first_day_of_week = absint( get_option( 'start_of_week' ) ); if ( 1 === $first_day_of_week ) { // Week begins on Monday, ISO 8601. $week_format = "DATE_FORMAT({$table_name}.date_created, '%x-%v')"; } else { // Week begins on day other than specified by ISO 8601, needs to be in sync with function simple_week_number. $week_format = "CONCAT(YEAR({$table_name}.date_created), '-', LPAD( FLOOR( ( DAYOFYEAR({$table_name}.date_created) + ( ( DATE_FORMAT(MAKEDATE(YEAR({$table_name}.date_created),1), '%w') - $first_day_of_week + 7 ) % 7 ) - 1 ) / 7 ) + 1 , 2, '0'))"; } // Whenever this is changed, double check method time_interval_id to make sure they are in sync. $mysql_date_format_mapping = array( 'hour' => "DATE_FORMAT({$table_name}.date_created, '%Y-%m-%d %H')", 'day' => "DATE_FORMAT({$table_name}.date_created, '%Y-%m-%d')", 'week' => $week_format, 'month' => "DATE_FORMAT({$table_name}.date_created, '%Y-%m')", 'quarter' => "CONCAT(YEAR({$table_name}.date_created), '-', QUARTER({$table_name}.date_created))", 'year' => "YEAR({$table_name}.date_created)", ); return $mysql_date_format_mapping[ $time_interval ]; } /** * Returns quarter for the DateTime. * * @param DateTime $datetime Local date & time. * @return int|null */ public static function quarter( $datetime ) { switch ( (int) $datetime->format( 'm' ) ) { case 1: case 2: case 3: return 1; case 4: case 5: case 6: return 2; case 7: case 8: case 9: return 3; case 10: case 11: case 12: return 4; } return null; } /** * Returns simple week number for the DateTime, for week starting on $first_day_of_week. * * The first week of the year is considered to be the week containing January 1. * The second week starts on the next $first_day_of_week. * * @param DateTime $datetime Local date for which the week number is to be calculated. * @param int $first_day_of_week 0 for Sunday to 6 for Saturday. * @return int */ public static function simple_week_number( $datetime, $first_day_of_week ) { $beg_of_year_day = new \DateTime( "{$datetime->format('Y')}-01-01" ); $adj_day_beg_of_year = ( (int) $beg_of_year_day->format( 'w' ) - $first_day_of_week + 7 ) % 7; $days_since_start_of_year = (int) $datetime->format( 'z' ) + 1; return (int) floor( ( ( $days_since_start_of_year + $adj_day_beg_of_year - 1 ) / 7 ) ) + 1; } /** * Returns ISO 8601 week number for the DateTime, if week starts on Monday, * otherwise returns simple week number. * * @see TimeInterval::simple_week_number() * * @param DateTime $datetime Local date for which the week number is to be calculated. * @param int $first_day_of_week 0 for Sunday to 6 for Saturday. * @return int */ public static function week_number( $datetime, $first_day_of_week ) { if ( 1 === $first_day_of_week ) { $week_number = (int) $datetime->format( 'W' ); } else { $week_number = self::simple_week_number( $datetime, $first_day_of_week ); } return $week_number; } /** * Returns time interval id for the DateTime. * * @param string $time_interval Time interval type (week, day, etc). * @param DateTime $datetime Date & time. * @return string */ public static function time_interval_id( $time_interval, $datetime ) { // Whenever this is changed, double check method db_datetime_format to make sure they are in sync. $php_time_format_for = array( 'hour' => 'Y-m-d H', 'day' => 'Y-m-d', 'week' => 'o-W', 'month' => 'Y-m', 'quarter' => 'Y-' . self::quarter( $datetime ), 'year' => 'Y', ); // If the week does not begin on Monday. $first_day_of_week = absint( get_option( 'start_of_week' ) ); if ( 'week' === $time_interval && 1 !== $first_day_of_week ) { $week_no = self::simple_week_number( $datetime, $first_day_of_week ); $week_no = str_pad( $week_no, 2, '0', STR_PAD_LEFT ); $year_no = $datetime->format( 'Y' ); return "$year_no-$week_no"; } return $datetime->format( $php_time_format_for[ $time_interval ] ); } /** * Calculates number of time intervals between two dates, closed interval on both sides. * * @param DateTime $start_datetime Start date & time. * @param DateTime $end_datetime End date & time. * @param string $interval Time interval increment, e.g. hour, day, week. * * @return int */ public static function intervals_between( $start_datetime, $end_datetime, $interval ) { switch ( $interval ) { case 'hour': $end_timestamp = (int) $end_datetime->format( 'U' ); $start_timestamp = (int) $start_datetime->format( 'U' ); $addendum = 0; // modulo HOUR_IN_SECONDS would normally work, but there are non-full hour timezones, e.g. Nepal. $start_min_sec = (int) $start_datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $start_datetime->format( 's' ); $end_min_sec = (int) $end_datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $end_datetime->format( 's' ); if ( $end_min_sec < $start_min_sec ) { $addendum = 1; } $diff_timestamp = $end_timestamp - $start_timestamp; return (int) floor( ( (int) $diff_timestamp ) / HOUR_IN_SECONDS ) + 1 + $addendum; case 'day': $end_timestamp = (int) $end_datetime->format( 'U' ); $start_timestamp = (int) $start_datetime->format( 'U' ); $addendum = 0; $end_hour_min_sec = (int) $end_datetime->format( 'H' ) * HOUR_IN_SECONDS + (int) $end_datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $end_datetime->format( 's' ); $start_hour_min_sec = (int) $start_datetime->format( 'H' ) * HOUR_IN_SECONDS + (int) $start_datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $start_datetime->format( 's' ); if ( $end_hour_min_sec < $start_hour_min_sec ) { $addendum = 1; } $diff_timestamp = $end_timestamp - $start_timestamp; return (int) floor( ( (int) $diff_timestamp ) / DAY_IN_SECONDS ) + 1 + $addendum; case 'week': // @todo Optimize? approximately day count / 7, but year end is tricky, a week can have fewer days. $week_count = 0; do { $start_datetime = self::next_week_start( $start_datetime ); $week_count++; } while ( $start_datetime <= $end_datetime ); return $week_count; case 'month': // Year diff in months: (end_year - start_year - 1) * 12. $year_diff_in_months = ( (int) $end_datetime->format( 'Y' ) - (int) $start_datetime->format( 'Y' ) - 1 ) * 12; // All the months in end_date year plus months from X to 12 in the start_date year. $month_diff = (int) $end_datetime->format( 'n' ) + ( 12 - (int) $start_datetime->format( 'n' ) ); // Add months for number of years between end_date and start_date. $month_diff += $year_diff_in_months + 1; return $month_diff; case 'quarter': // Year diff in quarters: (end_year - start_year - 1) * 4. $year_diff_in_quarters = ( (int) $end_datetime->format( 'Y' ) - (int) $start_datetime->format( 'Y' ) - 1 ) * 4; // All the quarters in end_date year plus quarters from X to 4 in the start_date year. $quarter_diff = self::quarter( $end_datetime ) + ( 4 - self::quarter( $start_datetime ) ); // Add quarters for number of years between end_date and start_date. $quarter_diff += $year_diff_in_quarters + 1; return $quarter_diff; case 'year': $year_diff = (int) $end_datetime->format( 'Y' ) - (int) $start_datetime->format( 'Y' ); return $year_diff + 1; } return 0; } /** * Returns a new DateTime object representing the next hour start/previous hour end if reversed. * * @param DateTime $datetime Date and time. * @param bool $reversed Going backwards in time instead of forward. * @return DateTime */ public static function next_hour_start( $datetime, $reversed = false ) { $hour_increment = $reversed ? 0 : 1; $timestamp = (int) $datetime->format( 'U' ); $seconds_into_hour = (int) $datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $datetime->format( 's' ); $hours_offset_timestamp = $timestamp + ( $hour_increment * HOUR_IN_SECONDS - $seconds_into_hour ); if ( $reversed ) { $hours_offset_timestamp --; } $hours_offset_time = new \DateTime(); $hours_offset_time->setTimestamp( $hours_offset_timestamp ); $hours_offset_time->setTimezone( new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ) ); return $hours_offset_time; } /** * Returns a new DateTime object representing the next day start, or previous day end if reversed. * * @param DateTime $datetime Date and time. * @param bool $reversed Going backwards in time instead of forward. * @return DateTime */ public static function next_day_start( $datetime, $reversed = false ) { $seconds_into_day = (int) $datetime->format( 'H' ) * HOUR_IN_SECONDS + (int) $datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $datetime->format( 's' ); // The day boundary is actually next midnight when going in reverse, so set it to day -1 at 23:59:59. if ( $reversed ) { $timestamp = (int) $datetime->format( 'U' ); $next_day_timestamp = $timestamp - ( $seconds_into_day + 1 ); } else { $day_increment = new \DateInterval( 'P1D' ); // Plus 1 Day. $next_datetime = clone $datetime; $next_datetime->add( $day_increment ); $timestamp = (int) $next_datetime->format( 'U' ); $next_day_timestamp = $timestamp - $seconds_into_day; } $next_day = new \DateTime(); $next_day->setTimestamp( $next_day_timestamp ); $next_day->setTimezone( new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ) ); return $next_day; } /** * Returns DateTime object representing the next week start, or previous week end if reversed. * * @param DateTime $datetime Date and time. * @param bool $reversed Going backwards in time instead of forward. * @return DateTime */ public static function next_week_start( $datetime, $reversed = false ) { $first_day_of_week = absint( get_option( 'start_of_week' ) ); $initial_week_no = self::week_number( $datetime, $first_day_of_week ); do { $datetime = self::next_day_start( $datetime, $reversed ); $current_week_no = self::week_number( $datetime, $first_day_of_week ); } while ( $current_week_no === $initial_week_no ); // The week boundary is actually next midnight when going in reverse, so set it to day -1 at 23:59:59. if ( $reversed ) { $timestamp = (int) $datetime->format( 'U' ); $end_of_day_timestamp = floor( $timestamp / DAY_IN_SECONDS ) * DAY_IN_SECONDS + DAY_IN_SECONDS - 1; $datetime->setTimestamp( $end_of_day_timestamp ); } return $datetime; } /** * Returns a new DateTime object representing the next month start, or previous month end if reversed. * * @param DateTime $datetime Date and time. * @param bool $reversed Going backwards in time instead of forward. * @return DateTime */ public static function next_month_start( $datetime, $reversed = false ) { $month_increment = 1; $year = $datetime->format( 'Y' ); $month = (int) $datetime->format( 'm' ); if ( $reversed ) { $beg_of_month_datetime = new \DateTime( "$year-$month-01 00:00:00", new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ) ); $timestamp = (int) $beg_of_month_datetime->format( 'U' ); $end_of_prev_month_timestamp = $timestamp - 1; $datetime->setTimestamp( $end_of_prev_month_timestamp ); } else { $month += $month_increment; if ( $month > 12 ) { $month = 1; $year ++; } $day = '01'; $datetime = new \DateTime( "$year-$month-$day 00:00:00", new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ) ); } return $datetime; } /** * Returns a new DateTime object representing the next quarter start, or previous quarter end if reversed. * * @param DateTime $datetime Date and time. * @param bool $reversed Going backwards in time instead of forward. * @return DateTime */ public static function next_quarter_start( $datetime, $reversed = false ) { $year = $datetime->format( 'Y' ); $month = (int) $datetime->format( 'n' ); switch ( $month ) { case 1: case 2: case 3: if ( $reversed ) { $month = 1; } else { $month = 4; } break; case 4: case 5: case 6: if ( $reversed ) { $month = 4; } else { $month = 7; } break; case 7: case 8: case 9: if ( $reversed ) { $month = 7; } else { $month = 10; } break; case 10: case 11: case 12: if ( $reversed ) { $month = 10; } else { $month = 1; $year ++; } break; } $datetime = new \DateTime( "$year-$month-01 00:00:00", new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ) ); if ( $reversed ) { $timestamp = (int) $datetime->format( 'U' ); $end_of_prev_month_timestamp = $timestamp - 1; $datetime->setTimestamp( $end_of_prev_month_timestamp ); } return $datetime; } /** * Return a new DateTime object representing the next year start, or previous year end if reversed. * * @param DateTime $datetime Date and time. * @param bool $reversed Going backwards in time instead of forward. * @return DateTime */ public static function next_year_start( $datetime, $reversed = false ) { $year_increment = 1; $year = (int) $datetime->format( 'Y' ); $month = '01'; $day = '01'; if ( $reversed ) { $datetime = new \DateTime( "$year-$month-$day 00:00:00", new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ) ); $timestamp = (int) $datetime->format( 'U' ); $end_of_prev_year_timestamp = $timestamp - 1; $datetime->setTimestamp( $end_of_prev_year_timestamp ); } else { $year += $year_increment; $datetime = new \DateTime( "$year-$month-$day 00:00:00", new \DateTimeZone( Wpfnl_functions::wpfnl_timezone_string() ) ); } return $datetime; } /** * Returns beginning of next time interval for provided DateTime. * * E.g. for current DateTime, beginning of next day, week, quarter, etc. * * @param DateTime $datetime Date and time. * @param string $time_interval Time interval, e.g. week, day, hour. * @param bool $reversed Going backwards in time instead of forward. * @return DateTime */ public static function iterate( $datetime, $time_interval, $reversed = false ) { return call_user_func( array( __CLASS__, "next_{$time_interval}_start" ), $datetime, $reversed ); } /** * Returns expected number of items on the page in case of date ordering. * * @param int $expected_interval_count Expected number of intervals in total. * @param int $items_per_page Number of items per page. * @param int $page_no Page number. * * @return float|int */ public static function expected_intervals_on_page( $expected_interval_count, $items_per_page, $page_no ) { $total_pages = (int) ceil( $expected_interval_count / $items_per_page ); if ( $page_no < $total_pages ) { return $items_per_page; } elseif ( $page_no === $total_pages ) { return $expected_interval_count - ( $page_no - 1 ) * $items_per_page; } else { return 0; } } /** * Returns true if there are any intervals that need to be filled in the response. * * @param int $expected_interval_count Expected number of intervals in total. * @param int $db_records Total number of records for given period in the database. * * @return bool */ public static function intervals_missing( $expected_interval_count, $db_records ) { if ( $expected_interval_count <= $db_records ) { return false; } return true; } /** * Normalize "*_between" parameters to "*_min" and "*_max" for numeric values * and "*_after" and "*_before" for date values. * * @param array $request Query params from REST API request. * @param string|array $param_names One or more param names to handle. Should not include "_between" suffix. * @param bool $is_date Boolean if the param is date is related. * @return array Normalized query values. */ public static function normalize_between_params( $request, $param_names, $is_date ) { if ( ! is_array( $param_names ) ) { $param_names = array( $param_names ); } $normalized = array(); foreach ( $param_names as $param_name ) { if ( ! is_array( $request[ $param_name . '_between' ] ) ) { continue; } $range = $request[ $param_name . '_between' ]; if ( 2 !== count( $range ) ) { continue; } $min = $is_date ? '_after' : '_min'; $max = $is_date ? '_before' : '_max'; if ( $range[0] < $range[1] ) { $normalized[ $param_name . $min ] = $range[0]; $normalized[ $param_name . $max ] = $range[1]; } else { $normalized[ $param_name . $min ] = $range[1]; $normalized[ $param_name . $max ] = $range[0]; } } return $normalized; } /** * Validate a "*_between" range argument (an array with 2 numeric items). * * @param mixed $value Parameter value. * @param WP_REST_Request $request REST Request. * @param string $param Parameter name. * @return WP_Error|boolean */ public static function rest_validate_between_numeric_arg( $value, $request, $param ) { if ( ! wp_is_numeric_array( $value ) ) { return new \WP_Error( 'rest_invalid_param', /* translators: 1: parameter name */ sprintf( __( '%1$s is not a numerically indexed array.', 'woocommerce' ), $param ) ); } if ( 2 !== count( $value ) || ! is_numeric( $value[0] ) || ! is_numeric( $value[1] ) ) { return new \WP_Error( 'rest_invalid_param', /* translators: %s: parameter name */ sprintf( __( '%s must contain 2 numbers.', 'woocommerce' ), $param ) ); } return true; } /** * Validate a "*_between" range argument (an array with 2 date items). * * @param mixed $value Parameter value. * @param WP_REST_Request $request REST Request. * @param string $param Parameter name. * @return WP_Error|boolean */ public static function rest_validate_between_date_arg( $value, $request, $param ) { if ( ! wp_is_numeric_array( $value ) ) { return new \WP_Error( 'rest_invalid_param', /* translators: 1: parameter name */ sprintf( __( '%1$s is not a numerically indexed array.', 'woocommerce' ), $param ) ); } if ( 2 !== count( $value ) || ! rest_parse_date( $value[0] ) || ! rest_parse_date( $value[1] ) ) { return new \WP_Error( 'rest_invalid_param', /* translators: %s: parameter name */ sprintf( __( '%s must contain 2 valid dates.', 'woocommerce' ), $param ) ); } return true; } } includes/core/rest-api/controllers/WebhookController.php000064400000050301147600245720017555 0ustar00 rest_authorization_required_code())); } return true; } /** * check if user has valid permission * * @param $request * @return bool|WP_Error * @since 1.0.0 */ public function update_items_permissions_check($request) { return true; if (!Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'steps', 'edit' )) { return new WP_Error('wpfunnels_rest_cannot_edit', __('Sorry, you cannot edit this resource.', 'wpfnl'), ['status' => rest_authorization_required_code()]); } return true; } /** * register rest routes * * @since 1.0.0 */ public function register_routes() { register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P\d+)', [ [ 'methods' => 'GET', 'callback' => [ $this, 'get_settings' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/get-supported-settings/(?P\d+)', [ [ 'methods' => 'GET', 'callback' => [ $this, 'get_supported_settings' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/delete-webhook/(?P\d+)', [ [ 'methods' => 'GET', 'callback' => [ $this, 'delete_webhook' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/update-webhook-status/(?P\d+)', [ [ 'methods' => 'GET', 'callback' => [ $this, 'update_webhook_status' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/get-supported-body-settings/(?P\d+)', [ [ 'methods' => 'GET', 'callback' => [ $this, 'get_supported_body_settings' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); register_rest_route($this->namespace, '/' . $this->rest_base . '/update-webhook/(?P\d+)', [ [ 'methods' => 'POST', 'callback' => [ $this, 'save_or_update_webhook' ], 'permission_callback' => [ $this, 'update_items_permissions_check' ] , ], ]); } /** * Get webhook settings by funnel ID * * @param \WP_REST_Request $request * @return Array $response */ public function get_settings( \WP_REST_Request $request ){ $response = $this->get_response_array(); if( isset( $request['funnel_id'] ) && $request['funnel_id'] ){ $funnel_id = $request['funnel_id']; $settings = Wpfnl_Pro_Webhook_Functions::get_webhook_settings( $funnel_id ); if( !empty($settings) ){ $response['status'] = true; $response['data'] = $settings; } } return rest_ensure_response( $response ); } /** * get supported settings * * @param \WP_REST_Request $request * @return Array $response */ public function get_supported_settings( \WP_REST_Request $request ){ if( isset($request['funnel_id']) ){ $offer_steps = Wpfnl_Pro_Webhook_Functions::get_upsell_ids( $request[ 'funnel_id' ] ); $supported_events = Wpfnl_Pro_Webhook_Functions::get_all_supported_event(); $request_body = Wpfnl_Pro_Webhook_Functions::get_all_body_fields(); $request_header_name = Wpfnl_Pro_Webhook_Functions::get_all_request_header_name(); $response = $this->get_response_array(); $steps = Wpfnl_functions::get_steps( $request[ 'funnel_id' ] ); $type = get_post_meta( $request[ 'funnel_id' ], '_wpfnl_funnel_type', true ); $is_landing = false; $is_checkout = false; $is_orderbump = false; $is_upsell = false; $is_downsell = false; $is_custom = false; foreach( $steps as $step ) { if ( 'landing' === $step[ 'step_type' ] ) { $is_landing = true; $supported_events[ 'optin_submitted_landing' ] = 'After Optin Form Submitted (' . $step[ 'name' ] . ')'; } elseif ( 'custom' === $step[ 'step_type' ] ) { $is_custom = true; $supported_events[ 'optin_submitted_custom' ] = 'After Optin Form Submitted (' . $step[ 'name' ] . ')'; } elseif ( $step[ 'step_type' ] == 'checkout' ) { $is_checkout = true; $ob_settings = get_post_meta( $step[ 'id' ], 'order-bump-settings', true ); if ( is_array( $ob_settings ) && !empty( $ob_settings ) ) { $is_orderbump = true; } } elseif ( 'upsell' === $step[ 'step_type' ] ) { $is_upsell = true; } elseif ( 'downsell' === $step[ 'step_type' ] ) { $is_downsell = true; } } if ( $is_orderbump ) { if ( !empty( $ob_settings ) ) { $supported_events[ 'any_order_bump_accepted' ] = 'Any Order Bump Accepted'; foreach( $ob_settings as $key => $settings ) { if ( !empty( $settings[ 'isEnabled' ] ) && 'yes' === $settings[ 'isEnabled' ] ) { if ( 'wc' === $type || !$type ) { $supported_events[ $key . '_order_bump_accepted' ] = ucwords( $settings[ 'name' ] ) . ' (#' . ( (int)( $key ) + 1 ) . ') Accepted'; $supported_events[ $key . '_order_bump_rejected' ] = ucwords( $settings[ 'name' ] ) . ' (#' . ( (int)( $key ) + 1 ) . ') Not Accepted'; } elseif ( 'lms' === $type ) { $supported_events[ $key . '_order_bump_enrolled_accepted' ] = ucwords( $settings[ 'name' ] ) . ' (#' . ( (int)( $key ) + 1 ) . ') Accepted'; $supported_events[ $key . '_order_bump_accepted' ] = ucwords( $settings[ 'name' ] ) . ' (#' . ( (int)( $key ) + 1 ) . ') Accepted( New enrollment only )'; $supported_events[ $key . '_order_bump_rejected' ] = ucwords( $settings[ 'name' ] ) . ' (#' . ( (int)( $key ) + 1 ) . ') Not Accepted'; } } } } } if ( $is_upsell || $is_downsell ) { if ( is_array( $offer_steps ) && !empty( $offer_steps ) ) { foreach( $offer_steps as $offer_step ) { if ( isset( $offer_step[ 'id' ], $offer_step[ 'title' ], $offer_step[ 'type' ] ) ) { if ( $is_upsell && 'upsell' === $offer_step[ 'type' ] ) { $id = $offer_step[ 'id' ]; $title = $offer_step[ 'title' ]; $supported_events[ "{$id}_upsell_accepted" ] = "Upsell Accepted - [ {$title} ]"; $supported_events[ "{$id}_upsell_rejected" ] = "Upsell Rejected - [ {$title} ]"; } elseif ( $is_downsell && 'downsell' === $offer_step[ 'type' ] ) { $id = $offer_step[ 'id' ]; $title = $offer_step[ 'title' ]; $supported_events[ "{$id}_downsell_accepted" ] = "Downsell Accepted - [ {$title} ]"; $supported_events[ "{$id}_downsell_rejected" ] = "Downsell Rejected - [ {$title} ]"; } } } } } if ( $type == 'lms' ) { $supported_events[ 'upsell_enrolled_accepted' ] = $supported_events[ 'upsell_accepted' ]; $supported_events[ 'upsell_accepted' ] = $supported_events[ 'upsell_accepted' ] . '( New enrollment only )'; $supported_events[ 'downsell_enrolled_accepted' ] = $supported_events[ 'downsell_accepted' ]; $supported_events[ 'downsell_accepted' ] = $supported_events[ 'downsell_accepted' ] . '( New enrollment only )'; } if( !$is_landing ){ $supported_events = $this->unset_landing_events( $supported_events ); } if( !$is_custom ){ $supported_events = $this->unset_custom_events( $supported_events ); } if( !$is_checkout ){ $supported_events = $this->unset_checkout_events( $supported_events ); } if( !$is_orderbump ){ $supported_events = $this->unset_orderbump_events( $supported_events ); } if( !$is_upsell ){ $supported_events = $this->unset_upsell_events( $supported_events ); } if( !$is_downsell ){ $supported_events = $this->unset_downsell_events( $supported_events ); } $settings = array(); $settings['conditions'] = $supported_events; $settings['request_body'] = $request_body; $settings['request_header_name'] = $request_header_name; $response =array( 'status' => true, 'data' => $settings ); } return rest_ensure_response( $response ); } /** * Unset landing event */ private function unset_landing_events( $supported_events ){ unset($supported_events['optin_submitted_landing']); return $supported_events; } /** * Unset custom event */ private function unset_custom_events( $supported_events ){ unset($supported_events['optin_submitted_custom']); return $supported_events; } /** * Unset checkout event */ private function unset_checkout_events( $supported_events ){ unset( $supported_events[ 'wpfnl_wc_checkout_order_placed' ] ); unset( $supported_events[ 'order_bump_accepted' ] ); unset( $supported_events[ 'any_order_bump_accepted' ] ); unset( $supported_events[ 'order_bump_rejected' ] ); return $supported_events; } /** * Unset Order bump events */ private function unset_orderbump_events( $supported_events ){ unset( $supported_events[ 'order_bump_accepted' ] ); unset( $supported_events[ 'any_order_bump_accepted' ] ); unset( $supported_events[ 'order_bump_rejected' ] ); return $supported_events; } /** * Unset upsell event */ private function unset_upsell_events( $supported_events ){ unset($supported_events['upsell_accepted']); unset($supported_events['upsell_enrolled_accepted']); unset($supported_events['upsell_rejected']); return $supported_events; } /** * Unset downsell event */ private function unset_downsell_events( $supported_events ){ unset($supported_events['downsell_accepted']); unset($supported_events['downsell_enrolled_accepted']); unset($supported_events['downsell_rejected']); return $supported_events; } /** * Get supported body settings * * @param \WP_REST_Request $request * @return Array $response */ public function get_supported_body_settings( \WP_REST_Request $request ){ $response = $this->get_response_array(); if( isset( $request['funnel_id'] ) && $request['funnel_id'] ){ if( isset( $request['data']['selected_condition']) && $request['data']['selected_condition'] ){ if( strpos($request['data']['selected_condition'], 'optin') !== false ){ $response['status'] = true; $response['data'] = $this->get_optin_fields(); } elseif( strpos( $request['data']['selected_condition'], 'order_bump') !== false || strpos( $request['data']['selected_condition'], 'upsell') !== false || strpos( $request['data']['selected_condition'], 'downsell') !== false || 'wpfnl_wc_checkout_order_placed' === $request['data']['selected_condition'] ){ $response['status'] = true; $response['data'] = $this->get_offer_fields(); } } } return rest_ensure_response( $response ); } /** * Get optin field * * @return array $supported_fields */ private function get_optin_fields(){ $supported_fields = array( 'first_name' => 'Opt-in first name', 'last_name' => 'Opt-in last name', 'email' => 'Opt-in email', 'phone' => 'Opt-in phone', 'website' => 'Opt-in website url', 'message' => 'Opt-in message', ); return $supported_fields; } /** * Get optin field * * @return array */ private function get_offer_fields(){ return [ 'product_id' => 'Product ID', 'product_name' => 'Product Name', 'product_quantity' => 'Product Quantity', 'order_id' => 'Order ID', 'order_total' => 'Order Total', 'order_status' => 'Order Status', 'payment_method' => 'Payment Method', 'shipping_method' => 'Shipping Method', 'customer_name' => 'Customer Name', 'customer_email' => 'Customer Email', 'customer_phone' => 'Customer Phone', 'customer_city' => 'Customer City', 'customer_address' => 'Customer Address' ]; } /** * Delete webhook by index * * @param \WP_REST_Request $request * @return Array $response */ public function delete_webhook( \WP_REST_Request $request ){ $response = $this->get_response_array(); if( isset( $request['funnel_id'] ) && $request['funnel_id'] && isset( $request['data']['index'] ) && $request['data']['index'] !== false ){ $funnel_id = $request['funnel_id']; $settings = Wpfnl_Pro_Webhook_Functions::get_webhook_settings( $funnel_id ); if( !empty($settings) ){ if( isset( $settings[$request['data']['index']] )){ unset($settings[$request['data']['index']]); if( !empty($settings)){ update_post_meta( $request['funnel_id'], '_wpfunnels_webhook_settings', $settings ); }else{ delete_post_meta( $request['funnel_id'],'_wpfunnels_webhook_settings' ); } $response['status'] = true; $response['data'] = $settings; } } } return rest_ensure_response( $response ); } /** * Update or save webhook by index * * @param \WP_REST_Request $request * @return Array $response */ public function save_or_update_webhook( \WP_REST_Request $request ){ $response = $this->get_response_array(); if( isset( $request['funnel_id'] ) && $request['funnel_id'] ){ $funnel_id = $request['funnel_id']; $settings = Wpfnl_Pro_Webhook_Functions::get_webhook_settings( $funnel_id ); $data = $request['data']['content']; $data['id'] = mt_rand().date('hs'); if( !empty($settings) ){ if( isset($request['data']['index']) && $request['data']['index'] != 'false'){ $settings[$request['data']['index']] = $data; update_post_meta( $funnel_id, '_wpfunnels_webhook_settings', $settings ); $response['status'] = true; $response['data'] = 'Update successful'; $settings = get_post_meta( $funnel_id, '_wpfunnels_webhook_settings', true); }else{ $key = array_search($request['data']['content']['id'], array_column($settings, 'id')); if( $key !== false ){ $settings[$key] = $data; update_post_meta( $funnel_id, '_wpfunnels_webhook_settings', $settings ); $response['status'] = true; $response['data'] = 'Update successful'; }else{ array_push($settings,$data); update_post_meta( $funnel_id, '_wpfunnels_webhook_settings', $settings ); $response['status'] = true; $response['data'] = 'Update successful'; } } }else{ $settings = array(); array_push($settings,$data); update_post_meta( $funnel_id, '_wpfunnels_webhook_settings', $settings ); $response['status'] = true; $response['data'] = 'Save successful'; } } return rest_ensure_response( $response ); } /** * Update webhook status * * @param \WP_REST_Request $request * @return Array $response */ public function update_webhook_status( \WP_REST_Request $request ){ $response = $this->get_response_array(); if( isset( $request['funnel_id'] ) && $request['funnel_id'] && isset( $request['data']['index'] ) && $request['data']['index'] !== false ){ $funnel_id = $request['funnel_id']; $settings = Wpfnl_Pro_Webhook_Functions::get_webhook_settings( $funnel_id ); if( !empty($settings) ){ if( isset( $settings[$request['data']['index']] )){ $settings[$request['data']['index']]['status'] = $request['data']['status']; update_post_meta( $request['funnel_id'], '_wpfunnels_webhook_settings', $settings ); $response['status'] = true; $response['data'] = $settings; } } } return rest_ensure_response( $response ); } /** * get common response message */ private function get_response_array(){ return array( 'status' => false, 'data' => 'Data not found' ); } }includes/core/rest-api/controllers/WpfnlTimeInterval.php000064400000004316147600245720017532 0ustar00format( DATE_ATOM ); } /** * Set UTC offset - this is a fixed offset instead of a timezone. * * @param int $offset Offset. */ public function set_utc_offset( $offset ) { $this->utc_offset = intval( $offset ); } /** * Get UTC offset if set, or default to the DateTime object's offset. */ public function getOffset() { return $this->utc_offset ? $this->utc_offset : parent::getOffset(); } /** * Set timezone. * * @param DateTimeZone $timezone DateTimeZone instance. * @return DateTime */ public function setTimezone( $timezone ) { $this->utc_offset = 0; return parent::setTimezone( $timezone ); } /** * Missing in PHP 5.2 so just here so it can be supported consistently. * * @since 3.0.0 * @return int */ public function getTimestamp() { return method_exists( 'DateTime', 'getTimestamp' ) ? parent::getTimestamp() : $this->format( 'U' ); } /** * Get the timestamp with the WordPress timezone offset added or subtracted. * * @since 3.0.0 * @return int */ public function getOffsetTimestamp() { return $this->getTimestamp() + $this->getOffset(); } /** * Format a date based on the offset timestamp. * * @since 3.0.0 * @param string $format Date format. * @return string */ public function date( $format ) { return gmdate( $format, $this->getOffsetTimestamp() ); } /** * Return a localised date based on offset timestamp. Wrapper for date_i18n function. * * @since 3.0.0 * @param string $format Date format. * @return string */ public function date_i18n( $format = 'Y-m-d' ) { return date_i18n( $format, $this->getOffsetTimestamp() ); } } includes/core/shortcodes/variable-template/variable-select-box.php000064400000002165147600245720021431 0ustar00
get_variation_default_attribute( $attribute_name ); wc_dropdown_variation_attribute_options( array( 'options' => $value, 'attribute' => $key, 'product' => $product, 'class' => 'wpfnl-variable-attribute-offer', 'selected' => __( 'Choose an option', 'wpfnl' ) ) ); ?>
includes/core/shortcodes/class-wpfnl-pro-offer-button.php000064400000012561147600245720017643 0ustar00attributes = $this->parse_attributes( $attributes ); } /** * Get shortcode attributes. * * @since 3.2.0 * @return array */ public function get_attributes() { return $this->attributes; } /** * parse attributes * * @param $attributes * @return array */ protected function parse_attributes( $attributes ) { $attributes = shortcode_atts( array( 'btn_text' => '', 'offer_type' => 'upsell', 'action' => 'accept', 'class' => '', 'dynamic_data_template_layout' => 'style1', 'show_product_price' => 'no', 'variation_tbl_title' => '', 'show_product_data' => 'no', 'btn_font_size' => '', 'btn_margin' => '', 'btn_padding' => '', 'btn_background_color' => '', 'btn_color' => '', 'btn_radius' => '', 'price_class' => '', ), $attributes ); return $attributes; } /** * retrieve offer product description */ public function get_content() { if( Wpfnl_functions::check_if_this_is_step_type('upsell') || Wpfnl_functions::check_if_this_is_step_type('downsell')) { $offer_product = Wpfnl_Offer_Product::getInstance()->get_offer_product(); if( !is_object($offer_product) || null === $offer_product) { return; } ob_start(); $data = \WPFunnels\Wpfnl_functions::get_sanitized_get_post(); $step_id = isset($data['post']['current_page']['id']) ? $data['post']['current_page']['id'] : get_the_ID(); if( isset($step_id) && $step_id ){ $step_type = get_post_meta($step_id, '_step_type', true); $offer_product_data = Wpfnl_Pro_functions::get_offer_product( $step_id, $step_type ); $offer_product = null; if( is_array($offer_product_data) ) { foreach ( $offer_product_data as $pr_index => $pr_data ) { $product_id = $pr_data['id']; $offer_product = wc_get_product( $product_id ); break; } } }else{ $offer_product = Wpfnl_Offer_Product::getInstance()->get_offer_product(); } $response = Wpfnl_Pro_functions::get_product_data_for_widget( $step_id ); $offer_product = isset($response['offer_product']) && $response['offer_product'] ? $response['offer_product'] : ''; $get_product_type = isset($response['get_product_type']) && $response['get_product_type'] ? $response['get_product_type'] : ''; $is_gbf = isset($response['is_gbf']) && $response['is_gbf'] ? $response['is_gbf'] : ''; $builder = 'shortcode'; if( !is_object($offer_product) || null === $offer_product) { return; } $button_style = ''; if ('' != $this->attributes['btn_font_size'] || '' != $this->attributes['btn_margin'] || '' != $this->attributes['btn_padding'] || '' != $this->attributes['btn_background_color'] || '' != $this->attributes['btn_color'] || '' != $this->attributes['btn_radius']){ $button_style = ' style="font-size:'.$this->attributes['btn_font_size'].'; margin: '.$this->attributes['btn_margin'].'; padding: '.$this->attributes['btn_padding'].'; background-color: '.$this->attributes['btn_background_color'].'; color: '.$this->attributes['btn_color'].'; border-radius: '.$this->attributes['btn_radius'].';"'; } if( 'yes' === $is_gbf && isset($this->attributes['show_product_data']) && 'yes' === $this->attributes['show_product_data'] && 'accept' === $this->attributes['action'] ){ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/styles/offer-'.$this->attributes['dynamic_data_template_layout'].'.php'; }else{ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/shortcode/offer-button.php'; } return ob_get_clean(); } return false; } public function render_text( $text ){ $html = ''; $html .= ''; $html .= ''.$text.''; $html .= ''; return $html; } }includes/core/shortcodes/class-wpfnl-pro-offer-description.php000064400000002522147600245720020647 0ustar00attributes = $this->parse_attributes( $attributes ); } /** * Get shortcode attributes. * * @since 3.2.0 * @return array */ public function get_attributes() { return $this->attributes; } /** * parse attributes * * @param $attributes * @return array */ protected function parse_attributes( $attributes ) { $attributes = shortcode_atts( array( ), $attributes ); return $attributes; } /** * retrieve offer product description */ public function get_content() { $offer_product = Wpfnl_Offer_Product::getInstance()->get_offer_product(); if( !is_object($offer_product) || null === $offer_product) { return; } return $offer_product->get_description(); } }includes/core/shortcodes/class-wpfnl-pro-offer-image.php000064400000002714147600245720017411 0ustar00attributes = $this->parse_attributes( $attributes ); } /** * Get shortcode attributes. * * @since 3.2.0 * @return array */ public function get_attributes() { return $this->attributes; } /** * parse attributes * * @param $attributes * @return array */ protected function parse_attributes( $attributes ) { $attributes = shortcode_atts( array( ), $attributes ); return $attributes; } /** * retrieve offer product Image */ public function get_content() { $offer_product = Wpfnl_Offer_Product::getInstance()->get_offer_product(); if( !is_object($offer_product) || null === $offer_product) { return; } $image = wp_get_attachment_image_src(get_post_thumbnail_id($offer_product->get_id()), 'single-post-thumbnail'); $image = ''; return $image; } }includes/core/shortcodes/class-wpfnl-pro-offer-price.php000064400000002247147600245720017432 0ustar00attributes = $this->parse_attributes( $attributes ); } /** * Get shortcode attributes. * * @since 3.2.0 * @return array */ public function get_attributes() { return $this->attributes; } /** * parse attributes * * @param $attributes * @return array */ protected function parse_attributes( $attributes ) { $attributes = shortcode_atts( array( ), $attributes ); return $attributes; } /** * retrieve offer product title */ public function get_content() { return Wpfnl_Offer_Product::getInstance()->get_offer_product_price(); } }includes/core/shortcodes/class-wpfnl-pro-offer-product-widget.php000064400000004116147600245720021266 0ustar00attributes = $this->parse_attributes( $attributes ); } /** * Get shortcode attributes. * * @since 3.2.0 * @return array */ public function get_attributes() { return $this->attributes; } /** * parse attributes * * @param $attributes * @return array */ protected function parse_attributes( $attributes ) { $attributes = shortcode_atts( array( ), $attributes ); return $attributes; } /** * retrieve offer product title */ public function get_content() { $offer_product = Wpfnl_Offer_Product::getInstance()->get_offer_product(); if( !is_object($offer_product) || null === $offer_product) { return; } $image = wp_get_attachment_image_src(get_post_thumbnail_id($offer_product->get_id()), 'single-post-thumbnail'); $contents = '
'.$offer_product->get_title().'
'.Wpfnl_Offer_Product::getInstance()->get_offer_product_price().'
'.$offer_product->get_description().'
'; return $contents; } }includes/core/shortcodes/class-wpfnl-pro-offer-title.php000064400000002540147600245720017445 0ustar00attributes = $this->parse_attributes( $attributes ); } /** * Get shortcode attributes. * * @since 3.2.0 * @return array */ public function get_attributes() { return $this->attributes; } /** * parse attributes * * @param $attributes * @return array */ protected function parse_attributes( $attributes ) { $attributes = shortcode_atts( array( ), $attributes ); return $attributes; } /** * retrieve offer product title */ public function get_content() { $offer_product = Wpfnl_Offer_Product::getInstance()->get_offer_product(); if( !is_object($offer_product) || null === $offer_product) { return; } return $offer_product->get_title(); } }includes/core/shortcodes/class-wpfnl-pro-shortcodes.php000064400000006060147600245720017403 0ustar00 __CLASS__ . '::render_offer_product_widget', 'wpfunnels_offer_product_title' => __CLASS__ . '::render_offer_product_title', 'wpfunnels_offer_product_price' => __CLASS__ . '::render_offer_product_price', 'wpfunnels_offer_product_description' => __CLASS__ . '::render_offer_product_description', 'wpfunnels_offer_product_image' => __CLASS__ . '::render_offer_product_image', 'wpf_offer_button' => __CLASS__ . '::render_offer_button', 'wpf_variable_offer' => __CLASS__ . '::render_variable_product_for_offer', ); foreach ( $shortcodes as $shortcode => $function ) { add_shortcode( $shortcode, $function ); } } /** * Render Product Widget * Title ,Price , Description, Image * @param $attr * @return string|void */ /** * render offer product title * * @param $atts */ public static function render_offer_product_title( $atts ) { $shortcode = new Wpfnl_Shortcode_Offer_Title( (array) $atts ); return $shortcode->get_content(); } /** * render offer product price * * @param $atts */ public static function render_offer_product_price( $atts ) { $shortcode = new Wpfnl_Shortcode_Offer_Price( (array) $atts ); return $shortcode->get_content(); } /** * render offer product description * * @param $atts */ public static function render_offer_product_description( $atts ) { $shortcode = new Wpfnl_Shortcode_Offer_Description( (array) $atts ); return $shortcode->get_content(); } /** * render offer product Image * * @param $atts */ public static function render_offer_product_image( $atts ) { $shortcode = new Wpfnl_Shortcode_Offer_Image( (array) $atts ); return $shortcode->get_content(); } public static function render_offer_product_widget($atts){ $shortcode = new Wpfnl_Shortcode_Offer_Widget( (array) $atts ); return $shortcode->get_content(); } /** * Render offer button * * @param $atts */ public static function render_offer_button($atts){ $shortcode = new Wpfnl_Shortcode_Offer_Button( (array) $atts ); return $shortcode->get_content(); } /** * Render variable product for offer step * * @param $atts */ public static function render_variable_product_for_offer($atts){ $shortcode = new Wpfnl_Shortcode_Variable_product( (array) $atts ); return $shortcode->get_content(); } } includes/core/shortcodes/class-wpfnl-pro-variable-product.php000064400000010223147600245720020465 0ustar00attributes = $this->parse_attributes( $attributes ); /* Load WC templates from wpfunnels plugin */ add_filter( 'woocommerce_locate_template', array( $this, 'override_woo_templates' ), 20, 3 ); } public function override_woo_templates( $template, $template_name, $template_path ) { // if ( Wpfnl_Pro_functions::maybe_offer_step() || Wpfnl_Pro_functions::maybe_admin_on_edit_page() ) { $_template = $template; $plugin_path = WPFNL_PRO_DIR . 'woocommerce/templates/'; if ( file_exists( $plugin_path . $template_name ) ) { $template = $plugin_path . $template_name; } if ( ! $template ) { $template = $_template; } // } return $template; } /** * Get shortcode attributes. * * @since 3.2.0 * @return array */ public function get_attributes() { return $this->attributes; } /** * parse attributes * * @param $attributes * @return array */ protected function parse_attributes( $attributes ) { $attributes = shortcode_atts( array( 'post_id' => '', ), $attributes ); return $attributes; } /** * retrieve offer product */ public function get_content() { if( isset($this->attributes['post_id']) && $this->attributes['post_id'] ){ $step_type = get_post_meta($this->attributes['post_id'], '_step_type', true); $_funnel_id = get_post_meta($this->attributes['post_id'], '_funnel_id', true); $type = get_post_meta($_funnel_id, '_wpfnl_funnel_type', true); if( 'lms' === $type ){ return false; } $offer_product_data = Wpfnl_Pro_functions::get_offer_product_data( $this->attributes['post_id']); $offer_product = null; if( is_array($offer_product_data) && isset($offer_product_data['id']) ) { $product_id = $offer_product_data['id']; $offer_product = wc_get_product( $product_id ); } }else{ $offer_product = Wpfnl_Offer_Product::getInstance()->get_offer_product(); } if( (!is_object($offer_product) || null === $offer_product) ) { return false; } $product_id = $offer_product->get_id(); $product = wc_get_product($product_id); if( $product ){ if( $product->get_type() !== 'variable' ) { return false; } wp_enqueue_script( 'wc-add-to-cart-variation' ); // Get Available variations? $get_variations = count( $product->get_children() ) <= apply_filters( 'woocommerce_ajax_variation_threshold', 30, $product ); // Load the template. wc_get_template( 'single-product/add-to-cart/variable.php', array( 'available_variations' => $get_variations ? $product->get_available_variations() : false, 'attributes' => $product->get_variation_attributes(), 'selected_attributes' => $product->get_default_attributes(), 'product' => $product, ) ); } } /** * render variable markup */ private function render_vaiable_markup( $key, $value, $product, $product_id ){ require WPFNL_PRO_DIR . 'includes/core/shortcodes/variable-template/variable-select-box.php'; } }includes/core/webhook/classes/class-wpfunnels-pro-webhook-functions.php000064400000021557147600245720022513 0ustar00 __( 'After Checkout Order Placed', 'wpfnl-pro' ), 'upsell_accepted' => __( 'Upsell Accepted (Any)', 'wpfnl-pro' ), 'upsell_rejected' => __( 'Upsell Rejected (Any)', 'wpfnl-pro' ), 'downsell_accepted' => __( 'Downsell Accepted (Any)', 'wpfnl-pro' ), 'downsell_rejected' => __( 'Downsell Rejected (Any)', 'wpfnl-pro' ), 'optin_submitted_landing' => __( 'After Optin Form Submitted', 'wpfnl-pro' ), 'optin_submitted_custom' => __( 'After Optin Form Submitted', 'wpfnl-pro' ) ]; } /** * Get all supported request body fields for webhook * * @return Array $supported_fields */ public static function get_all_body_fields(){ $supported_fields = array( 'product_id' => 'Product ID', 'product_name' => 'Product Name', 'product_quantity' => 'Product Quantity', 'order_id' => 'Order ID', 'order_total' => 'Order Total', 'order_status' => 'Order Status', 'payment_method' => 'Payment Method', 'shipping_method' => 'Shipping Method', 'customer_name' => 'Customer Name', 'customer_email' => 'Customer Email', 'customer_phone' => 'Customer Phone', 'customer_city' => 'Customer City', 'customer_address' => 'Customer Address', 'first_name' => 'Opt-in first name', 'last_name' => 'Opt-in last name', 'email' => 'Opt-in email', 'phone' => 'Opt-in phone', ); return $supported_fields; } /** * Get all request header name for webhook * * @return Array */ public static function get_all_request_header_name(){ return array( array( 'label' => 'Accept', 'value' => 'Accept', ), array( 'label' => 'Accept-Charset', 'value' => 'Accept-Charset', ), array( 'label' => 'Accept-Encoding', 'value' => 'Accept-Encoding', ), array( 'label' => 'Accept-Language', 'value' => 'Accept-Language', ), array( 'label' => 'Accept-Datetime', 'value' => 'Accept-Datetime', ), array( 'label' => 'Authorization', 'value' => 'Authorization', ), array( 'label' => 'Cache-Control', 'value' => 'Cache-Control', ), array( 'label' => 'Connection', 'value' => 'Connection', ), array( 'label' => 'Cookie', 'value' => 'Cookie', ), array( 'label' => 'Content-Length', 'value' => 'Content-Length', ), array( 'label' => 'Content-Type', 'value' => 'Content-Type', ), array( 'label' => 'Date', 'value' => 'Date', ), array( 'label' => 'Expect', 'value' => 'Expect', ), array( 'label' => 'Forwarded', 'value' => 'Forwarded', ), array( 'label' => 'From', 'value' => 'From', ), array( 'label' => 'Host', 'value' => 'Host', ), array( 'label' => 'If-Match', 'value' => 'If-Match', ), array( 'label' => 'If-Modified-Since', 'value' => 'If-Modified-Since', ), array( 'label' => 'If-None-Match', 'value' => 'If-None-Match', ), array( 'label' => 'If-Range', 'value' => 'If-Range', ), array( 'label' => 'If-Unmodified-Since', 'value' => 'If-Unmodified-Since', ), array( 'label' => 'Max-Forwards', 'value' => 'Max-Forwards', ), array( 'label' => 'Origin', 'value' => 'Origin', ), array( 'label' => 'Pragma', 'value' => 'Pragma', ), array( 'label' => 'Proxy-Authorization', 'value' => 'Proxy-Authorization', ), array( 'label' => 'Range', 'value' => 'Range', ), array( 'label' => 'Referer', 'value' => 'Referer', ), array( 'label' => 'TE', 'value' => 'TE', ), array( 'label' => 'User-Agent', 'value' => 'User-Agent', ), array( 'label' => 'Upgrade', 'value' => 'Upgrade', ), array( 'label' => 'Via', 'value' => 'Via', ), array( 'label' => 'Warning', 'value' => 'Warning', ), ); } /** * Get request url * * @param Array $setting * @return String $url */ public static function get_request_url( $setting ){ return $setting['request']['url']; } /** * Prepare request url * * @param Array $setting * @param String $order_id * @param String $event_name * * @return String $url */ public static function prepare_request_body( $settings , $order_id, $event_name ){ $body = array(); $body['Event name'] = $event_name; $order = wc_get_order($order_id); if( $settings['request']['body']['type'] === 'selected' ){ foreach( $settings['request']['body']['values'] as $key => $value ){ if( $key == 'order_id' ){ $body[$value] = $order_id; }elseif( $key === 'total_price' ){ $body[$value] = $order->get_total(); } } } return $body; } /** * Prepare request header for webhook * * @param Array $header */ public static function prepare_request_header( $headers ){ $formatted_header = array(); foreach( $headers as $header ){ $formatted_header[$header['name']] = $header['value']; } return $formatted_header; } /** * Order bump key matching */ public static function ob_key_matching( $product_id, $item_id ){ return $product_id == $item_id; } /** * Check webhook status * * @param String $status * @return Boolean */ public static function check_webhook_status( $status ){ if( $status === 'on' ){ return true; } return false; } /** * Match conditions * */ public static function match_conditions( $value, $rule ){ return $rule === $value; } /** * Get class instance */ public static function get_class_instance( $event, $settings, $record = [], $order_id = '', $offer_product = '' , $offer_status = '', $proudct_id = '', $type = '' , $cookie_data = [], $step_id = '' ){ $class_name = "WPFunnelsProWebHooks\\Events\\".'Wpfnl_Pro_Webhook_'.ucfirst($type).ucfirst($event); if ( class_exists( ucfirst( $class_name ) ) ) { if ( 'checkout' === $event ) { return new $class_name( $settings, $order_id ); } elseif ( $event === 'offer' ) { if ( !$type ) { return new $class_name( $settings, $order_id, $offer_product, $offer_status ); } else { if ( $cookie_data ) { return new $class_name( $settings, $cookie_data, $offer_status, $proudct_id, $step_id ); } } } else if ( $event === 'optin' ) { return new $class_name( $settings, $record ); } elseif ( $event === 'orderbump' ) { if ( !$type ) { return new $class_name( $settings, $order_id, $offer_status, $proudct_id ); } else { if ( $cookie_data ) { return new $class_name( $settings, $cookie_data, $offer_status, $proudct_id ); } } } } return false; } /** * Get the upsell and downsell IDs, titles, and types associated with a funnel. * * @param int $funnel_id The ID of the funnel. * * @return array An array containing the upsell and downsell post IDs, titles, and types. * @since 1.9.12 */ public static function get_upsell_ids( $funnel_id ) { global $wpdb; $query = $wpdb->prepare( 'SELECT posts.ID AS id, posts.post_title AS title, postmeta1.meta_value AS type FROM %i AS posts ', [ $wpdb->posts ] ); $query .= $wpdb->prepare( 'JOIN %i AS postmeta1 ', [ $wpdb->postmeta ] ); $query .= 'ON posts.ID = postmeta1.post_id '; $query .= $wpdb->prepare( 'JOIN %i AS postmeta2 ', [ $wpdb->postmeta ] ); $query .= 'ON posts.ID = postmeta2.post_id '; $query .= $wpdb->prepare( 'WHERE (postmeta1.meta_key = %s AND (postmeta1.meta_value = %s ', [ '_step_type', 'upsell' ] ); $query .= $wpdb->prepare( 'OR postmeta1.meta_value = %s)) ', [ 'downsell' ] ); $query .= $wpdb->prepare( 'AND (postmeta2.meta_key = %s AND postmeta2.meta_value = %d) ', [ '_funnel_id', $funnel_id ] ); return $wpdb->get_results( $query, ARRAY_A ); } }includes/core/webhook/events/class-wofunnels-pro-webhook-optin.php000064400000011217147600245720021472 0ustar00settings = $settings; $this->record = $record; } /** * Get event name * * @return String */ public function get_event_name(){ return 'After optin form submit'; } /** * Send data to request url through webhook */ public function send_data(){ $request_url = Wpfnl_Pro_Webhook_Functions::get_request_url( $this->settings ); $request_args = $this->prepare_request_args(); $response = wp_remote_request( $request_url, $request_args ); } /** * Prepare request arguments * */ public function prepare_request_args(){ $request_body = $this->prepare_request_body( $this->settings, $this->record ); $content_type = 'application/json'; return Wpfnl_Pro_functions::prepare_common_request_args( $this->settings, $content_type, $request_body, $this->get_event_name(), home_url( '/' ) ); } /** * Prepare request body * * @param $settings * @param Array $formatted_body */ private function prepare_request_body( $settings , $record ){ $type = $settings['request']['body']['type']; $formatted_body = array(); if( $type === 'all' ){ $formatted_body = $this->prepare_all_body_fields( $settings , $record ); }else{ $values = $settings['request']['body']['values']; $_body = array(); $_body = $this->prepare_selected_body_fields( $settings , $record ); $formatted_body['Event Name'] = $this->get_event_name(); foreach( $values as $value ){ if( isset( $_body[$value['value']] )){ $formatted_body[$value['key']] = $_body[$value['value']]; } } } return $formatted_body; } /** * Prepare request body field for type 'all' * * @param $order, $order_id, $offer_product * @return $formatted_body */ public function prepare_all_body_fields( $settings , $record ){ $formatted_body['Date'] = date("Y-m-d h:i"); $formatted_body['Event Name'] = $this->get_event_name(); if( isset($record->form_data['first_name']) ){ $formatted_body['First Name'] = isset($record->form_data['first_name']) ? $record->form_data['first_name'] : ''; } if( isset($record->form_data['last_name']) ){ $formatted_body['Last Name'] = isset($record->form_data['last_name']) ? $record->form_data['last_name'] : ''; } if( isset($record->form_data['phone']) ){ $formatted_body['Phone'] = isset($record->form_data['phone']) ? $record->form_data['phone'] : ''; } if( isset($record->form_data['email']) ){ $formatted_body['Email'] = isset($record->form_data['email']) ? $record->form_data['email'] : ''; } if( isset($record->form_data['message']) ){ $formatted_body['Message'] = isset($record->form_data['message']) ? $record->form_data['message'] : ''; } if( isset($record->form_data['web-url']) ){ $formatted_body['Website'] = isset($record->form_data['web-url']) ? $record->form_data['web-url'] : ''; } return $formatted_body; } /** * Prepare request body field for type 'selected' * * @param $order, $order_id, $offer_product * @return $formatted_body */ public function prepare_selected_body_fields( $settings , $record ){ $formatted_body['Date'] = date("Y-m-d h:i"); $formatted_body['first_name'] = isset($record->form_data['first_name']) ? $record->form_data['first_name'] : ''; $formatted_body['last_name'] = isset($record->form_data['last_name']) ? $record->form_data['last_name'] : ''; $formatted_body['Phone'] = isset($record->form_data['phone']) ? $record->form_data['phone'] : ''; $formatted_body['email'] = isset($record->form_data['email']) ? $record->form_data['email'] : ''; $formatted_body['message'] = isset($record->form_data['message']) ? $record->form_data['message'] : ''; $formatted_body['website'] = isset($record->form_data['web-url']) ? $record->form_data['web-url'] : ''; return $formatted_body; } }includes/core/webhook/events/class-wpfunnels-pro-webhook-checkout.php000064400000016111147600245720022145 0ustar00settings = $settings; $this->order_id = $order_id; } /** * Send data to a specified webhook URL using an HTTP request. * * This method constructs an HTTP request to send data to the configured webhook URL. * * @return \WP_Error|array The response from the HTTP request, or a WP_Error object on failure. * @since 1.9.12 */ public function send_data() { $request_url = Wpfnl_Pro_Webhook_Functions::get_request_url( $this->settings ); $request_args = $this->prepare_request_args(); return wp_remote_request( $request_url, $request_args ); } /** * Get event name * * @return String * @since 1.9.12 */ public function get_event_name(){ return 'Checkout Order Placed'; } /** * Prepare the request arguments for an HTTP request to the webhook URL. * * This method constructs the request body, content type, and other common request arguments * required for sending data to the webhook. * * @return array An array of request arguments to be used in the HTTP request. * @since 1.9.12 */ public function prepare_request_args(){ $request_body = $this->prepare_request_body(); return Wpfnl_Pro_functions::prepare_common_request_args( $this->settings, 'application/json', $request_body, $this->get_event_name(), home_url( '/' ) ); } /** * Prepare the request body data to be sent in the webhook request. * * This method constructs the request body based on the specified type and selected fields. * It formats the body data to match the expected format for the webhook request. * * @return array An array representing the formatted request body data. * @since 1.9.12 */ public function prepare_request_body() { $type = $this->settings[ 'request' ][ 'body' ][ 'type' ] ?? 'all'; if ( 'selected' === $type ) { return $this->prepare_selected_body_fields(); } return $this->prepare_all_body_fields(); } /** * Prepare all available body fields for the webhook request. * * This method retrieves various order-related data and formats it into an array. * * @return array An array containing formatted order-related data for the webhook request. * @since 1.9.12 */ public function prepare_all_body_fields() { // Get the order object based on the provided order ID. $order = wc_get_order($this->order_id); // Initialize a formatted body array with default values. $formatted_body = [ 'Date' => date( "Y-m-d h:i" ), 'Event Name' => $this->get_event_name(), 'Order ID' => $this->order_id, 'Order Status' => $order->get_status(), 'Order Total' => $order->get_total(), 'Payment Method' => $order->get_payment_method(), 'Shipping Method' => $order->get_shipping_method(), 'Customer Name' => $order->get_formatted_billing_full_name() ? $order->get_formatted_billing_full_name() : $order->get_formatted_shipping_full_name(), 'Customer Phone' => $order->get_billing_phone() ? $order->get_billing_phone() : '', 'Customer Email' => $order->get_billing_email() ? $order->get_billing_email() : '', 'Customer City' => $order->get_billing_city() ? strip_tags( $order->get_billing_city() ) : strip_tags( $order->get_shipping_city() ), 'Customer Address' => $order->get_billing_address_1() ? strip_tags( $order->get_billing_address_1() ) : strip_tags( $order->get_shipping_address_1() ), ]; foreach( $order->get_items() as $item ) { $formatted_body[ 'Items' ][] = [ 'Product ID' => $item->get_id(), 'Product Name' => $item->get_name(), 'Product Quantity' => $item->get_quantity() ]; } return $formatted_body; } /** * Prepare selected body fields for the webhook request based on user configuration. * * This method retrieves specific order-related data fields as configured by the user and formats them into an array. * * @return array An array containing formatted selected order-related data for the webhook request. * @since 1.9.12 */ public function prepare_selected_body_fields() { $order = wc_get_order( $this->order_id ); $selected_fields = $this->settings[ 'request' ][ 'body' ][ 'values' ] ?? []; $formatted_body = [ 'Date' => date( "Y-m-d h:i" ), 'Event Name' => $this->get_event_name() ]; if ( is_array( $selected_fields ) && !empty( $selected_fields ) ) { foreach( $selected_fields as $field ) { if ( isset( $field[ 'value' ], $field[ 'key' ] ) ) { switch( $field[ 'value' ] ) { case 'order_id': $formatted_body[ $field[ 'key' ] ] = $this->order_id; break; case 'order_total': $formatted_body[ $field[ 'key' ] ] = $order->get_total(); break; case 'order_status': $formatted_body[ $field[ 'key' ] ] = $order->get_status(); break; case 'payment_method': $formatted_body[ $field[ 'key' ] ] = $order->get_payment_method(); break; case 'shipping_method': $formatted_body[ $field[ 'key' ] ] = $order->get_shipping_method(); break; case 'customer_name': $formatted_body[ $field[ 'key' ] ] = $order->get_formatted_billing_full_name() ? $order->get_formatted_billing_full_name() : $order->get_formatted_shipping_full_name(); break; case 'customer_phone': $formatted_body[ $field[ 'key' ] ] = $order->get_billing_phone() ?? ''; break; case 'customer_email': $formatted_body[ $field[ 'key' ] ] = $order->get_billing_email() ?? ''; break; case 'customer_city': $formatted_body[ $field[ 'key' ] ] = $order->get_billing_city() ? strip_tags( $order->get_billing_city() ) : strip_tags( $order->get_shipping_city() ); break; case 'customer_address': $formatted_body[ $field[ 'key' ] ] = $order->get_billing_address_1() ? strip_tags( $order->get_billing_address_1() ) : strip_tags( $order->get_shipping_address_1() ); break; case 'product_name': $index = 0; foreach( $order->get_items() as $item ) { $formatted_body[ 'Items' ][ $index++ ][ $field[ 'key' ] ] = $item->get_name(); } break; case 'product_id': $index = 0; foreach( $order->get_items() as $item ) { $formatted_body[ 'Items' ][ $index++ ][ $field[ 'key' ] ] = $item->get_id(); } break; case 'product_quantity': $index = 0; foreach( $order->get_items() as $item ) { $formatted_body[ 'Items' ][ $index++ ][ $field[ 'key' ] ] = $item->get_quantity(); } break; default: break; } } } } return $formatted_body; } }includes/core/webhook/events/class-wpfunnels-pro-webhook-offer.php000064400000020415147600245720021443 0ustar00settings = $settings; $this->order_id = $order_id; $this->offer_product = $offer_product; $this->offer_status = $offer_status; } /** * Get event name * * @return String */ public function get_event_name(){ $step_id = $this->offer_product['step_id']; $funnel_id = get_post_meta($step_id, '_funnel_id', true); $step_type = get_post_meta($step_id, '_step_type', true); return ucfirst($step_type).' '.ucfirst($this->offer_status); } /** * Send data to request url through webhook */ public function send_data(){ $request_url = Wpfnl_Pro_Webhook_Functions::get_request_url( $this->settings ); $request_args = $this->prepare_request_args( $this->settings ); $response = wp_remote_request( $request_url, $request_args ); } /** * Prepare request arguments * * @param Array $settings * @return Array $request_args */ public function prepare_request_args( $settings ){ $request_body = $this->prepare_request_body( $this->settings , $this->order_id , $this->offer_product ); $content_type = 'application/json'; return Wpfnl_Pro_functions::prepare_common_request_args( $this->settings, $content_type, $request_body, $this->get_event_name(), home_url( '/' ) ); } /** * Prepare request body * * @param Array $settings * @return Array $formatted_body */ private function prepare_request_body( $settings , $order_id , $offer_product ){ $type = $settings['request']['body']['type']; $funnel_type = 'wc'; if( isset($offer_product['step_id']) && $offer_product['step_id'] ){ $funnel_id = get_post_meta( $offer_product['step_id'], '_funnel_id', true ); $funnel_type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ); } if( 'lms' == $funnel_type ){ $order = ''; }else{ $order = wc_get_order( $order_id ); } $formatted_body = array(); if( $type === 'all' ){ $formatted_body = $this->prepare_all_body_fields( $order, $order_id, $offer_product ); }else{ $values = $settings['request']['body']['values']; $_body = array(); $_body = $this->prepare_selected_body_fields( $order, $order_id, $offer_product ); $formatted_body['Event Name'] = $this->get_event_name(); foreach( $values as $value ){ if( isset( $_body[$value['value']] )){ $formatted_body[$value['key']] = $_body[$value['value']]; } } } return $formatted_body; } /** * Prepare request body field for type 'all' * * @param $order, $order_id, $offer_product * @return $formatted_body */ public function prepare_all_body_fields( $order, $order_id, $offer_product ){ $formatted_body['Date'] = date("Y-m-d h:i"); $formatted_body['Event Name'] = $this->get_event_name(); if( isset($offer_product['step_id']) && $offer_product['step_id'] ){ $funnel_id = get_post_meta($offer_product['step_id'],'_funnel_id', true); $type = get_post_meta($funnel_id,'_wpfnl_funnel_type', true); if( 'lms' === $type ){ if( Wpfnl_functions::is_lms_addon_active() ){ $lms_formatted_body = \WPFunnels\lms\helper\Wpfnl_lms_learndash_functions::get_webhook_data( $offer_product ); $formatted_body = array_merge($formatted_body,$lms_formatted_body); return $formatted_body; } return []; } } // Product info $formatted_body['Product ID'] = $offer_product['id']; $formatted_body['Product Name'] = $offer_product['name']; $formatted_body['Product Quantity'] = $offer_product['qty']; if( $this->offer_status == 'accepted' ){ // Order info $formatted_body['Order ID'] = $order_id; $formatted_body['Total Price'] = $order->get_total(); $formatted_body['Order Status'] = $order->get_status(); }else{ $formatted_body['Product Price'] = $offer_product['args']['total']; } // payment info $formatted_body['Payment Method'] = $order->get_payment_method(); // shipping info $formatted_body['Shipping Method'] = $order->get_shipping_method(); // customer info $formatted_body['Customer Name'] = $order->get_formatted_billing_full_name() ? $order->get_formatted_billing_full_name() : $order->get_formatted_shipping_full_name(); $formatted_body['Customer Phone'] = $order->get_billing_phone() ? $order->get_billing_phone() : ''; $formatted_body['Customer Email'] = $order->get_billing_email() ? $order->get_billing_email() : ''; $formatted_body['Customer City'] = $order->get_billing_city() ? strip_tags($order->get_billing_city()) : strip_tags($order->get_shipping_city()); $formatted_body['Customer Address'] = $order->get_billing_address_1() ? strip_tags($order->get_billing_address_1()) : strip_tags($order->get_shipping_address_1()); return $formatted_body; } /** * Prepare request body field for type 'selected' * * @param $order, $order_id, $offer_product * @return $formatted_body */ public function prepare_selected_body_fields( $order, $order_id, $offer_product ){ $formatted_body['Date'] = date("Y-m-d h:i"); if( isset($offer_product['step_id']) && $offer_product['step_id'] ){ $funnel_id = get_post_meta($offer_product['step_id'],'_funnel_id', true); $type = get_post_meta($funnel_id,'_wpfnl_funnel_type', true); if( 'lms' === $type ){ if( Wpfnl_functions::is_lms_addon_active() ){ $lms_formatted_body = \WPFunnels\lms\helper\Wpfnl_lms_learndash_functions::get_webhook_selected_data( $offer_product ); $formatted_body = array_merge($formatted_body,$lms_formatted_body); return $formatted_body; } return []; } } // Product info $formatted_body['product_id'] = $offer_product['id']; $formatted_body['product_name'] = $offer_product['name']; $formatted_body['product_quantity'] = $offer_product['qty']; // Order info $formatted_body['order_id'] = $order_id; $formatted_body['order_total'] = $order->get_total(); $formatted_body['order_status'] = $order->get_status(); // payment info $formatted_body['payment_method'] = $order->get_payment_method(); // shipping info $formatted_body['shipping_method'] = $order->get_shipping_method(); // customer info $formatted_body['customer_name'] = $order->get_formatted_billing_full_name() ? $order->get_formatted_billing_full_name() : $order->get_formatted_shipping_full_name(); $formatted_body['customer_phone'] = $order->get_billing_phone() ? $order->get_billing_phone() : ''; $formatted_body['customer_email'] = $order->get_billing_email() ? $order->get_billing_email() : ''; $formatted_body['customer_city'] = $order->get_billing_city() ? strip_tags($order->get_billing_city()) : strip_tags($order->get_shipping_city()); $formatted_body['customer_address'] = $order->get_billing_address_1() ? strip_tags($order->get_billing_address_1()) : strip_tags($order->get_shipping_address_1()); return $formatted_body; } }includes/core/webhook/events/class-wpfunnels-pro-webhook-order-bump.php000064400000026000147600245720022412 0ustar00settings = $settings; $this->order_id = $order_id; $this->offer_status = $offer_status; $this->proudct_id = $proudct_id; } /** * Get event name * * @return String */ public function get_event_name(){ return 'Order Bump '.ucfirst($this->offer_status); } /** * Send data to request url through webhook */ public function send_data(){ $request_url = Wpfnl_Pro_Webhook_Functions::get_request_url( $this->settings ); $request_args = $this->prepare_request_args( $this->settings ); $response = wp_remote_request( $request_url, $request_args ); } /** * Prepare request arguments * * @param Array $settings * @return Array $request_args */ public function prepare_request_args( $settings ){ $request_body = $this->prepare_request_body( $this->settings , $this->order_id, $this->offer_status ); $request_header = Wpfnl_Pro_Webhook_Functions::prepare_request_header($this->settings['request']['header']); $content_type = 'application/json'; return Wpfnl_Pro_functions::prepare_common_request_args( $this->settings, $content_type, $request_body, $this->get_event_name(), home_url( '/' ) ); } /** * Prepare request body * @param Array $settings * @return Array $formatted_body */ private function prepare_request_body( $settings , $order_id, $offer_status ){ $type = $settings['request']['body']['type']; $order = wc_get_order( $order_id ); $formatted_body = array(); if( $type === 'all' ){ $formatted_body = $this->prepare_all_body_fields( $order, $order_id, $offer_status ); }else{ $values = $settings['request']['body']['values']; $_body = array(); $_body = $this->prepare_selected_body_fields( $order, $order_id, $offer_status ); $formatted_body['Event Name'] = $this->get_event_name(); foreach( $values as $value ){ if( isset( $_body[$value['value']] )){ $formatted_body[$value['key']] = $_body[$value['value']]; } } } return $formatted_body; } /** * Prepare request body field for type 'all' * * @param $order, $order_id, $offer_product * @return $formatted_body */ public function prepare_all_body_fields( $order, $order_id, $offer_status ){ $formatted_body['Date'] = date("Y-m-d h:i"); $formatted_body['Event Name'] = $this->get_event_name(); if( $offer_status === 'accepted' ){ foreach ( $order->get_items() as $item_id => $item ) { $item_id = $item['variation_id'] ? $item['variation_id'] : $item['product_id']; if( $item->get_meta('_wpfunnels_order_bump') === 'yes' && $item_id == $this->proudct_id ){ $formatted_body['Product ID'] = $item->get_product_id(); $formatted_body['Product Name'] = $item->get_name(); $formatted_body['Product Quantity'] = $item->get_quantity(); $formatted_body['Order ID'] = $order_id; $formatted_body['Total Price'] = $item->get_total(); $formatted_body['Order Status'] = $order->get_status(); // payment info $formatted_body['Payment Method'] = $order->get_payment_method(); // shipping info $formatted_body['Shipping Method'] = $order->get_shipping_method(); // customer info $formatted_body['Customer Name'] = $order->get_formatted_billing_full_name() ? $order->get_formatted_billing_full_name() : $order->get_formatted_shipping_full_name(); $formatted_body['Customer Phone'] = $order->get_billing_phone() ? $order->get_billing_phone() : ''; $formatted_body['Customer Email'] = $order->get_billing_email() ? $order->get_billing_email() : ''; $formatted_body['Customer City'] = $order->get_billing_city() ? strip_tags($order->get_billing_city()) : strip_tags($order->get_shipping_city()); $formatted_body['Customer Address'] = $order->get_billing_address_1() ? strip_tags($order->get_billing_address_1()) : strip_tags($order->get_shipping_address_1()); } } }else{ $funnel_id = Wpfnl_functions::get_funnel_id_from_order( $order_id ); if( $funnel_id ){ $steps = Wpfnl_functions::get_steps( $funnel_id ); $key = array_search('checkout', array_column($steps, 'step_type')); if( $key !== false ){ $step_id = $steps[$key]['id']; $ob_settings = get_post_meta($step_id, 'order-bump-settings', true); foreach( $ob_settings as $settings ){ if( $settings['product'] == $this->proudct_id ){ $product = wc_get_product( $settings['product'] ); $formatted_body['Product ID'] = $settings['product']; $formatted_body['Product Name'] = $product ? ($product->get_name()) : ''; $formatted_body['Product Quantity'] =$settings['quantity']; // customer info $formatted_body['Customer Name'] = $order->get_formatted_billing_full_name() ? $order->get_formatted_billing_full_name() : $order->get_formatted_shipping_full_name(); $formatted_body['Customer Phone'] = $order->get_billing_phone() ? $order->get_billing_phone() : ''; $formatted_body['Customer Email'] = $order->get_billing_email() ? $order->get_billing_email() : ''; $formatted_body['Customer City'] = $order->get_billing_city() ? strip_tags($order->get_billing_city()) : strip_tags($order->get_shipping_city()); $formatted_body['Customer Address'] = $order->get_billing_address_1() ? strip_tags($order->get_billing_address_1()) : strip_tags($order->get_shipping_address_1()); } } } } } return $formatted_body; } /** * Prepare request body field for type 'selected' * * @param $order, $order_id, $offer_product * @return $formatted_body */ public function prepare_selected_body_fields( $order, $order_id, $offer_status ){ $formatted_body['Date'] = date("Y-m-d h:i"); if( $offer_status === 'accepted' ){ foreach ( $order->get_items() as $item_id => $item ) { if( $item->get_meta('_wpfunnels_order_bump') === 'yes' ){ // Product info $formatted_body['product_id'] = $item->get_product_id(); $formatted_body['product_name'] = $item->get_name(); $formatted_body['product_quantity'] = $item->get_quantity(); // Order info $formatted_body['order_id'] = $order_id; $formatted_body['order_total'] = $item->get_total(); $formatted_body['order_status'] = $order->get_status(); // payment info $formatted_body['payment_method'] = $order->get_payment_method(); // shipping info $formatted_body['shipping_method'] = $order->get_shipping_method(); // customer info $formatted_body['customer_name'] = $order->get_formatted_billing_full_name() ? $order->get_formatted_billing_full_name() : $order->get_formatted_shipping_full_name(); $formatted_body['customer_phone'] = $order->get_billing_phone() ? $order->get_billing_phone() : ''; $formatted_body['customer_email'] = $order->get_billing_email() ? $order->get_billing_email() : ''; $formatted_body['customer_city'] = $order->get_billing_city() ? strip_tags($order->get_billing_city()) : strip_tags($order->get_shipping_city()); $formatted_body['customer_address'] = $order->get_billing_address_1() ? strip_tags($order->get_billing_address_1()) : strip_tags($order->get_shipping_address_1()); } } }else{ $funnel_id = Wpfnl_functions::get_funnel_id_from_order( $order_id ); if( $funnel_id ){ $steps = Wpfnl_functions::get_steps( $funnel_id ); $key = array_search('checkout', array_column($steps, 'step_type')); if( $key !== false ){ $step_id = $steps[$key]['id']; $ob_settings = get_post_meta($step_id, 'order-bump-settings', true); foreach( $ob_settings as $settings ){ if( $settings['product'] == $this->proudct_id ){ $product = wc_get_product( $settings['product'] ); $formatted_body['product_id'] = $settings['product']; $formatted_body['product_name'] = $product ? ($product->get_name()) : ''; $formatted_body['product_quantity'] =$settings['quantity']; // customer info $formatted_body['customer_name'] = $order->get_formatted_billing_full_name() ? $order->get_formatted_billing_full_name() : $order->get_formatted_shipping_full_name(); $formatted_body['customer_phone'] = $order->get_billing_phone() ? $order->get_billing_phone() : ''; $formatted_body['customer_email'] = $order->get_billing_email() ? $order->get_billing_email() : ''; $formatted_body['customer_city'] = $order->get_billing_city() ? strip_tags($order->get_billing_city()) : strip_tags($order->get_shipping_city()); $formatted_body['customer_address'] = $order->get_billing_address_1() ? strip_tags($order->get_billing_address_1()) : strip_tags($order->get_shipping_address_1()); } } } } } return $formatted_body; } }includes/core/widgets/block/assets/dist/lms-offer-button.css000064400000014476147600245720020247 0ustar00.qubely-field-group-btn{align-items:center}.qubely-field-group-btn label{margin-bottom:0}.qubely-field-group-btn .qubely-field-child{display:flex;justify-content:end;align-items:center}.qubely-field-group-btn .qubely-field-child .qubley-group-button{display:inline-block;white-space:nowrap;padding:0px 9px;color:#8d96a0;font-size:12px;border-top:1px solid #d6d9dd;border-bottom:1px solid #d6d9dd;border-left:1px solid #d6d9dd;text-transform:capitalize;cursor:pointer;box-shadow:none;line-height:26px;height:26px}.qubely-field-group-btn .qubely-field-child .qubley-group-button.extra-padding{padding:0px 10px;font-weight:bold;font-size:15px}.qubely-field-group-btn .qubely-field-child .qubley-group-button:last-child{border-bottom-right-radius:3px;border-top-right-radius:3px;border-right:1px solid #d6d9dd}.qubely-field-group-btn .qubely-field-child .qubley-group-button:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.qubely-field-group-btn .qubely-field-child .qubley-group-button.qubley-active-group-btn{color:#2184f9;background:#d2e7ff;border-color:#a9d0ff}.qubely-field-group-btn .qubely-field-child .qubley-group-button.qubley-active-group-btn+.qubley-group-button{border-left-color:#a9d0ff} .qubely-input-range{display:flex}.qubely-input-range>*:not(:last-child){margin-right:8px}.qubely-input-range input[type="number"]{width:55px !important}.qubely-input-range input[type=range]{-webkit-appearance:none;margin-top:7px;margin-bottom:7px;width:100%;padding-left:0;padding-right:0;background:transparent}.qubely-input-range input[type=range]::-webkit-slider-runnable-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:#E5E7EA;border-radius:5px;border:0px solid #000}.qubely-input-range input[type=range]::-webkit-slider-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer;-webkit-appearance:none;margin-top:-5px}.qubely-input-range input[type=range]:focus::-webkit-slider-runnable-track{background:#f3f4f5}.qubely-input-range input[type=range]::-moz-range-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:#E5E7EA;border-radius:5px;border:0px solid #000}.qubely-input-range input[type=range]::-moz-range-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer}.qubely-input-range input[type=range]::-ms-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:transparent;border-color:transparent;border-width:14px 0;color:transparent}.qubely-input-range input[type=range]::-ms-fill-lower{background:#d7dadf;border:0px solid #000;border-radius:10px}.qubely-input-range input[type=range]::-ms-fill-upper{background:#E5E7EA;border:0px solid #000;border-radius:10px}.qubely-input-range input[type=range]::-ms-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer}.qubely-input-range input[type=range]:focus::-ms-fill-lower{background:#E5E7EA}.qubely-input-range input[type=range]:focus::-ms-fill-upper{background:#f3f4f5}.qubely-field-range>label{width:100%;margin-bottom:2px} .qubely-field.qubely-field-toggle{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.qubely-field.qubely-field-toggle>label{width:auto;margin-bottom:0}.qubely-field.qubely-field-toggle .components-toggle-control,.qubely-field.qubely-field-toggle .components-toggle-control .components-base-control__field{margin-bottom:0 !important}.qubely-field.qubely-field-toggle .components-toggle-control .components-base-control__field .components-form-toggle{margin-right:0}.qubely-field.qubely-field-toggle .components-form-toggle.is-checked .components-form-toggle__track{background-color:#2184F9} .qubely-field-font-family{min-width:calc(65% - 15px)}.qubely-field-font-weight{min-width:35%}.qubely-font-family-picker,.qubely-font-weight-picker-wrapper{-webkit-box-align:center;align-items:center;background-color:#fff;cursor:default;display:flex;flex-wrap:wrap;-webkit-box-pack:justify;justify-content:space-between;position:relative;box-sizing:border-box;border-color:#ccc;border-radius:4px;border-style:solid;border-width:1px;transition:all 100ms ease 0s;outline:0px !important;line-height:28px;height:30px;vertical-align:middle;padding-left:5px}.qubely-font-family-picker .editor-rich-text,.qubely-font-weight-picker-wrapper .editor-rich-text{width:100%}.qubely-font-family-picker .selected-font-family,.qubely-font-weight-picker-wrapper .selected-font-family{color:black}.qubely-font-family-option-wrapper{top:100%;background-color:#fff;box-shadow:rgba(0,0,0,0.1) 0px 0px 0px 1px,rgba(0,0,0,0.1) 0px 4px 11px;margin-bottom:8px;margin-top:8px;position:absolute;width:70%;box-sizing:border-box;border-radius:4px;z-index:100}.qubely-font-weight-wrapper{top:100%;left:62%;background-color:#fff;box-shadow:rgba(0,0,0,0.1) 0px 0px 0px 1px,rgba(0,0,0,0.1) 0px 4px 11px;margin-bottom:8px;margin-top:8px;position:absolute;width:40%;box-sizing:border-box;border-radius:4px;z-index:100}.qubely-font-family-options,.qubely-font-family-weights{max-height:270px;overflow-y:auto;padding-bottom:4px;padding-top:4px;position:relative;box-sizing:border-box}.qubely-active-font-family,.qubely-active-font-weight{background-color:#2684ff;color:#fff;cursor:default;display:block;font-size:inherit;width:100%;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing:border-box;padding:8px 12px}.qubely-font-family-option,.qubely-font-weight-option{background-color:transparent;color:inherit;cursor:default;display:block;font-size:inherit;width:100%;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing:border-box;padding:8px 12px}.qubely-font-family-option:hover,.qubely-font-weight-option:hover{background-color:#e8eaeb}.qubely-font-family-search-wrapper{display:flex;justify-content:space-between;width:100%}.qubely-font-family-search-wrapper input.qubely-font-family-search{border:none !important;background-color:transparent !important}.qubely-font-family-search-wrapper input.qubely-font-family-search:focus,.qubely-font-family-search-wrapper input.qubely-font-family-search:active,.qubely-font-family-search-wrapper input.qubely-font-family-search:hover{outline:0;box-shadow:none}.qubely-font-weight-picker-wrapper{display:flex;justify-content:space-between;width:100%}.qubely-font-select-icon{display:flex;align-items:center;padding-right:3px}.qubely-field.qubely-field-typography .components-dropdown.qubely-field{width:100%} includes/core/widgets/block/assets/dist/lms-offer-button.js000064400000554102147600245720020066 0ustar00!function(e,t){for(var n in t)e[n]=t[n]}(this,function(e){var t={};function n(i){if(t[i])return t[i].exports;var a=t[i]={i:i,l:!1,exports:{}};return e[i].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(i,a,function(t){return e[t]}.bind(null,a));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=51)}([function(e,t){e.exports=function(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){function n(t){return"function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?(e.exports=n=function(e){return typeof e},e.exports.default=e.exports,e.exports.__esModule=!0):(e.exports=n=function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.default=e.exports,e.exports.__esModule=!0),n(t)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){"use strict";n.d(t,"a",(function(){return M}));var i,a,r,o,s=n(23),l=n.n(s);function c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}n(18),l()(console.error),i={"(":9,"!":8,"*":7,"/":7,"%":7,"+":6,"-":6,"<":5,"<=":5,">":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},a=["(","?"],r={")":["("],":":["?","?:"]},o=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/;var f={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,n){if(e)throw t;return n}};var p={contextDelimiter:"",onMissingKey:null};function u(e,t){var n;for(n in this.data=e,this.pluralForms={},this.options={},p)this.options[n]=void 0!==t&&n in t?t[n]:p[n]}function d(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function v(e){for(var t=1;t=0||i[l]3&&void 0!==arguments[3]?arguments[3]:10,o=e[t];if(y(n)&&g(i))if("function"==typeof a)if("number"==typeof r){var s={callback:a,priority:r,namespace:i};if(o[n]){var l,c=o[n].handlers;for(l=c.length;l>0&&!(r>=c[l-1].priority);l--);l===c.length?c[l]=s:c.splice(l,0,s),o.__current.forEach((function(e){e.name===n&&e.currentIndex>=l&&e.currentIndex++}))}else o[n]={handlers:[s],runs:0};"hookAdded"!==n&&e.doAction("hookAdded",n,i,a,r)}else console.error("If specified, the hook priority must be a number.");else console.error("The hook callback must be a function.")}},b=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(i,a){var r=e[t];if(y(i)&&(n||g(a))){if(!r[i])return 0;var o=0;if(n)o=r[i].handlers.length,r[i]={runs:r[i].runs,handlers:[]};else for(var s=r[i].handlers,l=function(e){s[e].namespace===a&&(s.splice(e,1),o++,r.__current.forEach((function(t){t.name===i&&t.currentIndex>=e&&t.currentIndex--})))},c=s.length-1;c>=0;c--)l(c);return"hookRemoved"!==i&&e.doAction("hookRemoved",i,a),o}}},x=function(e,t){return function(n,i){var a=e[t];return void 0!==i?n in a&&a[n].handlers.some((function(e){return e.namespace===i})):n in a}},S=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(i){var a=e[t];a[i]||(a[i]={handlers:[],runs:0}),a[i].runs++;for(var r=a[i].handlers,o=arguments.length,s=new Array(o>1?o-1:0),l=1;l1&&void 0!==arguments[1]?arguments[1]:"default";i.data[t]=v(v(v({},m),i.data[t]),e),i.data[t][""]=v(v({},m[""]),i.data[t][""])},s=function(e,t){o(e,t),r()},l=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,n=arguments.length>2?arguments[2]:void 0,a=arguments.length>3?arguments[3]:void 0,r=arguments.length>4?arguments[4]:void 0;return i.data[e]||o(void 0,e),i.dcnpgettext(e,t,n,a,r)},c=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return e},_x=function(e,t,i){var a=l(i,t,e);return n?(a=n.applyFilters("i18n.gettext_with_context",a,e,t,i),n.applyFilters("i18n.gettext_with_context_"+c(i),a,e,t,i)):a};if(n){var f=function(e){h.test(e)&&r()};n.addAction("hookAdded","core/i18n",f),n.addAction("hookRemoved","core/i18n",f)}return{getLocaleData:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return i.data[e]},setLocaleData:s,resetLocaleData:function(e,t){i.data={},i.pluralForms={},s(e,t)},subscribe:function(e){return a.add(e),function(){return a.delete(e)}},__:function(e,t){var i=l(t,void 0,e);return n?(i=n.applyFilters("i18n.gettext",i,e,t),n.applyFilters("i18n.gettext_"+c(t),i,e,t)):i},_x:_x,_n:function(e,t,i,a){var r=l(a,void 0,e,t,i);return n?(r=n.applyFilters("i18n.ngettext",r,e,t,i,a),n.applyFilters("i18n.ngettext_"+c(a),r,e,t,i,a)):r},_nx:function(e,t,i,a,r){var o=l(r,a,e,t,i);return n?(o=n.applyFilters("i18n.ngettext_with_context",o,e,t,i,a,r),n.applyFilters("i18n.ngettext_with_context_"+c(r),o,e,t,i,a,r)):o},isRTL:function(){return"rtl"===_x("ltr","text direction")},hasTranslation:function(e,t,a){var r,o,s=t?t+""+e:e,l=!(null===(r=i.data)||void 0===r||null===(o=r[null!=a?a:"default"])||void 0===o||!o[s]);return n&&(l=n.applyFilters("i18n.has_translation",l,e,t,a),l=n.applyFilters("i18n.has_translation_"+c(a),l,e,t,a)),l}}}(0,0,O)),M=(_.getLocaleData.bind(_),_.setLocaleData.bind(_),_.resetLocaleData.bind(_),_.subscribe.bind(_),_.__.bind(_));_._x.bind(_),_._n.bind(_),_._nx.bind(_),_.isRTL.bind(_),_.hasTranslation.bind(_)},function(e,t,n){e.exports=n(36)},function(e,t){e.exports=function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){function n(t){return e.exports=n=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},e.exports.default=e.exports,e.exports.__esModule=!0,n(t)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){function n(){return e.exports=n=Object.assign||function(e){for(var t=1;t0&&(c=JSON.parse(JSON.stringify(o))),r&&!c.hasOwnProperty("unit")&&(c.unit="px"),"unit"===t&&l?c.unit=e:(c=l?Object.assign(c,o,S()({},window.qubelyDevice,e)):e,c=i?ca?a:c:c>1e3?1e3:c),s(c),this.setState({current:c})}},{key:"_minMax",value:function(e){var t=this._filterValue("unit");return this.props[e]&&0!=this.props[e]?"em"==t?Math.round(this.props[e]/16):this.props[e]:0}},{key:"_steps",value:function(){return"em"==this._filterValue("unit")?.001:this.props.step||1}},{key:"updateDevice",value:function(e){var t=this.props,n=t.value,i=t.onChange;void 0!==t.device&&i(P(P({},n),{},{device:e})),this.setState({device:e})}},{key:"render",value:function(){var e=this,t=this.props,n=t.unit,i=t.label,r=t.responsive,o=t.device,s=t.onDeviceChange,l=t.disabled,c=void 0!==l&&l,f=r?o||this.state.device:window.qubelyDevice;return wp.element.createElement("div",{className:"qubely-field-range qubely-field "+(r?"qubely-responsive":"")},(i||n||r)&&wp.element.createElement("div",{className:"qubely-d-flex qubely-align-center qubely-mb-10"},i&&wp.element.createElement("div",null,wp.element.createElement("label",{htmlFor:"input"},i)),r&&wp.element.createElement(j,{device:f,commonResponsiveDevice:o,className:"qubely-ml-10",onChange:function(t){o&&s?s(t):e.updateDevice(t)}}),n&&wp.element.createElement("div",{className:"qubely-unit-btn-group qubely-ml-auto"},("object"==O()(n)?n:["px","em","%"]).map((function(t){return wp.element.createElement("button",{className:e.props.value&&t==e.props.value.unit?"active":"",onClick:function(){e.setSettings(t,"unit")}},t)})))),wp.element.createElement("div",{className:"qubely-field-child"},wp.element.createElement("div",{className:"qubely-input-range"},wp.element.createElement("input",{type:"range",min:this._minMax("min"),max:this._minMax("max"),value:this._filterValue(),step:this._steps(),disabled:c,onChange:function(t){return e.setSettings(e._filterValue()==t.target.value?"":t.target.value,"range")}}),wp.element.createElement("input",a()({type:"number",step:this._steps(),onChange:function(t){return e.setSettings(t.target.value,"range")},value:this._filterValue()+(this.props.suffix?this.props.suffix:""),disabled:c},this.props.suffix&&{disabled:!0})))))}}]),r}(wp.element.Component);n(39);var A=wp.element,B=A.Component,R=A.Fragment,F=wp.components.ToggleControl,D=function(e){h()(a,e);var t,n,i=(t=a,n=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,i=b()(t);if(n){var a=b()(this).constructor;e=Reflect.construct(i,arguments,a)}else e=i.apply(this,arguments);return y()(this,e)});function a(e){var t;return f()(this,a),(t=i.call(this,e)).state={current:t._filterValue()},t}return u()(a,[{key:"_filterValue",value:function(){return this.props.value?this.props.responsive?this.props.value[window.qubelyDevice]||"":this.props.value:""}},{key:"setSettings",value:function(e){var t=this.props,n=t.value,i=t.responsive;(0,t.onChange)(i?Object.assign({},n,S()({},window.qubelyDevice,e)):e),this.setState({current:e})}},{key:"render",value:function(){var e=this,t=this.props,n=t.label,i=t.customClassName,a=t.responsive,r=t.device,o=t.onDeviceChange;return wp.element.createElement("div",{className:"qubely-field-toggle qubely-field"+(this.props.responsive?" qubely-responsive":"")+(i?" ".concat(i):"")},wp.element.createElement("label",null,n&&n,a&&wp.element.createElement(R,null,r?wp.element.createElement(j,{device:r,commonResponsiveDevice:r,className:"qubely-ml-10",onChange:function(e){return o(e)}}):wp.element.createElement(j,{onChange:function(t){return e.setState({current:t})}}))),wp.element.createElement(F,{checked:this._filterValue(),onChange:function(t){return e.setSettings(t)}}))}}]),a}(B),L=n(10),z=n.n(L),N=n(19),I=n.n(N),G=n(7),Y=n.n(G);function H(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function X(e){for(var t=1;t0){var i,a=this.findArrayIndex(e);t.splice(a,1),(i=t).unshift.apply(i,o()(n))}else{var r;(r=t).unshift.apply(r,o()(n)),t.length>10&&t.pop()}else t=o()(n);localStorage.setItem("qubelyFonts",JSON.stringify(t))}},{key:"render",value:function(){var e=this,t=this.props,n=t.value,i=t.label,a=t.device,r=(t.globalSource,t.globalSettings),s=t.onDeviceChange,l=t.globalTypoOptions,c=t.globalTypoValues,f=this.state,p=f.filterText,u=f.showFontFamiles,d=f.showFontWeights,v=JSON.parse(localStorage.getItem("qubelyFonts")),m=[],h=Z;return v&&(m=Z.filter((function(e){return!v.filter((function(t){return t.n==e.n||"Default"==e.n})).length>0})),h=[{n:"Default",f:"default",v:[]}].concat(o()(v),o()(m))),p.length>=2&&(h=h.filter((function(e){return-1!==e.n.toLowerCase().search(p.toLowerCase())}))),wp.element.createElement("div",{className:"qubely-field qubely-field-typography"},!r&&wp.element.createElement(D,{value:n.openTypography,label:i||te("Typography"),onChange:function(t){return e.setSettings("openTypography",t)}}),(n&&1==n.openTypography||r)&&wp.element.createElement(ae,null,!r&&wp.element.createElement(U,{label:te("Source"),options:[[te("Global"),"global"],[te("Custom"),"custom"]],value:void 0!==n.activeSource?n.activeSource:"custom",onChange:function(t){if("custom"===t)void 0===n.globalSource||void 0===n.activeSource?e.props.onChange($($({},n),{},{activeSource:t})):void 0!==n.globalSource&&"none"!==n.globalSource?e.props.onChange($($({},c[n.globalSource-1]),{},{activeSource:t,globalSource:n.globalSource,blockDefaultValues:n.blockDefaultValues})):void 0!==n.globalSource&&"none"===n.globalSource&&e.props.onChange($($({openTypography:!0,globalSource:"none",activeSource:"custom"},n.blockDefaultValues),{},{blockDefaultValues:n.blockDefaultValues}));else{var i,a={openTypography:!0,activeSource:"global",globalSource:void 0===n.globalSource?"none":n.globalSource,blockDefaultValues:n.blockDefaultValues};void 0!==n.activeSource&&"custom"!==n.activeSource||"none"!==n.globalSource&&void 0!==n.globalSource||(delete(i=JSON.parse(JSON.stringify(n))).activeSource,delete i.globalSource,delete i.blockDefaultValues,a.blockDefaultValues=i),e.props.onChange(a)}}}),"global"!==n.activeSource||r?wp.element.createElement(ae,null,wp.element.createElement(T,{unit:!0,step:1,min:8,max:200,responsive:!0,device:a,label:te("Font Size"),value:n&&n.size,onChange:function(t){return e.setSettings("size",t)},onDeviceChange:function(e){return s(e)}}),wp.element.createElement("div",{className:"qubely-field-group qubely-65-35"},wp.element.createElement("div",{className:"qubely-field qubely-field-font-family"},wp.element.createElement("label",null,te("Font Family")),wp.element.createElement("div",{className:"qubely-font-family-picker",ref:"qubelySelectedFontFamily",onClick:function(){e.setState({showFontFamiles:!u})}},wp.element.createElement("span",{className:"qubely-font-family-search-wrapper"},wp.element.createElement("input",{type:"text",className:"qubely-font-family-search".concat(u?"":" selected-font-family"),placeholder:te(u?"Search":n&&n.family||"Select"),value:p,onChange:function(t){return e.setState({filterText:t.target.value})}}),wp.element.createElement("span",{className:"qubely-font-select-icon"}," ",u?I.a.arrow_up:I.a.arrow_down," ")))),u&&wp.element.createElement("div",{className:"qubely-font-family-option-wrapper",ref:"qubelyFontFamilyWrapper"},wp.element.createElement("div",{className:"qubely-font-family-options"},h.length>0?h.map((function(t,i){var a=!1;n&&t.n==n.family&&(a=!0);var r=z()(S()({},"qubely-font-family-option",!a),S()({},"qubely-active-font-family",a));return wp.element.createElement("div",{className:r,id:"qubely-font-family-".concat(i),onClick:function(){e.setState({showFontFamiles:!1,filterText:""}),"Default"==t.n?e.setSettings("family","default"):e.handleTypographyChange(t.n)}},t.n)})):wp.element.createElement("div",{className:"qubely-font-family-option no-match",onClick:function(){return e.setState({showFontFamiles:!1,filterText:""})}}," No matched font "))),wp.element.createElement("div",{className:"qubely-field qubely-field-font-weight"},wp.element.createElement("label",null,te("Weight")),wp.element.createElement("div",{className:"qubely-font-weight-picker-wrapper",ref:"qubelySelectedFontWeight",onClick:function(){return e.setState({showFontWeights:!d})}},wp.element.createElement("div",{className:"qubely-font-weight-picker"}," ",n&&n.weight||"Select"," "),wp.element.createElement("span",{className:"qubely-font-select-icon"}," ",d?I.a.arrow_up:I.a.arrow_down," "))),d&&wp.element.createElement("div",{className:"qubely-font-weight-wrapper",ref:"qubelyFontWeightWrapper"},wp.element.createElement("div",{className:"qubely-font-family-weights"},["Default"].concat(o()(this._getWeight())).map((function(t){return wp.element.createElement("div",{className:"".concat(t==n.weight?"qubely-active-font-weight":"qubely-font-weight-option"),onClick:function(){e.setState({showFontWeights:!1}),e.setSettings("weight",t)}},t)}))))),wp.element.createElement(oe,{className:"qubely-field",renderToggle:function(e){var t=e.isOpen,n=e.onToggle;return wp.element.createElement("div",{className:"qubely-d-flex qubely-align-center"},wp.element.createElement("label",null,te("Advanced Typography")),wp.element.createElement("div",{className:"qubely-field-button-list qubely-ml-auto"},wp.element.createElement("button",{className:(1==t?"active":"")+" qubely-button qubely-button-rounded",onClick:n,"aria-expanded":t},wp.element.createElement("i",{className:"fas fa-cog"}))))},renderContent:function(){return wp.element.createElement("div",{style:{padding:"15px"}},!e.props.disableLineHeight&&wp.element.createElement(T,{label:te("Line Height"),value:n&&n.height,onChange:function(t){return e.setSettings("height",t)},min:8,max:200,step:1,unit:!0,responsive:!0,device:a,onDeviceChange:function(e){return s(e)}}),wp.element.createElement(T,{label:te("Letter Spacing"),value:n&&n.spacing,onChange:function(t){return e.setSettings("spacing",t)},min:-10,max:30,step:1,unit:!0,responsive:!0,device:a,onDeviceChange:function(e){return s(e)}}),wp.element.createElement("div",{className:"qubely-field qubely-d-flex qubely-align-center"},wp.element.createElement("div",null,te("Text Transform")),wp.element.createElement("div",{className:"qubely-field-button-list qubely-ml-auto"},["none","capitalize","uppercase","lowercase"].map((function(t,i){return wp.element.createElement(se,{text:t.charAt(0).toUpperCase()+t.slice(1)},wp.element.createElement("button",{className:(n.transform==t?"active":"")+" qubely-button",key:i,onClick:function(){return e.setSettings("transform",t)}},"none"==t&&wp.element.createElement("i",{className:"fas fa-ban"}),"capitalize"==t&&wp.element.createElement("span",null,"Aa"),"uppercase"==t&&wp.element.createElement("span",null,"AA"),"lowercase"==t&&wp.element.createElement("span",null,"aa")))})))))}})):wp.element.createElement(le,{label:"Size",value:void 0!==n.globalSource?n.globalSource:"none",options:l,onChange:function(t){if("none"===t&&"none"!==n.globalSource){var i={openTypography:!0,activeSource:"global",globalSource:t,blockDefaultValues:n.blockDefaultValues};e.props.onChange(i)}else e.setSettings("globalSource",t)}})))}}]),i}(ie);t.a=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return ce((function(t){return function(n){h()(r,n);var i=ee(r);function r(){var t;return f()(this,r),t=i.apply(this,arguments),S()(v()(t),"getGlobalSettings",l()(k.a.mark((function e(){var n,i,a,r;return k.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,JSON.parse(localStorage.getItem("qubely-global-settings"));case 2:n=e.sent,i=n.typography,a=[],r=[],void 0!==i&&(i.forEach((function(e,t){var n=e.name,i=e.value;a.push({label:n,value:t+1}),r.push(i)})),t.setState({typography:i,globalTypoOptions:[{label:"Default",value:"none"}].concat(a),globalTypoValues:r}));case 6:case"end":return e.stop()}}),e)})))),t.setState=t.setState.bind(v()(t)),t.state=e,t}return u()(r,[{key:"componentDidMount",value:function(){this.getGlobalSettings()}},{key:"render",value:function(){return wp.element.createElement(t,a()({},this.props,this.state,{setState:this.setState}))}}]),r}(ie)}),"withGLobalTypography")}()(fe)},function(e,t,n){var i=n(33),a=n(34),r=n(26),o=n(35);e.exports=function(e){return i(e)||a(e)||r(e)||o()},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var i;!function(){"use strict";var a={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function r(e){return s(c(e),arguments)}function o(e,t){return r.apply(null,[e].concat(t||[]))}function s(e,t){var n,i,o,s,l,c,f,p,u,d=1,v=e.length,m="";for(i=0;i=0),s.type){case"b":n=parseInt(n,10).toString(2);break;case"c":n=String.fromCharCode(parseInt(n,10));break;case"d":case"i":n=parseInt(n,10);break;case"j":n=JSON.stringify(n,null,s.width?parseInt(s.width):0);break;case"e":n=s.precision?parseFloat(n).toExponential(s.precision):parseFloat(n).toExponential();break;case"f":n=s.precision?parseFloat(n).toFixed(s.precision):parseFloat(n);break;case"g":n=s.precision?String(Number(n.toPrecision(s.precision))):parseFloat(n);break;case"o":n=(parseInt(n,10)>>>0).toString(8);break;case"s":n=String(n),n=s.precision?n.substring(0,s.precision):n;break;case"t":n=String(!!n),n=s.precision?n.substring(0,s.precision):n;break;case"T":n=Object.prototype.toString.call(n).slice(8,-1).toLowerCase(),n=s.precision?n.substring(0,s.precision):n;break;case"u":n=parseInt(n,10)>>>0;break;case"v":n=n.valueOf(),n=s.precision?n.substring(0,s.precision):n;break;case"x":n=(parseInt(n,10)>>>0).toString(16);break;case"X":n=(parseInt(n,10)>>>0).toString(16).toUpperCase()}a.json.test(s.type)?m+=n:(!a.number.test(s.type)||p&&!s.sign?u="":(u=p?"+":"-",n=n.toString().replace(a.sign,"")),c=s.pad_char?"0"===s.pad_char?"0":s.pad_char.charAt(1):" ",f=s.width-(u+n).length,l=s.width&&f>0?c.repeat(f):"",m+=s.align?u+n+l:"0"===c?u+l+n:l+u+n)}return m}var l=Object.create(null);function c(e){if(l[e])return l[e];for(var t,n=e,i=[],r=0;n;){if(null!==(t=a.text.exec(n)))i.push(t[0]);else if(null!==(t=a.modulo.exec(n)))i.push("%");else{if(null===(t=a.placeholder.exec(n)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){r|=1;var o=[],s=t[2],c=[];if(null===(c=a.key.exec(s)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(o.push(c[1]);""!==(s=s.substring(c[0].length));)if(null!==(c=a.key_access.exec(s)))o.push(c[1]);else{if(null===(c=a.index_access.exec(s)))throw new SyntaxError("[sprintf] failed to parse named argument key");o.push(c[1])}t[2]=o}else r|=2;if(3===r)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");i.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}n=n.substring(t[0].length)}return l[e]=i}t.sprintf=r,t.vsprintf=o,"undefined"!=typeof window&&(window.sprintf=r,window.vsprintf=o,void 0===(i=function(){return{sprintf:r,vsprintf:o}}.call(t,n,t,e))||(e.exports=i))}()},function(e,t){},function(e,t){function n(t,i){return e.exports=n=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},e.exports.default=e.exports,e.exports.__esModule=!0,n(t,i)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var i,a;void 0===(a="function"==typeof(i=function(e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var t=Object.assign||function(e){for(var t=1;t4&&void 0!==s[4]&&s[4],o=JSON.stringify(n.interaction),e.prev=2,e.next=5,wp.apiFetch({path:p,method:"POST",data:{block_css:n.css,interaction:o,post_id:t,is_remain:i,available_blocks:a,isPreviewing:r}});case 5:e.next=10;break;case 7:e.prev=7,e.t0=e.catch(2),console.log("Can't save css:",e.t0);case 10:case"end":return e.stop()}}),e,null,[[2,7]])})));return function(_x,t,n,i){return e.apply(this,arguments)}}(),d="",v={};function m(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return 1==t&&(d="",v={},t=!1),e.map((function(e){var t=e.attributes,n=e.name.split("/");if("wpfunnelspro"===n[0]&&t.uniqueId&&(d+=f(t,n[1],t.uniqueId,!0),void 0!==t.interaction)){var i=t.interaction,a=i.while_scroll_into_view,r=i.mouse_movement;if(void 0!==a&&!0===a.enable){var o=a.action_list;o=o.sort((function(e,t){return e.keyframe-t.keyframe}));var s={blockId:t.uniqueId,enable_mobile:void 0!==a.enable_mobile&&a.enable_mobile,enable_tablet:void 0!==a.enable_tablet&&a.enable_tablet,animation:o},l={x_offset:void 0===a.transform_origin_x?"center":a.transform_origin_x,y_offset:void 0===a.transform_origin_y?"center":a.transform_origin_y};s.origin=l,void 0===v.while_scroll_view?v.while_scroll_view=[s]:v.while_scroll_view.push(s)}if(void 0!==r&&!0===r.enable){var c={blockId:t.uniqueId,enable_mobile:void 0!==a.enable_mobile&&a.enable_mobile,enable_tablet:void 0!==a.enable_tablet&&a.enable_tablet,animation:r};void 0===v.mouse_movement?v.mouse_movement=[c]:v.mouse_movement.push(c)}}e.innerBlocks&&e.innerBlocks.length>0&&m(e.innerBlocks)})),{css:d,interaction:v}}function h(e){var t=!1;return e.forEach((function(e){var n=e.name,i=e.innerBlocks,r=void 0===i?[]:i,o=n.split("/"),s=a()(o,2),l=s[0];s[1],"wpfunnelspro"===l&&(t=!0),!t&&r.length>0&&(t=h(r))})),t}function g(e){e.forEach((function(e){var t;-1!=e.name.indexOf("core/block")&&(t=e.attributes.ref,wp.apiFetch({path:"qubely/v1/qubely_get_content",method:"POST",data:{postId:t}}).then((function(e){if(e.success){var t=m(wp.blocks.parse(e.data),!0);t.css&&wp.apiFetch({path:"qubely/v1/append_qubely_css",method:"POST",data:{css:t.css,post_id:c("core/editor").getCurrentPostId()}}).then((function(e){e.success}))}}))),e.innerBlocks&&e.innerBlocks.length>0&&g(e.innerBlocks)}))}function y(e){var t,n={available_blocks:[],interaction:!1,animation:!1,parallax:!1};return(t=e).length&&t.forEach((function(e){var t=e.name,i=e.attributes,r=(e.innerBlocks,t.split("/")),o=a()(r,2),s=o[0];if(o[1],"wpfunnelspro"===s){if(n.available_blocks.includes(t)||n.available_blocks.push(t),!1===n.interaction&&void 0!==i.interaction){var l=i.interaction,c=l.while_scroll_into_view,f=l.mouse_movement;(void 0!==c&&!0===c.enable||void 0!==f&&!0===f.enable)&&(n.interaction=!0)}!1===n.animation&&void 0!==i.animation&&void 0!==i.animation.animation&&""!==i.animation.animation&&(n.animation=!0)}})),n}var w=function(){var e=o()(l.a.mark((function e(){var t,n,i,a,r,o,s,f,p=arguments;return l.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:t=!(p.length>0&&void 0!==p[0])||p[0],window.bindWpfnlCss=!0,n=c("core/block-editor").getBlocks(),i=h(n),a=c("core/editor"),r=a.getCurrentPostId,o={css:"",interaction:{}},s=m(n,!0),o.interaction=s.interaction,o.css+=s.css,t&&g(n),localStorage.setItem("wpfnlGBCSS",JSON.stringify(o.css)),localStorage.setItem("wpfnlGBInteraction",JSON.stringify(o.interaction)),f=y(n),u(r(),o,i,f,!t),window.bindWpfnlCss=!1;case 15:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();t.a=w},function(e,t,n){e.exports=function(e,t){var n,i,a=0;function r(){var r,o,s=n,l=arguments.length;e:for(;s;){if(s.args.length===arguments.length){for(o=0;o0?p.slice(0,-1):"0",n.md="".concat(r,":").concat(l||"0").concat(e.unit," ").concat(c||"0").concat(e.unit," ").concat(f||"0").concat(e.unit," ").concat(p||"0").concat(e.unit,";")}if(e&&e.sm){var u=t.replace(new RegExp("{{key}}","g"),e.sm).split(":"),d=S()(u,2),v=d[0],m=d[1].split(" "),h=S()(m,4),g=h[0],y=h[1],w=h[2],b=h[3];b=b.length>0?b.slice(0,-1):"0",n.sm="".concat(v,":").concat(g||"0").concat(e.unit," ").concat(y||"0").concat(e.unit," ").concat(w||"0").concat(e.unit," ").concat(b||"0").concat(e.unit,";")}if(e&&e.xs){var x=t.replace(new RegExp("{{key}}","g"),e.xs).split(":"),E=S()(x,2),k=E[0],C=E[1].split(" "),O=S()(C,4),_=O[0],M=O[1],j=O[2],q=O[3];q=q.length>0?q.slice(0,-1):"0",n.xs="".concat(k,":").concat(_||"0").concat(e.unit," ").concat(M||"0").concat(e.unit," ").concat(j||"0").concat(e.unit," ").concat(q||"0").concat(e.unit,";")}return n},z=function(e){if("object"===o()(e.global)||"object"===o()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.radiusType?q(j(k(k({},e.global),{},{unit:e.unit?e.unit:"px"}),"border-radius:{{key}};"),t):q(L(k(k({},e.custom),{},{unit:e.unit?e.unit:"px"}),"border-radius:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.radiusType)n="border-radius:".concat(e.global||"0").concat(e.unit?e.unit:"px"," ");else{var i=e.custom?e.custom.split(" "):["0","0","0","0"],a=e.unit?e.unit:"px";n="border-radius:".concat(i[0]?i[0]:"0").concat(a," ").concat(i[1]?i[1]:"0").concat(a," ").concat(i[2]?i[2]:"0").concat(a," ").concat(i[3]?i[3]:"0").concat(a)}return"{"+n+"}"},N=function(e){if("object"===o()(e.global)||"object"===o()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.paddingType?q(j(k(k({},e.global),{},{unit:e.unit?e.unit:"px"}),"padding:{{key}};"),t):q(L(k(k({},e.custom),{},{unit:e.unit?e.unit:"px"}),"padding:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.paddingType)n="padding:".concat(e.global).concat(e.unit?e.unit:"px"," ");else{var i=e.custom?e.custom.split(" "):["0","0","0","0"],a=e.unit?e.unit:"px";n="padding:".concat(i[0]?i[0]:"0").concat(a," ").concat(i[1]?i[1]:"0").concat(a," ").concat(i[2]?i[2]:"0").concat(a," ").concat(i[3]?i[3]:"0").concat(a)}return"{"+n+"}"},I=function(e){if("object"===o()(e.global)||"object"===o()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.marginType?q(j(k(k({},e.global),{},{unit:e.unit?e.unit:"px"}),"margin:{{key}};"),t):q(L(k(k({},e.custom),{},{unit:e.unit?e.unit:"px"}),"margin:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.marginType)n="margin:".concat(e.global).concat(e.unit?e.unit:"px"," ");else{var i=e.custom?e.custom.split(" "):["0","0","0","0"],a=e.unit?e.unit:"px";n="margin:".concat(i[0]?i[0]:"0").concat(a," ").concat(i[1]?i[1]:"0").concat(a," ").concat(i[2]?i[2]:"0").concat(a," ").concat(i[3]?i[3]:"0").concat(a)}return"{"+n+"}"},G=function(e){var t={md:[],sm:[],xs:[]};return e.values.md&&t.md.push("flex-direction:row-reverse"),e.values.sm&&t.sm.push("flex-direction:column-reverse"),e.values.xs&&t.xs.push("flex-direction:column-reverse"),{md:t.md,sm:t.sm,xs:t.xs}},Y=function(e,t,n){return e.replace(new RegExp(t,"g"),n)},H=function(e){return"object"==o()(e)&&0!=Object.keys(e).length},X=function(e,t){var n="";return t.forEach((function(e){n+=e+";"})),e+"{"+n+"}"},V=function(e,t){var n="";return t.forEach((function(t){n+=e+t})),n},K=function(e,t,n){if(n="object"!=o()(n)?n:W(n).data,"string"==typeof e){if(e){if(n){var i=e;return"boolean"==typeof n?[i]:-1==i.indexOf("{{")&&i.indexOf("{")<0?[i+n]:[Y(i,"{{"+t+"}}",n)]}return[]}return[n]}var a=[];return e&&e.forEach((function(e){a.push(Y(e,"{{"+t+"}}",n))})),a},W=function(e){return e.openTypography?{data:P(e),action:"append"}:e.openBg?{data:(t=e,n="{",n+=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,a=arguments.length>4?arguments[4]:void 0,r=arguments.length>5?arguments[5]:void 0,o=arguments.length>6?arguments[6]:void 0,s=arguments.length>7?arguments[7]:void 0,l=arguments.length>8&&void 0!==arguments[8]?arguments[8]:"local",c=arguments.length>9&&void 0!==arguments[9]?arguments[9]:{},f=o?"background-color:"+o+";":"";return"image"==e?f+=("local"===l?t.hasOwnProperty("url")?"background-image:url("+t.url+");":"":c.hasOwnProperty("url")?"background-image:url("+c.url+");":"")+(n?"background-position:"+n+";":"")+(i?"background-attachment:"+i+";":"")+(a?"background-repeat:"+a+";":"")+(r?"background-size:"+r+";":""):"gradient"==e&&(s&&"linear"==s.type?f+="background-image: linear-gradient("+s.direction+"deg, "+s.color1+" "+s.start+"%,"+s.color2+" "+s.stop+"%);":(void 0===s.radial&&(s.radial="center"),f+="background-image: radial-gradient( circle at "+s.radial+" , "+s.color1+" "+s.start+"%,"+s.color2+" "+s.stop+"%);")),f}(t.bgType,t.bgImage,t.bgimgPosition,t.bgimgAttachment,t.bgimgRepeat,t.bgimgSize,t.bgDefaultColor,t.bgGradient,t.bgimageSource,t.externalImageUrl),n+="}","video"==t.bgType&&t.bgVideoFallback&&t.bgVideoFallback.url&&(n+="background-image: url("+t.bgVideoFallback.url+"); background-position: center; background-repeat: no-repeat; background-size: cover;"),"{}"!=n?n:{}),action:"append"}:e.openBorder?{data:_(e),action:"append"}:e.tableBorder?{data:M(e),action:"append"}:e.openShadow&&e.color?{data:O(e),action:"append"}:e.direction?{data:C(e,"return"),action:"append"}:void 0!==e.top||void 0!==e.left||void 0!==e.right||void 0!==e.bottom?{data:T(e),action:"replace"}:e.openShape?{data:A(e),action:"append"}:e.openColor?{data:B(e),action:"append"}:e.spaceTop||e.spaceBottom?{data:R(e),action:"append"}:e.selectedSize?{data:F(e),action:"append"}:e.openBorderRadius?{data:z(e),action:"append"}:e.openPadding?{data:N(e),action:"append"}:e.openMargin?{data:I(e),action:"append"}:e.openRowReverse?{data:G(e),action:"append"}:e.openTransfrom?{data:D(e),action:"append"}:{data:"",action:"append"};var t,n},J=function(e,t,n){var i=!0;return t.hasOwnProperty("condition")&&t.condition.forEach((function(t){var n=i;if("=="==t.relation||"==="==t.relation)if("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value)i=e[t.key]==t.value;else{var a=!1;t.value.forEach((function(n){e[t.key]==n&&(a=!0)})),a&&(i=!0)}else if("!="==t.relation||"!=="==t.relation)if("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value)i=e[t.key]!=t.value;else{var r=!1;t.value.forEach((function(n){e[t.key]!=n&&(r=!0)})),r&&(i=!0)}0==n&&(i=!1)})),i},U=function(e,t,n,i,a,r){var s=!1,l="",c=[],f=[];if(e[t].md&&(s=!0,l="object"===o()(e[t].md)?W(e[t].md).data:e[t].md+(e[t].unit||""),c.push({md:K(n,t,l)})),e[t].sm&&(s=!0,l="object"==o()(e[t].sm)?W(e[t].sm).data:e[t].sm+(e[t].unit||""),c.push({sm:K(n,t,l)})),e[t].xs&&(s=!0,l="object"==o()(e[t].xs)?W(e[t].xs).data:e[t].xs+(e[t].unit||""),c.push({xs:K(n,t,l)})),!s){var p=W(e[t]);"object"==o()(p.data)?0!=Object.keys(p.data).length&&(p.data.background&&f.push(n+p.data.background),H(p.data.md)&&c.push({md:X(n,p.data.md)}),H(p.data.sm)&&c.push({sm:X(n,p.data.sm)}),H(p.data.xs)&&c.push({xs:X(n,p.data.xs)}),p.data.simple&&f.push(n+p.data.simple),p.data.font&&f.push(p.data.font),p.data.shape&&(p.data.shape.forEach((function(e){f.push(n+e)})),H(p.data.data.md)&&c.push(V(n,p.data.data.md)),H(p.data.data.sm)&&c.push(V(n,p.data.data.sm)),H(p.data.data.xs)&&c.push(V(n,p.data.data.xs)))):p.data&&-1==p.data.indexOf("{{")&&("append"==p.action?f.push(n+p.data):(i(t,void 0,K(n,t,p.data)),f.push(K(n,t,p.data))))}if(c.nonResponsiveCSS=f,r)return c;i(t,"Object",c)},Z=function(e,t,n,i,a,r){var o=[];return"hideTablet"==t&&a?(o.push({sm:K(n,t,e[t])}),!r&&i(t,"Object",o)):"hideMobile"==t&&a?(o.push({xs:K(n,t,e[t])}),!r&&i(t,"Object",o)):void 0!==e[t]&&!r&&i(t,void 0,K(n,t,e[t])),r&&o.length>0?o:r&&void 0!==e[t]?K(n,t,e[t]):void 0},Q=function(e,t,n){var i=arguments.length>3&&void 0!==arguments[3]&&arguments[3],a={},r={};Object.keys(t).forEach((function(s){e[s]&&e[s].hasOwnProperty("style")&&e[s].style.forEach((function(e){var l=e.selector;J(t,e)&&("object"==o()(t[s])?a[s]=U(t,s,l,n,0,!0):r[s]=Z(t,s,l,n,i,!0))}))}));var s={responsiveCSS:a,nonResponsiveCSS:r};n(s)};function $(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function ee(e){for(var t=1;t0&&r.forEach((function(e){n[e]&&("hideTablet"===e&&0!==n[e].length?i+="@media (max-width: 1199px) and (min-width: 992px) {"+n[e][0]+"}":"hideMobile"===e&&0!==n[e].length?i+="@media (max-width: 991px) {"+n[e][0]+"}":"array"==typeof n[e]?i+=n[e].join(" "):i+=n[e])})),s.length>0&&s.forEach((function(e){var n=t[e];Object.keys(n).forEach((function(e){"nonResponsiveCSS"!==e&&"object"===o()(n[e])&&Object.keys(n[e]).length>0?n[e].hasOwnProperty("md")&&void 0!==n[e].md?i+=n[e].md:n[e].hasOwnProperty("sm")&&void 0!==n[e].sm?i+="@media (max-width: 1199px) {"+n[e].sm+"}":n[e].hasOwnProperty("xs")&&void 0!==n[e].xs&&(i+="@media (max-width: 991px) {"+n[e].xs+"}"):"nonResponsiveCSS"===e&&n.nonResponsiveCSS.length>0&&(i+=n.nonResponsiveCSS.join(" "))}))}));var l=(i=i.replace(new RegExp("{{QUBELY}}","g"),".qubely-block-"+a)).match(new RegExp("@import([^;]*);","g"));l&&(i=l+" "+(i=i.replace(new RegExp("@import([^;]*);","g"),"")));var c=window.document;if(null===c.getElementById("wpfnl-block-"+a)){var f=document.createElement("style");f.type="text/css",f.id="wpfnl-block-"+a,f.styleSheet?f.styleSheet.cssText=i:f.innerHTML=i,c.getElementsByTagName("head")[0].appendChild(f)}else c.getElementById("wpfnl-block-"+a).innerHTML=i})),b()(u()(e),"copyAttributes",(function(){var t=e.props,n=t.attributes,i=t.attributes.qubelyStyleAttributes,a=wp.qubelyComponents.HelperFunction.copyToClipboard,r={};i.forEach((function(e){r[e]=n[e]})),a(JSON.stringify(r))})),e.setState=e.setState.bind(u()(e)),e}return f()(s,[{key:"componentDidMount",value:function(){this.saveStyleAttributes()}},{key:"componentDidUpdate",value:function(e,t){var n=this,i=this.props,a=(i.name,i.clientId),r=i.attributes,s=i.attributes.uniqueId,l=this.state,c=l.responsiveCSS,f=l.nonResponsiveCSS,p=!1,u=wp.blocks.getBlockType(this.props.name).attributes,d=Object.keys(ae(e.attributes,r)),v=wp.data.select("core/block-editor"),m=v.getBlock,h=v.getBlocks,g=(v.getBlockRootClientId,v.getBlockIndex,m(a),!1);if(d.length>0&&(-1===d.indexOf("layout")&&-1===d.indexOf("style")&&-1===d.indexOf("recreateStyles")&&-1===d.indexOf("fillType")&&-1===d.indexOf("iconStyle")&&-1===d.indexOf("buttonFillType")&&-1===d.indexOf("tabStyle")&&-1===d.indexOf("separatorStyle")||(p=!0,this.saveStyleAttributes()),!p)){if(-1!==d.indexOf("uniqueId")){var y=window.document.getElementById("qubely-block-"+e.attributes.uniqueId);if(y&&!function t(n){if(0!==n.length){if(e.attributes.uniqueId!==s)for(var i=0;i0&&(t(n[i].innerBlocks),g))break}return g}}(h())){y.id="qubely-block-"+r.uniqueId;var w=y.innerHTML.replace(new RegExp("".concat(e.attributes.uniqueId),"g"),"".concat(r.uniqueId));y.innerHTML=w}else this.saveCSS(c,f)}d=d.filter((function(e){return"uniqueId"!==e}));var x={nonResponsiveCSS:f,responsiveCSS:c};d.length>0&&(d.forEach((function(e){!function(e,t,n,i){var a=arguments.length>4&&void 0!==arguments[4]&&arguments[4];e[i]&&e[i].hasOwnProperty("style")&&e[i].style.forEach((function(e){var r=e.selector;J(t,e)&&("object"==o()(t[i])?U(t,i,r,n,0,!1):Z(t,i,r,n,a,!1))}))}(u,r,(function(e,t,n){return function(e,t,n){x=ee(ee({},x),{},void 0===t?{nonResponsiveCSS:ee(ee({},x.nonResponsiveCSS),b()({},e,n))}:{responsiveCSS:ee(ee({},x.responsiveCSS),b()({},e,ee(ee({},x.responsiveCSS[e]),"simple"===t?{simple:n}:ee({},n))))}),"Object"===t&&r[e].openTypography&&"global"===r[e].activeSource&&"none"===r[e].globalSource&&(x.responsiveCSS[e]={})}(e,t,n)}),e),n.saveCSS(x.responsiveCSS,x.nonResponsiveCSS,"update")})),this.setState({responsiveCSS:x.responsiveCSS,nonResponsiveCSS:x.nonResponsiveCSS}))}}},{key:"render",value:function(){var t=this,n=this.props.attributes.showCopyAttr;return wp.element.createElement(ne,null,n&&wp.element.createElement(se,null,wp.element.createElement(re,{icon:"editor-code",label:"Copy Attributes",onClick:function(){return t.copyAttributes()}})),wp.element.createElement(e,a()({},this.props,this.state,{setState:this.setState})))}}]),s}(ie)}),"withCSSGenerator")}},function(e,t){e.exports=function(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n=0;--a){var r=this.tryEntries[a],o=r.completion;if("root"===r.tryLoc)return i("end");if(r.tryLoc<=this.prev){var s=n.call(r,"catchLoc"),l=n.call(r,"finallyLoc");if(s&&l){if(this.prev=0;--i){var a=this.tryEntries[i];if(a.tryLoc<=this.prev&&n.call(a,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),S(n),f}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var i=n.completion;if("throw"===i.type){var a=i.arg;S(n)}return a}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:k(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),f}},e}(e.exports);try{regeneratorRuntime=i}catch(e){"object"==typeof globalThis?globalThis.regeneratorRuntime=i:Function("r","regeneratorRuntime = r")(i)}},function(e,t){},function(e,t){},function(e,t){},function(e,t){e.exports=function(e){if(Array.isArray(e))return e},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){e.exports=function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var i,a,r=[],_n=!0,o=!1;try{for(n=n.call(e);!(_n=(i=n.next()).done)&&(r.push(i.value),!t||r.length!==t);_n=!0);}catch(e){o=!0,a=e}finally{try{_n||null==n.return||n.return()}finally{if(o)throw a}}return r}},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){e.exports=function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){},function(e,t,n){var i,a,r;a=[e,t,n(21)],void 0===(r="function"==typeof(i=function(e,t,n){"use strict";function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(t,"__esModule",{value:!0});var a=Object.assign||function(e){for(var t=1;t0?u.slice(0,-1):"0",n.md="".concat(r,":").concat(c||"0").concat(e.unit," ").concat(f||"0").concat(e.unit," ").concat(p||"0").concat(e.unit," ").concat(u||"0").concat(e.unit,";")}if(e&&e.sm){var d=t.replace(new RegExp("{{key}}","g"),e.sm).split(":"),v=s()(d,2),m=v[0],h=v[1].split(" "),g=s()(h,4),y=g[0],w=g[1],b=g[2],x=g[3];x=x.length>0?x.slice(0,-1):"0",n.sm="".concat(m,":").concat(y||"0").concat(e.unit," ").concat(w||"0").concat(e.unit," ").concat(b||"0").concat(e.unit," ").concat(x||"0").concat(e.unit,";")}if(e&&e.xs){var S=t.replace(new RegExp("{{key}}","g"),e.xs).split(":"),E=s()(S,2),k=E[0],C=E[1].split(" "),O=s()(C,4),_=O[0],M=O[1],j=O[2],q=O[3];q=q.length>0?q.slice(0,-1):"0",n.xs="".concat(k,":").concat(_||"0").concat(e.unit," ").concat(M||"0").concat(e.unit," ").concat(j||"0").concat(e.unit," ").concat(q||"0").concat(e.unit,";")}return n},j=function(e){if("object"===r()(e.global)||"object"===r()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.radiusType?b(w(p(p({},e.global),{},{unit:e.unit?e.unit:"px"}),"border-radius:{{key}};"),t):b(M(p(p({},e.custom),{},{unit:e.unit?e.unit:"px"}),"border-radius:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.radiusType)n="border-radius:".concat(e.global||"0").concat(e.unit?e.unit:"px"," ");else{var i=e.custom?e.custom.split(" "):["0","0","0","0"],a=e.unit?e.unit:"px";n="border-radius:".concat(i[0]?i[0]:"0").concat(a," ").concat(i[1]?i[1]:"0").concat(a," ").concat(i[2]?i[2]:"0").concat(a," ").concat(i[3]?i[3]:"0").concat(a)}return"{"+n+"}"},q=function(e){if("object"===r()(e.global)||"object"===r()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.paddingType?b(w(p(p({},e.global),{},{unit:e.unit?e.unit:"px"}),"padding:{{key}};"),t):b(M(p(p({},e.custom),{},{unit:e.unit?e.unit:"px"}),"padding:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.paddingType)n="padding:".concat(e.global).concat(e.unit?e.unit:"px"," ");else{var i=e.custom?e.custom.split(" "):["0","0","0","0"],a=e.unit?e.unit:"px";n="padding:".concat(i[0]?i[0]:"0").concat(a," ").concat(i[1]?i[1]:"0").concat(a," ").concat(i[2]?i[2]:"0").concat(a," ").concat(i[3]?i[3]:"0").concat(a)}return"{"+n+"}"},P=function(e){if("object"===r()(e.global)||"object"===r()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.marginType?b(w(p(p({},e.global),{},{unit:e.unit?e.unit:"px"}),"margin:{{key}};"),t):b(M(p(p({},e.custom),{},{unit:e.unit?e.unit:"px"}),"margin:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.marginType)n="margin:".concat(e.global).concat(e.unit?e.unit:"px"," ");else{var i=e.custom?e.custom.split(" "):["0","0","0","0"],a=e.unit?e.unit:"px";n="margin:".concat(i[0]?i[0]:"0").concat(a," ").concat(i[1]?i[1]:"0").concat(a," ").concat(i[2]?i[2]:"0").concat(a," ").concat(i[3]?i[3]:"0").concat(a)}return"{"+n+"}"},T=function(e){var t={md:[],sm:[],xs:[]};return e.values.md&&t.md.push("flex-direction:row-reverse"),e.values.sm&&t.sm.push("flex-direction:column-reverse"),e.values.xs&&t.xs.push("flex-direction:column-reverse"),{md:t.md,sm:t.sm,xs:t.xs}},__=(n(6),n(8),n(14),n(4),n(9),n(11),n(5),n(12),n(3),n(46),n(10),wp.i18n.__),A={},B=window.wpfnl_pro_block_object.plugin+"assets/img/blocks";A.qubely=wp.element.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M15.8 8c0-2.2-.8-4-2.3-5.5-1.5-1.5-3.4-2.3-5.5-2.3s-4 .8-5.5 2.3c-1.5 1.5-2.3 3.3-2.3 5.5s.8 4 2.3 5.5c1.5 1.5 3.3 2.3 5.5 2.3.9 0 1.8-.1 2.6-.4l-2.2-2.3c-.1-.1-.3-.2-.4-.2-1.4 0-2.5-.5-3.4-1.4-1-.9-1.4-2.1-1.4-3.5s.5-2.6 1.4-3.5c.9-.9 2-1.4 3.4-1.4s2.5.5 3.4 1.4c.9.9 1.4 2.1 1.4 3.5 0 .7-.1 1.4-.4 2-.2.5-.8.6-1.2.2-1.1-1.1-2.8-1.2-4-.2l2.5 2.6 2.1 2.2c.9.9 2.4 1 3.4.1l.3-.3-1.3-1.3c-.2-.2-.2-.4 0-.6 1-1.3 1.6-2.9 1.6-4.7z"})),A.solid=wp.element.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"19",height:"2"},wp.element.createElement("switch",null,wp.element.createElement("g",null,wp.element.createElement("path",{d:"M0 0h19v2H0z"})))),A.dot=wp.element.createElement("svg",{id:"Layer_1",xmlns:"http://www.w3.org/2000/svg",width:"18",height:"2"},wp.element.createElement("switch",null,wp.element.createElement("g",null,wp.element.createElement("g",null,wp.element.createElement("g",{transform:"translate(-1378 -121)"},wp.element.createElement("g",{transform:"translate(1229 110)"},wp.element.createElement("g",{transform:"translate(149 11)"},wp.element.createElement("circle",{class:"st0",cx:"1",cy:"1",r:"1"}),wp.element.createElement("circle",{class:"st0",cx:"17",cy:"1",r:"1"}),wp.element.createElement("circle",{class:"st0",cx:"5",cy:"1",r:"1"}),wp.element.createElement("circle",{class:"st0",cx:"13",cy:"1",r:"1"}),wp.element.createElement("circle",{class:"st0",cx:"9",cy:"1",r:"1"})))))))),A.dash=wp.element.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"2"},wp.element.createElement("switch",null,wp.element.createElement("g",null,wp.element.createElement("path",{d:"M18 2h-2V0h2v2zm-4 0h-2V0h2v2zm-4 0H8V0h2v2zM6 2H4V0h2v2zM2 2H0V0h2v2z"})))),A.wave=wp.element.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"21",height:"4"},wp.element.createElement("switch",null,wp.element.createElement("g",null,wp.element.createElement("path",{d:"M8 3.5c-.8 0-1.7-.3-2.5-.9C4 1.5 2.4 1.5.7 2.6c-.2.1-.5.1-.7-.2-.1-.2-.1-.5.2-.7 2-1.3 4-1.3 5.8 0 1.5 1 2.8 1 4.2-.2 1.6-1.4 3.4-1.4 5.3 0 1.5 1.1 3.1 1.2 4.7.1.2-.1.5-.1.7.1.1.2.1.5-.1.7-2 1.3-3.9 1.3-5.8-.1-1.5-1.1-2.9-1.1-4.1 0C9.9 3.1 9 3.5 8 3.5z"})))),A.vertical_top=wp.element.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{transform:"translate(1)",fill:"none"},wp.element.createElement("rect",{class:"qubely-svg-fill",x:"4",y:"4",width:"6",height:"12",rx:"1"}),wp.element.createElement("path",{class:"qubely-svg-stroke",d:"M0 1h14","stroke-width":"2","stroke-linecap":"square"}))),A.vertical_middle=wp.element.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("g",{transform:"translate(1 1)"},wp.element.createElement("rect",{class:"qubely-svg-fill",x:"4",width:"6",height:"14",rx:"1"}),wp.element.createElement("path",{d:"M0 7h2",class:"qubely-svg-stroke","stroke-width":"2","stroke-linecap":"square"})),wp.element.createElement("path",{d:"M13 8h2",class:"qubely-svg-stroke","stroke-width":"2","stroke-linecap":"square"}))),A.vertical_bottom=wp.element.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{transform:"translate(1)",fill:"none"},wp.element.createElement("rect",{class:"qubely-svg-fill",x:"4",width:"6",height:"12",rx:"1"}),wp.element.createElement("path",{d:"M0 15h14",class:"qubely-svg-stroke","stroke-width":"2","stroke-linecap":"square"}))),A.icon_classic=wp.element.createElement("img",{src:"".concat(B,"/icon/classic.svg"),alt:__("Classic")}),A.icon_fill=wp.element.createElement("img",{src:"".concat(B,"/icon/fill.svg"),alt:__("Fill")}),A.icon_line=wp.element.createElement("img",{src:"".concat(B,"/icon/outline.svg"),alt:__("Underline")}),A.btn_fill=wp.element.createElement("img",{src:"".concat(B,"/button/fill.svg"),alt:__("Fill")}),A.btn_outline=wp.element.createElement("img",{src:"".concat(B,"/button/outline.svg"),alt:__("Outline")}),A.pie_fill=wp.element.createElement("img",{src:"".concat(B,"/pieprogress/fill.svg"),alt:__("Fill")}),A.pie_outline=wp.element.createElement("img",{src:"".concat(B,"/pieprogress/outline.svg"),alt:__("Outline")}),A.pie_outline_fill=wp.element.createElement("img",{src:"".concat(B,"/pieprogress/outline-fill.svg"),alt:__("Outline Fill")}),A.corner_square=wp.element.createElement("svg",{width:"12",height:"12",viewBox:"0 0 12 12",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M0 1h10.967v10.763","stroke-width":"2",className:"qubely-svg-stroke",fill:"none"})),A.corner_rounded=wp.element.createElement("svg",{width:"12",height:"12",viewBox:"0 0 12 12",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M0 1h6.967c2.209 0 4 1.791 4 4v6.763","stroke-width":"2",className:"qubely-svg-stroke",fill:"none"})),A.corner_round=wp.element.createElement("svg",{width:"12",height:"12",viewBox:"0 0 12 12",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M0 1h1.967c4.971 0 9 4.029 9 9v1.763","stroke-width":"2",className:"qubely-svg-stroke",fill:"none"})),A.tab_tabs=wp.element.createElement("img",{src:"".concat(B,"/tab/tabs.svg"),alt:__("Tabs")}),A.tab_pills=wp.element.createElement("img",{src:"".concat(B,"/tab/pills.svg"),alt:__("Pills")}),A.tab_underline=wp.element.createElement("img",{src:"".concat(B,"/tab/underline.svg"),alt:__("Underline")}),A.verticaltabs_1=wp.element.createElement("img",{src:"".concat(B,"/verticaltabs/layout-1.svg"),alt:__("Layout 1")}),A.verticaltabs_2=wp.element.createElement("img",{src:"".concat(B,"/verticaltabs/layout-2.svg"),alt:__("Layout 2")}),A.verticaltabs_3=wp.element.createElement("img",{src:"".concat(B,"/verticaltabs/layout-3.svg"),alt:__("Layout 3")}),A.social_normal=wp.element.createElement("img",{src:"".concat(B,"//socialicon/normal.svg"),alt:__("Normal")}),A.social_fill=wp.element.createElement("img",{src:"".concat(B,"//socialicon/fill.svg"),alt:__("Fill")}),A.accordion_fill=wp.element.createElement("img",{src:"".concat(B,"/accordion/fill.svg"),alt:__("Fill")}),A.accordion_classic=wp.element.createElement("img",{src:"".concat(B,"/accordion/classic.svg"),alt:__("Classic")}),A.infobox_1=wp.element.createElement("img",{src:"".concat(B,"/infobox/1.svg"),alt:__("Layout 1")}),A.infobox_2=wp.element.createElement("img",{src:"".concat(B,"/infobox/2.svg"),alt:__("Layout 2")}),A.infobox_3=wp.element.createElement("img",{src:"".concat(B,"/infobox/3.svg"),alt:__("Layout 3")}),A.infobox_4=wp.element.createElement("img",{src:"".concat(B,"/infobox/4.svg"),alt:__("Layout 4")}),A.testimonial_1=wp.element.createElement("img",{src:"".concat(B,"/testimonial/1.svg"),alt:__("Testimonial 1")}),A.testimonial_2=wp.element.createElement("img",{src:"".concat(B,"/testimonial/2.svg"),alt:__("Testimonial 2")}),A.testimonial_3=wp.element.createElement("img",{src:"".concat(B,"/testimonial/3.svg"),alt:__("Testimonial 3")}),A.avatar_left=wp.element.createElement("img",{src:"".concat(B,"/testimonial/avatars/1.svg"),alt:__("Avatar Left")}),A.avatar_right=wp.element.createElement("img",{src:"".concat(B,"/testimonial/avatars/2.svg"),alt:__("Avatar Right")}),A.avatar_top=wp.element.createElement("img",{src:"".concat(B,"/testimonial/avatars/3.svg"),alt:__("Avatar Top")}),A.avatar_bottom=wp.element.createElement("img",{src:"".concat(B,"/testimonial/avatars/4.svg"),alt:__("Avatar Bottom")}),A.team_1=wp.element.createElement("img",{src:"".concat(B,"/team/1.svg"),alt:__("Layout 1")}),A.team_2=wp.element.createElement("img",{src:"".concat(B,"/team/2.svg"),alt:__("Layout 2")}),A.team_3=wp.element.createElement("img",{src:"".concat(B,"/team/3.svg"),alt:__("Layout 3")}),A.list_fill=wp.element.createElement("img",{src:"".concat(B,"/list/1.svg"),alt:__("Fill")}),A.list_classic=wp.element.createElement("img",{src:"".concat(B,"/list/2.svg"),alt:__("Classic")}),A.form_classic=wp.element.createElement("img",{src:"".concat(B,"/form/classic.svg"),alt:__("Classic")}),A.form_material=wp.element.createElement("img",{src:"".concat(B,"/form/material.svg"),alt:__("Material")}),A.videopopup_fill=wp.element.createElement("img",{src:"".concat(B,"/videopopup/fill.svg"),alt:__("Fill")}),A.videopopup_classic=wp.element.createElement("img",{src:"".concat(B,"/videopopup/classic.svg"),alt:__("Classic")}),A.postgrid_1=wp.element.createElement("img",{src:"".concat(B,"/postgrid/1.svg"),alt:__("Layout 1")}),A.postgrid_2=wp.element.createElement("img",{src:"".concat(B,"/postgrid/2.svg"),alt:__("Layout 2")}),A.postgrid_3=wp.element.createElement("img",{src:"".concat(B,"/postgrid/pro1.svg"),alt:__("Layout 3")}),A.postgrid_4=wp.element.createElement("img",{src:"".concat(B,"/postgrid/pro2.svg"),alt:__("Layout 4")}),A.postgrid_5=wp.element.createElement("img",{src:"".concat(B,"/postgrid/pro3.svg"),alt:__("Layout 4")}),A.postgrid_design_1=wp.element.createElement("img",{src:"".concat(B,"/postgrid/11.svg"),alt:__("Design 1")}),A.postgrid_design_2=wp.element.createElement("img",{src:"".concat(B,"/postgrid/12.svg"),alt:__("Design 2")}),A.postgrid_design_3=wp.element.createElement("img",{src:"".concat(B,"/postgrid/13.svg"),alt:__("Design 3")}),A.postgrid_design_4=wp.element.createElement("img",{src:"".concat(B,"/postgrid/14.svg"),alt:__("Design 4")}),A.postgrid_design_5=wp.element.createElement("img",{src:"".concat(B,"/postgrid/15.svg"),alt:__("Design 5")}),A.postgrid_design_6=wp.element.createElement("img",{src:"".concat(B,"/postgrid/16.svg"),alt:__("Design 6")}),A.h1=wp.element.createElement("svg",{width:"17",height:"13",viewBox:"0 0 17 13",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M16.809 13h-1.147v-4.609c0-.55.013-.986.039-1.309l-.276.259c-.109.094-.474.394-1.096.898l-.576-.728 2.1-1.65h.957v7.139z"}))),A.h2=wp.element.createElement("svg",{width:"19",height:"13",viewBox:"0 0 19 13",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M18.278 13h-4.839v-.869l1.841-1.851c.544-.557.904-.951 1.082-1.184.177-.233.307-.452.388-.657.081-.205.122-.425.122-.659 0-.322-.097-.576-.291-.762-.194-.186-.461-.278-.803-.278-.273 0-.538.05-.793.151-.256.101-.551.283-.886.547l-.62-.757c.397-.335.783-.573 1.157-.713s.773-.21 1.196-.21c.664 0 1.196.173 1.597.52.4.347.601.813.601 1.399 0 .322-.058.628-.173.918-.116.29-.293.588-.532.896-.239.308-.637.723-1.194 1.248l-1.24 1.201v.049h3.389v1.011z"}))),A.h3=wp.element.createElement("svg",{width:"19",height:"14",viewBox:"0 0 19 14",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M18.01 7.502c0 .452-.132.829-.396 1.13-.264.301-.635.504-1.113.608v.039c.573.072 1.003.25 1.289.535.286.285.43.663.43 1.135 0 .687-.243 1.217-.728 1.589-.485.373-1.175.559-2.07.559-.791 0-1.458-.129-2.002-.386v-1.021c.303.15.623.265.962.347.339.081.664.122.977.122.553 0 .967-.103 1.24-.308.273-.205.41-.522.41-.952 0-.381-.151-.661-.454-.84-.303-.179-.778-.269-1.426-.269h-.62v-.933h.63c1.139 0 1.709-.394 1.709-1.182 0-.306-.099-.542-.298-.708-.199-.166-.492-.249-.879-.249-.27 0-.531.038-.781.115-.251.076-.547.225-.889.447l-.562-.801c.654-.482 1.414-.723 2.28-.723.719 0 1.281.155 1.685.464.404.309.605.736.605 1.279z"}))),A.h4=wp.element.createElement("svg",{width:"19",height:"13",viewBox:"0 0 19 13",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M18.532 11.442h-.962v1.558h-1.118v-1.558h-3.262v-.884l3.262-4.717h1.118v4.648h.962v.952zm-2.08-.952v-1.792c0-.638.016-1.16.049-1.567h-.039c-.091.215-.234.475-.43.781l-1.772 2.578h2.192z"}))),A.h5=wp.element.createElement("svg",{width:"19",height:"14",viewBox:"0 0 19 14",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M15.861 8.542c.719 0 1.289.19 1.709.571.42.381.63.9.63 1.558 0 .762-.238 1.357-.715 1.785-.477.428-1.155.642-2.034.642-.798 0-1.424-.129-1.88-.386v-1.04c.264.15.566.265.908.347.342.081.659.122.952.122.518 0 .911-.116 1.182-.347.27-.231.405-.57.405-1.016 0-.853-.544-1.279-1.631-1.279-.153 0-.342.015-.566.046-.225.031-.422.066-.591.105l-.513-.303.273-3.486h3.711v1.021h-2.7l-.161 1.768.417-.068c.164-.026.365-.039.603-.039z"}))),A.h6=wp.element.createElement("svg",{width:"19",height:"14",viewBox:"0 0 19 14",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M13.459 9.958c0-2.793 1.138-4.189 3.413-4.189.358 0 .661.028.908.083v.957c-.247-.072-.534-.107-.859-.107-.765 0-1.34.205-1.724.615-.384.41-.592 1.068-.625 1.973h.059c.153-.264.368-.468.645-.613.277-.145.602-.217.977-.217.648 0 1.152.199 1.514.596.361.397.542.936.542 1.616 0 .749-.209 1.34-.627 1.775-.418.435-.989.652-1.711.652-.511 0-.955-.123-1.333-.369s-.668-.604-.872-1.074c-.203-.47-.305-1.036-.305-1.697zm2.49 2.192c.394 0 .697-.127.911-.381.213-.254.32-.617.32-1.089 0-.41-.1-.732-.3-.967-.2-.234-.5-.352-.901-.352-.247 0-.475.053-.684.159-.208.106-.373.251-.493.435s-.181.372-.181.564c0 .459.125.846.374 1.16.249.314.567.471.955.471z"}))),A.p=wp.element.createElement("svg",{width:"20px",height:"20px",viewBox:"0 0 1792 1792",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M1534 189v73q0 29-18.5 61t-42.5 32q-50 0-54 1-26 6-32 31-3 11-3 64v1152q0 25-18 43t-43 18h-108q-25 0-43-18t-18-43v-1218h-143v1218q0 25-17.5 43t-43.5 18h-108q-26 0-43.5-18t-17.5-43v-496q-147-12-245-59-126-58-192-179-64-117-64-259 0-166 88-286 88-118 209-159 111-37 417-37h479q25 0 43 18t18 43z"})),A.span=wp.element.createElement("svg",{width:"20px",height:"20px",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20"},wp.element.createElement("rect",{x:"0",fill:"none",width:"20px",height:"20px"}),wp.element.createElement("g",null,wp.element.createElement("path",{d:"M9 6l-4 4 4 4-1 2-6-6 6-6zm2 8l4-4-4-4 1-2 6 6-6 6z"}))),A.div=wp.element.createElement("svg",{width:"20px",height:"20px",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20"},wp.element.createElement("rect",{x:"0",fill:"none",width:"20px",height:"20px"}),wp.element.createElement("g",null,wp.element.createElement("path",{d:"M9 6l-4 4 4 4-1 2-6-6 6-6zm2 8l4-4-4-4 1-2 6 6-6 6z"}))),A.pricing={1:wp.element.createElement("img",{src:"".concat(B,"/pricing/1.svg"),alt:__("Layout 1")}),2:wp.element.createElement("img",{src:"".concat(B,"/pricing/2.svg"),alt:__("Layout 2")}),3:wp.element.createElement("img",{src:"".concat(B,"/pricing/3.svg"),alt:__("Layout 3")}),4:wp.element.createElement("img",{src:"".concat(B,"/pricing/4.svg"),alt:__("Layout 4")}),5:wp.element.createElement("img",{src:"".concat(B,"/pricing/5.svg"),alt:__("Layout 5")}),6:wp.element.createElement("img",{src:"".concat(B,"/pricing/6.svg"),alt:__("Layout 6")})},A.pricing.badge={1:wp.element.createElement("img",{src:"".concat(B,"/pricing/badges/1.svg"),alt:__("Badge 1")}),2:wp.element.createElement("img",{src:"".concat(B,"/pricing/badges/2.svg"),alt:__("Badge 2")}),3:wp.element.createElement("img",{src:"".concat(B,"/pricing/badges/3.svg"),alt:__("Badge 3")}),4:wp.element.createElement("img",{src:"".concat(B,"/pricing/badges/4.svg"),alt:__("Badge 4")}),5:wp.element.createElement("img",{src:"".concat(B,"/pricing/badges/5.svg"),alt:__("Badge 5")}),6:wp.element.createElement("img",{src:"".concat(B,"/pricing/badges/6.svg"),alt:__("Badge 6")})},A.image={simple:wp.element.createElement("img",{src:"".concat(B,"/image/simple.svg"),alt:__("Simple")}),blurb:wp.element.createElement("img",{src:"".concat(B,"/image/blurb.svg"),alt:__("Blurb")})},A.copy=wp.element.createElement("svg",{width:"20px",height:"15px",viewBox:"0 0 1792 1792",class:"dashicon",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M1696 384q40 0 68 28t28 68v1216q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-288h-544q-40 0-68-28t-28-68v-672q0-40 20-88t48-76l408-408q28-28 76-48t88-20h416q40 0 68 28t28 68v328q68-40 128-40h416zm-544 213l-299 299h299v-299zm-640-384l-299 299h299v-299zm196 647l316-316v-416h-384v416q0 40-28 68t-68 28h-416v640h512v-256q0-40 20-88t48-76zm956 804v-1152h-384v416q0 40-28 68t-68 28h-416v640h896z"})),A.paste=wp.element.createElement("svg",{width:"20px",height:"15px",viewBox:"0 0 1792 1792",class:"dashicon",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"})),A.spacing={top:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"}))),right:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"}))),bottom:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M3 20h16v2h-16z"}))),left:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#2184F9",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"})))},A.border={top:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"}))),right:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"}))),bottom:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M3 20h16v2h-16z"}))),left:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#2184F9",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"})))},A.borderRadius={topLeft:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{d:"M1.88 0c-1.038 0-1.88.842-1.88 1.88v6.71h1.88v-5.77c0-.519.421-.94.94-.94h5.77v-1.88h-6.71z",id:"Path",fill:"#2184F9"}),wp.element.createElement("path",{d:"M13.41 0v1.88h5.77c.519 0 .94.421.94.94v5.77h1.88v-6.71c0-1.038-.842-1.88-1.88-1.88h-6.71zM1.88 13.41h-1.88v6.71c0 1.038.842 1.88 1.88 1.88h6.71v-1.88h-5.77c-.519 0-.94-.421-.94-.94v-5.77zM13.41 20.12v1.88h6.71c1.038 0 1.88-.842 1.88-1.88v-6.71h-1.88v5.77c0 .519-.421.94-.94.94h-5.77z",fill:"#CACCCE"}))),topRight:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{d:"M1.88 0c-1.038 0-1.88.842-1.88 1.88v6.71h1.88v-5.77c0-.519.421-.94.94-.94h5.77v-1.88h-6.71z",id:"Path",fill:"#CACCCE"}),wp.element.createElement("path",{d:"M13.41 0v1.88h5.77c.519 0 .94.421.94.94v5.77h1.88v-6.71c0-1.038-.842-1.88-1.88-1.88h-6.71z",id:"Path",fill:"#2184F9"}),wp.element.createElement("path",{d:"M1.88 13.41h-1.88v6.71c0 1.038.842 1.88 1.88 1.88h6.71v-1.88h-5.77c-.519 0-.94-.421-.94-.94v-5.77zM13.41 20.12v1.88h6.71c1.038 0 1.88-.842 1.88-1.88v-6.71h-1.88v5.77c0 .519-.421.94-.94.94h-5.77z",fill:"#CACCCE"}))),bottomRight:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{d:"M1.88 0c-1.038 0-1.88.842-1.88 1.88v6.71h1.88v-5.77c0-.519.421-.94.94-.94h5.77v-1.88h-6.71zM13.41 0v1.88h5.77c.519 0 .94.421.94.94v5.77h1.88v-6.71c0-1.038-.842-1.88-1.88-1.88h-6.71zM1.88 13.41h-1.88v6.71c0 1.038.842 1.88 1.88 1.88h6.71v-1.88h-5.77c-.519 0-.94-.421-.94-.94v-5.77z",id:"Path",fill:"#CACCCE"}),wp.element.createElement("path",{d:"M13.41 20.12v1.88h6.71c1.038 0 1.88-.842 1.88-1.88v-6.71h-1.88v5.77c0 .519-.421.94-.94.94h-5.77z",fill:"#2184F9"}))),bottomLeft:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{d:"M1.88 0c-1.038 0-1.88.842-1.88 1.88v6.71h1.88v-5.77c0-.519.421-.94.94-.94h5.77v-1.88h-6.71zM13.41 0v1.88h5.77c.519 0 .94.421.94.94v5.77h1.88v-6.71c0-1.038-.842-1.88-1.88-1.88h-6.71z",id:"Path",fill:"#CACCCE"}),wp.element.createElement("path",{d:"M1.88 13.41h-1.88v6.71c0 1.038.842 1.88 1.88 1.88h6.71v-1.88h-5.77c-.519 0-.94-.421-.94-.94v-5.77z",id:"Path",fill:"#2184F9"}),wp.element.createElement("path",{d:"M13.41 20.12v1.88h6.71c1.038 0 1.88-.842 1.88-1.88v-6.71h-1.88v5.77c0 .519-.421.94-.94.94h-5.77z",fill:"#CACCCE"})))},A.inlineColorIcon=wp.element.createElement("svg",{"aria-hidden":"true",role:"img",focusable:"false",class:"dashicon dashicons-editor-textcolor",xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 20 20"},wp.element.createElement("path",{d:"M13.23 15h1.9L11 4H9L5 15h1.88l1.07-3h4.18zm-1.53-4.54H8.51L10 5.6z"})),A.highlighterIcon=wp.element.createElement("svg",{"aria-hidden":"true",role:"img",focusable:"false",class:"dashicon dashicons-admin-customizer",xmlns:"http://www.w3.org/2000/svg",width:"18",height:"17",viewBox:"0 0 20 20"},wp.element.createElement("path",{d:"M18.33 3.57s.27-.8-.31-1.36c-.53-.52-1.22-.24-1.22-.24-.61.3-5.76 3.47-7.67 5.57-.86.96-2.06 3.79-1.09 4.82.92.98 3.96-.17 4.79-1 2.06-2.06 5.21-7.17 5.5-7.79zM1.4 17.65c2.37-1.56 1.46-3.41 3.23-4.64.93-.65 2.22-.62 3.08.29.63.67.8 2.57-.16 3.46-1.57 1.45-4 1.55-6.15.89z"})),A.upperCaseIcon=wp.element.createElement("svg",{viewBox:"0 0 20 20",height:"25",width:"25",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("mask",{id:"a",fill:"#fff"},wp.element.createElement("path",{d:"m20 20h-20v-20h20z",fill:"#fff","fill-rule":"evenodd"})),wp.element.createElement("path",{d:"m2 3v2.5h4.16666667v10.5h2.5v-10.5h4.16666663v-2.5zm16 4.5h-7.5v2.5h2.5v6h2.5v-6h2.5z",mask:"url(#a)"})),A.arrow_down=wp.element.createElement("svg",{width:"18",height:"18",viewBox:"0 0 1792 1792",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z"})),A.arrow_up=wp.element.createElement("svg",{width:"18",height:"18",viewBox:"0 0 1792 1792",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M1395 1184q0 13-10 23l-50 50q-10 10-23 10t-23-10l-393-393-393 393q-10 10-23 10t-23-10l-50-50q-10-10-10-23t10-23l466-466q10-10 23-10t23 10l466 466q10 10 10 23z"})),A.circleThin=wp.element.createElement("svg",(u={version:"1.1",xmlns:"http://www.w3.org/2000/svg"},c()(u,"xmlns","http://www.w3.org/1999/xlink"),c()(u,"width","15"),c()(u,"height","15"),c()(u,"viewBox","0 0 12 14"),u),wp.element.createElement("path",{d:"M6 2q-1.016 0-1.941 0.398t-1.594 1.066-1.066 1.594-0.398 1.941 0.398 1.941 1.066 1.594 1.594 1.066 1.941 0.398 1.941-0.398 1.594-1.066 1.066-1.594 0.398-1.941-0.398-1.941-1.066-1.594-1.594-1.066-1.941-0.398zM12 7q0 1.633-0.805 3.012t-2.184 2.184-3.012 0.805-3.012-0.805-2.184-2.184-0.805-3.012 0.805-3.012 2.184-2.184 3.012-0.805 3.012 0.805 2.184 2.184 0.805 3.012z"})),A.circleDot=wp.element.createElement("svg",(d={version:"1.1",xmlns:"http://www.w3.org/2000/svg"},c()(d,"xmlns","http://www.w3.org/1999/xlink"),c()(d,"width","15"),c()(d,"height","15"),c()(d,"viewBox","0 0 12 14"),c()(d,"fill","#2184f9"),d),wp.element.createElement("path",{d:"M8 7q0 0.828-0.586 1.414t-1.414 0.586-1.414-0.586-0.586-1.414 0.586-1.414 1.414-0.586 1.414 0.586 0.586 1.414zM6 2.75q-1.156 0-2.133 0.57t-1.547 1.547-0.57 2.133 0.57 2.133 1.547 1.547 2.133 0.57 2.133-0.57 1.547-1.547 0.57-2.133-0.57-2.133-1.547-1.547-2.133-0.57zM12 7q0 1.633-0.805 3.012t-2.184 2.184-3.012 0.805-3.012-0.805-2.184-2.184-0.805-3.012 0.805-3.012 2.184-2.184 3.012-0.805 3.012 0.805 2.184 2.184 0.805 3.012z"})),A.ellipsis_v=wp.element.createElement("svg",(v={version:"1.1",xmlns:"http://www.w3.org/2000/svg"},c()(v,"xmlns","http://www.w3.org/1999/xlink"),c()(v,"width","3"),c()(v,"height","15"),c()(v,"viewBox","0 0 3 14"),v),wp.element.createElement("path",{d:"M3 9.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531zM3 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531zM3 1.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531z"})),A.ellipsis_h=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"11",height:"15",viewBox:"0 0 11 14"},wp.element.createElement("path",{d:"M3 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531zM7 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531zM11 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531z"})),A.left=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"13",height:"15",viewBox:"0 0 13 14"},wp.element.createElement("path",{d:"M12 7v1q0 0.414-0.254 0.707t-0.66 0.293h-5.5l2.289 2.297q0.297 0.281 0.297 0.703t-0.297 0.703l-0.586 0.594q-0.289 0.289-0.703 0.289-0.406 0-0.711-0.289l-5.086-5.094q-0.289-0.289-0.289-0.703 0-0.406 0.289-0.711l5.086-5.078q0.297-0.297 0.711-0.297 0.406 0 0.703 0.297l0.586 0.578q0.297 0.297 0.297 0.711t-0.297 0.711l-2.289 2.289h5.5q0.406 0 0.66 0.293t0.254 0.707z"})),A.plus=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"14",height:"30",fill:"#9b9b9b",viewBox:"0 0 11 14"},wp.element.createElement("path",{d:"M11 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-3.25v3.25q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-3.25h-3.25q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h3.25v-3.25q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531v3.25h3.25q0.312 0 0.531 0.219t0.219 0.531z"})),A.plus_circle=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"12",height:"14",viewBox:"0 0 12 14"},wp.element.createElement("path",{d:"M9.5 7.5v-1q0-0.203-0.148-0.352t-0.352-0.148h-2v-2q0-0.203-0.148-0.352t-0.352-0.148h-1q-0.203 0-0.352 0.148t-0.148 0.352v2h-2q-0.203 0-0.352 0.148t-0.148 0.352v1q0 0.203 0.148 0.352t0.352 0.148h2v2q0 0.203 0.148 0.352t0.352 0.148h1q0.203 0 0.352-0.148t0.148-0.352v-2h2q0.203 0 0.352-0.148t0.148-0.352zM12 7q0 1.633-0.805 3.012t-2.184 2.184-3.012 0.805-3.012-0.805-2.184-2.184-0.805-3.012 0.805-3.012 2.184-2.184 3.012-0.805 3.012 0.805 2.184 2.184 0.805 3.012z"})),A.delete=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"13",height:"15",viewBox:"0 0 13 15",fill:"#F7F7F7"},wp.element.createElement("path",{d:"M9.5 7.5v-1q0-0.203-0.148-0.352t-0.352-0.148h-2v-2q0-0.203-0.148-0.352t-0.352-0.148h-1q-0.203 0-0.352 0.148t-0.148 0.352v2h-2q-0.203 0-0.352 0.148t-0.148 0.352v1q0 0.203 0.148 0.352t0.352 0.148h2v2q0 0.203 0.148 0.352t0.352 0.148h1q0.203 0 0.352-0.148t0.148-0.352v-2h2q0.203 0 0.352-0.148t0.148-0.352zM12 7q0 1.633-0.805 3.012t-2.184 2.184-3.012 0.805-3.012-0.805-2.184-2.184-0.805-3.012 0.805-3.012 2.184-2.184 3.012-0.805 3.012 0.805 2.184 2.184 0.805 3.012z"})),A.addColor=wp.element.createElement("svg",{width:"16",height:"16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"#CDCDCD","fill-rule":"evenodd"},wp.element.createElement("rect",{x:"6.957",width:"2.087",height:"16",rx:"1.043"}),wp.element.createElement("path",{d:"M15.652 8a1.09 1.09 0 01-1.09 1.09H1.438a1.09 1.09 0 110-2.18H14.56c.603 0 1.091.488 1.091 1.09z"}))),wp.i18n.__,wp.element.useState;var R=wp.components;R.Tooltip,R.Dropdown,R.ColorPicker,wp.i18n.__,n(27).diff;var F=wp.element,D=(F.Component,F.Fragment,F.createRef,wp.components),L=(D.Tooltip,D.Dropdown,D.PanelBody,D.Notice,D.RangeControl,D.Modal,wp.data.select,wp.editPost);L.PluginSidebar,L.PluginSidebarMoreMenuItem;var z=function(e,t,n){return e.replace(new RegExp(t,"g"),n)},N=function(e){return"object"==r()(e)&&0!=Object.keys(e).length},I=function(e,t){return e.replace(new RegExp("{{QUBELY}}","g"),".qubely-block-"+t)},G=function(e,t){var n="";return t.forEach((function(e){n+=e+";"})),e+"{"+n+"}"},Y=function(e,t){var n="";return t.forEach((function(t){n+=e+t})),n},H=function(e,t,n,i){if(i="object"!=r()(i)?i:X(i).data,"string"==typeof e){if(e){if(i){var a=I(e,t);return"boolean"==typeof i?[a]:-1==a.indexOf("{{")&&a.indexOf("{")<0?[a+i]:[z(a,"{{"+n+"}}",i)]}return[]}return[I(i,t)]}var o=[];return e.forEach((function(e){o.push(z(I(e,t),"{{"+n+"}}",i))})),o},X=function(e){return e.openTypography?{data:x(e),action:"append"}:e.openBg?{data:(t=e,n="{",n+=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,a=arguments.length>4?arguments[4]:void 0,r=arguments.length>5?arguments[5]:void 0,o=arguments.length>6?arguments[6]:void 0,s=arguments.length>7?arguments[7]:void 0,l=arguments.length>8&&void 0!==arguments[8]?arguments[8]:"local",c=arguments.length>9&&void 0!==arguments[9]?arguments[9]:{},f=o?"background-color:"+o+";":"";return"image"==e?f+=("local"===l?t.hasOwnProperty("url")?"background-image:url("+t.url+");":"":c.hasOwnProperty("url")?"background-image:url("+c.url+");":"")+(n?"background-position:"+n+";":"")+(i?"background-attachment:"+i+";":"")+(a?"background-repeat:"+a+";":"")+(r?"background-size:"+r+";":""):"gradient"==e&&(s&&"linear"==s.type?f+="background-image: linear-gradient("+s.direction+"deg, "+s.color1+" "+s.start+"%,"+s.color2+" "+s.stop+"%);":(void 0===s.radial&&(s.radial="center"),f+="background-image: radial-gradient( circle at "+s.radial+" , "+s.color1+" "+s.start+"%,"+s.color2+" "+s.stop+"%);")),f}(t.bgType,t.bgImage,t.bgimgPosition,t.bgimgAttachment,t.bgimgRepeat,t.bgimgSize,t.bgDefaultColor,t.bgGradient,t.bgimageSource,t.externalImageUrl),n+="}","video"==t.bgType&&t.bgVideoFallback&&t.bgVideoFallback.url&&(n+="background-image: url("+t.bgVideoFallback.url+"); background-position: center; background-repeat: no-repeat; background-size: cover;"),"{}"!=n?n:{}),action:"append"}:e.openBorder?{data:g(e),action:"append"}:e.tableBorder?{data:y(e),action:"append"}:e.openShadow&&e.color?{data:h(e),action:"append"}:e.direction?{data:m(e,"return"),action:"append"}:void 0!==e.top||void 0!==e.left||void 0!==e.right||void 0!==e.bottom?{data:S(e),action:"replace"}:e.openShape?{data:E(e),action:"append"}:e.openColor?{data:k(e),action:"append"}:e.spaceTop||e.spaceBottom?{data:C(e),action:"append"}:e.selectedSize?{data:O(e),action:"append"}:e.openBorderRadius?{data:j(e),action:"append"}:e.openPadding?{data:q(e),action:"append"}:e.openMargin?{data:P(e),action:"append"}:e.openRowReverse?{data:T(e),action:"append"}:e.openTransfrom?{data:_(e),action:"append"}:{data:"",action:"append"};var t,n},V=function(e,t,n,i){var a=!0;return t.hasOwnProperty("condition")&&t.condition.forEach((function(t){var n=a;if("=="==t.relation||"==="==t.relation)if("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value)a=e[t.key]==t.value;else{var i=!1;t.value.forEach((function(n){e[t.key]==n&&(i=!0)})),i&&(a=!0)}else if("!="==t.relation||"!=="==t.relation)if("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value)a=e[t.key]!=t.value;else{var r=!1;t.value.forEach((function(n){e[t.key]!=n&&(r=!0)})),r&&(a=!0)}0==n&&(a=!1)})),a},K=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=arguments.length>3&&void 0!==arguments[3]&&arguments[3],a=window.document,r="qubely-block-"+t;if(n&&i&&(r="qubely-global-styles"),null===a.getElementById(r)){var o=document.createElement("style");o.type="text/css",o.id=r,o.styleSheet?o.styleSheet.cssText=e:o.innerHTML=e,a.getElementsByTagName("head")[0].appendChild(o)}else a.getElementById(r).innerHTML=e};n(24),wp.wpfnlProComponents={Typography:i.a,CssGenerator:{CssGenerator:function(e,t,n){var i=arguments.length>3&&void 0!==arguments[3]&&arguments[3],a=arguments.length>4&&void 0!==arguments[4]&&arguments[4],o=arguments.length>5&&void 0!==arguments[5]&&arguments[5],s=arguments.length>6&&void 0!==arguments[6]?arguments[6]:void 0;if(n){var l="",c=[],f=[],p=[],u=[];if(Object.keys(e).forEach((function(o){var l=[];(l=!1===a?"string"==typeof t?wp.blocks.getBlockType("wpfunnelspro/"+t).attributes:t:s)[o]&&l[o].hasOwnProperty("style")&&l[o].style.forEach((function(t,a){var s=t.selector;if(V(e,t))if("object"==r()(e[o])){var l=!1,d="";if(e[o].md&&(l=!0,d="object"==r()(e[o].md)?X(e[o].md).data:e[o].md+(e[o].unit||""),c=c.concat(H(s,n,o,d))),e[o].sm&&(l=!0,d="object"==r()(e[o].sm)?X(e[o].sm).data:e[o].sm+(e[o].unit||""),f=f.concat(H(s,n,o,d))),e[o].xs&&(l=!0,d="object"==r()(e[o].xs)?X(e[o].xs).data:e[o].xs+(e[o].unit||""),p=p.concat(H(s,n,o,d))),!l){var v=X(e[o]),m=I(s,n);"object"==r()(v.data)?0!=Object.keys(v.data).length&&(v.data.background&&u.push(m+v.data.background),N(v.data.md)&&c.push(G(m,v.data.md)),N(v.data.sm)&&f.push(G(m,v.data.sm)),N(v.data.xs)&&p.push(G(m,v.data.xs)),v.data.simple&&u.push(m+v.data.simple),v.data.font&&c.unshift(v.data.font),v.data.shape&&(v.data.shape.forEach((function(e){u.push(m+e)})),N(v.data.data.md)&&c.push(Y(m,v.data.data.md)),N(v.data.data.sm)&&f.push(Y(m,v.data.data.sm)),N(v.data.data.xs)&&p.push(Y(m,v.data.data.xs)))):v.data&&-1==v.data.indexOf("{{")&&("append"==v.action?u.push(m+v.data):u.push(H(s,n,o,v.data)))}}else"hideTablet"==o?i&&(f=f.concat(H(s,n,o,e[o]))):"hideMobile"==o?i&&(p=p.concat(H(s,n,o,e[o]))):e[o]&&(u=u.concat(H(s,n,o,e[o])))}))})),c.length>0&&(l+=c.join("")),f.length>0&&(l+="@media (max-width: 1199px) {"+f.join("")+"}"),p.length>0&&(l+="@media (max-width: 991px) {"+p.join("")+"}"),u.length>0&&(l+=u.join("")),a&&(l=l.replace(new RegExp(".qubely-block-global","g"),o?".qubely-frontend":".qubely-editor")),i)return l;K(l,n,a,o)}},objectReplace:G,objectAppend:Y,singleField:H}}},,,,function(e,t,n){e.exports=n(58)},function(e,t){},function(e,t){},,,,,function(e,t,n){"use strict";n.r(t);var i=n(6),a=n.n(i),r=n(0),o=n.n(r),s=n(2),l=n(10),c=n.n(l),f=n(15),p=n.n(f),u=n(16),d=n(24),v=(wp.element.useState,wp.components),m=v.TextControl,h=v.SelectControl,g=v.RangeControl,y=v.PanelBody,w=v.Panel,b=wp.blockEditor,x=b.InspectorControls,S=(b.ColorPalette,b.withColors),E=b.BlockControls,k=b.AlignmentToolbar,C=b.BlockAlignmentToolbar,O=b.RichText,_=b.PanelColorSettings,M=b.useBlockProps,j=wp.element,q=(j.RawHTML,j.Component,wp.compose),P=(q.withInstanceId,q.compose),T=function(e){var t=e.clientId,n=e.attributes,i=e.className,r=e.buttonColor,l=e.buttonTextColor,f=e.setAttributes,p=n.uniqueId,d=t.substr(0,6);p?p&&p!=d&&f({uniqueId:d}):f({uniqueId:d});var v,b,S,j,q,P,T,A,B,R,F,D,L=function(e){f({offerAction:e}),f("reject"==e?{lmsOfferButtonShortcode:""}:{lmsOfferButtonShortcode:window.wpfnl_pro_block_object.lmsOfferButtonRender})},z=c()(o()({},"wpfnl-block-".concat(p),p),i),N=c()("wpfunnels-block-offer-button");return wp.element.createElement(React.Fragment,null,(S=n.buttonText,n.text_color,j=n.buttonRadius,q=n.paddingTopBottom,P=n.paddingLeftRight,T=n.outline,A=n.offerType,B=n.offerAction,R=n.typography,F=n.lmsTypography,D=n.device,wp.element.createElement(x,{key:"wpfnl-next-step-button-inspector-controls"},wp.element.createElement(w,null,wp.element.createElement(y,{title:"Settings",initialOpen:!0},wp.element.createElement(h,{label:Object(s.a)("Select button type","wpfnl-pro"),value:A,className:"wpfunnel-texteditor",onChange:function(e){return f({offerType:e})},options:[{value:"upsell",label:"Upsell"},{value:"downsell",label:"Downsell"}]}),wp.element.createElement(h,{label:Object(s.a)("Select button action","wpfnl-pro"),className:"wpfunnel-texteditor",value:B,onChange:function(e){return L(e)},options:[{value:"accept",label:"Accept"},{value:"reject",label:"Reject"}]}),"reject"==n.offerAction&&wp.element.createElement(m,{label:Object(s.a)("Button Text","wpfnl-pro"),format:"string",onChange:function(e){return f({buttonText:e})},value:S}))),wp.element.createElement(_,{title:Object(s.a)("Style","wpfnl-pro"),colorSettings:[{value:r.color,onChange:function(e){var t;f(null==(t=e)?{buttonColor:"transparent"}:{buttonColor:t})},label:Object(s.a)("Button Background color","wpfnl-pro")},{value:l.color,onChange:function(e){var t;f(null==(t=e)?{buttonTextColor:"#28303d"}:{buttonTextColor:t})},label:Object(s.a)("Button Text color","wpfnl-pro")}]},wp.element.createElement(h,{label:Object(s.a)("Outline Style","wpfnl-pro"),value:T,onChange:function(e){return f({outline:e})},options:[{value:"fill",label:"Fill"},{value:"outline",label:"Outline"}]}),wp.element.createElement(g,{label:Object(s.a)("Radius","wpfnl-pro"),value:j,onChange:function(e){return f({buttonRadius:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement("hr",{className:"wpfnl-gutenberg-editor-separator"}),wp.element.createElement("h2",null,Object(s.a)("Padding","wpfnl-pro")),wp.element.createElement(g,{label:Object(s.a)("Padding Top & Bottom","wpfnl-pro"),value:q,onChange:function(e){return f({paddingTopBottom:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(g,{label:Object(s.a)("Padding Left & Right","wpfnl-pro"),value:P,onChange:function(e){return f({paddingLeftRight:e})},allowReset:!0,min:0,max:100,step:1})),"accept"==n.offerAction&&wp.element.createElement(y,{title:Object(s.a)("Typography"),initialOpen:!1},wp.element.createElement(u.a,{label:Object(s.a)("Typography"),value:F,onChange:function(e){return f({lmsTypography:e})},disableLineHeight:!0,device:D,onDeviceChange:function(e){return f({device:e})}})),"reject"==n.offerAction&&wp.element.createElement(y,{title:Object(s.a)("Typography"),initialOpen:!1},wp.element.createElement(u.a,{label:Object(s.a)("Typography"),value:R,onChange:function(e){return f({typography:e})},disableLineHeight:!0,device:D,onDeviceChange:function(e){return f({device:e})}})))),(v=M(),n.outline,b={backgroundColor:"fill"===n.outline?n.buttonColor:"transparent",color:n.buttonTextColor?n.buttonTextColor:"#fff",padding:n.paddingTopBottom+"px "+n.paddingLeftRight+"px",textAlign:n.buttonTextAlign,textDecoration:"none",display:"inline-block",borderRadius:n.buttonRadius,borderWidth:"fill"===n.outline?"0":"2px",borderStyle:"solid",borderColor:r.color,fontWeight:n.typography.weight,fontFamily:n.typography.family},"accept"==n.offerAction&&L("accept"),wp.element.createElement("div",{className:z+[" wp-block-wpfnl-offer-btn-".concat(n.buttonAlign)],style:{textAlign:n.buttonAlign}},wp.element.createElement("div",{className:"wpfnl-lms-offerbtn-wrapper"},wp.element.createElement(E,null,wp.element.createElement(k,{value:n.buttonTextAlign,onChange:function(e){f({buttonTextAlign:e})}}),wp.element.createElement(C,{value:n.buttonAlign,onChange:function(e){f({buttonAlign:e})},controls:["left","center","right"]})),"accept"==n.offerAction&&wp.element.createElement(O,a()({},v,{style:b,tagName:"p",value:n.lmsOfferButtonShortcode,id:"btn-join",className:"btn-join"})),"reject"==n.offerAction&&wp.element.createElement(O,a()({},v,{style:b,tagName:"p",value:n.buttonText,onChange:function(e){return f({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(n.offerAction),className:N}))))))};T.propTypes={attributes:p.a.object.isRequired,instanceId:p.a.number,setAttributes:p.a.func,buttonColor:p.a.object,buttonTextColor:p.a.object,setButtonColor:p.a.func.isRequired,setButtonTextColor:p.a.func.isRequired};var A=P([S({buttonColor:"background-color",buttonTextColor:"color"}),Object(d.a)()])(T),B=(n(52),n(53),wp.element),R=(B.RawHTML,B.useEffect,wp.blockEditor.RichText),F=function(e){var t,n,i=e.attributes,a=c()(""),r=c()("wpfunnels-block-offer-button"),o={backgroundColor:"fill"===i.outline?i.buttonColor?i.buttonColor:"":"transparent",color:i.buttonTextColor?i.buttonTextColor:"#fff",padding:i.paddingTopBottom+"px "+i.paddingLeftRight+"px",textAlign:i.buttonTextAlign,textDecoration:"none",display:"inline-block",borderRadius:i.buttonRadius,borderWidth:"fill"===i.outline?"0":"2px",borderStyle:"solid",borderColor:i.buttonColor?i.buttonColor:"#fff",fontWeight:i.typography.weight,fontFamily:i.typography.family};return console.log(i.typography.size),t=i.offerAction,n=document.getElementById("offer-button-style"),console.log(n),n&&(n.innerHTML="#wpfunnels_"+i.offerType+"_"+t+" { display: block; background-color: "+i.buttonColor+" !important; color: "+i.buttonTextColor+" !important; border-radius: "+i.buttonRadius+"px !important;padding-right: "+i.paddingLeftRight+"px !important; padding: "+i.paddingTopBottom+"px "+i.paddingLeftRight+"px !important; }\n"),wp.element.createElement("div",{className:a+[" wp-block-wpfnl-offer-btn-".concat(i.buttonAlign)],style:{textAlign:i.buttonAlign}},wp.element.createElement(R.Content,{tagName:"a",href:"#",id:"wpfunnels_".concat(i.offerType,"_").concat(i.offerAction),className:r,style:o,value:i.buttonText,"data-offertype":"".concat(i.offerType)}))};F.propTypes={attributes:p.a.object.isRequired};var D=F,L=(n(47),n(22)),__=wp.i18n.__;(0,wp.blocks.registerBlockType)("wpfunnelspro/lms-offer-button",{title:__("WPFunnels LMS Offer Button","wpfnl"),icon:"shield",category:"common",attributes:{buttonAlign:{type:"string",default:"center"},buttonTextAlign:{type:"string",default:"center"},buttonText:{type:"string",default:"I will pass"},offerAction:{type:"string",default:"accept"},offerType:{type:"string",default:"upsell"},outline:{type:"string",default:"fill"},buttonColor:{type:"string",default:"#39414d"},buttonTextColor:{type:"string",default:"#fff"},paddingTopBottom:{type:"number",default:14},paddingLeftRight:{type:"number",default:25},buttonRadius:{type:"number",default:5},typography:{type:"object",default:{},style:[{selector:".wpfnl-lms-offerbtn-wrapper .wpfunnels-block-offer-button"}]},lmsTypography:{type:"object",default:{},style:[{selector:".wpfnl-lms-offerbtn-wrapper #btn-join"}]},device:{type:"string",default:"md"},lmsOfferButtonShortcode:{type:"string",default:"
"}},edit:A,save:D}),window.qubelyDevice="md",window.bindWpfnlCss=!1,window.globalSaving=!1,wp.data.subscribe((function(){try{if(!1===window.bindWpfnlCss){var e=wp.data.select("core/editor").hasNonPostEntityChanges(),t=wp.data.select("core/editor").isSavingPost(),n=wp.data.select("core/editor").isAutosavingPost();wp.data.select("core/editor").isPublishingPost()||t&&!n?(Object(L.a)(!0),window.bindWpfnlCss=!0,setTimeout((function(){window.bindWpfnlCss=!1}),500)):wp.data.select("core/editor").isPreviewingPost()?Object(L.a)(!1):e&&$(".components-button.editor-post-publish-button.editor-post-publish-button__button.is-primary").click((function(){setTimeout((function(){!1===window.bindWpfnlCss&&($(".components-button.editor-entities-saved-states__save-button.is-primary").bind("click",(function(){console.log("saving hasNonPostEntityChanges"),Object(L.a)(!0)})),window.bindWpfnlCss=!0)}))}))}}catch(e){console.error(e)}}))}]));includes/core/widgets/block/assets/dist/offer-button.css000064400000014476147600245720017456 0ustar00.qubely-field-group-btn{align-items:center}.qubely-field-group-btn label{margin-bottom:0}.qubely-field-group-btn .qubely-field-child{display:flex;justify-content:end;align-items:center}.qubely-field-group-btn .qubely-field-child .qubley-group-button{display:inline-block;white-space:nowrap;padding:0px 9px;color:#8d96a0;font-size:12px;border-top:1px solid #d6d9dd;border-bottom:1px solid #d6d9dd;border-left:1px solid #d6d9dd;text-transform:capitalize;cursor:pointer;box-shadow:none;line-height:26px;height:26px}.qubely-field-group-btn .qubely-field-child .qubley-group-button.extra-padding{padding:0px 10px;font-weight:bold;font-size:15px}.qubely-field-group-btn .qubely-field-child .qubley-group-button:last-child{border-bottom-right-radius:3px;border-top-right-radius:3px;border-right:1px solid #d6d9dd}.qubely-field-group-btn .qubely-field-child .qubley-group-button:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.qubely-field-group-btn .qubely-field-child .qubley-group-button.qubley-active-group-btn{color:#2184f9;background:#d2e7ff;border-color:#a9d0ff}.qubely-field-group-btn .qubely-field-child .qubley-group-button.qubley-active-group-btn+.qubley-group-button{border-left-color:#a9d0ff} .qubely-input-range{display:flex}.qubely-input-range>*:not(:last-child){margin-right:8px}.qubely-input-range input[type="number"]{width:55px !important}.qubely-input-range input[type=range]{-webkit-appearance:none;margin-top:7px;margin-bottom:7px;width:100%;padding-left:0;padding-right:0;background:transparent}.qubely-input-range input[type=range]::-webkit-slider-runnable-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:#E5E7EA;border-radius:5px;border:0px solid #000}.qubely-input-range input[type=range]::-webkit-slider-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer;-webkit-appearance:none;margin-top:-5px}.qubely-input-range input[type=range]:focus::-webkit-slider-runnable-track{background:#f3f4f5}.qubely-input-range input[type=range]::-moz-range-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:#E5E7EA;border-radius:5px;border:0px solid #000}.qubely-input-range input[type=range]::-moz-range-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer}.qubely-input-range input[type=range]::-ms-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:transparent;border-color:transparent;border-width:14px 0;color:transparent}.qubely-input-range input[type=range]::-ms-fill-lower{background:#d7dadf;border:0px solid #000;border-radius:10px}.qubely-input-range input[type=range]::-ms-fill-upper{background:#E5E7EA;border:0px solid #000;border-radius:10px}.qubely-input-range input[type=range]::-ms-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer}.qubely-input-range input[type=range]:focus::-ms-fill-lower{background:#E5E7EA}.qubely-input-range input[type=range]:focus::-ms-fill-upper{background:#f3f4f5}.qubely-field-range>label{width:100%;margin-bottom:2px} .qubely-field.qubely-field-toggle{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.qubely-field.qubely-field-toggle>label{width:auto;margin-bottom:0}.qubely-field.qubely-field-toggle .components-toggle-control,.qubely-field.qubely-field-toggle .components-toggle-control .components-base-control__field{margin-bottom:0 !important}.qubely-field.qubely-field-toggle .components-toggle-control .components-base-control__field .components-form-toggle{margin-right:0}.qubely-field.qubely-field-toggle .components-form-toggle.is-checked .components-form-toggle__track{background-color:#2184F9} .qubely-field-font-family{min-width:calc(65% - 15px)}.qubely-field-font-weight{min-width:35%}.qubely-font-family-picker,.qubely-font-weight-picker-wrapper{-webkit-box-align:center;align-items:center;background-color:#fff;cursor:default;display:flex;flex-wrap:wrap;-webkit-box-pack:justify;justify-content:space-between;position:relative;box-sizing:border-box;border-color:#ccc;border-radius:4px;border-style:solid;border-width:1px;transition:all 100ms ease 0s;outline:0px !important;line-height:28px;height:30px;vertical-align:middle;padding-left:5px}.qubely-font-family-picker .editor-rich-text,.qubely-font-weight-picker-wrapper .editor-rich-text{width:100%}.qubely-font-family-picker .selected-font-family,.qubely-font-weight-picker-wrapper .selected-font-family{color:black}.qubely-font-family-option-wrapper{top:100%;background-color:#fff;box-shadow:rgba(0,0,0,0.1) 0px 0px 0px 1px,rgba(0,0,0,0.1) 0px 4px 11px;margin-bottom:8px;margin-top:8px;position:absolute;width:70%;box-sizing:border-box;border-radius:4px;z-index:100}.qubely-font-weight-wrapper{top:100%;left:62%;background-color:#fff;box-shadow:rgba(0,0,0,0.1) 0px 0px 0px 1px,rgba(0,0,0,0.1) 0px 4px 11px;margin-bottom:8px;margin-top:8px;position:absolute;width:40%;box-sizing:border-box;border-radius:4px;z-index:100}.qubely-font-family-options,.qubely-font-family-weights{max-height:270px;overflow-y:auto;padding-bottom:4px;padding-top:4px;position:relative;box-sizing:border-box}.qubely-active-font-family,.qubely-active-font-weight{background-color:#2684ff;color:#fff;cursor:default;display:block;font-size:inherit;width:100%;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing:border-box;padding:8px 12px}.qubely-font-family-option,.qubely-font-weight-option{background-color:transparent;color:inherit;cursor:default;display:block;font-size:inherit;width:100%;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing:border-box;padding:8px 12px}.qubely-font-family-option:hover,.qubely-font-weight-option:hover{background-color:#e8eaeb}.qubely-font-family-search-wrapper{display:flex;justify-content:space-between;width:100%}.qubely-font-family-search-wrapper input.qubely-font-family-search{border:none !important;background-color:transparent !important}.qubely-font-family-search-wrapper input.qubely-font-family-search:focus,.qubely-font-family-search-wrapper input.qubely-font-family-search:active,.qubely-font-family-search-wrapper input.qubely-font-family-search:hover{outline:0;box-shadow:none}.qubely-font-weight-picker-wrapper{display:flex;justify-content:space-between;width:100%}.qubely-font-select-icon{display:flex;align-items:center;padding-right:3px}.qubely-field.qubely-field-typography .components-dropdown.qubely-field{width:100%} includes/core/widgets/block/assets/dist/offer-button.js000064400000644010147600245720017273 0ustar00!function(e,t){for(var n in t)e[n]=t[n]}(this,function(e){var t={};function n(a){if(t[a])return t[a].exports;var i=t[a]={i:a,l:!1,exports:{}};return e[a].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,a){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:a})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var a=Object.create(null);if(n.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(a,i,function(t){return e[t]}.bind(null,i));return a},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=48)}([function(e,t){e.exports=function(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){function n(t){return"function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?(e.exports=n=function(e){return typeof e},e.exports.default=e.exports,e.exports.__esModule=!0):(e.exports=n=function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.default=e.exports,e.exports.__esModule=!0),n(t)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){"use strict";n.d(t,"a",(function(){return P}));var a,i,r,o,l=n(23),s=n.n(l);function c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}n(18),s()(console.error),a={"(":9,"!":8,"*":7,"/":7,"%":7,"+":6,"-":6,"<":5,"<=":5,">":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},i=["(","?"],r={")":["("],":":["?","?:"]},o=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/;var p={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,n){if(e)throw t;return n}};var f={contextDelimiter:"",onMissingKey:null};function u(e,t){var n;for(n in this.data=e,this.pluralForms={},this.options={},f)this.options[n]=void 0!==t&&n in t?t[n]:f[n]}function d(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function m(e){for(var t=1;t=0||a[s]3&&void 0!==arguments[3]?arguments[3]:10,o=e[t];if(w(n)&&g(a))if("function"==typeof i)if("number"==typeof r){var l={callback:i,priority:r,namespace:a};if(o[n]){var s,c=o[n].handlers;for(s=c.length;s>0&&!(r>=c[s-1].priority);s--);s===c.length?c[s]=l:c.splice(s,0,l),o.__current.forEach((function(e){e.name===n&&e.currentIndex>=s&&e.currentIndex++}))}else o[n]={handlers:[l],runs:0};"hookAdded"!==n&&e.doAction("hookAdded",n,a,i,r)}else console.error("If specified, the hook priority must be a number.");else console.error("The hook callback must be a function.")}},b=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(a,i){var r=e[t];if(w(a)&&(n||g(i))){if(!r[a])return 0;var o=0;if(n)o=r[a].handlers.length,r[a]={runs:r[a].runs,handlers:[]};else for(var l=r[a].handlers,s=function(e){l[e].namespace===i&&(l.splice(e,1),o++,r.__current.forEach((function(t){t.name===a&&t.currentIndex>=e&&t.currentIndex--})))},c=l.length-1;c>=0;c--)s(c);return"hookRemoved"!==a&&e.doAction("hookRemoved",a,i),o}}},x=function(e,t){return function(n,a){var i=e[t];return void 0!==a?n in i&&i[n].handlers.some((function(e){return e.namespace===a})):n in i}},E=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(a){var i=e[t];i[a]||(i[a]={handlers:[],runs:0}),i[a].runs++;for(var r=i[a].handlers,o=arguments.length,l=new Array(o>1?o-1:0),s=1;s1&&void 0!==arguments[1]?arguments[1]:"default";a.data[t]=m(m(m({},v),a.data[t]),e),a.data[t][""]=m(m({},v[""]),a.data[t][""])},l=function(e,t){o(e,t),r()},s=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,n=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,r=arguments.length>4?arguments[4]:void 0;return a.data[e]||o(void 0,e),a.dcnpgettext(e,t,n,i,r)},c=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return e},_x=function(e,t,a){var i=s(a,t,e);return n?(i=n.applyFilters("i18n.gettext_with_context",i,e,t,a),n.applyFilters("i18n.gettext_with_context_"+c(a),i,e,t,a)):i};if(n){var p=function(e){h.test(e)&&r()};n.addAction("hookAdded","core/i18n",p),n.addAction("hookRemoved","core/i18n",p)}return{getLocaleData:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return a.data[e]},setLocaleData:l,resetLocaleData:function(e,t){a.data={},a.pluralForms={},l(e,t)},subscribe:function(e){return i.add(e),function(){return i.delete(e)}},__:function(e,t){var a=s(t,void 0,e);return n?(a=n.applyFilters("i18n.gettext",a,e,t),n.applyFilters("i18n.gettext_"+c(t),a,e,t)):a},_x:_x,_n:function(e,t,a,i){var r=s(i,void 0,e,t,a);return n?(r=n.applyFilters("i18n.ngettext",r,e,t,a,i),n.applyFilters("i18n.ngettext_"+c(i),r,e,t,a,i)):r},_nx:function(e,t,a,i,r){var o=s(r,i,e,t,a);return n?(o=n.applyFilters("i18n.ngettext_with_context",o,e,t,a,i,r),n.applyFilters("i18n.ngettext_with_context_"+c(r),o,e,t,a,i,r)):o},isRTL:function(){return"rtl"===_x("ltr","text direction")},hasTranslation:function(e,t,i){var r,o,l=t?t+""+e:e,s=!(null===(r=a.data)||void 0===r||null===(o=r[null!=i?i:"default"])||void 0===o||!o[l]);return n&&(s=n.applyFilters("i18n.has_translation",s,e,t,i),s=n.applyFilters("i18n.has_translation_"+c(i),s,e,t,i)),s}}}(0,0,O)),P=(_.getLocaleData.bind(_),_.setLocaleData.bind(_),_.resetLocaleData.bind(_),_.subscribe.bind(_),_.__.bind(_));_._x.bind(_),_._n.bind(_),_._nx.bind(_),_.isRTL.bind(_),_.hasTranslation.bind(_)},function(e,t,n){e.exports=n(36)},function(e,t){e.exports=function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){function n(t){return e.exports=n=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},e.exports.default=e.exports,e.exports.__esModule=!0,n(t)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){function n(){return e.exports=n=Object.assign||function(e){for(var t=1;t0&&(c=JSON.parse(JSON.stringify(o))),r&&!c.hasOwnProperty("unit")&&(c.unit="px"),"unit"===t&&s?c.unit=e:(c=s?Object.assign(c,o,E()({},window.qubelyDevice,e)):e,c=a?ci?i:c:c>1e3?1e3:c),l(c),this.setState({current:c})}},{key:"_minMax",value:function(e){var t=this._filterValue("unit");return this.props[e]&&0!=this.props[e]?"em"==t?Math.round(this.props[e]/16):this.props[e]:0}},{key:"_steps",value:function(){return"em"==this._filterValue("unit")?.001:this.props.step||1}},{key:"updateDevice",value:function(e){var t=this.props,n=t.value,a=t.onChange;void 0!==t.device&&a(j(j({},n),{},{device:e})),this.setState({device:e})}},{key:"render",value:function(){var e=this,t=this.props,n=t.unit,a=t.label,r=t.responsive,o=t.device,l=t.onDeviceChange,s=t.disabled,c=void 0!==s&&s,p=r?o||this.state.device:window.qubelyDevice;return wp.element.createElement("div",{className:"qubely-field-range qubely-field "+(r?"qubely-responsive":"")},(a||n||r)&&wp.element.createElement("div",{className:"qubely-d-flex qubely-align-center qubely-mb-10"},a&&wp.element.createElement("div",null,wp.element.createElement("label",{htmlFor:"input"},a)),r&&wp.element.createElement(T,{device:p,commonResponsiveDevice:o,className:"qubely-ml-10",onChange:function(t){o&&l?l(t):e.updateDevice(t)}}),n&&wp.element.createElement("div",{className:"qubely-unit-btn-group qubely-ml-auto"},("object"==O()(n)?n:["px","em","%"]).map((function(t){return wp.element.createElement("button",{className:e.props.value&&t==e.props.value.unit?"active":"",onClick:function(){e.setSettings(t,"unit")}},t)})))),wp.element.createElement("div",{className:"qubely-field-child"},wp.element.createElement("div",{className:"qubely-input-range"},wp.element.createElement("input",{type:"range",min:this._minMax("min"),max:this._minMax("max"),value:this._filterValue(),step:this._steps(),disabled:c,onChange:function(t){return e.setSettings(e._filterValue()==t.target.value?"":t.target.value,"range")}}),wp.element.createElement("input",i()({type:"number",step:this._steps(),onChange:function(t){return e.setSettings(t.target.value,"range")},value:this._filterValue()+(this.props.suffix?this.props.suffix:""),disabled:c},this.props.suffix&&{disabled:!0})))))}}]),r}(wp.element.Component);n(39);var q=wp.element,R=q.Component,B=q.Fragment,N=wp.components.ToggleControl,L=function(e){h()(i,e);var t,n,a=(t=i,n=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,a=b()(t);if(n){var i=b()(this).constructor;e=Reflect.construct(a,arguments,i)}else e=a.apply(this,arguments);return w()(this,e)});function i(e){var t;return p()(this,i),(t=a.call(this,e)).state={current:t._filterValue()},t}return u()(i,[{key:"_filterValue",value:function(){return this.props.value?this.props.responsive?this.props.value[window.qubelyDevice]||"":this.props.value:""}},{key:"setSettings",value:function(e){var t=this.props,n=t.value,a=t.responsive;(0,t.onChange)(a?Object.assign({},n,E()({},window.qubelyDevice,e)):e),this.setState({current:e})}},{key:"render",value:function(){var e=this,t=this.props,n=t.label,a=t.customClassName,i=t.responsive,r=t.device,o=t.onDeviceChange;return wp.element.createElement("div",{className:"qubely-field-toggle qubely-field"+(this.props.responsive?" qubely-responsive":"")+(a?" ".concat(a):"")},wp.element.createElement("label",null,n&&n,i&&wp.element.createElement(B,null,r?wp.element.createElement(T,{device:r,commonResponsiveDevice:r,className:"qubely-ml-10",onChange:function(e){return o(e)}}):wp.element.createElement(T,{onChange:function(t){return e.setState({current:t})}}))),wp.element.createElement(N,{checked:this._filterValue(),onChange:function(t){return e.setSettings(t)}}))}}]),i}(R),D=n(10),F=n.n(D),z=n(19),I=n.n(z),G=n(7),W=n.n(G);function H(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function Y(e){for(var t=1;t0){var a,i=this.findArrayIndex(e);t.splice(i,1),(a=t).unshift.apply(a,o()(n))}else{var r;(r=t).unshift.apply(r,o()(n)),t.length>10&&t.pop()}else t=o()(n);localStorage.setItem("qubelyFonts",JSON.stringify(t))}},{key:"render",value:function(){var e=this,t=this.props,n=t.value,a=t.label,i=t.device,r=(t.globalSource,t.globalSettings),l=t.onDeviceChange,s=t.globalTypoOptions,c=t.globalTypoValues,p=this.state,f=p.filterText,u=p.showFontFamiles,d=p.showFontWeights,m=JSON.parse(localStorage.getItem("qubelyFonts")),v=[],h=Z;return m&&(v=Z.filter((function(e){return!m.filter((function(t){return t.n==e.n||"Default"==e.n})).length>0})),h=[{n:"Default",f:"default",v:[]}].concat(o()(m),o()(v))),f.length>=2&&(h=h.filter((function(e){return-1!==e.n.toLowerCase().search(f.toLowerCase())}))),wp.element.createElement("div",{className:"qubely-field qubely-field-typography"},!r&&wp.element.createElement(L,{value:n.openTypography,label:a||te("Typography"),onChange:function(t){return e.setSettings("openTypography",t)}}),(n&&1==n.openTypography||r)&&wp.element.createElement(ie,null,!r&&wp.element.createElement(U,{label:te("Source"),options:[[te("Global"),"global"],[te("Custom"),"custom"]],value:void 0!==n.activeSource?n.activeSource:"custom",onChange:function(t){if("custom"===t)void 0===n.globalSource||void 0===n.activeSource?e.props.onChange($($({},n),{},{activeSource:t})):void 0!==n.globalSource&&"none"!==n.globalSource?e.props.onChange($($({},c[n.globalSource-1]),{},{activeSource:t,globalSource:n.globalSource,blockDefaultValues:n.blockDefaultValues})):void 0!==n.globalSource&&"none"===n.globalSource&&e.props.onChange($($({openTypography:!0,globalSource:"none",activeSource:"custom"},n.blockDefaultValues),{},{blockDefaultValues:n.blockDefaultValues}));else{var a,i={openTypography:!0,activeSource:"global",globalSource:void 0===n.globalSource?"none":n.globalSource,blockDefaultValues:n.blockDefaultValues};void 0!==n.activeSource&&"custom"!==n.activeSource||"none"!==n.globalSource&&void 0!==n.globalSource||(delete(a=JSON.parse(JSON.stringify(n))).activeSource,delete a.globalSource,delete a.blockDefaultValues,i.blockDefaultValues=a),e.props.onChange(i)}}}),"global"!==n.activeSource||r?wp.element.createElement(ie,null,wp.element.createElement(A,{unit:!0,step:1,min:8,max:200,responsive:!0,device:i,label:te("Font Size"),value:n&&n.size,onChange:function(t){return e.setSettings("size",t)},onDeviceChange:function(e){return l(e)}}),wp.element.createElement("div",{className:"qubely-field-group qubely-65-35"},wp.element.createElement("div",{className:"qubely-field qubely-field-font-family"},wp.element.createElement("label",null,te("Font Family")),wp.element.createElement("div",{className:"qubely-font-family-picker",ref:"qubelySelectedFontFamily",onClick:function(){e.setState({showFontFamiles:!u})}},wp.element.createElement("span",{className:"qubely-font-family-search-wrapper"},wp.element.createElement("input",{type:"text",className:"qubely-font-family-search".concat(u?"":" selected-font-family"),placeholder:te(u?"Search":n&&n.family||"Select"),value:f,onChange:function(t){return e.setState({filterText:t.target.value})}}),wp.element.createElement("span",{className:"qubely-font-select-icon"}," ",u?I.a.arrow_up:I.a.arrow_down," ")))),u&&wp.element.createElement("div",{className:"qubely-font-family-option-wrapper",ref:"qubelyFontFamilyWrapper"},wp.element.createElement("div",{className:"qubely-font-family-options"},h.length>0?h.map((function(t,a){var i=!1;n&&t.n==n.family&&(i=!0);var r=F()(E()({},"qubely-font-family-option",!i),E()({},"qubely-active-font-family",i));return wp.element.createElement("div",{className:r,id:"qubely-font-family-".concat(a),onClick:function(){e.setState({showFontFamiles:!1,filterText:""}),"Default"==t.n?e.setSettings("family","default"):e.handleTypographyChange(t.n)}},t.n)})):wp.element.createElement("div",{className:"qubely-font-family-option no-match",onClick:function(){return e.setState({showFontFamiles:!1,filterText:""})}}," No matched font "))),wp.element.createElement("div",{className:"qubely-field qubely-field-font-weight"},wp.element.createElement("label",null,te("Weight")),wp.element.createElement("div",{className:"qubely-font-weight-picker-wrapper",ref:"qubelySelectedFontWeight",onClick:function(){return e.setState({showFontWeights:!d})}},wp.element.createElement("div",{className:"qubely-font-weight-picker"}," ",n&&n.weight||"Select"," "),wp.element.createElement("span",{className:"qubely-font-select-icon"}," ",d?I.a.arrow_up:I.a.arrow_down," "))),d&&wp.element.createElement("div",{className:"qubely-font-weight-wrapper",ref:"qubelyFontWeightWrapper"},wp.element.createElement("div",{className:"qubely-font-family-weights"},["Default"].concat(o()(this._getWeight())).map((function(t){return wp.element.createElement("div",{className:"".concat(t==n.weight?"qubely-active-font-weight":"qubely-font-weight-option"),onClick:function(){e.setState({showFontWeights:!1}),e.setSettings("weight",t)}},t)}))))),wp.element.createElement(oe,{className:"qubely-field",renderToggle:function(e){var t=e.isOpen,n=e.onToggle;return wp.element.createElement("div",{className:"qubely-d-flex qubely-align-center"},wp.element.createElement("label",null,te("Advanced Typography")),wp.element.createElement("div",{className:"qubely-field-button-list qubely-ml-auto"},wp.element.createElement("button",{className:(1==t?"active":"")+" qubely-button qubely-button-rounded",onClick:n,"aria-expanded":t},wp.element.createElement("i",{className:"fas fa-cog"}))))},renderContent:function(){return wp.element.createElement("div",{style:{padding:"15px"}},!e.props.disableLineHeight&&wp.element.createElement(A,{label:te("Line Height"),value:n&&n.height,onChange:function(t){return e.setSettings("height",t)},min:8,max:200,step:1,unit:!0,responsive:!0,device:i,onDeviceChange:function(e){return l(e)}}),wp.element.createElement(A,{label:te("Letter Spacing"),value:n&&n.spacing,onChange:function(t){return e.setSettings("spacing",t)},min:-10,max:30,step:1,unit:!0,responsive:!0,device:i,onDeviceChange:function(e){return l(e)}}),wp.element.createElement("div",{className:"qubely-field qubely-d-flex qubely-align-center"},wp.element.createElement("div",null,te("Text Transform")),wp.element.createElement("div",{className:"qubely-field-button-list qubely-ml-auto"},["none","capitalize","uppercase","lowercase"].map((function(t,a){return wp.element.createElement(le,{text:t.charAt(0).toUpperCase()+t.slice(1)},wp.element.createElement("button",{className:(n.transform==t?"active":"")+" qubely-button",key:a,onClick:function(){return e.setSettings("transform",t)}},"none"==t&&wp.element.createElement("i",{className:"fas fa-ban"}),"capitalize"==t&&wp.element.createElement("span",null,"Aa"),"uppercase"==t&&wp.element.createElement("span",null,"AA"),"lowercase"==t&&wp.element.createElement("span",null,"aa")))})))))}})):wp.element.createElement(se,{label:"Size",value:void 0!==n.globalSource?n.globalSource:"none",options:s,onChange:function(t){if("none"===t&&"none"!==n.globalSource){var a={openTypography:!0,activeSource:"global",globalSource:t,blockDefaultValues:n.blockDefaultValues};e.props.onChange(a)}else e.setSettings("globalSource",t)}})))}}]),a}(ae);t.a=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return ce((function(t){return function(n){h()(r,n);var a=ee(r);function r(){var t;return p()(this,r),t=a.apply(this,arguments),E()(m()(t),"getGlobalSettings",s()(S.a.mark((function e(){var n,a,i,r;return S.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,JSON.parse(localStorage.getItem("qubely-global-settings"));case 2:n=e.sent,a=n.typography,i=[],r=[],void 0!==a&&(a.forEach((function(e,t){var n=e.name,a=e.value;i.push({label:n,value:t+1}),r.push(a)})),t.setState({typography:a,globalTypoOptions:[{label:"Default",value:"none"}].concat(i),globalTypoValues:r}));case 6:case"end":return e.stop()}}),e)})))),t.setState=t.setState.bind(m()(t)),t.state=e,t}return u()(r,[{key:"componentDidMount",value:function(){this.getGlobalSettings()}},{key:"render",value:function(){return wp.element.createElement(t,i()({},this.props,this.state,{setState:this.setState}))}}]),r}(ae)}),"withGLobalTypography")}()(pe)},function(e,t,n){var a=n(33),i=n(34),r=n(26),o=n(35);e.exports=function(e){return a(e)||i(e)||r(e)||o()},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var a;!function(){"use strict";var i={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function r(e){return l(c(e),arguments)}function o(e,t){return r.apply(null,[e].concat(t||[]))}function l(e,t){var n,a,o,l,s,c,p,f,u,d=1,m=e.length,v="";for(a=0;a=0),l.type){case"b":n=parseInt(n,10).toString(2);break;case"c":n=String.fromCharCode(parseInt(n,10));break;case"d":case"i":n=parseInt(n,10);break;case"j":n=JSON.stringify(n,null,l.width?parseInt(l.width):0);break;case"e":n=l.precision?parseFloat(n).toExponential(l.precision):parseFloat(n).toExponential();break;case"f":n=l.precision?parseFloat(n).toFixed(l.precision):parseFloat(n);break;case"g":n=l.precision?String(Number(n.toPrecision(l.precision))):parseFloat(n);break;case"o":n=(parseInt(n,10)>>>0).toString(8);break;case"s":n=String(n),n=l.precision?n.substring(0,l.precision):n;break;case"t":n=String(!!n),n=l.precision?n.substring(0,l.precision):n;break;case"T":n=Object.prototype.toString.call(n).slice(8,-1).toLowerCase(),n=l.precision?n.substring(0,l.precision):n;break;case"u":n=parseInt(n,10)>>>0;break;case"v":n=n.valueOf(),n=l.precision?n.substring(0,l.precision):n;break;case"x":n=(parseInt(n,10)>>>0).toString(16);break;case"X":n=(parseInt(n,10)>>>0).toString(16).toUpperCase()}i.json.test(l.type)?v+=n:(!i.number.test(l.type)||f&&!l.sign?u="":(u=f?"+":"-",n=n.toString().replace(i.sign,"")),c=l.pad_char?"0"===l.pad_char?"0":l.pad_char.charAt(1):" ",p=l.width-(u+n).length,s=l.width&&p>0?c.repeat(p):"",v+=l.align?u+n+s:"0"===c?u+s+n:s+u+n)}return v}var s=Object.create(null);function c(e){if(s[e])return s[e];for(var t,n=e,a=[],r=0;n;){if(null!==(t=i.text.exec(n)))a.push(t[0]);else if(null!==(t=i.modulo.exec(n)))a.push("%");else{if(null===(t=i.placeholder.exec(n)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){r|=1;var o=[],l=t[2],c=[];if(null===(c=i.key.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(o.push(c[1]);""!==(l=l.substring(c[0].length));)if(null!==(c=i.key_access.exec(l)))o.push(c[1]);else{if(null===(c=i.index_access.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");o.push(c[1])}t[2]=o}else r|=2;if(3===r)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");a.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}n=n.substring(t[0].length)}return s[e]=a}t.sprintf=r,t.vsprintf=o,"undefined"!=typeof window&&(window.sprintf=r,window.vsprintf=o,void 0===(a=function(){return{sprintf:r,vsprintf:o}}.call(t,n,t,e))||(e.exports=a))}()},function(e,t){},function(e,t){function n(t,a){return e.exports=n=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},e.exports.default=e.exports,e.exports.__esModule=!0,n(t,a)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var a,i;void 0===(i="function"==typeof(a=function(e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var t=Object.assign||function(e){for(var t=1;t4&&void 0!==l[4]&&l[4],o=JSON.stringify(n.interaction),e.prev=2,e.next=5,wp.apiFetch({path:f,method:"POST",data:{block_css:n.css,interaction:o,post_id:t,is_remain:a,available_blocks:i,isPreviewing:r}});case 5:e.next=10;break;case 7:e.prev=7,e.t0=e.catch(2),console.log("Can't save css:",e.t0);case 10:case"end":return e.stop()}}),e,null,[[2,7]])})));return function(_x,t,n,a){return e.apply(this,arguments)}}(),d="",m={};function v(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return 1==t&&(d="",m={},t=!1),e.map((function(e){var t=e.attributes,n=e.name.split("/");if("wpfunnelspro"===n[0]&&t.uniqueId&&(d+=p(t,n[1],t.uniqueId,!0),void 0!==t.interaction)){var a=t.interaction,i=a.while_scroll_into_view,r=a.mouse_movement;if(void 0!==i&&!0===i.enable){var o=i.action_list;o=o.sort((function(e,t){return e.keyframe-t.keyframe}));var l={blockId:t.uniqueId,enable_mobile:void 0!==i.enable_mobile&&i.enable_mobile,enable_tablet:void 0!==i.enable_tablet&&i.enable_tablet,animation:o},s={x_offset:void 0===i.transform_origin_x?"center":i.transform_origin_x,y_offset:void 0===i.transform_origin_y?"center":i.transform_origin_y};l.origin=s,void 0===m.while_scroll_view?m.while_scroll_view=[l]:m.while_scroll_view.push(l)}if(void 0!==r&&!0===r.enable){var c={blockId:t.uniqueId,enable_mobile:void 0!==i.enable_mobile&&i.enable_mobile,enable_tablet:void 0!==i.enable_tablet&&i.enable_tablet,animation:r};void 0===m.mouse_movement?m.mouse_movement=[c]:m.mouse_movement.push(c)}}e.innerBlocks&&e.innerBlocks.length>0&&v(e.innerBlocks)})),{css:d,interaction:m}}function h(e){var t=!1;return e.forEach((function(e){var n=e.name,a=e.innerBlocks,r=void 0===a?[]:a,o=n.split("/"),l=i()(o,2),s=l[0];l[1],"wpfunnelspro"===s&&(t=!0),!t&&r.length>0&&(t=h(r))})),t}function g(e){e.forEach((function(e){var t;-1!=e.name.indexOf("core/block")&&(t=e.attributes.ref,wp.apiFetch({path:"qubely/v1/qubely_get_content",method:"POST",data:{postId:t}}).then((function(e){if(e.success){var t=v(wp.blocks.parse(e.data),!0);t.css&&wp.apiFetch({path:"qubely/v1/append_qubely_css",method:"POST",data:{css:t.css,post_id:c("core/editor").getCurrentPostId()}}).then((function(e){e.success}))}}))),e.innerBlocks&&e.innerBlocks.length>0&&g(e.innerBlocks)}))}function w(e){var t,n={available_blocks:[],interaction:!1,animation:!1,parallax:!1};return(t=e).length&&t.forEach((function(e){var t=e.name,a=e.attributes,r=(e.innerBlocks,t.split("/")),o=i()(r,2),l=o[0];if(o[1],"wpfunnelspro"===l){if(n.available_blocks.includes(t)||n.available_blocks.push(t),!1===n.interaction&&void 0!==a.interaction){var s=a.interaction,c=s.while_scroll_into_view,p=s.mouse_movement;(void 0!==c&&!0===c.enable||void 0!==p&&!0===p.enable)&&(n.interaction=!0)}!1===n.animation&&void 0!==a.animation&&void 0!==a.animation.animation&&""!==a.animation.animation&&(n.animation=!0)}})),n}var y=function(){var e=o()(s.a.mark((function e(){var t,n,a,i,r,o,l,p,f=arguments;return s.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:t=!(f.length>0&&void 0!==f[0])||f[0],window.bindWpfnlCss=!0,n=c("core/block-editor").getBlocks(),a=h(n),i=c("core/editor"),r=i.getCurrentPostId,o={css:"",interaction:{}},l=v(n,!0),o.interaction=l.interaction,o.css+=l.css,t&&g(n),localStorage.setItem("wpfnlGBCSS",JSON.stringify(o.css)),localStorage.setItem("wpfnlGBInteraction",JSON.stringify(o.interaction)),p=w(n),u(r(),o,a,p,!t),window.bindWpfnlCss=!1;case 15:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();t.a=y},function(e,t,n){e.exports=function(e,t){var n,a,i=0;function r(){var r,o,l=n,s=arguments.length;e:for(;l;){if(l.args.length===arguments.length){for(o=0;o0?f.slice(0,-1):"0",n.md="".concat(r,":").concat(s||"0").concat(e.unit," ").concat(c||"0").concat(e.unit," ").concat(p||"0").concat(e.unit," ").concat(f||"0").concat(e.unit,";")}if(e&&e.sm){var u=t.replace(new RegExp("{{key}}","g"),e.sm).split(":"),d=E()(u,2),m=d[0],v=d[1].split(" "),h=E()(v,4),g=h[0],w=h[1],y=h[2],b=h[3];b=b.length>0?b.slice(0,-1):"0",n.sm="".concat(m,":").concat(g||"0").concat(e.unit," ").concat(w||"0").concat(e.unit," ").concat(y||"0").concat(e.unit," ").concat(b||"0").concat(e.unit,";")}if(e&&e.xs){var x=t.replace(new RegExp("{{key}}","g"),e.xs).split(":"),C=E()(x,2),S=C[0],k=C[1].split(" "),O=E()(k,4),_=O[0],P=O[1],T=O[2],M=O[3];M=M.length>0?M.slice(0,-1):"0",n.xs="".concat(S,":").concat(_||"0").concat(e.unit," ").concat(P||"0").concat(e.unit," ").concat(T||"0").concat(e.unit," ").concat(M||"0").concat(e.unit,";")}return n},F=function(e){if("object"===o()(e.global)||"object"===o()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.radiusType?M(T(S(S({},e.global),{},{unit:e.unit?e.unit:"px"}),"border-radius:{{key}};"),t):M(D(S(S({},e.custom),{},{unit:e.unit?e.unit:"px"}),"border-radius:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.radiusType)n="border-radius:".concat(e.global||"0").concat(e.unit?e.unit:"px"," ");else{var a=e.custom?e.custom.split(" "):["0","0","0","0"],i=e.unit?e.unit:"px";n="border-radius:".concat(a[0]?a[0]:"0").concat(i," ").concat(a[1]?a[1]:"0").concat(i," ").concat(a[2]?a[2]:"0").concat(i," ").concat(a[3]?a[3]:"0").concat(i)}return"{"+n+"}"},z=function(e){if("object"===o()(e.global)||"object"===o()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.paddingType?M(T(S(S({},e.global),{},{unit:e.unit?e.unit:"px"}),"padding:{{key}};"),t):M(D(S(S({},e.custom),{},{unit:e.unit?e.unit:"px"}),"padding:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.paddingType)n="padding:".concat(e.global).concat(e.unit?e.unit:"px"," ");else{var a=e.custom?e.custom.split(" "):["0","0","0","0"],i=e.unit?e.unit:"px";n="padding:".concat(a[0]?a[0]:"0").concat(i," ").concat(a[1]?a[1]:"0").concat(i," ").concat(a[2]?a[2]:"0").concat(i," ").concat(a[3]?a[3]:"0").concat(i)}return"{"+n+"}"},I=function(e){if("object"===o()(e.global)||"object"===o()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.marginType?M(T(S(S({},e.global),{},{unit:e.unit?e.unit:"px"}),"margin:{{key}};"),t):M(D(S(S({},e.custom),{},{unit:e.unit?e.unit:"px"}),"margin:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.marginType)n="margin:".concat(e.global).concat(e.unit?e.unit:"px"," ");else{var a=e.custom?e.custom.split(" "):["0","0","0","0"],i=e.unit?e.unit:"px";n="margin:".concat(a[0]?a[0]:"0").concat(i," ").concat(a[1]?a[1]:"0").concat(i," ").concat(a[2]?a[2]:"0").concat(i," ").concat(a[3]?a[3]:"0").concat(i)}return"{"+n+"}"},G=function(e){var t={md:[],sm:[],xs:[]};return e.values.md&&t.md.push("flex-direction:row-reverse"),e.values.sm&&t.sm.push("flex-direction:column-reverse"),e.values.xs&&t.xs.push("flex-direction:column-reverse"),{md:t.md,sm:t.sm,xs:t.xs}},W=function(e,t,n){return e.replace(new RegExp(t,"g"),n)},H=function(e){return"object"==o()(e)&&0!=Object.keys(e).length},Y=function(e,t){var n="";return t.forEach((function(e){n+=e+";"})),e+"{"+n+"}"},V=function(e,t){var n="";return t.forEach((function(t){n+=e+t})),n},X=function(e,t,n){if(n="object"!=o()(n)?n:K(n).data,"string"==typeof e){if(e){if(n){var a=e;return"boolean"==typeof n?[a]:-1==a.indexOf("{{")&&a.indexOf("{")<0?[a+n]:[W(a,"{{"+t+"}}",n)]}return[]}return[n]}var i=[];return e&&e.forEach((function(e){i.push(W(e,"{{"+t+"}}",n))})),i},K=function(e){return e.openTypography?{data:j(e),action:"append"}:e.openBg?{data:(t=e,n="{",n+=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,a=arguments.length>3?arguments[3]:void 0,i=arguments.length>4?arguments[4]:void 0,r=arguments.length>5?arguments[5]:void 0,o=arguments.length>6?arguments[6]:void 0,l=arguments.length>7?arguments[7]:void 0,s=arguments.length>8&&void 0!==arguments[8]?arguments[8]:"local",c=arguments.length>9&&void 0!==arguments[9]?arguments[9]:{},p=o?"background-color:"+o+";":"";return"image"==e?p+=("local"===s?t.hasOwnProperty("url")?"background-image:url("+t.url+");":"":c.hasOwnProperty("url")?"background-image:url("+c.url+");":"")+(n?"background-position:"+n+";":"")+(a?"background-attachment:"+a+";":"")+(i?"background-repeat:"+i+";":"")+(r?"background-size:"+r+";":""):"gradient"==e&&(l&&"linear"==l.type?p+="background-image: linear-gradient("+l.direction+"deg, "+l.color1+" "+l.start+"%,"+l.color2+" "+l.stop+"%);":(void 0===l.radial&&(l.radial="center"),p+="background-image: radial-gradient( circle at "+l.radial+" , "+l.color1+" "+l.start+"%,"+l.color2+" "+l.stop+"%);")),p}(t.bgType,t.bgImage,t.bgimgPosition,t.bgimgAttachment,t.bgimgRepeat,t.bgimgSize,t.bgDefaultColor,t.bgGradient,t.bgimageSource,t.externalImageUrl),n+="}","video"==t.bgType&&t.bgVideoFallback&&t.bgVideoFallback.url&&(n+="background-image: url("+t.bgVideoFallback.url+"); background-position: center; background-repeat: no-repeat; background-size: cover;"),"{}"!=n?n:{}),action:"append"}:e.openBorder?{data:_(e),action:"append"}:e.tableBorder?{data:P(e),action:"append"}:e.openShadow&&e.color?{data:O(e),action:"append"}:e.direction?{data:k(e,"return"),action:"append"}:void 0!==e.top||void 0!==e.left||void 0!==e.right||void 0!==e.bottom?{data:A(e),action:"replace"}:e.openShape?{data:q(e),action:"append"}:e.openColor?{data:R(e),action:"append"}:e.spaceTop||e.spaceBottom?{data:B(e),action:"append"}:e.selectedSize?{data:N(e),action:"append"}:e.openBorderRadius?{data:F(e),action:"append"}:e.openPadding?{data:z(e),action:"append"}:e.openMargin?{data:I(e),action:"append"}:e.openRowReverse?{data:G(e),action:"append"}:e.openTransfrom?{data:L(e),action:"append"}:{data:"",action:"append"};var t,n},J=function(e,t,n){var a=!0;return t.hasOwnProperty("condition")&&t.condition.forEach((function(t){var n=a;if("=="==t.relation||"==="==t.relation)if("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value)a=e[t.key]==t.value;else{var i=!1;t.value.forEach((function(n){e[t.key]==n&&(i=!0)})),i&&(a=!0)}else if("!="==t.relation||"!=="==t.relation)if("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value)a=e[t.key]!=t.value;else{var r=!1;t.value.forEach((function(n){e[t.key]!=n&&(r=!0)})),r&&(a=!0)}0==n&&(a=!1)})),a},U=function(e,t,n,a,i,r){var l=!1,s="",c=[],p=[];if(e[t].md&&(l=!0,s="object"===o()(e[t].md)?K(e[t].md).data:e[t].md+(e[t].unit||""),c.push({md:X(n,t,s)})),e[t].sm&&(l=!0,s="object"==o()(e[t].sm)?K(e[t].sm).data:e[t].sm+(e[t].unit||""),c.push({sm:X(n,t,s)})),e[t].xs&&(l=!0,s="object"==o()(e[t].xs)?K(e[t].xs).data:e[t].xs+(e[t].unit||""),c.push({xs:X(n,t,s)})),!l){var f=K(e[t]);"object"==o()(f.data)?0!=Object.keys(f.data).length&&(f.data.background&&p.push(n+f.data.background),H(f.data.md)&&c.push({md:Y(n,f.data.md)}),H(f.data.sm)&&c.push({sm:Y(n,f.data.sm)}),H(f.data.xs)&&c.push({xs:Y(n,f.data.xs)}),f.data.simple&&p.push(n+f.data.simple),f.data.font&&p.push(f.data.font),f.data.shape&&(f.data.shape.forEach((function(e){p.push(n+e)})),H(f.data.data.md)&&c.push(V(n,f.data.data.md)),H(f.data.data.sm)&&c.push(V(n,f.data.data.sm)),H(f.data.data.xs)&&c.push(V(n,f.data.data.xs)))):f.data&&-1==f.data.indexOf("{{")&&("append"==f.action?p.push(n+f.data):(a(t,void 0,X(n,t,f.data)),p.push(X(n,t,f.data))))}if(c.nonResponsiveCSS=p,r)return c;a(t,"Object",c)},Z=function(e,t,n,a,i,r){var o=[];return"hideTablet"==t&&i?(o.push({sm:X(n,t,e[t])}),!r&&a(t,"Object",o)):"hideMobile"==t&&i?(o.push({xs:X(n,t,e[t])}),!r&&a(t,"Object",o)):void 0!==e[t]&&!r&&a(t,void 0,X(n,t,e[t])),r&&o.length>0?o:r&&void 0!==e[t]?X(n,t,e[t]):void 0},Q=function(e,t,n){var a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],i={},r={};Object.keys(t).forEach((function(l){e[l]&&e[l].hasOwnProperty("style")&&e[l].style.forEach((function(e){var s=e.selector;J(t,e)&&("object"==o()(t[l])?i[l]=U(t,l,s,n,0,!0):r[l]=Z(t,l,s,n,a,!0))}))}));var l={responsiveCSS:i,nonResponsiveCSS:r};n(l)};function $(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function ee(e){for(var t=1;t0&&r.forEach((function(e){n[e]&&("hideTablet"===e&&0!==n[e].length?a+="@media (max-width: 1199px) and (min-width: 992px) {"+n[e][0]+"}":"hideMobile"===e&&0!==n[e].length?a+="@media (max-width: 991px) {"+n[e][0]+"}":"array"==typeof n[e]?a+=n[e].join(" "):a+=n[e])})),l.length>0&&l.forEach((function(e){var n=t[e];Object.keys(n).forEach((function(e){"nonResponsiveCSS"!==e&&"object"===o()(n[e])&&Object.keys(n[e]).length>0?n[e].hasOwnProperty("md")&&void 0!==n[e].md?a+=n[e].md:n[e].hasOwnProperty("sm")&&void 0!==n[e].sm?a+="@media (max-width: 1199px) {"+n[e].sm+"}":n[e].hasOwnProperty("xs")&&void 0!==n[e].xs&&(a+="@media (max-width: 991px) {"+n[e].xs+"}"):"nonResponsiveCSS"===e&&n.nonResponsiveCSS.length>0&&(a+=n.nonResponsiveCSS.join(" "))}))}));var s=(a=a.replace(new RegExp("{{QUBELY}}","g"),".qubely-block-"+i)).match(new RegExp("@import([^;]*);","g"));s&&(a=s+" "+(a=a.replace(new RegExp("@import([^;]*);","g"),"")));var c=window.document;if(null===c.getElementById("wpfnl-block-"+i)){var p=document.createElement("style");p.type="text/css",p.id="wpfnl-block-"+i,p.styleSheet?p.styleSheet.cssText=a:p.innerHTML=a,c.getElementsByTagName("head")[0].appendChild(p)}else c.getElementById("wpfnl-block-"+i).innerHTML=a})),b()(u()(e),"copyAttributes",(function(){var t=e.props,n=t.attributes,a=t.attributes.qubelyStyleAttributes,i=wp.qubelyComponents.HelperFunction.copyToClipboard,r={};a.forEach((function(e){r[e]=n[e]})),i(JSON.stringify(r))})),e.setState=e.setState.bind(u()(e)),e}return p()(l,[{key:"componentDidMount",value:function(){this.saveStyleAttributes()}},{key:"componentDidUpdate",value:function(e,t){var n=this,a=this.props,i=(a.name,a.clientId),r=a.attributes,l=a.attributes.uniqueId,s=this.state,c=s.responsiveCSS,p=s.nonResponsiveCSS,f=!1,u=wp.blocks.getBlockType(this.props.name).attributes,d=Object.keys(ie(e.attributes,r)),m=wp.data.select("core/block-editor"),v=m.getBlock,h=m.getBlocks,g=(m.getBlockRootClientId,m.getBlockIndex,v(i),!1);if(d.length>0&&(-1===d.indexOf("layout")&&-1===d.indexOf("style")&&-1===d.indexOf("recreateStyles")&&-1===d.indexOf("fillType")&&-1===d.indexOf("iconStyle")&&-1===d.indexOf("buttonFillType")&&-1===d.indexOf("tabStyle")&&-1===d.indexOf("separatorStyle")||(f=!0,this.saveStyleAttributes()),!f)){if(-1!==d.indexOf("uniqueId")){var w=window.document.getElementById("qubely-block-"+e.attributes.uniqueId);if(w&&!function t(n){if(0!==n.length){if(e.attributes.uniqueId!==l)for(var a=0;a0&&(t(n[a].innerBlocks),g))break}return g}}(h())){w.id="qubely-block-"+r.uniqueId;var y=w.innerHTML.replace(new RegExp("".concat(e.attributes.uniqueId),"g"),"".concat(r.uniqueId));w.innerHTML=y}else this.saveCSS(c,p)}d=d.filter((function(e){return"uniqueId"!==e}));var x={nonResponsiveCSS:p,responsiveCSS:c};d.length>0&&(d.forEach((function(e){!function(e,t,n,a){var i=arguments.length>4&&void 0!==arguments[4]&&arguments[4];e[a]&&e[a].hasOwnProperty("style")&&e[a].style.forEach((function(e){var r=e.selector;J(t,e)&&("object"==o()(t[a])?U(t,a,r,n,0,!1):Z(t,a,r,n,i,!1))}))}(u,r,(function(e,t,n){return function(e,t,n){x=ee(ee({},x),{},void 0===t?{nonResponsiveCSS:ee(ee({},x.nonResponsiveCSS),b()({},e,n))}:{responsiveCSS:ee(ee({},x.responsiveCSS),b()({},e,ee(ee({},x.responsiveCSS[e]),"simple"===t?{simple:n}:ee({},n))))}),"Object"===t&&r[e].openTypography&&"global"===r[e].activeSource&&"none"===r[e].globalSource&&(x.responsiveCSS[e]={})}(e,t,n)}),e),n.saveCSS(x.responsiveCSS,x.nonResponsiveCSS,"update")})),this.setState({responsiveCSS:x.responsiveCSS,nonResponsiveCSS:x.nonResponsiveCSS}))}}},{key:"render",value:function(){var t=this,n=this.props.attributes.showCopyAttr;return wp.element.createElement(ne,null,n&&wp.element.createElement(le,null,wp.element.createElement(re,{icon:"editor-code",label:"Copy Attributes",onClick:function(){return t.copyAttributes()}})),wp.element.createElement(e,i()({},this.props,this.state,{setState:this.setState})))}}]),l}(ae)}),"withCSSGenerator")}},function(e,t){e.exports=function(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,a=new Array(t);n=0;--i){var r=this.tryEntries[i],o=r.completion;if("root"===r.tryLoc)return a("end");if(r.tryLoc<=this.prev){var l=n.call(r,"catchLoc"),s=n.call(r,"finallyLoc");if(l&&s){if(this.prev=0;--a){var i=this.tryEntries[a];if(i.tryLoc<=this.prev&&n.call(i,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),E(n),p}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var a=n.completion;if("throw"===a.type){var i=a.arg;E(n)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:S(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),p}},e}(e.exports);try{regeneratorRuntime=a}catch(e){"object"==typeof globalThis?globalThis.regeneratorRuntime=a:Function("r","regeneratorRuntime = r")(a)}},function(e,t){},function(e,t){},function(e,t){},function(e,t){e.exports=function(e){if(Array.isArray(e))return e},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){e.exports=function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var a,i,r=[],_n=!0,o=!1;try{for(n=n.call(e);!(_n=(a=n.next()).done)&&(r.push(a.value),!t||r.length!==t);_n=!0);}catch(e){o=!0,i=e}finally{try{_n||null==n.return||n.return()}finally{if(o)throw i}}return r}},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){e.exports=function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){},function(e,t,n){var a,i,r;i=[e,t,n(21)],void 0===(r="function"==typeof(a=function(e,t,n){"use strict";function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(t,"__esModule",{value:!0});var i=Object.assign||function(e){for(var t=1;t0?u.slice(0,-1):"0",n.md="".concat(r,":").concat(c||"0").concat(e.unit," ").concat(p||"0").concat(e.unit," ").concat(f||"0").concat(e.unit," ").concat(u||"0").concat(e.unit,";")}if(e&&e.sm){var d=t.replace(new RegExp("{{key}}","g"),e.sm).split(":"),m=l()(d,2),v=m[0],h=m[1].split(" "),g=l()(h,4),w=g[0],y=g[1],b=g[2],x=g[3];x=x.length>0?x.slice(0,-1):"0",n.sm="".concat(v,":").concat(w||"0").concat(e.unit," ").concat(y||"0").concat(e.unit," ").concat(b||"0").concat(e.unit," ").concat(x||"0").concat(e.unit,";")}if(e&&e.xs){var E=t.replace(new RegExp("{{key}}","g"),e.xs).split(":"),C=l()(E,2),S=C[0],k=C[1].split(" "),O=l()(k,4),_=O[0],P=O[1],T=O[2],M=O[3];M=M.length>0?M.slice(0,-1):"0",n.xs="".concat(S,":").concat(_||"0").concat(e.unit," ").concat(P||"0").concat(e.unit," ").concat(T||"0").concat(e.unit," ").concat(M||"0").concat(e.unit,";")}return n},T=function(e){if("object"===r()(e.global)||"object"===r()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.radiusType?b(y(f(f({},e.global),{},{unit:e.unit?e.unit:"px"}),"border-radius:{{key}};"),t):b(P(f(f({},e.custom),{},{unit:e.unit?e.unit:"px"}),"border-radius:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.radiusType)n="border-radius:".concat(e.global||"0").concat(e.unit?e.unit:"px"," ");else{var a=e.custom?e.custom.split(" "):["0","0","0","0"],i=e.unit?e.unit:"px";n="border-radius:".concat(a[0]?a[0]:"0").concat(i," ").concat(a[1]?a[1]:"0").concat(i," ").concat(a[2]?a[2]:"0").concat(i," ").concat(a[3]?a[3]:"0").concat(i)}return"{"+n+"}"},M=function(e){if("object"===r()(e.global)||"object"===r()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.paddingType?b(y(f(f({},e.global),{},{unit:e.unit?e.unit:"px"}),"padding:{{key}};"),t):b(P(f(f({},e.custom),{},{unit:e.unit?e.unit:"px"}),"padding:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.paddingType)n="padding:".concat(e.global).concat(e.unit?e.unit:"px"," ");else{var a=e.custom?e.custom.split(" "):["0","0","0","0"],i=e.unit?e.unit:"px";n="padding:".concat(a[0]?a[0]:"0").concat(i," ").concat(a[1]?a[1]:"0").concat(i," ").concat(a[2]?a[2]:"0").concat(i," ").concat(a[3]?a[3]:"0").concat(i)}return"{"+n+"}"},j=function(e){if("object"===r()(e.global)||"object"===r()(e.custom)){var t={md:[],sm:[],xs:[]};return{md:(t="global"==e.marginType?b(y(f(f({},e.global),{},{unit:e.unit?e.unit:"px"}),"margin:{{key}};"),t):b(P(f(f({},e.custom),{},{unit:e.unit?e.unit:"px"}),"margin:{{key}};"),t)).md,sm:t.sm,xs:t.xs}}var n="";if("global"==e.marginType)n="margin:".concat(e.global).concat(e.unit?e.unit:"px"," ");else{var a=e.custom?e.custom.split(" "):["0","0","0","0"],i=e.unit?e.unit:"px";n="margin:".concat(a[0]?a[0]:"0").concat(i," ").concat(a[1]?a[1]:"0").concat(i," ").concat(a[2]?a[2]:"0").concat(i," ").concat(a[3]?a[3]:"0").concat(i)}return"{"+n+"}"},A=function(e){var t={md:[],sm:[],xs:[]};return e.values.md&&t.md.push("flex-direction:row-reverse"),e.values.sm&&t.sm.push("flex-direction:column-reverse"),e.values.xs&&t.xs.push("flex-direction:column-reverse"),{md:t.md,sm:t.sm,xs:t.xs}},__=(n(6),n(8),n(14),n(4),n(9),n(11),n(5),n(12),n(3),n(46),n(10),wp.i18n.__),q={},R=window.wpfnl_pro_block_object.plugin+"assets/img/blocks";q.qubely=wp.element.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M15.8 8c0-2.2-.8-4-2.3-5.5-1.5-1.5-3.4-2.3-5.5-2.3s-4 .8-5.5 2.3c-1.5 1.5-2.3 3.3-2.3 5.5s.8 4 2.3 5.5c1.5 1.5 3.3 2.3 5.5 2.3.9 0 1.8-.1 2.6-.4l-2.2-2.3c-.1-.1-.3-.2-.4-.2-1.4 0-2.5-.5-3.4-1.4-1-.9-1.4-2.1-1.4-3.5s.5-2.6 1.4-3.5c.9-.9 2-1.4 3.4-1.4s2.5.5 3.4 1.4c.9.9 1.4 2.1 1.4 3.5 0 .7-.1 1.4-.4 2-.2.5-.8.6-1.2.2-1.1-1.1-2.8-1.2-4-.2l2.5 2.6 2.1 2.2c.9.9 2.4 1 3.4.1l.3-.3-1.3-1.3c-.2-.2-.2-.4 0-.6 1-1.3 1.6-2.9 1.6-4.7z"})),q.solid=wp.element.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"19",height:"2"},wp.element.createElement("switch",null,wp.element.createElement("g",null,wp.element.createElement("path",{d:"M0 0h19v2H0z"})))),q.dot=wp.element.createElement("svg",{id:"Layer_1",xmlns:"http://www.w3.org/2000/svg",width:"18",height:"2"},wp.element.createElement("switch",null,wp.element.createElement("g",null,wp.element.createElement("g",null,wp.element.createElement("g",{transform:"translate(-1378 -121)"},wp.element.createElement("g",{transform:"translate(1229 110)"},wp.element.createElement("g",{transform:"translate(149 11)"},wp.element.createElement("circle",{class:"st0",cx:"1",cy:"1",r:"1"}),wp.element.createElement("circle",{class:"st0",cx:"17",cy:"1",r:"1"}),wp.element.createElement("circle",{class:"st0",cx:"5",cy:"1",r:"1"}),wp.element.createElement("circle",{class:"st0",cx:"13",cy:"1",r:"1"}),wp.element.createElement("circle",{class:"st0",cx:"9",cy:"1",r:"1"})))))))),q.dash=wp.element.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"2"},wp.element.createElement("switch",null,wp.element.createElement("g",null,wp.element.createElement("path",{d:"M18 2h-2V0h2v2zm-4 0h-2V0h2v2zm-4 0H8V0h2v2zM6 2H4V0h2v2zM2 2H0V0h2v2z"})))),q.wave=wp.element.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"21",height:"4"},wp.element.createElement("switch",null,wp.element.createElement("g",null,wp.element.createElement("path",{d:"M8 3.5c-.8 0-1.7-.3-2.5-.9C4 1.5 2.4 1.5.7 2.6c-.2.1-.5.1-.7-.2-.1-.2-.1-.5.2-.7 2-1.3 4-1.3 5.8 0 1.5 1 2.8 1 4.2-.2 1.6-1.4 3.4-1.4 5.3 0 1.5 1.1 3.1 1.2 4.7.1.2-.1.5-.1.7.1.1.2.1.5-.1.7-2 1.3-3.9 1.3-5.8-.1-1.5-1.1-2.9-1.1-4.1 0C9.9 3.1 9 3.5 8 3.5z"})))),q.vertical_top=wp.element.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{transform:"translate(1)",fill:"none"},wp.element.createElement("rect",{class:"qubely-svg-fill",x:"4",y:"4",width:"6",height:"12",rx:"1"}),wp.element.createElement("path",{class:"qubely-svg-stroke",d:"M0 1h14","stroke-width":"2","stroke-linecap":"square"}))),q.vertical_middle=wp.element.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("g",{transform:"translate(1 1)"},wp.element.createElement("rect",{class:"qubely-svg-fill",x:"4",width:"6",height:"14",rx:"1"}),wp.element.createElement("path",{d:"M0 7h2",class:"qubely-svg-stroke","stroke-width":"2","stroke-linecap":"square"})),wp.element.createElement("path",{d:"M13 8h2",class:"qubely-svg-stroke","stroke-width":"2","stroke-linecap":"square"}))),q.vertical_bottom=wp.element.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{transform:"translate(1)",fill:"none"},wp.element.createElement("rect",{class:"qubely-svg-fill",x:"4",width:"6",height:"12",rx:"1"}),wp.element.createElement("path",{d:"M0 15h14",class:"qubely-svg-stroke","stroke-width":"2","stroke-linecap":"square"}))),q.icon_classic=wp.element.createElement("img",{src:"".concat(R,"/icon/classic.svg"),alt:__("Classic")}),q.icon_fill=wp.element.createElement("img",{src:"".concat(R,"/icon/fill.svg"),alt:__("Fill")}),q.icon_line=wp.element.createElement("img",{src:"".concat(R,"/icon/outline.svg"),alt:__("Underline")}),q.btn_fill=wp.element.createElement("img",{src:"".concat(R,"/button/fill.svg"),alt:__("Fill")}),q.btn_outline=wp.element.createElement("img",{src:"".concat(R,"/button/outline.svg"),alt:__("Outline")}),q.pie_fill=wp.element.createElement("img",{src:"".concat(R,"/pieprogress/fill.svg"),alt:__("Fill")}),q.pie_outline=wp.element.createElement("img",{src:"".concat(R,"/pieprogress/outline.svg"),alt:__("Outline")}),q.pie_outline_fill=wp.element.createElement("img",{src:"".concat(R,"/pieprogress/outline-fill.svg"),alt:__("Outline Fill")}),q.corner_square=wp.element.createElement("svg",{width:"12",height:"12",viewBox:"0 0 12 12",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M0 1h10.967v10.763","stroke-width":"2",className:"qubely-svg-stroke",fill:"none"})),q.corner_rounded=wp.element.createElement("svg",{width:"12",height:"12",viewBox:"0 0 12 12",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M0 1h6.967c2.209 0 4 1.791 4 4v6.763","stroke-width":"2",className:"qubely-svg-stroke",fill:"none"})),q.corner_round=wp.element.createElement("svg",{width:"12",height:"12",viewBox:"0 0 12 12",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M0 1h1.967c4.971 0 9 4.029 9 9v1.763","stroke-width":"2",className:"qubely-svg-stroke",fill:"none"})),q.tab_tabs=wp.element.createElement("img",{src:"".concat(R,"/tab/tabs.svg"),alt:__("Tabs")}),q.tab_pills=wp.element.createElement("img",{src:"".concat(R,"/tab/pills.svg"),alt:__("Pills")}),q.tab_underline=wp.element.createElement("img",{src:"".concat(R,"/tab/underline.svg"),alt:__("Underline")}),q.verticaltabs_1=wp.element.createElement("img",{src:"".concat(R,"/verticaltabs/layout-1.svg"),alt:__("Layout 1")}),q.verticaltabs_2=wp.element.createElement("img",{src:"".concat(R,"/verticaltabs/layout-2.svg"),alt:__("Layout 2")}),q.verticaltabs_3=wp.element.createElement("img",{src:"".concat(R,"/verticaltabs/layout-3.svg"),alt:__("Layout 3")}),q.social_normal=wp.element.createElement("img",{src:"".concat(R,"//socialicon/normal.svg"),alt:__("Normal")}),q.social_fill=wp.element.createElement("img",{src:"".concat(R,"//socialicon/fill.svg"),alt:__("Fill")}),q.accordion_fill=wp.element.createElement("img",{src:"".concat(R,"/accordion/fill.svg"),alt:__("Fill")}),q.accordion_classic=wp.element.createElement("img",{src:"".concat(R,"/accordion/classic.svg"),alt:__("Classic")}),q.infobox_1=wp.element.createElement("img",{src:"".concat(R,"/infobox/1.svg"),alt:__("Layout 1")}),q.infobox_2=wp.element.createElement("img",{src:"".concat(R,"/infobox/2.svg"),alt:__("Layout 2")}),q.infobox_3=wp.element.createElement("img",{src:"".concat(R,"/infobox/3.svg"),alt:__("Layout 3")}),q.infobox_4=wp.element.createElement("img",{src:"".concat(R,"/infobox/4.svg"),alt:__("Layout 4")}),q.testimonial_1=wp.element.createElement("img",{src:"".concat(R,"/testimonial/1.svg"),alt:__("Testimonial 1")}),q.testimonial_2=wp.element.createElement("img",{src:"".concat(R,"/testimonial/2.svg"),alt:__("Testimonial 2")}),q.testimonial_3=wp.element.createElement("img",{src:"".concat(R,"/testimonial/3.svg"),alt:__("Testimonial 3")}),q.avatar_left=wp.element.createElement("img",{src:"".concat(R,"/testimonial/avatars/1.svg"),alt:__("Avatar Left")}),q.avatar_right=wp.element.createElement("img",{src:"".concat(R,"/testimonial/avatars/2.svg"),alt:__("Avatar Right")}),q.avatar_top=wp.element.createElement("img",{src:"".concat(R,"/testimonial/avatars/3.svg"),alt:__("Avatar Top")}),q.avatar_bottom=wp.element.createElement("img",{src:"".concat(R,"/testimonial/avatars/4.svg"),alt:__("Avatar Bottom")}),q.team_1=wp.element.createElement("img",{src:"".concat(R,"/team/1.svg"),alt:__("Layout 1")}),q.team_2=wp.element.createElement("img",{src:"".concat(R,"/team/2.svg"),alt:__("Layout 2")}),q.team_3=wp.element.createElement("img",{src:"".concat(R,"/team/3.svg"),alt:__("Layout 3")}),q.list_fill=wp.element.createElement("img",{src:"".concat(R,"/list/1.svg"),alt:__("Fill")}),q.list_classic=wp.element.createElement("img",{src:"".concat(R,"/list/2.svg"),alt:__("Classic")}),q.form_classic=wp.element.createElement("img",{src:"".concat(R,"/form/classic.svg"),alt:__("Classic")}),q.form_material=wp.element.createElement("img",{src:"".concat(R,"/form/material.svg"),alt:__("Material")}),q.videopopup_fill=wp.element.createElement("img",{src:"".concat(R,"/videopopup/fill.svg"),alt:__("Fill")}),q.videopopup_classic=wp.element.createElement("img",{src:"".concat(R,"/videopopup/classic.svg"),alt:__("Classic")}),q.postgrid_1=wp.element.createElement("img",{src:"".concat(R,"/postgrid/1.svg"),alt:__("Layout 1")}),q.postgrid_2=wp.element.createElement("img",{src:"".concat(R,"/postgrid/2.svg"),alt:__("Layout 2")}),q.postgrid_3=wp.element.createElement("img",{src:"".concat(R,"/postgrid/pro1.svg"),alt:__("Layout 3")}),q.postgrid_4=wp.element.createElement("img",{src:"".concat(R,"/postgrid/pro2.svg"),alt:__("Layout 4")}),q.postgrid_5=wp.element.createElement("img",{src:"".concat(R,"/postgrid/pro3.svg"),alt:__("Layout 4")}),q.postgrid_design_1=wp.element.createElement("img",{src:"".concat(R,"/postgrid/11.svg"),alt:__("Design 1")}),q.postgrid_design_2=wp.element.createElement("img",{src:"".concat(R,"/postgrid/12.svg"),alt:__("Design 2")}),q.postgrid_design_3=wp.element.createElement("img",{src:"".concat(R,"/postgrid/13.svg"),alt:__("Design 3")}),q.postgrid_design_4=wp.element.createElement("img",{src:"".concat(R,"/postgrid/14.svg"),alt:__("Design 4")}),q.postgrid_design_5=wp.element.createElement("img",{src:"".concat(R,"/postgrid/15.svg"),alt:__("Design 5")}),q.postgrid_design_6=wp.element.createElement("img",{src:"".concat(R,"/postgrid/16.svg"),alt:__("Design 6")}),q.h1=wp.element.createElement("svg",{width:"17",height:"13",viewBox:"0 0 17 13",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M16.809 13h-1.147v-4.609c0-.55.013-.986.039-1.309l-.276.259c-.109.094-.474.394-1.096.898l-.576-.728 2.1-1.65h.957v7.139z"}))),q.h2=wp.element.createElement("svg",{width:"19",height:"13",viewBox:"0 0 19 13",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M18.278 13h-4.839v-.869l1.841-1.851c.544-.557.904-.951 1.082-1.184.177-.233.307-.452.388-.657.081-.205.122-.425.122-.659 0-.322-.097-.576-.291-.762-.194-.186-.461-.278-.803-.278-.273 0-.538.05-.793.151-.256.101-.551.283-.886.547l-.62-.757c.397-.335.783-.573 1.157-.713s.773-.21 1.196-.21c.664 0 1.196.173 1.597.52.4.347.601.813.601 1.399 0 .322-.058.628-.173.918-.116.29-.293.588-.532.896-.239.308-.637.723-1.194 1.248l-1.24 1.201v.049h3.389v1.011z"}))),q.h3=wp.element.createElement("svg",{width:"19",height:"14",viewBox:"0 0 19 14",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M18.01 7.502c0 .452-.132.829-.396 1.13-.264.301-.635.504-1.113.608v.039c.573.072 1.003.25 1.289.535.286.285.43.663.43 1.135 0 .687-.243 1.217-.728 1.589-.485.373-1.175.559-2.07.559-.791 0-1.458-.129-2.002-.386v-1.021c.303.15.623.265.962.347.339.081.664.122.977.122.553 0 .967-.103 1.24-.308.273-.205.41-.522.41-.952 0-.381-.151-.661-.454-.84-.303-.179-.778-.269-1.426-.269h-.62v-.933h.63c1.139 0 1.709-.394 1.709-1.182 0-.306-.099-.542-.298-.708-.199-.166-.492-.249-.879-.249-.27 0-.531.038-.781.115-.251.076-.547.225-.889.447l-.562-.801c.654-.482 1.414-.723 2.28-.723.719 0 1.281.155 1.685.464.404.309.605.736.605 1.279z"}))),q.h4=wp.element.createElement("svg",{width:"19",height:"13",viewBox:"0 0 19 13",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M18.532 11.442h-.962v1.558h-1.118v-1.558h-3.262v-.884l3.262-4.717h1.118v4.648h.962v.952zm-2.08-.952v-1.792c0-.638.016-1.16.049-1.567h-.039c-.091.215-.234.475-.43.781l-1.772 2.578h2.192z"}))),q.h5=wp.element.createElement("svg",{width:"19",height:"14",viewBox:"0 0 19 14",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M15.861 8.542c.719 0 1.289.19 1.709.571.42.381.63.9.63 1.558 0 .762-.238 1.357-.715 1.785-.477.428-1.155.642-2.034.642-.798 0-1.424-.129-1.88-.386v-1.04c.264.15.566.265.908.347.342.081.659.122.952.122.518 0 .911-.116 1.182-.347.27-.231.405-.57.405-1.016 0-.853-.544-1.279-1.631-1.279-.153 0-.342.015-.566.046-.225.031-.422.066-.591.105l-.513-.303.273-3.486h3.711v1.021h-2.7l-.161 1.768.417-.068c.164-.026.365-.039.603-.039z"}))),q.h6=wp.element.createElement("svg",{width:"19",height:"14",viewBox:"0 0 19 14",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{className:"qubely-svg-fill","fill-rule":"nonzero"},wp.element.createElement("path",{d:"M10.83 13h-2.109v-5.792h-5.924v5.792h-2.101v-12.85h2.101v5.256h5.924v-5.256h2.109z"}),wp.element.createElement("path",{d:"M13.459 9.958c0-2.793 1.138-4.189 3.413-4.189.358 0 .661.028.908.083v.957c-.247-.072-.534-.107-.859-.107-.765 0-1.34.205-1.724.615-.384.41-.592 1.068-.625 1.973h.059c.153-.264.368-.468.645-.613.277-.145.602-.217.977-.217.648 0 1.152.199 1.514.596.361.397.542.936.542 1.616 0 .749-.209 1.34-.627 1.775-.418.435-.989.652-1.711.652-.511 0-.955-.123-1.333-.369s-.668-.604-.872-1.074c-.203-.47-.305-1.036-.305-1.697zm2.49 2.192c.394 0 .697-.127.911-.381.213-.254.32-.617.32-1.089 0-.41-.1-.732-.3-.967-.2-.234-.5-.352-.901-.352-.247 0-.475.053-.684.159-.208.106-.373.251-.493.435s-.181.372-.181.564c0 .459.125.846.374 1.16.249.314.567.471.955.471z"}))),q.p=wp.element.createElement("svg",{width:"20px",height:"20px",viewBox:"0 0 1792 1792",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M1534 189v73q0 29-18.5 61t-42.5 32q-50 0-54 1-26 6-32 31-3 11-3 64v1152q0 25-18 43t-43 18h-108q-25 0-43-18t-18-43v-1218h-143v1218q0 25-17.5 43t-43.5 18h-108q-26 0-43.5-18t-17.5-43v-496q-147-12-245-59-126-58-192-179-64-117-64-259 0-166 88-286 88-118 209-159 111-37 417-37h479q25 0 43 18t18 43z"})),q.span=wp.element.createElement("svg",{width:"20px",height:"20px",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20"},wp.element.createElement("rect",{x:"0",fill:"none",width:"20px",height:"20px"}),wp.element.createElement("g",null,wp.element.createElement("path",{d:"M9 6l-4 4 4 4-1 2-6-6 6-6zm2 8l4-4-4-4 1-2 6 6-6 6z"}))),q.div=wp.element.createElement("svg",{width:"20px",height:"20px",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20"},wp.element.createElement("rect",{x:"0",fill:"none",width:"20px",height:"20px"}),wp.element.createElement("g",null,wp.element.createElement("path",{d:"M9 6l-4 4 4 4-1 2-6-6 6-6zm2 8l4-4-4-4 1-2 6 6-6 6z"}))),q.pricing={1:wp.element.createElement("img",{src:"".concat(R,"/pricing/1.svg"),alt:__("Layout 1")}),2:wp.element.createElement("img",{src:"".concat(R,"/pricing/2.svg"),alt:__("Layout 2")}),3:wp.element.createElement("img",{src:"".concat(R,"/pricing/3.svg"),alt:__("Layout 3")}),4:wp.element.createElement("img",{src:"".concat(R,"/pricing/4.svg"),alt:__("Layout 4")}),5:wp.element.createElement("img",{src:"".concat(R,"/pricing/5.svg"),alt:__("Layout 5")}),6:wp.element.createElement("img",{src:"".concat(R,"/pricing/6.svg"),alt:__("Layout 6")})},q.pricing.badge={1:wp.element.createElement("img",{src:"".concat(R,"/pricing/badges/1.svg"),alt:__("Badge 1")}),2:wp.element.createElement("img",{src:"".concat(R,"/pricing/badges/2.svg"),alt:__("Badge 2")}),3:wp.element.createElement("img",{src:"".concat(R,"/pricing/badges/3.svg"),alt:__("Badge 3")}),4:wp.element.createElement("img",{src:"".concat(R,"/pricing/badges/4.svg"),alt:__("Badge 4")}),5:wp.element.createElement("img",{src:"".concat(R,"/pricing/badges/5.svg"),alt:__("Badge 5")}),6:wp.element.createElement("img",{src:"".concat(R,"/pricing/badges/6.svg"),alt:__("Badge 6")})},q.image={simple:wp.element.createElement("img",{src:"".concat(R,"/image/simple.svg"),alt:__("Simple")}),blurb:wp.element.createElement("img",{src:"".concat(R,"/image/blurb.svg"),alt:__("Blurb")})},q.copy=wp.element.createElement("svg",{width:"20px",height:"15px",viewBox:"0 0 1792 1792",class:"dashicon",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M1696 384q40 0 68 28t28 68v1216q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-288h-544q-40 0-68-28t-28-68v-672q0-40 20-88t48-76l408-408q28-28 76-48t88-20h416q40 0 68 28t28 68v328q68-40 128-40h416zm-544 213l-299 299h299v-299zm-640-384l-299 299h299v-299zm196 647l316-316v-416h-384v416q0 40-28 68t-68 28h-416v640h512v-256q0-40 20-88t48-76zm956 804v-1152h-384v416q0 40-28 68t-68 28h-416v640h896z"})),q.paste=wp.element.createElement("svg",{width:"20px",height:"15px",viewBox:"0 0 1792 1792",class:"dashicon",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"})),q.spacing={top:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"}))),right:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"}))),bottom:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M3 20h16v2h-16z"}))),left:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#2184F9",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"})))},q.border={top:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"}))),right:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"}))),bottom:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#CACCCE",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#2184F9",d:"M3 20h16v2h-16z"}))),left:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{fill:"#2184F9",d:"M0 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M20 3h2v16h-2z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 0h16v2h-16z"}),wp.element.createElement("path",{fill:"#CACCCE",d:"M3 20h16v2h-16z"})))},q.borderRadius={topLeft:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{d:"M1.88 0c-1.038 0-1.88.842-1.88 1.88v6.71h1.88v-5.77c0-.519.421-.94.94-.94h5.77v-1.88h-6.71z",id:"Path",fill:"#2184F9"}),wp.element.createElement("path",{d:"M13.41 0v1.88h5.77c.519 0 .94.421.94.94v5.77h1.88v-6.71c0-1.038-.842-1.88-1.88-1.88h-6.71zM1.88 13.41h-1.88v6.71c0 1.038.842 1.88 1.88 1.88h6.71v-1.88h-5.77c-.519 0-.94-.421-.94-.94v-5.77zM13.41 20.12v1.88h6.71c1.038 0 1.88-.842 1.88-1.88v-6.71h-1.88v5.77c0 .519-.421.94-.94.94h-5.77z",fill:"#CACCCE"}))),topRight:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{d:"M1.88 0c-1.038 0-1.88.842-1.88 1.88v6.71h1.88v-5.77c0-.519.421-.94.94-.94h5.77v-1.88h-6.71z",id:"Path",fill:"#CACCCE"}),wp.element.createElement("path",{d:"M13.41 0v1.88h5.77c.519 0 .94.421.94.94v5.77h1.88v-6.71c0-1.038-.842-1.88-1.88-1.88h-6.71z",id:"Path",fill:"#2184F9"}),wp.element.createElement("path",{d:"M1.88 13.41h-1.88v6.71c0 1.038.842 1.88 1.88 1.88h6.71v-1.88h-5.77c-.519 0-.94-.421-.94-.94v-5.77zM13.41 20.12v1.88h6.71c1.038 0 1.88-.842 1.88-1.88v-6.71h-1.88v5.77c0 .519-.421.94-.94.94h-5.77z",fill:"#CACCCE"}))),bottomRight:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{d:"M1.88 0c-1.038 0-1.88.842-1.88 1.88v6.71h1.88v-5.77c0-.519.421-.94.94-.94h5.77v-1.88h-6.71zM13.41 0v1.88h5.77c.519 0 .94.421.94.94v5.77h1.88v-6.71c0-1.038-.842-1.88-1.88-1.88h-6.71zM1.88 13.41h-1.88v6.71c0 1.038.842 1.88 1.88 1.88h6.71v-1.88h-5.77c-.519 0-.94-.421-.94-.94v-5.77z",id:"Path",fill:"#CACCCE"}),wp.element.createElement("path",{d:"M13.41 20.12v1.88h6.71c1.038 0 1.88-.842 1.88-1.88v-6.71h-1.88v5.77c0 .519-.421.94-.94.94h-5.77z",fill:"#2184F9"}))),bottomLeft:wp.element.createElement("svg",{width:"22",height:"22",viewBox:"0 0 22 22",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"none"},wp.element.createElement("path",{d:"M1.88 0c-1.038 0-1.88.842-1.88 1.88v6.71h1.88v-5.77c0-.519.421-.94.94-.94h5.77v-1.88h-6.71zM13.41 0v1.88h5.77c.519 0 .94.421.94.94v5.77h1.88v-6.71c0-1.038-.842-1.88-1.88-1.88h-6.71z",id:"Path",fill:"#CACCCE"}),wp.element.createElement("path",{d:"M1.88 13.41h-1.88v6.71c0 1.038.842 1.88 1.88 1.88h6.71v-1.88h-5.77c-.519 0-.94-.421-.94-.94v-5.77z",id:"Path",fill:"#2184F9"}),wp.element.createElement("path",{d:"M13.41 20.12v1.88h6.71c1.038 0 1.88-.842 1.88-1.88v-6.71h-1.88v5.77c0 .519-.421.94-.94.94h-5.77z",fill:"#CACCCE"})))},q.inlineColorIcon=wp.element.createElement("svg",{"aria-hidden":"true",role:"img",focusable:"false",class:"dashicon dashicons-editor-textcolor",xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 20 20"},wp.element.createElement("path",{d:"M13.23 15h1.9L11 4H9L5 15h1.88l1.07-3h4.18zm-1.53-4.54H8.51L10 5.6z"})),q.highlighterIcon=wp.element.createElement("svg",{"aria-hidden":"true",role:"img",focusable:"false",class:"dashicon dashicons-admin-customizer",xmlns:"http://www.w3.org/2000/svg",width:"18",height:"17",viewBox:"0 0 20 20"},wp.element.createElement("path",{d:"M18.33 3.57s.27-.8-.31-1.36c-.53-.52-1.22-.24-1.22-.24-.61.3-5.76 3.47-7.67 5.57-.86.96-2.06 3.79-1.09 4.82.92.98 3.96-.17 4.79-1 2.06-2.06 5.21-7.17 5.5-7.79zM1.4 17.65c2.37-1.56 1.46-3.41 3.23-4.64.93-.65 2.22-.62 3.08.29.63.67.8 2.57-.16 3.46-1.57 1.45-4 1.55-6.15.89z"})),q.upperCaseIcon=wp.element.createElement("svg",{viewBox:"0 0 20 20",height:"25",width:"25",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("mask",{id:"a",fill:"#fff"},wp.element.createElement("path",{d:"m20 20h-20v-20h20z",fill:"#fff","fill-rule":"evenodd"})),wp.element.createElement("path",{d:"m2 3v2.5h4.16666667v10.5h2.5v-10.5h4.16666663v-2.5zm16 4.5h-7.5v2.5h2.5v6h2.5v-6h2.5z",mask:"url(#a)"})),q.arrow_down=wp.element.createElement("svg",{width:"18",height:"18",viewBox:"0 0 1792 1792",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z"})),q.arrow_up=wp.element.createElement("svg",{width:"18",height:"18",viewBox:"0 0 1792 1792",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("path",{d:"M1395 1184q0 13-10 23l-50 50q-10 10-23 10t-23-10l-393-393-393 393q-10 10-23 10t-23-10l-50-50q-10-10-10-23t10-23l466-466q10-10 23-10t23 10l466 466q10 10 10 23z"})),q.circleThin=wp.element.createElement("svg",(u={version:"1.1",xmlns:"http://www.w3.org/2000/svg"},c()(u,"xmlns","http://www.w3.org/1999/xlink"),c()(u,"width","15"),c()(u,"height","15"),c()(u,"viewBox","0 0 12 14"),u),wp.element.createElement("path",{d:"M6 2q-1.016 0-1.941 0.398t-1.594 1.066-1.066 1.594-0.398 1.941 0.398 1.941 1.066 1.594 1.594 1.066 1.941 0.398 1.941-0.398 1.594-1.066 1.066-1.594 0.398-1.941-0.398-1.941-1.066-1.594-1.594-1.066-1.941-0.398zM12 7q0 1.633-0.805 3.012t-2.184 2.184-3.012 0.805-3.012-0.805-2.184-2.184-0.805-3.012 0.805-3.012 2.184-2.184 3.012-0.805 3.012 0.805 2.184 2.184 0.805 3.012z"})),q.circleDot=wp.element.createElement("svg",(d={version:"1.1",xmlns:"http://www.w3.org/2000/svg"},c()(d,"xmlns","http://www.w3.org/1999/xlink"),c()(d,"width","15"),c()(d,"height","15"),c()(d,"viewBox","0 0 12 14"),c()(d,"fill","#2184f9"),d),wp.element.createElement("path",{d:"M8 7q0 0.828-0.586 1.414t-1.414 0.586-1.414-0.586-0.586-1.414 0.586-1.414 1.414-0.586 1.414 0.586 0.586 1.414zM6 2.75q-1.156 0-2.133 0.57t-1.547 1.547-0.57 2.133 0.57 2.133 1.547 1.547 2.133 0.57 2.133-0.57 1.547-1.547 0.57-2.133-0.57-2.133-1.547-1.547-2.133-0.57zM12 7q0 1.633-0.805 3.012t-2.184 2.184-3.012 0.805-3.012-0.805-2.184-2.184-0.805-3.012 0.805-3.012 2.184-2.184 3.012-0.805 3.012 0.805 2.184 2.184 0.805 3.012z"})),q.ellipsis_v=wp.element.createElement("svg",(m={version:"1.1",xmlns:"http://www.w3.org/2000/svg"},c()(m,"xmlns","http://www.w3.org/1999/xlink"),c()(m,"width","3"),c()(m,"height","15"),c()(m,"viewBox","0 0 3 14"),m),wp.element.createElement("path",{d:"M3 9.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531zM3 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531zM3 1.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531z"})),q.ellipsis_h=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"11",height:"15",viewBox:"0 0 11 14"},wp.element.createElement("path",{d:"M3 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531zM7 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531zM11 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531z"})),q.left=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"13",height:"15",viewBox:"0 0 13 14"},wp.element.createElement("path",{d:"M12 7v1q0 0.414-0.254 0.707t-0.66 0.293h-5.5l2.289 2.297q0.297 0.281 0.297 0.703t-0.297 0.703l-0.586 0.594q-0.289 0.289-0.703 0.289-0.406 0-0.711-0.289l-5.086-5.094q-0.289-0.289-0.289-0.703 0-0.406 0.289-0.711l5.086-5.078q0.297-0.297 0.711-0.297 0.406 0 0.703 0.297l0.586 0.578q0.297 0.297 0.297 0.711t-0.297 0.711l-2.289 2.289h5.5q0.406 0 0.66 0.293t0.254 0.707z"})),q.plus=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"14",height:"30",fill:"#9b9b9b",viewBox:"0 0 11 14"},wp.element.createElement("path",{d:"M11 5.75v1.5q0 0.312-0.219 0.531t-0.531 0.219h-3.25v3.25q0 0.312-0.219 0.531t-0.531 0.219h-1.5q-0.312 0-0.531-0.219t-0.219-0.531v-3.25h-3.25q-0.312 0-0.531-0.219t-0.219-0.531v-1.5q0-0.312 0.219-0.531t0.531-0.219h3.25v-3.25q0-0.312 0.219-0.531t0.531-0.219h1.5q0.312 0 0.531 0.219t0.219 0.531v3.25h3.25q0.312 0 0.531 0.219t0.219 0.531z"})),q.plus_circle=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"12",height:"14",viewBox:"0 0 12 14"},wp.element.createElement("path",{d:"M9.5 7.5v-1q0-0.203-0.148-0.352t-0.352-0.148h-2v-2q0-0.203-0.148-0.352t-0.352-0.148h-1q-0.203 0-0.352 0.148t-0.148 0.352v2h-2q-0.203 0-0.352 0.148t-0.148 0.352v1q0 0.203 0.148 0.352t0.352 0.148h2v2q0 0.203 0.148 0.352t0.352 0.148h1q0.203 0 0.352-0.148t0.148-0.352v-2h2q0.203 0 0.352-0.148t0.148-0.352zM12 7q0 1.633-0.805 3.012t-2.184 2.184-3.012 0.805-3.012-0.805-2.184-2.184-0.805-3.012 0.805-3.012 2.184-2.184 3.012-0.805 3.012 0.805 2.184 2.184 0.805 3.012z"})),q.delete=wp.element.createElement("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",width:"13",height:"15",viewBox:"0 0 13 15",fill:"#F7F7F7"},wp.element.createElement("path",{d:"M9.5 7.5v-1q0-0.203-0.148-0.352t-0.352-0.148h-2v-2q0-0.203-0.148-0.352t-0.352-0.148h-1q-0.203 0-0.352 0.148t-0.148 0.352v2h-2q-0.203 0-0.352 0.148t-0.148 0.352v1q0 0.203 0.148 0.352t0.352 0.148h2v2q0 0.203 0.148 0.352t0.352 0.148h1q0.203 0 0.352-0.148t0.148-0.352v-2h2q0.203 0 0.352-0.148t0.148-0.352zM12 7q0 1.633-0.805 3.012t-2.184 2.184-3.012 0.805-3.012-0.805-2.184-2.184-0.805-3.012 0.805-3.012 2.184-2.184 3.012-0.805 3.012 0.805 2.184 2.184 0.805 3.012z"})),q.addColor=wp.element.createElement("svg",{width:"16",height:"16",xmlns:"http://www.w3.org/2000/svg"},wp.element.createElement("g",{fill:"#CDCDCD","fill-rule":"evenodd"},wp.element.createElement("rect",{x:"6.957",width:"2.087",height:"16",rx:"1.043"}),wp.element.createElement("path",{d:"M15.652 8a1.09 1.09 0 01-1.09 1.09H1.438a1.09 1.09 0 110-2.18H14.56c.603 0 1.091.488 1.091 1.09z"}))),wp.i18n.__,wp.element.useState;var B=wp.components;B.Tooltip,B.Dropdown,B.ColorPicker,wp.i18n.__,n(27).diff;var N=wp.element,L=(N.Component,N.Fragment,N.createRef,wp.components),D=(L.Tooltip,L.Dropdown,L.PanelBody,L.Notice,L.RangeControl,L.Modal,wp.data.select,wp.editPost);D.PluginSidebar,D.PluginSidebarMoreMenuItem;var F=function(e,t,n){return e.replace(new RegExp(t,"g"),n)},z=function(e){return"object"==r()(e)&&0!=Object.keys(e).length},I=function(e,t){return e.replace(new RegExp("{{QUBELY}}","g"),".qubely-block-"+t)},G=function(e,t){var n="";return t.forEach((function(e){n+=e+";"})),e+"{"+n+"}"},W=function(e,t){var n="";return t.forEach((function(t){n+=e+t})),n},H=function(e,t,n,a){if(a="object"!=r()(a)?a:Y(a).data,"string"==typeof e){if(e){if(a){var i=I(e,t);return"boolean"==typeof a?[i]:-1==i.indexOf("{{")&&i.indexOf("{")<0?[i+a]:[F(i,"{{"+n+"}}",a)]}return[]}return[I(a,t)]}var o=[];return e.forEach((function(e){o.push(F(I(e,t),"{{"+n+"}}",a))})),o},Y=function(e){return e.openTypography?{data:x(e),action:"append"}:e.openBg?{data:(t=e,n="{",n+=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,a=arguments.length>3?arguments[3]:void 0,i=arguments.length>4?arguments[4]:void 0,r=arguments.length>5?arguments[5]:void 0,o=arguments.length>6?arguments[6]:void 0,l=arguments.length>7?arguments[7]:void 0,s=arguments.length>8&&void 0!==arguments[8]?arguments[8]:"local",c=arguments.length>9&&void 0!==arguments[9]?arguments[9]:{},p=o?"background-color:"+o+";":"";return"image"==e?p+=("local"===s?t.hasOwnProperty("url")?"background-image:url("+t.url+");":"":c.hasOwnProperty("url")?"background-image:url("+c.url+");":"")+(n?"background-position:"+n+";":"")+(a?"background-attachment:"+a+";":"")+(i?"background-repeat:"+i+";":"")+(r?"background-size:"+r+";":""):"gradient"==e&&(l&&"linear"==l.type?p+="background-image: linear-gradient("+l.direction+"deg, "+l.color1+" "+l.start+"%,"+l.color2+" "+l.stop+"%);":(void 0===l.radial&&(l.radial="center"),p+="background-image: radial-gradient( circle at "+l.radial+" , "+l.color1+" "+l.start+"%,"+l.color2+" "+l.stop+"%);")),p}(t.bgType,t.bgImage,t.bgimgPosition,t.bgimgAttachment,t.bgimgRepeat,t.bgimgSize,t.bgDefaultColor,t.bgGradient,t.bgimageSource,t.externalImageUrl),n+="}","video"==t.bgType&&t.bgVideoFallback&&t.bgVideoFallback.url&&(n+="background-image: url("+t.bgVideoFallback.url+"); background-position: center; background-repeat: no-repeat; background-size: cover;"),"{}"!=n?n:{}),action:"append"}:e.openBorder?{data:g(e),action:"append"}:e.tableBorder?{data:w(e),action:"append"}:e.openShadow&&e.color?{data:h(e),action:"append"}:e.direction?{data:v(e,"return"),action:"append"}:void 0!==e.top||void 0!==e.left||void 0!==e.right||void 0!==e.bottom?{data:E(e),action:"replace"}:e.openShape?{data:C(e),action:"append"}:e.openColor?{data:S(e),action:"append"}:e.spaceTop||e.spaceBottom?{data:k(e),action:"append"}:e.selectedSize?{data:O(e),action:"append"}:e.openBorderRadius?{data:T(e),action:"append"}:e.openPadding?{data:M(e),action:"append"}:e.openMargin?{data:j(e),action:"append"}:e.openRowReverse?{data:A(e),action:"append"}:e.openTransfrom?{data:_(e),action:"append"}:{data:"",action:"append"};var t,n},V=function(e,t,n,a){var i=!0;return t.hasOwnProperty("condition")&&t.condition.forEach((function(t){var n=i;if("=="==t.relation||"==="==t.relation)if("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value)i=e[t.key]==t.value;else{var a=!1;t.value.forEach((function(n){e[t.key]==n&&(a=!0)})),a&&(i=!0)}else if("!="==t.relation||"!=="==t.relation)if("string"==typeof t.value||"number"==typeof t.value||"boolean"==typeof t.value)i=e[t.key]!=t.value;else{var r=!1;t.value.forEach((function(n){e[t.key]!=n&&(r=!0)})),r&&(i=!0)}0==n&&(i=!1)})),i},X=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],i=window.document,r="qubely-block-"+t;if(n&&a&&(r="qubely-global-styles"),null===i.getElementById(r)){var o=document.createElement("style");o.type="text/css",o.id=r,o.styleSheet?o.styleSheet.cssText=e:o.innerHTML=e,i.getElementsByTagName("head")[0].appendChild(o)}else i.getElementById(r).innerHTML=e};n(24),wp.wpfnlProComponents={Typography:a.a,CssGenerator:{CssGenerator:function(e,t,n){var a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],i=arguments.length>4&&void 0!==arguments[4]&&arguments[4],o=arguments.length>5&&void 0!==arguments[5]&&arguments[5],l=arguments.length>6&&void 0!==arguments[6]?arguments[6]:void 0;if(n){var s="",c=[],p=[],f=[],u=[];if(Object.keys(e).forEach((function(o){var s=[];(s=!1===i?"string"==typeof t?wp.blocks.getBlockType("wpfunnelspro/"+t).attributes:t:l)[o]&&s[o].hasOwnProperty("style")&&s[o].style.forEach((function(t,i){var l=t.selector;if(V(e,t))if("object"==r()(e[o])){var s=!1,d="";if(e[o].md&&(s=!0,d="object"==r()(e[o].md)?Y(e[o].md).data:e[o].md+(e[o].unit||""),c=c.concat(H(l,n,o,d))),e[o].sm&&(s=!0,d="object"==r()(e[o].sm)?Y(e[o].sm).data:e[o].sm+(e[o].unit||""),p=p.concat(H(l,n,o,d))),e[o].xs&&(s=!0,d="object"==r()(e[o].xs)?Y(e[o].xs).data:e[o].xs+(e[o].unit||""),f=f.concat(H(l,n,o,d))),!s){var m=Y(e[o]),v=I(l,n);"object"==r()(m.data)?0!=Object.keys(m.data).length&&(m.data.background&&u.push(v+m.data.background),z(m.data.md)&&c.push(G(v,m.data.md)),z(m.data.sm)&&p.push(G(v,m.data.sm)),z(m.data.xs)&&f.push(G(v,m.data.xs)),m.data.simple&&u.push(v+m.data.simple),m.data.font&&c.unshift(m.data.font),m.data.shape&&(m.data.shape.forEach((function(e){u.push(v+e)})),z(m.data.data.md)&&c.push(W(v,m.data.data.md)),z(m.data.data.sm)&&p.push(W(v,m.data.data.sm)),z(m.data.data.xs)&&f.push(W(v,m.data.data.xs)))):m.data&&-1==m.data.indexOf("{{")&&("append"==m.action?u.push(v+m.data):u.push(H(l,n,o,m.data)))}}else"hideTablet"==o?a&&(p=p.concat(H(l,n,o,e[o]))):"hideMobile"==o?a&&(f=f.concat(H(l,n,o,e[o]))):e[o]&&(u=u.concat(H(l,n,o,e[o])))}))})),c.length>0&&(s+=c.join("")),p.length>0&&(s+="@media (max-width: 1199px) {"+p.join("")+"}"),f.length>0&&(s+="@media (max-width: 991px) {"+f.join("")+"}"),u.length>0&&(s+=u.join("")),i&&(s=s.replace(new RegExp(".qubely-block-global","g"),o?".qubely-frontend":".qubely-editor")),a)return s;X(s,n,i,o)}},objectReplace:G,objectAppend:W,singleField:H}}},function(e,t,n){e.exports=n(57)},function(e,t){},function(e,t){},,,,,,,function(e,t,n){"use strict";n.r(t);var a={buttonAlign:{type:"string",default:"center"},buttonTextAlign:{type:"string",default:"center"},buttonText:{type:"string",default:"Accept Offer"},offerAction:{type:"string",default:"accept"},offerType:{type:"string",default:"upsell"},outline:{type:"string",default:"fill"},buttonColor:{type:"string",default:"#39414d"},buttonTextColor:{type:"string",default:"#fff"},paddingTopBottom:{type:"number",default:14},paddingLeftRight:{type:"number",default:25},buttonRadius:{type:"number",default:5},typography:{type:"object",default:{},style:[{selector:".wpfnl-offerbtn-wrapper .wpfunnels-block-offer-button"}]},device:{type:"string",default:"md"},offerVariableShortCode:{type:"string",default:"
"},variationTblTitle:{type:"string",default:""},showProductPrice:{type:"string",default:"no"},productPriceAlignment:{type:"string",default:""},showProductData:{type:"string",default:"no"},offerTemplateLayout:{type:"string",default:"no"},templateLeftColWidth:{type:"number",default:45},templateRightColWidth:{type:"number",default:55},templateColGutterWidth:{type:"number",default:30},templateLayoutRadius:{type:"number",default:20},templateImageWidth:{type:"number",default:""},templateImageRadius:{type:"number",default:10},templateTopMargin:{type:"number",default:""},templateRightMargin:{type:"number",default:""},templateBottomMargin:{type:"number",default:""},templateLeftMargin:{type:"number",default:""},templateTopPadding:{type:"number",default:""},templateRightPadding:{type:"number",default:""},templateBottomPadding:{type:"number",default:""},templateLeftPadding:{type:"number",default:""},headingColor:{type:"string",default:"#1F392A"},headingTypography:{type:"object",default:{},style:[{selector:".dynamic-offer-template-default .template-content .template-product-title"}]},headingBottomMargin:{type:"number",default:""},descriptionColor:{type:"string",default:"#1F392A"},descriptionTypography:{type:"object",default:{},style:[{selector:".dynamic-offer-template-default .template-content .template-product-description"}]},descriptionBottomMargin:{type:"number",default:""},regularPriceColor:{type:"string",default:""},regularPriceTypography:{type:"object",default:{},style:[{selector:".dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del bdi"}]},discountPriceColor:{type:"string",default:""},discountPriceTypography:{type:"object",default:{},style:[{selector:".dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price bdi"}]},priceBottomMargin:{type:"number",default:""},isGbf:{type:"string",default:window.wpfnl_pro_block_object.isGbf},isDynamic:{type:"string",default:"yes"},productInfo:{type:"object",default:{img:window.wpfnl_pro_block_object.productInfo.img,title:window.wpfnl_pro_block_object.productInfo.title,description:window.wpfnl_pro_block_object.productInfo.description,price:window.wpfnl_pro_block_object.productInfo.price}}},i=n(6),r=n.n(i),o=n(0),l=n.n(o),s=n(2),c=n(10),p=n.n(c),f=n(15),u=n.n(f),d=n(16),m=n(24),v=wp.blockEditor,h=(v.BlockControls,v.AlignmentToolbar,v.BlockAlignmentToolbar,v.RichText),g=(v.withColors,v.useBlockProps),w=wp.element,y=w.RawHTML,b=(w.Component,(0,wp.compose.compose)([])((function(e){var t=e.attributes,n=g(),a=window.wpfnl_pro_block_object.is_variable_product,i=(t.outline,{backgroundColor:"fill"===t.outline?t.buttonColor:"transparent",color:t.buttonTextColor?t.buttonTextColor:"#fff",padding:t.paddingTopBottom+"px "+t.paddingLeftRight+"px",textAlign:t.buttonTextAlign,textDecoration:"none",display:"inline-block",borderRadius:t.buttonRadius,borderWidth:"fill"===t.outline?"0":"2px",borderStyle:"solid",borderColor:"#39414d".color,fontWeight:t.typography.weight,fontFamily:t.typography.family});return wp.element.createElement(React.Fragment,null,wp.element.createElement("div",null,wp.element.createElement("div",{className:"wpfnl-reset dynamic-offer-template-default dynamic-offer-template1"},wp.element.createElement("div",{className:"template-left"},wp.element.createElement("div",{className:"product-img"},wp.element.createElement("img",{src:t.productInfo.img,alt:"product-img"}))),wp.element.createElement("div",{className:"template-right"},wp.element.createElement("div",{className:"template-content"},wp.element.createElement("h2",{className:"template-product-title"},t.productInfo.title),wp.element.createElement("div",{className:"template-product-description"},t.productInfo.description),wp.element.createElement("div",{className:"wpfnl-offerbtn-wrapper",id:"wpfnl-offerbtn-wrapper"},a&&"accept"==t.offerAction&&wp.element.createElement("div",null,t.variationTblTitle&&wp.element.createElement("h5",{className:"wpfnl-product-variation-title"},t.variationTblTitle),wp.element.createElement("div",{className:"has-variation-product"},wp.element.createElement("div",{className:"wpfnl-product-variation"},"yes"==t.showProductPrice&&wp.element.createElement("span",{className:"offer-btn-loader"}),wp.element.createElement(y,null,t.offerVariableShortCode)),wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==t.showProductPrice?t.productPriceAlignment:"")},"yes"==t.showProductPrice&&wp.element.createElement(y,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},t.productInfo.price),wp.element.createElement(h,r()({},n,{style:i,tagName:"p",value:t.buttonText,onChange:function(e){return setAttributes({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(t.offerAction),className:"wpfunnels-block-offer-button"}))))),a&&"reject"==t.offerAction&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==t.showProductPrice?t.productPriceAlignment:"")},"yes"==t.showProductPrice&&wp.element.createElement(y,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},t.productInfo.price),wp.element.createElement(h,r()({},n,{style:i,tagName:"p",value:t.buttonText,onChange:function(e){return setAttributes({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(t.offerAction),className:"wpfunnels-block-offer-button"}))),!a&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==t.showProductPrice?t.productPriceAlignment:"")},"yes"==t.showProductPrice&&wp.element.createElement(y,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},t.productInfo.price),wp.element.createElement(h,r()({},n,{style:i,tagName:"p",value:t.buttonText,onChange:function(e){return setAttributes({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(t.offerAction),className:"wpfunnels-block-offer-button"})))))))))}))),x=wp.blockEditor,E=(x.BlockControls,x.AlignmentToolbar,x.BlockAlignmentToolbar,x.RichText),C=(x.withColors,x.useBlockProps),S=wp.element,k=S.RawHTML,O=(S.Component,(0,wp.compose.compose)([])((function(e){var t=e.attributes,n=C(),a=window.wpfnl_pro_block_object.is_variable_product,i=(t.outline,{backgroundColor:"fill"===t.outline?t.buttonColor:"transparent",color:t.buttonTextColor?t.buttonTextColor:"#fff",padding:t.paddingTopBottom+"px "+t.paddingLeftRight+"px",textAlign:t.buttonTextAlign,textDecoration:"none",display:"inline-block",borderRadius:t.buttonRadius,borderWidth:"fill"===t.outline?"0":"2px",borderStyle:"solid",borderColor:"#39414d".color,fontWeight:t.typography.weight,fontFamily:t.typography.family});return wp.element.createElement(React.Fragment,null,wp.element.createElement("div",null,wp.element.createElement("div",{className:"wpfnl-reset dynamic-offer-template-default dynamic-offer-template1 dynamic-offer-template2"},wp.element.createElement("div",{className:"template-left"},wp.element.createElement("div",{className:"product-img"},wp.element.createElement("img",{src:t.productInfo.img,alt:"product-img"}))),wp.element.createElement("div",{className:"template-right"},wp.element.createElement("div",{className:"template-content"},wp.element.createElement("h2",{className:"template-product-title"},t.productInfo.title),wp.element.createElement("div",{className:"template-product-description"},t.productInfo.description),wp.element.createElement("div",{className:"wpfnl-offerbtn-wrapper",id:"wpfnl-offerbtn-wrapper"},a&&"accept"==t.offerAction&&wp.element.createElement("div",null,t.variationTblTitle&&wp.element.createElement("h5",{className:"wpfnl-product-variation-title"},t.variationTblTitle),wp.element.createElement("div",{className:"has-variation-product"},wp.element.createElement("div",{className:"wpfnl-product-variation"},"yes"==t.showProductPrice&&wp.element.createElement("span",{className:"offer-btn-loader"}),wp.element.createElement(k,null,t.offerVariableShortCode)),wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==t.showProductPrice?t.productPriceAlignment:"")},"yes"==t.showProductPrice&&wp.element.createElement(k,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},t.productInfo.price),wp.element.createElement(E,r()({},n,{style:i,tagName:"p",value:t.buttonText,onChange:function(e){return setAttributes({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(t.offerAction),className:"wpfunnels-block-offer-button"}))))),a&&"reject"==t.offerAction&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==t.showProductPrice?t.productPriceAlignment:"")},"yes"==t.showProductPrice&&wp.element.createElement(k,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},t.productInfo.price),wp.element.createElement(E,r()({},n,{style:i,tagName:"p",value:t.buttonText,onChange:function(e){return setAttributes({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(t.offerAction),className:"wpfunnels-block-offer-button"}))),!a&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==t.showProductPrice?t.productPriceAlignment:"")},"yes"==t.showProductPrice&&wp.element.createElement(k,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},t.productInfo.price),wp.element.createElement(E,r()({},n,{style:i,tagName:"p",value:t.buttonText,onChange:function(e){return setAttributes({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(t.offerAction),className:"wpfunnels-block-offer-button"})))))))))}))),_=wp.blockEditor,P=(_.BlockControls,_.AlignmentToolbar,_.BlockAlignmentToolbar,_.RichText),T=(_.withColors,_.useBlockProps),M=wp.element,j=M.RawHTML,A=(M.Component,(0,wp.compose.compose)([])((function(e){var t=e.attributes,n=T(),a=window.wpfnl_pro_block_object.is_variable_product,i=(t.outline,{backgroundColor:"fill"===t.outline?t.buttonColor:"transparent",color:t.buttonTextColor?t.buttonTextColor:"#fff",padding:t.paddingTopBottom+"px "+t.paddingLeftRight+"px",textAlign:t.buttonTextAlign,textDecoration:"none",display:"inline-block",borderRadius:t.buttonRadius,borderWidth:"fill"===t.outline?"0":"2px",borderStyle:"solid",borderColor:"#39414d".color,fontWeight:t.typography.weight,fontFamily:t.typography.family});return wp.element.createElement(React.Fragment,null,wp.element.createElement("div",null,wp.element.createElement("div",{className:"wpfnl-reset dynamic-offer-template-default dynamic-offer-template1 dynamic-offer-template3"},wp.element.createElement("div",{className:"template-left"},wp.element.createElement("div",{className:"product-img"},wp.element.createElement("img",{src:t.productInfo.img,alt:"product-img"}))),wp.element.createElement("div",{className:"template-right"},wp.element.createElement("div",{className:"template-content"},wp.element.createElement("h2",{className:"template-product-title"},t.productInfo.title),wp.element.createElement("div",{className:"template-product-description"},t.productInfo.description),wp.element.createElement("div",{className:"wpfnl-offerbtn-wrapper",id:"wpfnl-offerbtn-wrapper"},a&&"accept"==t.offerAction&&wp.element.createElement("div",null,t.variationTblTitle&&wp.element.createElement("h5",{className:"wpfnl-product-variation-title"},t.variationTblTitle),wp.element.createElement("div",{className:"has-variation-product"},wp.element.createElement("div",{className:"wpfnl-product-variation"},"yes"==t.showProductPrice&&wp.element.createElement("span",{className:"offer-btn-loader"}),wp.element.createElement(j,null,t.offerVariableShortCode)),wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==t.showProductPrice?t.productPriceAlignment:"")},"yes"==t.showProductPrice&&wp.element.createElement(j,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},t.productInfo.price),wp.element.createElement(P,r()({},n,{style:i,tagName:"p",value:t.buttonText,onChange:function(e){return setAttributes({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(t.offerAction),className:"wpfunnels-block-offer-button"}))))),a&&"reject"==t.offerAction&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==t.showProductPrice?t.productPriceAlignment:"")},"yes"==t.showProductPrice&&wp.element.createElement(j,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},t.productInfo.price),wp.element.createElement(P,r()({},n,{style:i,tagName:"p",value:t.buttonText,onChange:function(e){return setAttributes({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(t.offerAction),className:"wpfunnels-block-offer-button"}))),!a&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==t.showProductPrice?t.productPriceAlignment:"")},"yes"==t.showProductPrice&&wp.element.createElement(j,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},t.productInfo.price),wp.element.createElement(P,r()({},n,{style:i,tagName:"p",value:t.buttonText,onChange:function(e){return setAttributes({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(t.offerAction),className:"wpfunnels-block-offer-button"})))))))))}))),q=wp.element,R=(q.useState,q.useEffect),B=wp.components,N=B.TextControl,L=B.SelectControl,D=B.RangeControl,F=B.PanelBody,z=(B.SizingControl,B.Panel),I=wp.blockEditor,G=I.InspectorControls,W=I.ColorPalette,H=I.withColors,Y=I.BlockControls,V=I.AlignmentToolbar,X=I.BlockAlignmentToolbar,K=I.RichText,J=(I.PanelColorSettings,I.useBlockProps),U=wp.element,Z=U.RawHTML,Q=(U.Component,wp.compose),ee=(Q.withInstanceId,Q.compose),te=function(e){var t=e.clientId,n=e.attributes,a=e.className,i=e.buttonColor,o=(e.headingColor,e.buttonTextColor,e.setAttributes),c=n.uniqueId,f=t.substr(0,6);c?c&&c!=f&&o({uniqueId:f}):o({uniqueId:f});var u=[n.offerTemplateLayout,n.templateLeftColWidth,n.templateRightColWidth,n.templateLayoutRadius,n.templateColGutterWidth,n.templateTopPadding,n.templateRightPadding,n.templateBottomPadding,n.templateLeftPadding,n.templateTopMargin,n.templateRightMargin,n.templateBottomMargin,n.templateLeftMargin,n.templateImageWidth,n.headingColor,n.headingBottomMargin,n.regularPriceColor,n.discountPriceColor,n.priceBottomMargin,n.descriptionColor,n.descriptionBottomMargin];R((function(){var e=document.createElement("style");e.setAttribute("id","wpfnl-offer-template-style");var t=".dynamic-offer-template-default {padding-top:"+n.templateTopPadding+"px; padding-right:"+n.templateRightPadding+"px; padding-bottom:"+n.templateBottomPadding+"px; padding-left:"+n.templateLeftPadding+"px; margin-top:"+n.templateTopMargin+"px; margin-right:"+n.templateRightMargin+"px; margin-bottom:"+n.templateBottomMargin+"px; margin-left:"+n.templateLeftMargin+"px; border-radius:"+n.templateLayoutRadius+"px;}";t+=".dynamic-offer-template-default .template-left {width:"+n.templateLeftColWidth+"%;}",t+=".dynamic-offer-template-default .template-right {width:"+n.templateRightColWidth+"%;}","style2"==n.offerTemplateLayout?t+=".dynamic-offer-template-default .template-right {padding-left: 0!important; padding-right:"+n.templateColGutterWidth+"px!important;}":"style3"==n.offerTemplateLayout?t+=".dynamic-offer-template-default .template-right {padding-left: 0!important; padding-right: 0!important; padding-top:"+n.templateColGutterWidth+"px!important;}":t+=".dynamic-offer-template-default .template-right {padding-left:"+n.templateColGutterWidth+"px!important; padding-right: 0!important;}";var a;a=null!=n.templateImageWidth&&n.templateImageWidth?n.templateImageWidth+"px":"100%",t+=".dynamic-offer-template-default .product-img img { border-radius: "+n.templateImageRadius+"px; max-width: "+a+"; }",t+=".dynamic-offer-template-default .template-content .template-product-title { color: "+n.headingColor+"; margin-bottom: "+n.headingBottomMargin+"px; }",t+=".dynamic-offer-template-default .template-content .template-product-description { color: "+n.descriptionColor+"; margin-bottom: "+n.descriptionBottomMargin+"px; }",t+=".dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price bdi { color: "+n.regularPriceColor+";}",t+=".dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del bdi, .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del { color: "+n.discountPriceColor+";}",t+=".dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price { margin-bottom: "+n.priceBottomMargin+";}",document.head.appendChild(e),e.appendChild(document.createTextNode(t))}),u);var m,v,h,g,w,y,x,E,C,S,k,_,P,T,M,j,q,B,I,H,U,Q,$,ee,te,ne,ae,ie,re,oe,le,se,ce,pe,fe,ue,de=function(e){o({offerAction:e}),o("reject"==e?{offerVariableShortCode:""}:{offerVariableShortCode:window.wpfnl_pro_block_object.variableRender})},me=p()(l()({},"wpfnl-block-".concat(c),c),a),ve=p()("wpfunnels-block-offer-button");return wp.element.createElement(React.Fragment,null,(m=n.buttonText,n.text_color,v=n.buttonRadius,h=n.paddingTopBottom,g=n.paddingLeftRight,w=n.outline,y=n.offerType,x=n.offerAction,E=n.typography,C=n.device,S=n.showProductPrice,k=n.productPriceAlignment,_=n.showProductData,P=n.offerTemplateLayout,T=n.templateLeftColWidth,M=n.templateRightColWidth,j=n.templateColGutterWidth,q=n.templateLayoutRadius,B=n.templateImageRadius,I=n.templateTopMargin,H=n.templateRightMargin,U=n.templateBottomMargin,Q=n.templateLeftMargin,$=n.templateTopPadding,ee=n.templateRightPadding,te=n.templateBottomPadding,ne=n.templateLeftPadding,ae=n.templateImageWidth,n.headingColor,ie=n.headingTypography,re=n.headingBottomMargin,oe=n.descriptionTypography,le=n.descriptionBottomMargin,se=n.regularPriceTypography,ce=n.discountPriceTypography,pe=n.priceBottomMargin,fe=n.variationTblTitle,ue=window.wpfnl_pro_block_object.is_variable_product,wp.element.createElement(G,{key:"wpfnl-next-step-button-inspector-controls"},wp.element.createElement(z,null,wp.element.createElement(F,{title:"Settings",initialOpen:!0},wp.element.createElement(L,{label:Object(s.a)("Select button type","wpfnl-pro"),value:y,className:"wpfunnel-texteditor",onChange:function(e){return o({offerType:e})},options:[{value:"upsell",label:"Upsell"},{value:"downsell",label:"Downsell"}]}),wp.element.createElement(L,{label:Object(s.a)("Select button action","wpfnl-pro"),className:"wpfunnel-texteditor",value:x,onChange:function(e){return de(e)},options:[{value:"accept",label:"Accept"},{value:"reject",label:"Reject"}]}),wp.element.createElement(N,{label:Object(s.a)("Button Text","wpfnl-pro"),format:"string",onChange:function(e){return o({buttonText:e})},value:m}),"accept"==n.offerAction&&wp.element.createElement("div",null,wp.element.createElement("hr",{className:"wpfnl-hr"}),ue&&wp.element.createElement(N,{label:Object(s.a)("Variation Table Title","wpfnl-pro"),format:"string",onChange:function(e){return o({variationTblTitle:e})},value:fe}),wp.element.createElement(L,{label:Object(s.a)("Show Product Price","wpfnl-pro"),className:"wpfunnel-texteditor",value:S,onChange:function(e){return o({showProductPrice:e})},options:[{value:"yes",label:"Show"},{value:"no",label:"Hide"}]}),"yes"==n.showProductPrice&&wp.element.createElement(L,{label:Object(s.a)("Product Price Alignment","wpfnl-pro"),className:"wpfunnel-texteditor",value:k,onChange:function(e){return o({productPriceAlignment:e})},options:[{value:"",label:"On The Left Of Button"},{value:"price-right",label:"On The Right Of Button"},{value:"price-top",label:"Above The Button"},{value:"price-bottom",label:"Below The Button"}]}),"yes"==n.isGbf&&wp.element.createElement(L,{label:Object(s.a)("Show Product Data","wpfnl-pro"),className:"wpfunnel-texteditor",value:_,onChange:function(e){o({showProductData:e,offerTemplateLayout:"style1"})},options:[{value:"yes",label:"Show"},{value:"no",label:"Hide"}]}),"yes"==n.isGbf&&"yes"==n.showProductData&&wp.element.createElement(L,{label:Object(s.a)("Select Template Style","wpfnl-pro"),className:"wpfunnel-texteditor",value:P,onChange:function(e){return o({offerTemplateLayout:e})},options:[{value:"style1",label:"Left Image Right Content"},{value:"style2",label:"Left Content Right Image"},{value:"style3",label:"Top Image Bottom Content"}]})))),"yes"==n.isGbf&&"yes"==n.showProductData&&wp.element.createElement(z,null,wp.element.createElement(F,{title:"Template Style",initialOpen:!1},wp.element.createElement(F,{title:"Template Lyout Style",className:"inner-pannel",initialOpen:!1},wp.element.createElement(D,{label:Object(s.a)("Image Column Width (%)","wpfnl-pro"),value:T,onChange:function(e){return o({templateLeftColWidth:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(D,{label:Object(s.a)("Content Column Width (%)","wpfnl-pro"),value:M,onChange:function(e){return o({templateRightColWidth:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(D,{label:Object(s.a)("Column Gutter Width (px)","wpfnl-pro"),value:j,onChange:function(e){return o({templateColGutterWidth:e})},allowReset:!0,min:0,max:50,step:1}),wp.element.createElement(D,{label:Object(s.a)("Border Radius (px)","wpfnl-pro"),value:q,onChange:function(e){return o({templateLayoutRadius:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement("hr",{className:"wpfnl-hr"}),wp.element.createElement(D,{label:Object(s.a)("Margin top (px)","wpfnl-pro"),value:I,onChange:function(e){return o({templateTopMargin:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(D,{label:Object(s.a)("Margin Right (px)","wpfnl-pro"),value:H,onChange:function(e){return o({templateRightMargin:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(D,{label:Object(s.a)("Margin Bottom (px)","wpfnl-pro"),value:U,onChange:function(e){return o({templateBottomMargin:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(D,{label:Object(s.a)("Margin Left (px)","wpfnl-pro"),value:Q,onChange:function(e){return o({templateLeftMargin:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement("hr",{className:"wpfnl-hr"}),wp.element.createElement(D,{label:Object(s.a)("Padding top (px)","wpfnl-pro"),value:$,onChange:function(e){return o({templateTopPadding:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(D,{label:Object(s.a)("Padding Right (px)","wpfnl-pro"),value:ee,onChange:function(e){return o({templateRightPadding:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(D,{label:Object(s.a)("Padding Bottom (px)","wpfnl-pro"),value:te,onChange:function(e){return o({templateBottomPadding:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(D,{label:Object(s.a)("Padding Left (px)","wpfnl-pro"),value:ne,onChange:function(e){return o({templateLeftPadding:e})},allowReset:!0,min:0,max:100,step:1})),wp.element.createElement(F,{title:"Image Style",className:"inner-pannel",initialOpen:!1},wp.element.createElement(D,{label:Object(s.a)("Max Width (px)","wpfnl-pro"),value:ae,onChange:function(e){return o({templateImageWidth:e})},allowReset:!0,min:0,max:1920,step:1}),wp.element.createElement(D,{label:Object(s.a)("Border Radius (px)","wpfnl-pro"),value:B,onChange:function(e){return o({templateImageRadius:e})},allowReset:!0,min:0,max:100,step:1})),wp.element.createElement(F,{title:"Heading Style",className:"inner-pannel",initialOpen:!1},wp.element.createElement(W,{onChange:function(e){return o({headingColor:e})},value:n.headingColor}),wp.element.createElement(F,{title:Object(s.a)("Typography"),initialOpen:!1},wp.element.createElement(d.a,{label:Object(s.a)("Typography"),value:ie,onChange:function(e){return o({headingTypography:e})},disableLineHeight:!0,device:C,onDeviceChange:function(e){return o({device:e})}})),wp.element.createElement("hr",{className:"wpfnl-hr"}),wp.element.createElement(D,{label:Object(s.a)("Margin Bottom (px)","wpfnl-pro"),value:re,onChange:function(e){return o({headingBottomMargin:e})},allowReset:!0,min:0,max:100,step:1})),wp.element.createElement(F,{title:"Description Style",className:"inner-pannel",initialOpen:!1},wp.element.createElement(W,{onChange:function(e){return o({descriptionColor:e})},value:n.descriptionColor}),wp.element.createElement(F,{title:Object(s.a)("Typography"),initialOpen:!1},wp.element.createElement(d.a,{label:Object(s.a)("Typography"),value:oe,onChange:function(e){return o({descriptionTypography:e})},disableLineHeight:!0,device:C,onDeviceChange:function(e){return o({device:e})}})),wp.element.createElement("hr",{className:"wpfnl-hr"}),wp.element.createElement(D,{label:Object(s.a)("Margin Bottom (px)","wpfnl-pro"),value:le,onChange:function(e){return o({descriptionBottomMargin:e})},allowReset:!0,min:0,max:100,step:1})),wp.element.createElement(F,{title:"Price Style",className:"inner-pannel",initialOpen:!1},wp.element.createElement("h2",null,Object(s.a)("Regular Price Color","wpfnl-pro")),wp.element.createElement(W,{title:"Regular Price Color",onChange:function(e){return o({regularPriceColor:e})},value:n.regularPriceColor}),wp.element.createElement(F,{title:Object(s.a)("Typography"),initialOpen:!1},wp.element.createElement(d.a,{label:Object(s.a)("Regular Price Typography"),value:se,onChange:function(e){return o({regularPriceTypography:e})},disableLineHeight:!0,device:C,onDeviceChange:function(e){return o({device:e})}})),wp.element.createElement("hr",{className:"wpfnl-hr"}),wp.element.createElement("h2",null,Object(s.a)("Discount Price Color","wpfnl-pro")),wp.element.createElement(W,{title:"Discount Price Color",onChange:function(e){return o({discountPriceColor:e})},value:n.discountPriceColor}),wp.element.createElement(F,{title:Object(s.a)("Typography"),initialOpen:!1},wp.element.createElement(d.a,{label:Object(s.a)("Discount Price Typography"),value:ce,onChange:function(e){return o({discountPriceTypography:e})},disableLineHeight:!0,device:C,onDeviceChange:function(e){return o({device:e})}})),wp.element.createElement("hr",{className:"wpfnl-hr"}),wp.element.createElement(D,{label:Object(s.a)("Margin Bottom (px)","wpfnl-pro"),value:pe,onChange:function(e){return o({priceBottomMargin:e})},allowReset:!0,min:0,max:100,step:1})))),wp.element.createElement(z,null,wp.element.createElement(F,{title:"Button Style",initialOpen:!1},wp.element.createElement("label",{className:"blocks-base-control__label"},"Text Color"),wp.element.createElement(W,{onChange:function(e){return o({buttonTextColor:e})},value:n.buttonTextColor,enableAlpha:!0}),wp.element.createElement("label",{className:"blocks-base-control__label"},"Background Color"),wp.element.createElement(W,{onChange:function(e){return o({buttonColor:e})},value:n.buttonColor,enableAlpha:!0}),wp.element.createElement(L,{label:Object(s.a)("Outline Style","wpfnl-pro"),value:w,onChange:function(e){return o({outline:e})},options:[{value:"fill",label:"Fill"},{value:"outline",label:"Outline"}]}),wp.element.createElement(D,{label:Object(s.a)("Radius","wpfnl-pro"),value:v,onChange:function(e){return o({buttonRadius:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement("hr",{className:"wpfnl-gutenberg-editor-separator"}),wp.element.createElement("h2",null,Object(s.a)("Padding","wpfnl-pro")),wp.element.createElement(D,{label:Object(s.a)("Padding Top & Bottom","wpfnl-pro"),value:h,onChange:function(e){return o({paddingTopBottom:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(D,{label:Object(s.a)("Padding Left & Right","wpfnl-pro"),value:g,onChange:function(e){return o({paddingLeftRight:e})},allowReset:!0,min:0,max:100,step:1}),wp.element.createElement(F,{title:Object(s.a)("Typography"),initialOpen:!1},wp.element.createElement(d.a,{label:Object(s.a)("Typography"),value:E,onChange:function(e){return o({typography:e})},disableLineHeight:!0,device:C,onDeviceChange:function(e){return o({device:e})}})))))),function(){var e=J(),t=window.wpfnl_pro_block_object.is_variable_product,a=(n.outline,{backgroundColor:"fill"===n.outline?n.buttonColor:"transparent",color:n.buttonTextColor?n.buttonTextColor:"#fff",padding:n.paddingTopBottom+"px "+n.paddingLeftRight+"px",textAlign:n.buttonTextAlign,textDecoration:"none",display:"inline-block",borderRadius:n.buttonRadius,borderWidth:"fill"===n.outline?"0":"2px",borderStyle:"solid",borderColor:i.color,fontWeight:n.typography.weight,fontFamily:n.typography.family});return"accept"==n.offerAction&&de("accept"),"no"==n.offerTemplateLayout||n.offerTemplateLayout,wp.element.createElement("div",null,"no"==n.showProductData&&wp.element.createElement("div",{className:me+[" wp-block-wpfnl-offer-btn-".concat(n.buttonAlign)],style:{textAlign:n.buttonAlign}},wp.element.createElement("div",{className:"wpfnl-offerbtn-wrapper",id:"wpfnl-offerbtn-wrapper"},wp.element.createElement(Y,null,wp.element.createElement(V,{value:n.buttonTextAlign,onChange:function(e){o({buttonTextAlign:e})}}),wp.element.createElement(X,{value:n.buttonAlign,onChange:function(e){o({buttonAlign:e})},controls:["left","center","right"]})),t&&"accept"==n.offerAction&&wp.element.createElement("div",null,n.variationTblTitle&&wp.element.createElement("h5",{className:"wpfnl-product-variation-title"},n.variationTblTitle),wp.element.createElement("div",{className:"has-variation-product"},wp.element.createElement("div",{className:"wpfnl-product-variation"},"yes"==n.showProductPrice&&wp.element.createElement("span",{className:"offer-btn-loader"}),wp.element.createElement(Z,null,n.offerVariableShortCode)),wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==n.showProductPrice?n.productPriceAlignment:"")},"yes"==n.showProductPrice&&wp.element.createElement(Z,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},n.productInfo.price),wp.element.createElement(K,r()({},e,{style:a,tagName:"p",value:n.buttonText,onChange:function(e){return o({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(n.offerAction),className:ve}))))),t&&"reject"==n.offerAction&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==n.showProductPrice?n.productPriceAlignment:"")},"yes"==n.showProductPrice&&wp.element.createElement(Z,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},n.productInfo.price),wp.element.createElement(K,r()({},e,{style:a,tagName:"p",value:n.buttonText,onChange:function(e){return o({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(n.offerAction),className:ve}))),!t&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==n.showProductPrice?n.productPriceAlignment:"")},"yes"==n.showProductPrice&&wp.element.createElement(Z,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},n.productInfo.price),wp.element.createElement(K,r()({},e,{style:a,tagName:"p",value:n.buttonText,onChange:function(e){return o({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(n.offerAction),className:ve}))))),"yes"==n.showProductData&&"no"==n.isGbf&&wp.element.createElement("div",{className:"wpfnl-offerbtn-wrapper",id:"wpfnl-offerbtn-wrapper"},wp.element.createElement(Y,null,wp.element.createElement(V,{value:n.buttonTextAlign,onChange:function(e){o({buttonTextAlign:e})}}),wp.element.createElement(X,{value:n.buttonAlign,onChange:function(e){o({buttonAlign:e})},controls:["left","center","right"]})),t&&"accept"==n.offerAction&&wp.element.createElement("div",null,n.variationTblTitle&&wp.element.createElement("h5",{className:"wpfnl-product-variation-title"},n.variationTblTitle),wp.element.createElement("div",{className:"has-variation-product"},wp.element.createElement("div",{className:"wpfnl-product-variation"},"yes"==n.showProductPrice&&wp.element.createElement("span",{className:"offer-btn-loader"}),wp.element.createElement(Z,null,n.offerVariableShortCode)),wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==n.showProductPrice?n.productPriceAlignment:"")},"yes"==n.showProductPrice&&wp.element.createElement(Z,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},n.productInfo.price),wp.element.createElement(K,r()({},e,{style:a,tagName:"p",value:n.buttonText,onChange:function(e){return o({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(n.offerAction),className:ve}))))),t&&"reject"==n.offerAction&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==n.showProductPrice?n.productPriceAlignment:"")},"yes"==n.showProductPrice&&wp.element.createElement(Z,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},n.productInfo.price),wp.element.createElement(K,r()({},e,{style:a,tagName:"p",value:n.buttonText,onChange:function(e){return o({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(n.offerAction),className:ve}))),!t&&wp.element.createElement("div",{className:"wpfnl-offerbtn-and-price-wrapper ".concat("yes"==n.showProductPrice?n.productPriceAlignment:"")},"yes"==n.showProductPrice&&wp.element.createElement(Z,{className:"wpfnl-offer-product-price",id:"wpfnl-offer-product-price"},n.productInfo.price),wp.element.createElement(K,r()({},e,{style:a,tagName:"p",value:n.buttonText,onChange:function(e){return o({buttonText:e})},placeholder:Object(s.a)("Accept","wpfnl-pro"),id:"wpfunnels_upsell_".concat(n.offerAction),className:ve})))),"yes"==n.showProductData&&"yes"==n.isGbf&&"yes"==n.isDynamic&&"style1"==n.offerTemplateLayout&&wp.element.createElement(b,{attributes:n}),"yes"==n.showProductData&&"yes"==n.isGbf&&"yes"==n.isDynamic&&"style2"==n.offerTemplateLayout&&wp.element.createElement(O,{attributes:n}),"yes"==n.showProductData&&"yes"==n.isGbf&&"yes"==n.isDynamic&&"style3"==n.offerTemplateLayout&&wp.element.createElement(A,{attributes:n}))}())};te.propTypes={attributes:u.a.object.isRequired,instanceId:u.a.number,setAttributes:u.a.func,buttonColor:u.a.object,buttonTextColor:u.a.object,setButtonColor:u.a.func.isRequired,setButtonTextColor:u.a.func.isRequired};var ne=ee([H({buttonColor:"background-color",buttonTextColor:"color"}),Object(m.a)()])(te),ae=(n(49),n(50),wp.element),ie=(ae.RawHTML,ae.useEffect,wp.blockEditor.RichText),re=function(e){var t,n,a=e.attributes,i=(p()(""),p()("wpfunnels-block-offer-button")),r={backgroundColor:"fill"===a.outline?a.buttonColor?a.buttonColor:"":"transparent",color:a.buttonTextColor?a.buttonTextColor:"#fff",padding:a.paddingTopBottom+"px "+a.paddingLeftRight+"px",textAlign:a.buttonTextAlign,textDecoration:"none",display:"inline-block",borderRadius:a.buttonRadius,borderWidth:"fill"===a.outline?"0":"2px",borderStyle:"solid",borderColor:a.buttonColor?a.buttonColor:"#fff",fontWeight:a.typography.weight,fontFamily:a.typography.family};return t=a.offerAction,(n=document.getElementById("offer-button-style"))&&(n.innerHTML="#wpfunnels_"+a.offerType+"_"+t+" { display: block; background-color: "+a.buttonColor+" !important; color: "+a.buttonTextColor+" !important; border-radius: "+a.buttonRadius+"px !important;padding-right: "+a.paddingLeftRight+"px !important; padding: "+a.paddingTopBottom+"px "+a.paddingLeftRight+"px !important; }\n"),wp.element.createElement("div",null,wp.element.createElement(ie.Content,{tagName:"a",href:"#",id:"wpfunnels_".concat(a.offerType,"_").concat(a.offerAction),className:i,style:r,value:a.buttonText,"data-offertype":"".concat(a.offerType)}))};re.propTypes={attributes:u.a.object.isRequired};var oe=re,le=(n(47),n(22)),__=wp.i18n.__;(0,wp.blocks.registerBlockType)("wpfunnelspro/offer-button",{title:__("WPFunnels Offer Button","wpfnl"),icon:"shield",category:"common",attributes:a,edit:ne,save:oe}),window.qubelyDevice="md",window.bindWpfnlCss=!1,window.globalSaving=!1,wp.data.subscribe((function(){try{if(!1===window.bindWpfnlCss){var e=wp.data.select("core/editor").hasNonPostEntityChanges(),t=wp.data.select("core/editor").isSavingPost(),n=wp.data.select("core/editor").isAutosavingPost();wp.data.select("core/editor").isPublishingPost()||t&&!n?(Object(le.a)(!0),window.bindWpfnlCss=!0,setTimeout((function(){window.bindWpfnlCss=!1}),500)):wp.data.select("core/editor").isPreviewingPost()?Object(le.a)(!1):e&&$(".components-button.editor-post-publish-button.editor-post-publish-button__button.is-primary").click((function(){setTimeout((function(){!1===window.bindWpfnlCss&&($(".components-button.editor-entities-saved-states__save-button.is-primary").bind("click",(function(){console.log("saving hasNonPostEntityChanges"),Object(le.a)(!0)})),window.bindWpfnlCss=!0)}))}))}}catch(e){console.error(e)}}))}]));includes/core/widgets/block/assets/dist/offer-price.js000064400000011751147600245720017062 0ustar00!function(e,t){for(var r in t)e[r]=t[r]}(this,function(e){var t={};function r(o){if(t[o])return t[o].exports;var n=t[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(o,n,function(t){return e[t]}.bind(null,n));return o},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=55)}({0:function(e,t){e.exports=function(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e},e.exports.default=e.exports,e.exports.__esModule=!0},1:function(e,t){function r(t){return"function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?(e.exports=r=function(e){return typeof e},e.exports.default=e.exports,e.exports.__esModule=!0):(e.exports=r=function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.default=e.exports,e.exports.__esModule=!0),r(t)}e.exports=r,e.exports.default=e.exports,e.exports.__esModule=!0},11:function(e,t,r){var o=r(1).default,n=r(4);e.exports=function(e,t){if(t&&("object"===o(t)||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return n(e)},e.exports.default=e.exports,e.exports.__esModule=!0},20:function(e,t){function r(t,o){return e.exports=r=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},e.exports.default=e.exports,e.exports.__esModule=!0,r(t,o)}e.exports=r,e.exports.default=e.exports,e.exports.__esModule=!0},4:function(e,t){e.exports=function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e},e.exports.default=e.exports,e.exports.__esModule=!0},5:function(e,t){function r(t){return e.exports=r=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)},e.exports.default=e.exports,e.exports.__esModule=!0,r(t)}e.exports=r,e.exports.default=e.exports,e.exports.__esModule=!0},55:function(e,t,r){e.exports=r(60)},60:function(e,t,r){"use strict";r.r(t);var o=r(8),n=r.n(o),u=r(4),c=r.n(u),f=r(9),s=r.n(f),l=r(11),i=r.n(l),p=r(5),a=r.n(p),d=r(0),x=r.n(d);var _=wp.element,y=_.Component,b=_.RawHTML,m=wp.data.withSelect,w=function(e){s()(u,e);var t,r,o=(t=u,r=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,o=a()(t);if(r){var n=a()(this).constructor;e=Reflect.construct(o,arguments,n)}else e=o.apply(this,arguments);return i()(this,e)});function u(){var e;return n()(this,u),e=o.apply(this,arguments),x()(c()(e),"renderOfferPrice",(function(){var t=e.props.attributes;return wp.element.createElement(React.Fragment,null,!0===t.showAjaxLoader&&wp.element.createElement("div",{className:"wpnfl-gutenberg-loader"},wp.element.createElement("div",{className:"loader"})),wp.element.createElement(b,null,t.offerProductPriceShortCode))})),x()(c()(e),"render",(function(){return wp.element.createElement(React.Fragment,null,e.renderOfferPrice())})),e}return u}(y),v=m((function(e,t){var r=t.setAttributes,o=t.attributes.isHtml,n="";return o||jQuery.ajax({url:window.wpfnl_pro_block_object.ajaxUrl,data:{action:"wpfnl_offer_product_price_shortcode",nonce:window.wpfnl_pro_block_object.wpfnl_ajax_nonce,id:window.wpfnl_pro_block_object.data_post_id,wpfunnels_gb:!0},dataType:"json",type:"POST",success:function(e){r({isHtml:!0}),r({showAjaxLoader:!1}),r({offerProductPriceShortCode:e.data.html}),n=e}}),{formHTML:n}}))(w),__=wp.i18n.__;(0,wp.blocks.registerBlockType)("wpfunnelspro/offer-price",{title:__("WPFunnels Offer Price","wpfnl"),icon:"shield",category:"common",attributes:{offerProductPriceShortCode:{type:"string"},showAjaxLoader:{type:"boolean",default:!1},isHtml:{type:"boolean",default:!1}},edit:v,save:function(e){return e.attributes,e.className,null}})},8:function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},e.exports.default=e.exports,e.exports.__esModule=!0},9:function(e,t,r){var o=r(20);e.exports=function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&o(e,t)},e.exports.default=e.exports,e.exports.__esModule=!0}}));includes/core/widgets/block/assets/dist/offer-product.js000064400000053170147600245720017441 0ustar00!function(e,t){for(var n in t)e[n]=t[n]}(this,function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=56)}({0:function(e,t){e.exports=function(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e},e.exports.default=e.exports,e.exports.__esModule=!0},1:function(e,t){function n(t){return"function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?(e.exports=n=function(e){return typeof e},e.exports.default=e.exports,e.exports.__esModule=!0):(e.exports=n=function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.default=e.exports,e.exports.__esModule=!0),n(t)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},11:function(e,t,n){var r=n(1).default,o=n(4);e.exports=function(e,t){if(t&&("object"===r(t)||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return o(e)},e.exports.default=e.exports,e.exports.__esModule=!0},18:function(e,t,n){var r;!function(){"use strict";var o={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function i(e){return a(l(e),arguments)}function s(e,t){return i.apply(null,[e].concat(t||[]))}function a(e,t){var n,r,s,a,c,l,u,p,f,d=1,h=e.length,w="";for(r=0;r=0),a.type){case"b":n=parseInt(n,10).toString(2);break;case"c":n=String.fromCharCode(parseInt(n,10));break;case"d":case"i":n=parseInt(n,10);break;case"j":n=JSON.stringify(n,null,a.width?parseInt(a.width):0);break;case"e":n=a.precision?parseFloat(n).toExponential(a.precision):parseFloat(n).toExponential();break;case"f":n=a.precision?parseFloat(n).toFixed(a.precision):parseFloat(n);break;case"g":n=a.precision?String(Number(n.toPrecision(a.precision))):parseFloat(n);break;case"o":n=(parseInt(n,10)>>>0).toString(8);break;case"s":n=String(n),n=a.precision?n.substring(0,a.precision):n;break;case"t":n=String(!!n),n=a.precision?n.substring(0,a.precision):n;break;case"T":n=Object.prototype.toString.call(n).slice(8,-1).toLowerCase(),n=a.precision?n.substring(0,a.precision):n;break;case"u":n=parseInt(n,10)>>>0;break;case"v":n=n.valueOf(),n=a.precision?n.substring(0,a.precision):n;break;case"x":n=(parseInt(n,10)>>>0).toString(16);break;case"X":n=(parseInt(n,10)>>>0).toString(16).toUpperCase()}o.json.test(a.type)?w+=n:(!o.number.test(a.type)||p&&!a.sign?f="":(f=p?"+":"-",n=n.toString().replace(o.sign,"")),l=a.pad_char?"0"===a.pad_char?"0":a.pad_char.charAt(1):" ",u=a.width-(f+n).length,c=a.width&&u>0?l.repeat(u):"",w+=a.align?f+n+c:"0"===l?f+c+n:c+f+n)}return w}var c=Object.create(null);function l(e){if(c[e])return c[e];for(var t,n=e,r=[],i=0;n;){if(null!==(t=o.text.exec(n)))r.push(t[0]);else if(null!==(t=o.modulo.exec(n)))r.push("%");else{if(null===(t=o.placeholder.exec(n)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){i|=1;var s=[],a=t[2],l=[];if(null===(l=o.key.exec(a)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(s.push(l[1]);""!==(a=a.substring(l[0].length));)if(null!==(l=o.key_access.exec(a)))s.push(l[1]);else{if(null===(l=o.index_access.exec(a)))throw new SyntaxError("[sprintf] failed to parse named argument key");s.push(l[1])}t[2]=s}else i|=2;if(3===i)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");r.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}n=n.substring(t[0].length)}return c[e]=r}t.sprintf=i,t.vsprintf=s,"undefined"!=typeof window&&(window.sprintf=i,window.vsprintf=s,void 0===(r=function(){return{sprintf:i,vsprintf:s}}.call(t,n,t,e))||(e.exports=r))}()},2:function(e,t,n){"use strict";n.d(t,"a",(function(){return S}));var r,o,i,s,a=n(23),c=n.n(a);function l(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}n(18),c()(console.error),r={"(":9,"!":8,"*":7,"/":7,"%":7,"+":6,"-":6,"<":5,"<=":5,">":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},o=["(","?"],i={")":["("],":":["?","?:"]},s=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/;var u={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,n){if(e)throw t;return n}};var p={contextDelimiter:"",onMissingKey:null};function f(e,t){var n;for(n in this.data=e,this.pluralForms={},this.options={},p)this.options[n]=void 0!==t&&n in t?t[n]:p[n]}function d(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function h(e){for(var t=1;t=0||r[c]3&&void 0!==arguments[3]?arguments[3]:10,s=e[t];if(m(n)&&b(r))if("function"==typeof o)if("number"==typeof i){var a={callback:o,priority:i,namespace:r};if(s[n]){var c,l=s[n].handlers;for(c=l.length;c>0&&!(i>=l[c-1].priority);c--);c===l.length?l[c]=a:l.splice(c,0,a),s.__current.forEach((function(e){e.name===n&&e.currentIndex>=c&&e.currentIndex++}))}else s[n]={handlers:[a],runs:0};"hookAdded"!==n&&e.doAction("hookAdded",n,r,o,i)}else console.error("If specified, the hook priority must be a number.");else console.error("The hook callback must be a function.")}},y=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(r,o){var i=e[t];if(m(r)&&(n||b(o))){if(!i[r])return 0;var s=0;if(n)s=i[r].handlers.length,i[r]={runs:i[r].runs,handlers:[]};else for(var a=i[r].handlers,c=function(e){a[e].namespace===o&&(a.splice(e,1),s++,i.__current.forEach((function(t){t.name===r&&t.currentIndex>=e&&t.currentIndex--})))},l=a.length-1;l>=0;l--)c(l);return"hookRemoved"!==r&&e.doAction("hookRemoved",r,o),s}}},x=function(e,t){return function(n,r){var o=e[t];return void 0!==r?n in o&&o[n].handlers.some((function(e){return e.namespace===r})):n in o}},v=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return function(r){var o=e[t];o[r]||(o[r]={handlers:[],runs:0}),o[r].runs++;for(var i=o[r].handlers,s=arguments.length,a=new Array(s>1?s-1:0),c=1;c1&&void 0!==arguments[1]?arguments[1]:"default";r.data[t]=h(h(h({},w),r.data[t]),e),r.data[t][""]=h(h({},w[""]),r.data[t][""])},a=function(e,t){s(e,t),i()},c=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,n=arguments.length>2?arguments[2]:void 0,o=arguments.length>3?arguments[3]:void 0,i=arguments.length>4?arguments[4]:void 0;return r.data[e]||s(void 0,e),r.dcnpgettext(e,t,n,o,i)},l=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return e},_x=function(e,t,r){var o=c(r,t,e);return n?(o=n.applyFilters("i18n.gettext_with_context",o,e,t,r),n.applyFilters("i18n.gettext_with_context_"+l(r),o,e,t,r)):o};if(n){var u=function(e){_.test(e)&&i()};n.addAction("hookAdded","core/i18n",u),n.addAction("hookRemoved","core/i18n",u)}return{getLocaleData:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default";return r.data[e]},setLocaleData:a,resetLocaleData:function(e,t){r.data={},r.pluralForms={},a(e,t)},subscribe:function(e){return o.add(e),function(){return o.delete(e)}},__:function(e,t){var r=c(t,void 0,e);return n?(r=n.applyFilters("i18n.gettext",r,e,t),n.applyFilters("i18n.gettext_"+l(t),r,e,t)):r},_x:_x,_n:function(e,t,r,o){var i=c(o,void 0,e,t,r);return n?(i=n.applyFilters("i18n.ngettext",i,e,t,r,o),n.applyFilters("i18n.ngettext_"+l(o),i,e,t,r,o)):i},_nx:function(e,t,r,o,i){var s=c(i,o,e,t,r);return n?(s=n.applyFilters("i18n.ngettext_with_context",s,e,t,r,o,i),n.applyFilters("i18n.ngettext_with_context_"+l(i),s,e,t,r,o,i)):s},isRTL:function(){return"rtl"===_x("ltr","text direction")},hasTranslation:function(e,t,o){var i,s,a=t?t+""+e:e,c=!(null===(i=r.data)||void 0===i||null===(s=i[null!=o?o:"default"])||void 0===s||!s[a]);return n&&(c=n.applyFilters("i18n.has_translation",c,e,t,o),c=n.applyFilters("i18n.has_translation_"+l(o),c,e,t,o)),c}}}(0,0,T)),S=(k.getLocaleData.bind(k),k.setLocaleData.bind(k),k.resetLocaleData.bind(k),k.subscribe.bind(k),k.__.bind(k));k._x.bind(k),k._n.bind(k),k._nx.bind(k),k.isRTL.bind(k),k.hasTranslation.bind(k)},20:function(e,t){function n(t,r){return e.exports=n=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},e.exports.default=e.exports,e.exports.__esModule=!0,n(t,r)}e.exports=n,e.exports.default=e.exports,e.exports.__esModule=!0},23:function(e,t,n){e.exports=function(e,t){var n,r,o=0;function i(){var i,s,a=n,c=arguments.length;e:for(;a;){if(a.args.length===arguments.length){for(s=0;s*:not(:last-child){margin-right:8px}.qubely-input-range input[type="number"]{width:55px !important}.qubely-input-range input[type=range]{-webkit-appearance:none;margin-top:7px;margin-bottom:7px;width:100%;padding-left:0;padding-right:0;background:transparent}.qubely-input-range input[type=range]::-webkit-slider-runnable-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:#E5E7EA;border-radius:5px;border:0px solid #000}.qubely-input-range input[type=range]::-webkit-slider-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer;-webkit-appearance:none;margin-top:-5px}.qubely-input-range input[type=range]:focus::-webkit-slider-runnable-track{background:#f3f4f5}.qubely-input-range input[type=range]::-moz-range-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:#E5E7EA;border-radius:5px;border:0px solid #000}.qubely-input-range input[type=range]::-moz-range-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer}.qubely-input-range input[type=range]::-ms-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:transparent;border-color:transparent;border-width:14px 0;color:transparent}.qubely-input-range input[type=range]::-ms-fill-lower{background:#d7dadf;border:0px solid #000;border-radius:10px}.qubely-input-range input[type=range]::-ms-fill-upper{background:#E5E7EA;border:0px solid #000;border-radius:10px}.qubely-input-range input[type=range]::-ms-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer}.qubely-input-range input[type=range]:focus::-ms-fill-lower{background:#E5E7EA}.qubely-input-range input[type=range]:focus::-ms-fill-upper{background:#f3f4f5}.qubely-field-range>label{width:100%;margin-bottom:2px}.qubely-field.qubely-field-toggle{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.qubely-field.qubely-field-toggle>label{width:auto;margin-bottom:0}.qubely-field.qubely-field-toggle .components-toggle-control,.qubely-field.qubely-field-toggle .components-toggle-control .components-base-control__field{margin-bottom:0 !important}.qubely-field.qubely-field-toggle .components-toggle-control .components-base-control__field .components-form-toggle{margin-right:0}.qubely-field.qubely-field-toggle .components-form-toggle.is-checked .components-form-toggle__track{background-color:#2184F9}.qubely-field-font-family{min-width:calc(65% - 15px)}.qubely-field-font-weight{min-width:35%}.qubely-font-family-picker,.qubely-font-weight-picker-wrapper{-webkit-box-align:center;align-items:center;background-color:#fff;cursor:default;display:flex;flex-wrap:wrap;-webkit-box-pack:justify;justify-content:space-between;position:relative;box-sizing:border-box;border-color:#ccc;border-radius:4px;border-style:solid;border-width:1px;transition:all 100ms ease 0s;outline:0px !important;line-height:28px;height:30px;vertical-align:middle;padding-left:5px}.qubely-font-family-picker .editor-rich-text,.qubely-font-weight-picker-wrapper .editor-rich-text{width:100%}.qubely-font-family-picker .selected-font-family,.qubely-font-weight-picker-wrapper .selected-font-family{color:black}.qubely-font-family-option-wrapper{top:100%;background-color:#fff;box-shadow:rgba(0,0,0,0.1) 0px 0px 0px 1px,rgba(0,0,0,0.1) 0px 4px 11px;margin-bottom:8px;margin-top:8px;position:absolute;width:70%;box-sizing:border-box;border-radius:4px;z-index:100}.qubely-font-weight-wrapper{top:100%;left:62%;background-color:#fff;box-shadow:rgba(0,0,0,0.1) 0px 0px 0px 1px,rgba(0,0,0,0.1) 0px 4px 11px;margin-bottom:8px;margin-top:8px;position:absolute;width:40%;box-sizing:border-box;border-radius:4px;z-index:100}.qubely-font-family-options,.qubely-font-family-weights{max-height:270px;overflow-y:auto;padding-bottom:4px;padding-top:4px;position:relative;box-sizing:border-box}.qubely-active-font-family,.qubely-active-font-weight{background-color:#2684ff;color:#fff;cursor:default;display:block;font-size:inherit;width:100%;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing:border-box;padding:8px 12px}.qubely-font-family-option,.qubely-font-weight-option{background-color:transparent;color:inherit;cursor:default;display:block;font-size:inherit;width:100%;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing:border-box;padding:8px 12px}.qubely-font-family-option:hover,.qubely-font-weight-option:hover{background-color:#e8eaeb}.qubely-font-family-search-wrapper{display:flex;justify-content:space-between;width:100%}.qubely-font-family-search-wrapper input.qubely-font-family-search{border:none !important;background-color:transparent !important}.qubely-font-family-search-wrapper input.qubely-font-family-search:focus,.qubely-font-family-search-wrapper input.qubely-font-family-search:active,.qubely-font-family-search-wrapper input.qubely-font-family-search:hover{outline:0;box-shadow:none}.qubely-font-weight-picker-wrapper{display:flex;justify-content:space-between;width:100%}.qubely-font-select-icon{display:flex;align-items:center;padding-right:3px}.qubely-field.qubely-field-typography .components-dropdown.qubely-field{width:100%}.qubely-row{position:relative}.qubely-column-layout{display:flex;flex-wrap:nowrap}.qubely-column-1{flex:1}.qubely-column-2{flex:2}.qubely-column-3{flex:3}.qubely-column-4{flex:4}.wp-block-qubely-row{display:block}.wp-block-qubely-row{flex-wrap:wrap}.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout{flex-wrap:wrap;display:flex}.wp-block-qubely-row .block-editor-block-list__layout{margin-left:0;margin-right:0}.qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout{display:flex;flex-wrap:wrap}.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout>[data-type="qubely/column"]{display:flex;flex-direction:column;padding-left:0;padding-right:0;word-break:break-word;overflow-wrap:break-word;width:100%}.wp-block-qubely-row .block-editor-block-list__layout .editor-block-list__block{max-width:none}.wp-block-qubely-row [data-type="qubely/column"]{pointer-events:none}.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout>[data-type="qubely/column"]>.editor-block-list__block-edit{margin-top:0;margin-bottom:0;margin-left:0;margin-right:0}:not(.components-disabled)>.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout>[data-type="qubely/column"]>.editor-block-list__block-edit>*{pointer-events:all}.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout>[data-type="qubely/column"]>.editor-block-list__block-edit>div>.block-editor-inner-blocks{margin-top:-28px;margin-bottom:-28px}.wp-block-qubely-row .block-editor-block-list__layout{margin-left:0;margin-right:0}.wp-block-qubely-row .block-editor-block-list__layout .editor-block-list__block{max-width:none}div[data-type="qubely/row"].is-selected div[data-type="qubely/column"]:last-child>div>div>.components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle,div[data-type="qubely/row"].is-selected div[data-type="qubely/column"]:last-child>div>div>.components-resizable-box__container.qubely-column-resizer::after,div[data-type="qubely/row"].is-resizing div[data-type="qubely/column"]:last-child>div>div>.components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle,div[data-type="qubely/row"].is-resizing div[data-type="qubely/column"]:last-child>div>div>.components-resizable-box__container.qubely-column-resizer::after{display:none}.wp-block-qubely-row [data-type="qubely/column"] .components-resizable-box__container:hover .components-resizable-box__handle{display:block;z-index:9}.components-resizable-box__container.qubely-column-resizer.is-selected-column>span>.components-resizable-box__handle,div[data-type="qubely/row"].is-selected .components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle,div[data-type="qubely/row"].is-resizing .components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle{right:-15px;top:0;background:transparent;z-index:9999;width:auto;height:100%;display:flex;align-items:center;transform:translateX(50%);background:transparent}.components-resizable-box__container.qubely-column-resizer.is-selected-column>span>.components-resizable-box__handle::before,div[data-type="qubely/row"].is-selected .components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle::before,div[data-type="qubely/row"].is-resizing .components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle::before{background:#e3e4e7;border:none;opacity:1;border-radius:30px;width:3.5px;right:calc(50% - 2px)}.components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle{width:8px}.qubely-flex-box{display:flex}.qubely-flex-column{flex-direction:column}.qubely-flex-1{flex:1}.qubely-flex-2{flex:2}.qubely-flex-3{flex:3}.qubely-flex-4{flex:4}.item-center{align-items:center}.item-left{align-items:flex-start}.item-right{align-items:flex-end}.justify-center{justify-content:center}.justify-left{justify-content:flex-start}.justify-right{justify-content:flex-end}.qubely-palette-fields .qubely-palette-unit{padding-bottom:12px;padding-left:3px}[data-type="qubely/columns"] .block-editor-block-list__layout .editor-block-list__block .editor-block-list__block-edit{margin-top:14px;margin-bottom:14px}#wpwrap [data-type="qubely/columns"] .block-editor-block-list__layout .editor-block-list__block .editor-block-list__block-edit p:last-child{margin-bottom:0}#wpwrap [data-type="qubely/columns"] .is-selected .qubely-column[data-gwidth]::before{content:attr(data-gwidth);position:absolute;font-size:10px;top:-1px;right:-1px;background:#2178c3;padding:0 8px;border-radius:unset;color:#fff}.qubely-field-group{position:relative;display:flex}.qubely-field-group:not(:last-child){margin-bottom:20px}.qubely-field-group.qubely-65-35 .qubely-field:first-child{max-width:calc(65% - 15px)}.qubely-field-group.qubely-65-35 .qubely-field:last-child{max-width:35%}.qubely-field-group .qubely-field{flex-grow:1}.qubely-field-group .qubely-field label{font-size:13px;color:#87888A;display:block}.qubely-field-group.qubely-wide{display:block}.qubely-field-group.qubely-wide .qubely-field{width:100%}.qubely-field-group.qubely-wide .qubely-field:not(:last-child){margin-bottom:15px}.qubely-field-group:not(.qubely-wide) .qubely-field:not(:last-child){margin-right:15px;margin-bottom:0}.qubely-field{position:relative;margin-bottom:20px}.qubely-field:last-child{margin-bottom:0}.qubely-field .qubely-field-child button:focus{outline:1px dotted rgba(0,0,0,0.2)}.qubely-field input[type="text"],.qubely-field input[type="number"]:not(.components-range-control__number){width:100%;padding:0 2px 0 0 !important;height:26px;border:1px solid #8d96a0;margin:0;text-indent:6px}.qubely-field>label{width:100%;display:block;line-height:18px;margin-bottom:10px}.qubely-field>label::after{content:'';display:block;clear:both}.qubely-field>label .qubely-device{margin-left:5px;display:inline-block}.qubely-field>label .qubely-device>span{padding:0 2.5px}.qubely-field-group .qubely-device{position:absolute;top:0;left:15px;transform:translateY(-50%);background:#fff;border:1px solid #E2E4E7;padding:0px 2px;font-size:0;overflow:hidden}.qubely-field-group .qubely-device>button{border:none;padding:4px 2.5px}.qubely-device>button{padding:0px;cursor:pointer;font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;font-family:"Font Awesome 5 Free";font-size:9px;background:#E5E7EA;color:#4d515a;height:18px;line-height:19px;width:18px;border-radius:50%;border:none}.qubely-device>button:not(:last-child){margin-right:4px}.qubely-device>button.qubely-device-desktop::before{content:"\f108"}.qubely-device>button.qubely-device-tablet::before{content:"\f3fa"}.qubely-device>button.qubely-device-mobile::before{content:"\f3cd"}.qubely-device>button:focus{outline:0;box-shadow:0}.qubely-device>button.active{background:#D2E7FF;color:#2184F9}.qubely-group-color-palatte{margin:0}.qubely-group-color-palatte:not(:last-child){margin-bottom:20px}.qubely-group-color-palatte>button{position:relative;float:left !important;margin-right:10px !important;height:28px !important;width:28px !important;font-size:0px !important;border-radius:50% !important;border:none;box-shadow:none !important}.qubely-group-color-palatte>button::after{content:'\f05e';-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-family:"Font Awesome 5 Free";font-weight:900;font-size:28px;color:red;left:50%;top:50%;position:absolute;transform:translate(-50%, -50%)}.qubely-group-color-palatte .components-color-palette__item-wrapper{margin-bottom:0;margin-right:10px}.qubely-group-color-palatte::after{content:'';display:block;clear:both}.qubely-unit-btn-group{overflow:hidden;display:inline-block;font-size:0}.qubely-unit-btn-group button{line-height:1;display:inline-block;border:none;font-size:10px;padding:0;border-radius:2px !important;color:#8D96A0;text-transform:uppercase;background:transparent;cursor:pointer}.qubely-unit-btn-group button:focus{outline:0;box-shadow:none}.qubely-unit-btn-group button.active{color:#2184F9}.qubely-unit-btn-group button:not(:last-child){margin-right:5px}.qubely-d-flex{display:flex}.qubely-align-center{align-items:center}.qubely-align-justified{justify-content:space-evenly}.qubely-align-justified>div{padding:0 5px;display:inline-block !important;margin-bottom:0 !important}.qubely-align-justified>div label{display:block;margin-bottom:8px !important;text-align:center}.qubely-align-justified>div:first-child{padding-left:0}.qubely-align-justified>div:last-child{padding-right:0}.qubely-ml-auto{margin-left:auto}.qubely-ml-10{margin-left:10px}.qubely-ml-15{margin-left:15px}.qubely-mr-auto{margin-right:auto}.qubely-mr-15{margin-right:15px}.qubely-mb-15{margin-bottom:15px}.qubely-mb-20{margin-bottom:20px}.qubely-mb-10{margin-bottom:10px}.qubely-mb-0{margin-bottom:0 !important}.qubely-w-100{width:100%}.qubely-text-right{text-align:right}.qubely-d-block{display:block}.qubely-button{background:#FFF;border:1px solid #E5E7EA;min-height:26px;display:inline-flex;align-items:center;padding:3px 8px;text-align:center;color:#8D96A0;transition:all 400ms;border-radius:3px;cursor:pointer}.qubely-button:focus{box-shadow:none;outline:0}.qubely-button.active{background:#D2E7FF;color:#2184F9;border-color:#A9D0FF}.qubely-button.active+.qubely-button{border-left-color:#A9D0FF}.qubely-button.qubely-button-rounded{border-radius:3px !important}.qubely-field-button-list{display:inline-flex;vertical-align:middle}.qubely-field-button-list .qubely-button{position:relative;flex:1 1 auto}.qubely-field-button-list .qubely-button:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.qubely-field-button-list .qubely-button:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.qubely-field-button-list .qubely-button:not(:first-child){margin-left:-1px}.qubely-common-button,.qubely-button-group .qubely-button{border:1px solid #6c7781;text-align:center;flex-grow:1;padding:4px 6px;font-size:12px;cursor:pointer;color:#555;border-color:#ccc;background:#f7f7f7;box-shadow:inset 0 -1px 0 #ccc;vertical-align:top}.qubely-common-button:first-child,.qubely-button-group .qubely-button:first-child{border-radius:4px 0 0 4px}.qubely-common-button:last-child,.qubely-button-group .qubely-button:last-child{border-radius:0 4px 4px 0}.qubely-common-button:not(:last-child),.qubely-button-group .qubely-button:not(:last-child){border-right:none}.qubely-common-button:hover,.qubely-button-group .qubely-button:hover{background:#fafafa;border-color:#999;box-shadow:inset 0 -1px 0 #999;color:#23282d;text-decoration:none}.qubely-common-button.active,.qubely-button-group .active.qubely-button{background:#0085ba;border-color:#00648c;color:#ffffff;box-shadow:inset 0 -1px 0 #00648c}.qubely-common-button.active:hover,.qubely-button-group .active.qubely-button:hover{background:#007eb1;border-color:#00435d;color:#fff;box-shadow:inset 0 -1px 0 #00435d}.qubely-button-group{display:flex;border-radius:5px;width:100%;margin:0}.qubely-button-group.qubely-popup-button-group button{padding:5px 9px}.qubely-background-tab-wrap .qubely-gradient{margin-bottom:0}.components-resizable-box__container .qubely-column{max-width:100%}.block-editor-block-list__layout .components-resizable-box__container{max-width:100% !important}.components-resizable-box__container .block-editor-inner-blocks .editor-block-list__block{padding-left:0;padding-right:0}.components-resizable-box__container .block-editor-inner-blocks .editor-block-list__block .editor-block-list__block-edit{margin:0}.qubely-row .qubely-column .block-editor-inner-blocks .block-editor-block-list__layout{margin-left:0;margin-right:0}.qubely-row .block-editor-block-list__layout .editor-block-list__block{padding-left:0;padding-right:0}.edit-post-visual-editor .qubely-row .editor-block-list__block .editor-block-list__block-edit{margin-left:0;margin-right:0}.qubely-row .block-editor-block-list__layout .editor-block-list__block .editor-block-list__block-edit>.editor-block-contextual-toolbar+[data-block] .components-resizable-box__container{position:relative}.qubely-row .block-editor-block-list__layout .editor-block-list__block.is_hovered[data-type="qubely/columns"]>.editor-block-list__block-edit::before{outline:1px solid green}.qubely-row .block-editor-block-list__layout .editor-block-list__block .editor-block-list__block-edit>.editor-block-contextual-toolbar+[data-block] .components-resizable-box__container{position:relative}.qubely-row .block-editor-block-list__layout .editor-block-list__block.is_hovered .components-resizable-box__container{position:relative}.qubely-row .block-editor-block-list__layout .editor-block-list__block.is_hovered .components-resizable-box__container::before{z-index:9999;content:"";position:absolute;outline:2px solid green;transition:outline .1s linear;pointer-events:none;right:-14px;left:-14px;top:-14px;bottom:-14px}.qubely-row-preset{background:#F7F8FC;padding:25px 15px}.qubely-row-preset .qubely-row-preset-title{font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;text-align:center;font-size:16px;margin-bottom:18px;color:#191E23}.qubely-row-preset .qubely-row-preset-group{max-width:700px;margin:0 auto}.qubely-row-preset .qubely-row-preset-group button{cursor:pointer;width:calc(16.66% - 14px);background:transparent;border:none;height:50px;margin:7px !important;display:inline-flex;padding:0 !important;outline:1px solid transparent;transition:300ms;opacity:1}.qubely-row-preset .qubely-row-preset-group button i{height:50px;background:#E2E6EC;border:1px solid #E2E6EC;border-radius:3px;display:inline-block;position:relative;transition:300ms;font-style:normal}.qubely-row-preset .qubely-row-preset-group button i:not(:last-of-type){margin-right:2px}.qubely-row-preset .qubely-row-preset-group button:hover{border:none;opacity:1}.qubely-row-preset .qubely-row-preset-group button:hover i{font-style:normal;background:#FFFFFF;border:1px solid #2184F9;box-shadow:0 0 0 1px rgba(33,132,249,0.2)}.qubely-row-preset .import-layout-btn-container .is-button.is-primary{text-shadow:none;background:#2184F9;box-shadow:none;border:0;padding:10px 12px;line-height:1;height:auto;transition:color 300ms, background-color 300ms}.qubely-row-preset .import-layout-btn-container .is-button.is-primary:hover{background:#1d74df}.qubely-component-remove-button{position:absolute;align-items:center;justify-content:center;top:13px;right:15px;padding:0;transition:200ms;font-size:17px;color:#CCCFD5}.qubely-component-remove-button:hover{color:#B4B7BC}.qubely-inspect-tabs{border:1px solid #e2e4e7;padding:10px;border-radius:4px;margin-top:30px;position:relative}.qubely-inspect-tabs .components-tab-panel__tabs{position:absolute;top:-15px;border:2px solid #e2e4e7;display:inline-block;border-radius:4px;overflow:hidden}.qubely-inspect-tabs .components-tab-panel__tabs button{padding:7px 15px;box-shadow:none;cursor:pointer;border:none;cursor:pointer;transition:400ms}.qubely-inspect-tabs .components-tab-panel__tabs button.active-tab{background:#0085ba;color:#fff}.qubely-inspect-tabs .components-tab-panel__tab-content{margin-top:10px}.qubely-inspect-tabs .components-tab-panel__tab-content .components-panel__body-title{display:none}.qubely-inspect-tabs .components-tab-panel__tab-content .components-panel__body{border-top:none}#qubelyImportLayoutBtn{vertical-align:middle;display:inline-flex;align-items:center;background:#2184F9;color:#fff;text-decoration:none;border-radius:3px;white-space:nowrap;border-width:1px;border-style:solid;font-size:13px;margin:0 15px 0 15px;padding:9px 12px;border:0;cursor:pointer;-webkit-appearance:none;transition:400ms}#qubelyImportLayoutBtn:hover,#qubelyImportLayoutBtn:focus,#qubelyImportLayoutBtn:active{color:#fff;text-decoration:none;background:#1d74df}#qubelyImportLayoutBtn img{height:16px;width:16px;margin-right:10px}.qubely-editor-btn{background:none;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-appearance:button;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.qubely-editor-btn::-moz-focus-inner{border:0;padding:0}.qubely-field-type-switch .qubely-field-switch-button button{border:1px solid #D8D8D8;padding:8px 13px;font-size:13px;color:#333;cursor:pointer}.qubely-field-type-switch .qubely-field-switch-button button .qubely-gradient{position:relative;border:2px solid #333;background-image:linear-gradient(45deg, #333 50%, #fff 50%)}.qubely-field-type-switch .qubely-field-switch-button button .qubely-gradient i{font-style:normal;width:22px;visibility:hidden}.qubely-field-type-switch .qubely-field-switch-button button:not(:last-child){border-right:none}.qubely-field-type-switch .qubely-field-switch-button button:first-child{border-radius:4px 0 0 4px}.qubely-field-type-switch .qubely-field-switch-button button:last-child{border-radius:0 4px 4px 0}.qubely-field-type-switch .qubely-field-switch-button button.active{background:#0085ba;border-color:#006a95;color:#fff}.qubely-field-type-switch .qubely-field-switch-button button.active .qubely-gradient{border-color:#fff;background-image:linear-gradient(45deg, #0085ba 50%, #fff 50%)}.qubely-field-type-switch .qubely-field-switch-button button.active+button{border-left-color:#006a95}.qubely-label-inline-group{display:flex;align-items:center;justify-content:space-between}.qubley-margin-indicator,.qubley-padding-indicator{opacity:0}.qubley-margin-indicator .qubely-indicator-bottom,.qubley-margin-indicator .qubely-indicator-top,.qubley-margin-indicator .qubely-indicator-left,.qubley-margin-indicator .qubely-indicator-right,.qubley-padding-indicator .qubely-indicator-bottom,.qubley-padding-indicator .qubely-indicator-top,.qubley-padding-indicator .qubely-indicator-left,.qubley-padding-indicator .qubely-indicator-right{position:absolute;opacity:.4;background-color:rgba(3,225,109,0.3);display:flex;justify-content:center;align-items:center;font-size:13px;color:#222;z-index:1}.qubley-margin-indicator .qubely-indicator-bottom,.qubley-margin-indicator .qubely-indicator-top,.qubley-padding-indicator .qubely-indicator-bottom,.qubley-padding-indicator .qubely-indicator-top{height:0px;top:0;left:0;right:0}.qubley-margin-indicator .qubely-indicator-bottom,.qubley-padding-indicator .qubely-indicator-bottom{top:auto;bottom:0}.qubley-margin-indicator .qubely-indicator-left,.qubley-margin-indicator .qubely-indicator-right,.qubley-padding-indicator .qubely-indicator-left,.qubley-padding-indicator .qubely-indicator-right{width:0px;top:0;bottom:0;left:0}.qubley-margin-indicator .qubely-indicator-right,.qubley-padding-indicator .qubely-indicator-right{left:auto;right:0}.qubely-section:hover .qubley-margin-indicator,.qubely-section:hover .qubley-padding-indicator,.qubely-section.active .qubley-margin-indicator,.qubely-section.active .qubley-padding-indicator,.padding-input-dragging .qubley-margin-indicator,.padding-input-dragging .qubley-padding-indicator,.margin-input-dragging .qubley-margin-indicator,.margin-input-dragging .qubley-padding-indicator,.padding-dragging .qubley-margin-indicator,.padding-dragging .qubley-padding-indicator,.margin-dragging .qubley-margin-indicator,.margin-dragging .qubley-padding-indicator{opacity:1}.qubley-margin-indicator .qubely-indicator-bottom,.qubley-margin-indicator .qubely-indicator-top,.qubley-margin-indicator .qubely-indicator-left,.qubley-margin-indicator .qubely-indicator-right{background:rgba(0,123,243,0.3)}.qubley-margin-indicator .qubely-indicator-bottom{transform:translateY(100%)}.qubley-margin-indicator .qubely-indicator-top{transform:translateY(-100%)}.qubley-margin-indicator .qubely-indicator-left{transform:translateX(-100%)}.qubley-margin-indicator .qubely-indicator-right{transform:translateX(100%)}.not-clickable,.disable-slide .qubely-field-child{filter:opacity(0.3);pointer-events:none}.block-editor-block-list__block .qubely-row *>[data-type="qubely/column"] .block-list-appender{margin:0}.qubely-row *>[data-type="qubely/column"].has-child-selected{z-index:1}.block-editor-block-list__layout .block-editor-block-list__block[data-type="qubely/row"].is-selected{z-index:inherit}span.block-editor-block-icon img{max-width:24px}.qubely-import-button-wrapper{display:inline-flex;align-items:center}.editor-styles-wrapper .wpfunnels-block-offer-button.rich-text{margin:0}#wpfnl-offerbtn-wrapper{width:100%;max-width:460px}#wpfnl-offerbtn-wrapper .wpfnl-product-variation-title{background:#FDF4ED;border-radius:10px;font-weight:700;font-size:20px;line-height:1.1;color:#363B4C;padding:15px 27px;margin:0 0 13px 0;border:none;box-shadow:none}#wpfnl-offerbtn-wrapper .wpfnl-product-variation{position:relative}#wpfnl-offerbtn-wrapper .has-variation-product{border:1px solid #F8F8FF;border-radius:10px;padding:4px 24px 24px}#wpfnl-offerbtn-wrapper .offer-variation-wrapper{display:flex;flex-flow:row wrap;width:100%;margin-bottom:10px;position:relative}#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation{margin-right:15px;margin-top:15px;width:calc(100% / 2 - 8px)}#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation:nth-child(even){margin-right:0}#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation .variation-attr-name{font-size:16px;line-height:1.3;color:#363B4C;text-transform:capitalize;margin-bottom:6px;text-align:left;font-weight:500}#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation .variation-attr-value{position:relative}#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation select{width:100%;-webkit-appearance:auto;appearance:auto;padding:10px 14px;color:#85868F;border:1px solid #E5E8F3;border-radius:5px;font-size:16px;background:#FFFFFF;outline:none;box-shadow:none}#wpfnl-offerbtn-wrapper .offer-variation-wrapper .wpfnl-select-variation{display:block;width:100%;font-size:16px;font-weight:500;color:#ee5f5a;margin-bottom:5px}#wpfnl-offerbtn-wrapper .offer-variation-wrapper .reset_variations{line-height:1;font-size:16px;font-weight:500;color:#000000;text-transform:capitalize;padding:7px 5px;text-align:center;width:100%;margin-top:15px;background:#fdf4ed;border-radius:5px}#wpfnl-offerbtn-wrapper .offer-variation-wrapper .reset_variations[style*="visibility: hidden"]{display:none}#wpfnl-offerbtn-wrapper .offer-variation-wrapper .reset_variations[style*="visibility: visible"]{display:block}#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper{display:flex;flex-flow:row wrap;align-items:center;justify-content:flex-start;gap:10px}#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper.price-right{flex-direction:row-reverse;justify-content:flex-end}#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper.price-top{flex-direction:column;align-items:flex-start}#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper.price-bottom{flex-direction:column-reverse;align-items:flex-start}#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper .wpfnl-offer-product-price{display:inline-flex;align-items:center;flex-flow:row-reverse}#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper del{font-weight:500;font-size:20px;line-height:1.1;color:rgba(54,59,76,0.8);margin-left:6px !important;margin-right:8px}#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper ins{color:#E08746;font-weight:700;font-size:32px;line-height:1.18;text-decoration:none}#wpfnl-offerbtn-wrapper .offer-btn-loader{display:block;background:#00000036;width:calc(100% + 20px);height:100%;position:absolute;top:8px;left:-10px;border-radius:10px;z-index:2;display:none}#wpfnl-offerbtn-wrapper .offer-btn-loader:before{content:"";position:absolute;left:50%;top:50%;width:40px;height:40px;margin-top:-20px;margin-left:-20px;border:3px solid #353535;border-radius:100%;transform:translate(-50%, -50%);border-top-color:#fff;animation:spin 0.7s linear infinite}.wp-block-wpfnl-offer-btn-center #wpfnl-offerbtn-wrapper{margin:0 auto}.wp-block-wpfnl-offer-btn-center #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper{justify-content:center}.wp-block-wpfnl-offer-btn-center #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper.price-top{align-items:center}.wp-block-wpfnl-offer-btn-center #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper.price-right{align-items:flex-end}.wp-block-wpfnl-offer-btn-center #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper.price-bottom{align-items:center}.wp-block-wpfnl-offer-btn-right #wpfnl-offerbtn-wrapper{margin-left:auto;margin-right:0}.wp-block-wpfnl-offer-btn-right #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper{justify-content:flex-end}.wp-block-wpfnl-offer-btn-right #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper.price-top{align-items:flex-end}.wp-block-wpfnl-offer-btn-right #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper.price-right{justify-content:flex-start}.wp-block-wpfnl-offer-btn-right #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper.price-bottom{align-items:flex-end}.dynamic-offer-template-default{display:flex;flex-flow:row wrap;align-items:center;justify-content:space-between;padding:50px;box-shadow:0px 15px 70px 0px #2a352e26;border-radius:20px}.dynamic-offer-template-default .template-left{width:45%}.dynamic-offer-template-default .template-right{width:55%;padding-left:30px}.dynamic-offer-template1 .product-img img{display:block;max-width:100%;border-radius:10px}.dynamic-offer-template1 .template-content .template-product-title{margin-bottom:20px;font-size:52px;line-height:1.19em;font-weight:700;color:#1F392A}.dynamic-offer-template1 .template-content .template-product-description p,.dynamic-offer-template1 .template-content .template-product-description{font-size:18px;line-height:1.5em;font-weight:400;color:#717874}.dynamic-offer-template1 .template-content .template-product-description{margin-bottom:20px}.dynamic-offer-template1 .template-content .price{color:#FE7E6D;font-size:36px;line-height:1.19;font-weight:700}.dynamic-offer-template1 .template-content .price del{font-size:80%;color:#8c918e}.dynamic-offer-template1 .template-content .price ins{font-weight:700}.dynamic-offer-template1 #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price{display:flex;justify-content:flex-end}.dynamic-offer-template1 #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price bdi{color:#FE7E6D;font-size:36px;line-height:1;font-weight:700;text-decoration:none}.dynamic-offer-template1 #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del bdi{font-size:22px;color:#8c918e;font-weight:500}.dynamic-offer-template2{flex-direction:row-reverse}.dynamic-offer-template2 .product-img img{margin-left:auto}.dynamic-offer-template2 .template-right{padding-left:0;padding-right:30px}.dynamic-offer-template3{display:block}.dynamic-offer-template3 .template-left{width:100%}.dynamic-offer-template3 .template-right{width:100%;padding-left:0}.dynamic-offer-template3 .product-img img{margin:0 auto}@media only screen and (max-width: 1199px){.dynamic-offer-template1 .template-content .template-product-title{font-size:38px}.dynamic-offer-template1 .template-content .price{font-size:28px}}@media only screen and (max-width: 991px){.dynamic-offer-template-default{padding:20px;border-radius:10px}.dynamic-offer-template1 .template-content .template-product-title{margin-bottom:10px;font-size:26px}.dynamic-offer-template1 .template-content .template-product-description p,.dynamic-offer-template1 .template-content .template-product-description{font-size:16px}.dynamic-offer-template1 .template-content .template-product-description{margin-bottom:14px}}@media only screen and (max-width: 767px){.dynamic-offer-template-default{display:block}.dynamic-offer-template-default .template-left{width:100%}.dynamic-offer-template-default .template-right{width:100%;padding-left:0;margin-top:20px}.dynamic-offer-template2 .template-right{padding-right:0}} .wp-block-gb-basic-esnext-02{color:#000000;background:#BADA55;border:0.2rem solid green;padding:2rem}.wpfunnel-texteditor{padding:5px}.components-circular-option-picker{padding:10px}.blocks-base-control__label{margin:5px}.qubely-field-group-btn{align-items:center}.qubely-field-group-btn label{margin-bottom:0}.qubely-field-group-btn .qubely-field-child{display:flex;justify-content:end;align-items:center}.qubely-field-group-btn .qubely-field-child .qubley-group-button{display:inline-block;white-space:nowrap;padding:0px 9px;color:#8d96a0;font-size:12px;border-top:1px solid #d6d9dd;border-bottom:1px solid #d6d9dd;border-left:1px solid #d6d9dd;text-transform:capitalize;cursor:pointer;box-shadow:none;line-height:26px;height:26px}.qubely-field-group-btn .qubely-field-child .qubley-group-button.extra-padding{padding:0px 10px;font-weight:bold;font-size:15px}.qubely-field-group-btn .qubely-field-child .qubley-group-button:last-child{border-bottom-right-radius:3px;border-top-right-radius:3px;border-right:1px solid #d6d9dd}.qubely-field-group-btn .qubely-field-child .qubley-group-button:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.qubely-field-group-btn .qubely-field-child .qubley-group-button.qubley-active-group-btn{color:#2184f9;background:#d2e7ff;border-color:#a9d0ff}.qubely-field-group-btn .qubely-field-child .qubley-group-button.qubley-active-group-btn+.qubley-group-button{border-left-color:#a9d0ff}.qubely-input-range{display:flex}.qubely-input-range>*:not(:last-child){margin-right:8px}.qubely-input-range input[type="number"]{width:55px !important}.qubely-input-range input[type=range]{-webkit-appearance:none;margin-top:7px;margin-bottom:7px;width:100%;padding-left:0;padding-right:0;background:transparent}.qubely-input-range input[type=range]::-webkit-slider-runnable-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:#E5E7EA;border-radius:5px;border:0px solid #000}.qubely-input-range input[type=range]::-webkit-slider-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer;-webkit-appearance:none;margin-top:-5px}.qubely-input-range input[type=range]:focus::-webkit-slider-runnable-track{background:#f3f4f5}.qubely-input-range input[type=range]::-moz-range-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:#E5E7EA;border-radius:5px;border:0px solid #000}.qubely-input-range input[type=range]::-moz-range-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer}.qubely-input-range input[type=range]::-ms-track{width:100%;height:4px;cursor:pointer;animate:0.2s;background:transparent;border-color:transparent;border-width:14px 0;color:transparent}.qubely-input-range input[type=range]::-ms-fill-lower{background:#d7dadf;border:0px solid #000;border-radius:10px}.qubely-input-range input[type=range]::-ms-fill-upper{background:#E5E7EA;border:0px solid #000;border-radius:10px}.qubely-input-range input[type=range]::-ms-thumb{border:1px solid #fff;height:14px;width:14px;border-radius:8px;background:#606871;cursor:pointer}.qubely-input-range input[type=range]:focus::-ms-fill-lower{background:#E5E7EA}.qubely-input-range input[type=range]:focus::-ms-fill-upper{background:#f3f4f5}.qubely-field-range>label{width:100%;margin-bottom:2px}.qubely-field.qubely-field-toggle{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.qubely-field.qubely-field-toggle>label{width:auto;margin-bottom:0}.qubely-field.qubely-field-toggle .components-toggle-control,.qubely-field.qubely-field-toggle .components-toggle-control .components-base-control__field{margin-bottom:0 !important}.qubely-field.qubely-field-toggle .components-toggle-control .components-base-control__field .components-form-toggle{margin-right:0}.qubely-field.qubely-field-toggle .components-form-toggle.is-checked .components-form-toggle__track{background-color:#2184F9}.qubely-field-font-family{min-width:calc(65% - 15px)}.qubely-field-font-weight{min-width:35%}.qubely-font-family-picker,.qubely-font-weight-picker-wrapper{-webkit-box-align:center;align-items:center;background-color:#fff;cursor:default;display:flex;flex-wrap:wrap;-webkit-box-pack:justify;justify-content:space-between;position:relative;box-sizing:border-box;border-color:#ccc;border-radius:4px;border-style:solid;border-width:1px;transition:all 100ms ease 0s;outline:0px !important;line-height:28px;height:30px;vertical-align:middle;padding-left:5px}.qubely-font-family-picker .editor-rich-text,.qubely-font-weight-picker-wrapper .editor-rich-text{width:100%}.qubely-font-family-picker .selected-font-family,.qubely-font-weight-picker-wrapper .selected-font-family{color:black}.qubely-font-family-option-wrapper{top:100%;background-color:#fff;box-shadow:rgba(0,0,0,0.1) 0px 0px 0px 1px,rgba(0,0,0,0.1) 0px 4px 11px;margin-bottom:8px;margin-top:8px;position:absolute;width:70%;box-sizing:border-box;border-radius:4px;z-index:100}.qubely-font-weight-wrapper{top:100%;left:62%;background-color:#fff;box-shadow:rgba(0,0,0,0.1) 0px 0px 0px 1px,rgba(0,0,0,0.1) 0px 4px 11px;margin-bottom:8px;margin-top:8px;position:absolute;width:40%;box-sizing:border-box;border-radius:4px;z-index:100}.qubely-font-family-options,.qubely-font-family-weights{max-height:270px;overflow-y:auto;padding-bottom:4px;padding-top:4px;position:relative;box-sizing:border-box}.qubely-active-font-family,.qubely-active-font-weight{background-color:#2684ff;color:#fff;cursor:default;display:block;font-size:inherit;width:100%;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing:border-box;padding:8px 12px}.qubely-font-family-option,.qubely-font-weight-option{background-color:transparent;color:inherit;cursor:default;display:block;font-size:inherit;width:100%;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing:border-box;padding:8px 12px}.qubely-font-family-option:hover,.qubely-font-weight-option:hover{background-color:#e8eaeb}.qubely-font-family-search-wrapper{display:flex;justify-content:space-between;width:100%}.qubely-font-family-search-wrapper input.qubely-font-family-search{border:none !important;background-color:transparent !important}.qubely-font-family-search-wrapper input.qubely-font-family-search:focus,.qubely-font-family-search-wrapper input.qubely-font-family-search:active,.qubely-font-family-search-wrapper input.qubely-font-family-search:hover{outline:0;box-shadow:none}.qubely-font-weight-picker-wrapper{display:flex;justify-content:space-between;width:100%}.qubely-font-select-icon{display:flex;align-items:center;padding-right:3px}.qubely-field.qubely-field-typography .components-dropdown.qubely-field{width:100%}.qubely-row{position:relative}.qubely-column-layout{display:flex;flex-wrap:nowrap}.qubely-column-1{flex:1}.qubely-column-2{flex:2}.qubely-column-3{flex:3}.qubely-column-4{flex:4}.wp-block-qubely-row{display:block}.wp-block-qubely-row{flex-wrap:wrap}.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout{flex-wrap:wrap;display:flex}.wp-block-qubely-row .block-editor-block-list__layout{margin-left:0;margin-right:0}.qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout{display:flex;flex-wrap:wrap}.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout>[data-type="qubely/column"]{display:flex;flex-direction:column;padding-left:0;padding-right:0;word-break:break-word;overflow-wrap:break-word;width:100%}.wp-block-qubely-row .block-editor-block-list__layout .editor-block-list__block{max-width:none}.wp-block-qubely-row [data-type="qubely/column"]{pointer-events:none}.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout>[data-type="qubely/column"]>.editor-block-list__block-edit{margin-top:0;margin-bottom:0;margin-left:0;margin-right:0}:not(.components-disabled)>.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout>[data-type="qubely/column"]>.editor-block-list__block-edit>*{pointer-events:all}.wp-block-qubely-row>.block-editor-inner-blocks>.block-editor-block-list__layout>[data-type="qubely/column"]>.editor-block-list__block-edit>div>.block-editor-inner-blocks{margin-top:-28px;margin-bottom:-28px}.wp-block-qubely-row .block-editor-block-list__layout{margin-left:0;margin-right:0}.wp-block-qubely-row .block-editor-block-list__layout .editor-block-list__block{max-width:none}div[data-type="qubely/row"].is-selected div[data-type="qubely/column"]:last-child>div>div>.components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle,div[data-type="qubely/row"].is-selected div[data-type="qubely/column"]:last-child>div>div>.components-resizable-box__container.qubely-column-resizer::after,div[data-type="qubely/row"].is-resizing div[data-type="qubely/column"]:last-child>div>div>.components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle,div[data-type="qubely/row"].is-resizing div[data-type="qubely/column"]:last-child>div>div>.components-resizable-box__container.qubely-column-resizer::after{display:none}.wp-block-qubely-row [data-type="qubely/column"] .components-resizable-box__container:hover .components-resizable-box__handle{display:block;z-index:9}.components-resizable-box__container.qubely-column-resizer.is-selected-column>span>.components-resizable-box__handle,div[data-type="qubely/row"].is-selected .components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle,div[data-type="qubely/row"].is-resizing .components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle{right:-15px;top:0;background:transparent;z-index:9999;width:auto;height:100%;display:flex;align-items:center;transform:translateX(50%);background:transparent}.components-resizable-box__container.qubely-column-resizer.is-selected-column>span>.components-resizable-box__handle::before,div[data-type="qubely/row"].is-selected .components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle::before,div[data-type="qubely/row"].is-resizing .components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle::before{background:#e3e4e7;border:none;opacity:1;border-radius:30px;width:3.5px;right:calc(50% - 2px)}.components-resizable-box__container.qubely-column-resizer>span>.components-resizable-box__handle{width:8px}.qubely-flex-box{display:flex}.qubely-flex-column{flex-direction:column}.qubely-flex-1{flex:1}.qubely-flex-2{flex:2}.qubely-flex-3{flex:3}.qubely-flex-4{flex:4}.item-center{align-items:center}.item-left{align-items:flex-start}.item-right{align-items:flex-end}.justify-center{justify-content:center}.justify-left{justify-content:flex-start}.justify-right{justify-content:flex-end}.qubely-palette-fields .qubely-palette-unit{padding-bottom:12px;padding-left:3px}[data-type="qubely/columns"] .block-editor-block-list__layout .editor-block-list__block .editor-block-list__block-edit{margin-top:14px;margin-bottom:14px}#wpwrap [data-type="qubely/columns"] .block-editor-block-list__layout .editor-block-list__block .editor-block-list__block-edit p:last-child{margin-bottom:0}#wpwrap [data-type="qubely/columns"] .is-selected .qubely-column[data-gwidth]::before{content:attr(data-gwidth);position:absolute;font-size:10px;top:-1px;right:-1px;background:#2178c3;padding:0 8px;border-radius:unset;color:#fff}.qubely-field-group{position:relative;display:flex}.qubely-field-group:not(:last-child){margin-bottom:20px}.qubely-field-group.qubely-65-35 .qubely-field:first-child{max-width:calc(65% - 15px)}.qubely-field-group.qubely-65-35 .qubely-field:last-child{max-width:35%}.qubely-field-group .qubely-field{flex-grow:1}.qubely-field-group .qubely-field label{font-size:13px;color:#87888A;display:block}.qubely-field-group.qubely-wide{display:block}.qubely-field-group.qubely-wide .qubely-field{width:100%}.qubely-field-group.qubely-wide .qubely-field:not(:last-child){margin-bottom:15px}.qubely-field-group:not(.qubely-wide) .qubely-field:not(:last-child){margin-right:15px;margin-bottom:0}.qubely-field{position:relative;margin-bottom:20px}.qubely-field:last-child{margin-bottom:0}.qubely-field .qubely-field-child button:focus{outline:1px dotted rgba(0,0,0,0.2)}.qubely-field input[type="text"],.qubely-field input[type="number"]:not(.components-range-control__number){width:100%;padding:0 2px 0 0 !important;height:26px;border:1px solid #8d96a0;margin:0;text-indent:6px}.qubely-field>label{width:100%;display:block;line-height:18px;margin-bottom:10px}.qubely-field>label::after{content:'';display:block;clear:both}.qubely-field>label .qubely-device{margin-left:5px;display:inline-block}.qubely-field>label .qubely-device>span{padding:0 2.5px}.qubely-field-group .qubely-device{position:absolute;top:0;left:15px;transform:translateY(-50%);background:#fff;border:1px solid #E2E4E7;padding:0px 2px;font-size:0;overflow:hidden}.qubely-field-group .qubely-device>button{border:none;padding:4px 2.5px}.qubely-device>button{padding:0px;cursor:pointer;font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;font-family:"Font Awesome 5 Free";font-size:9px;background:#E5E7EA;color:#4d515a;height:18px;line-height:19px;width:18px;border-radius:50%;border:none}.qubely-device>button:not(:last-child){margin-right:4px}.qubely-device>button.qubely-device-desktop::before{content:"\f108"}.qubely-device>button.qubely-device-tablet::before{content:"\f3fa"}.qubely-device>button.qubely-device-mobile::before{content:"\f3cd"}.qubely-device>button:focus{outline:0;box-shadow:0}.qubely-device>button.active{background:#D2E7FF;color:#2184F9}.qubely-group-color-palatte{margin:0}.qubely-group-color-palatte:not(:last-child){margin-bottom:20px}.qubely-group-color-palatte>button{position:relative;float:left !important;margin-right:10px !important;height:28px !important;width:28px !important;font-size:0px !important;border-radius:50% !important;border:none;box-shadow:none !important}.qubely-group-color-palatte>button::after{content:'\f05e';-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-family:"Font Awesome 5 Free";font-weight:900;font-size:28px;color:red;left:50%;top:50%;position:absolute;transform:translate(-50%, -50%)}.qubely-group-color-palatte .components-color-palette__item-wrapper{margin-bottom:0;margin-right:10px}.qubely-group-color-palatte::after{content:'';display:block;clear:both}.qubely-unit-btn-group{overflow:hidden;display:inline-block;font-size:0}.qubely-unit-btn-group button{line-height:1;display:inline-block;border:none;font-size:10px;padding:0;border-radius:2px !important;color:#8D96A0;text-transform:uppercase;background:transparent;cursor:pointer}.qubely-unit-btn-group button:focus{outline:0;box-shadow:none}.qubely-unit-btn-group button.active{color:#2184F9}.qubely-unit-btn-group button:not(:last-child){margin-right:5px}.qubely-d-flex{display:flex}.qubely-align-center{align-items:center}.qubely-align-justified{justify-content:space-evenly}.qubely-align-justified>div{padding:0 5px;display:inline-block !important;margin-bottom:0 !important}.qubely-align-justified>div label{display:block;margin-bottom:8px !important;text-align:center}.qubely-align-justified>div:first-child{padding-left:0}.qubely-align-justified>div:last-child{padding-right:0}.qubely-ml-auto{margin-left:auto}.qubely-ml-10{margin-left:10px}.qubely-ml-15{margin-left:15px}.qubely-mr-auto{margin-right:auto}.qubely-mr-15{margin-right:15px}.qubely-mb-15{margin-bottom:15px}.qubely-mb-20{margin-bottom:20px}.qubely-mb-10{margin-bottom:10px}.qubely-mb-0{margin-bottom:0 !important}.qubely-w-100{width:100%}.qubely-text-right{text-align:right}.qubely-d-block{display:block}.qubely-button{background:#FFF;border:1px solid #E5E7EA;min-height:26px;display:inline-flex;align-items:center;padding:3px 8px;text-align:center;color:#8D96A0;transition:all 400ms;border-radius:3px;cursor:pointer}.qubely-button:focus{box-shadow:none;outline:0}.qubely-button.active{background:#D2E7FF;color:#2184F9;border-color:#A9D0FF}.qubely-button.active+.qubely-button{border-left-color:#A9D0FF}.qubely-button.qubely-button-rounded{border-radius:3px !important}.qubely-field-button-list{display:inline-flex;vertical-align:middle}.qubely-field-button-list .qubely-button{position:relative;flex:1 1 auto}.qubely-field-button-list .qubely-button:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.qubely-field-button-list .qubely-button:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.qubely-field-button-list .qubely-button:not(:first-child){margin-left:-1px}.qubely-common-button,.qubely-button-group .qubely-button{border:1px solid #6c7781;text-align:center;flex-grow:1;padding:4px 6px;font-size:12px;cursor:pointer;color:#555;border-color:#ccc;background:#f7f7f7;box-shadow:inset 0 -1px 0 #ccc;vertical-align:top}.qubely-common-button:first-child,.qubely-button-group .qubely-button:first-child{border-radius:4px 0 0 4px}.qubely-common-button:last-child,.qubely-button-group .qubely-button:last-child{border-radius:0 4px 4px 0}.qubely-common-button:not(:last-child),.qubely-button-group .qubely-button:not(:last-child){border-right:none}.qubely-common-button:hover,.qubely-button-group .qubely-button:hover{background:#fafafa;border-color:#999;box-shadow:inset 0 -1px 0 #999;color:#23282d;text-decoration:none}.qubely-common-button.active,.qubely-button-group .active.qubely-button{background:#0085ba;border-color:#00648c;color:#ffffff;box-shadow:inset 0 -1px 0 #00648c}.qubely-common-button.active:hover,.qubely-button-group .active.qubely-button:hover{background:#007eb1;border-color:#00435d;color:#fff;box-shadow:inset 0 -1px 0 #00435d}.qubely-button-group{display:flex;border-radius:5px;width:100%;margin:0}.qubely-button-group.qubely-popup-button-group button{padding:5px 9px}.qubely-background-tab-wrap .qubely-gradient{margin-bottom:0}.components-resizable-box__container .qubely-column{max-width:100%}.block-editor-block-list__layout .components-resizable-box__container{max-width:100% !important}.components-resizable-box__container .block-editor-inner-blocks .editor-block-list__block{padding-left:0;padding-right:0}.components-resizable-box__container .block-editor-inner-blocks .editor-block-list__block .editor-block-list__block-edit{margin:0}.qubely-row .qubely-column .block-editor-inner-blocks .block-editor-block-list__layout{margin-left:0;margin-right:0}.qubely-row .block-editor-block-list__layout .editor-block-list__block{padding-left:0;padding-right:0}.edit-post-visual-editor .qubely-row .editor-block-list__block .editor-block-list__block-edit{margin-left:0;margin-right:0}.qubely-row .block-editor-block-list__layout .editor-block-list__block .editor-block-list__block-edit>.editor-block-contextual-toolbar+[data-block] .components-resizable-box__container{position:relative}.qubely-row .block-editor-block-list__layout .editor-block-list__block.is_hovered[data-type="qubely/columns"]>.editor-block-list__block-edit::before{outline:1px solid green}.qubely-row .block-editor-block-list__layout .editor-block-list__block .editor-block-list__block-edit>.editor-block-contextual-toolbar+[data-block] .components-resizable-box__container{position:relative}.qubely-row .block-editor-block-list__layout .editor-block-list__block.is_hovered .components-resizable-box__container{position:relative}.qubely-row .block-editor-block-list__layout .editor-block-list__block.is_hovered .components-resizable-box__container::before{z-index:9999;content:"";position:absolute;outline:2px solid green;transition:outline .1s linear;pointer-events:none;right:-14px;left:-14px;top:-14px;bottom:-14px}.qubely-row-preset{background:#F7F8FC;padding:25px 15px}.qubely-row-preset .qubely-row-preset-title{font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;text-align:center;font-size:16px;margin-bottom:18px;color:#191E23}.qubely-row-preset .qubely-row-preset-group{max-width:700px;margin:0 auto}.qubely-row-preset .qubely-row-preset-group button{cursor:pointer;width:calc(16.66% - 14px);background:transparent;border:none;height:50px;margin:7px !important;display:inline-flex;padding:0 !important;outline:1px solid transparent;transition:300ms;opacity:1}.qubely-row-preset .qubely-row-preset-group button i{height:50px;background:#E2E6EC;border:1px solid #E2E6EC;border-radius:3px;display:inline-block;position:relative;transition:300ms;font-style:normal}.qubely-row-preset .qubely-row-preset-group button i:not(:last-of-type){margin-right:2px}.qubely-row-preset .qubely-row-preset-group button:hover{border:none;opacity:1}.qubely-row-preset .qubely-row-preset-group button:hover i{font-style:normal;background:#FFFFFF;border:1px solid #2184F9;box-shadow:0 0 0 1px rgba(33,132,249,0.2)}.qubely-row-preset .import-layout-btn-container .is-button.is-primary{text-shadow:none;background:#2184F9;box-shadow:none;border:0;padding:10px 12px;line-height:1;height:auto;transition:color 300ms, background-color 300ms}.qubely-row-preset .import-layout-btn-container .is-button.is-primary:hover{background:#1d74df}.qubely-component-remove-button{position:absolute;align-items:center;justify-content:center;top:13px;right:15px;padding:0;transition:200ms;font-size:17px;color:#CCCFD5}.qubely-component-remove-button:hover{color:#B4B7BC}.qubely-inspect-tabs{border:1px solid #e2e4e7;padding:10px;border-radius:4px;margin-top:30px;position:relative}.qubely-inspect-tabs .components-tab-panel__tabs{position:absolute;top:-15px;border:2px solid #e2e4e7;display:inline-block;border-radius:4px;overflow:hidden}.qubely-inspect-tabs .components-tab-panel__tabs button{padding:7px 15px;box-shadow:none;cursor:pointer;border:none;cursor:pointer;transition:400ms}.qubely-inspect-tabs .components-tab-panel__tabs button.active-tab{background:#0085ba;color:#fff}.qubely-inspect-tabs .components-tab-panel__tab-content{margin-top:10px}.qubely-inspect-tabs .components-tab-panel__tab-content .components-panel__body-title{display:none}.qubely-inspect-tabs .components-tab-panel__tab-content .components-panel__body{border-top:none}#qubelyImportLayoutBtn{vertical-align:middle;display:inline-flex;align-items:center;background:#2184F9;color:#fff;text-decoration:none;border-radius:3px;white-space:nowrap;border-width:1px;border-style:solid;font-size:13px;margin:0 15px 0 15px;padding:9px 12px;border:0;cursor:pointer;-webkit-appearance:none;transition:400ms}#qubelyImportLayoutBtn:hover,#qubelyImportLayoutBtn:focus,#qubelyImportLayoutBtn:active{color:#fff;text-decoration:none;background:#1d74df}#qubelyImportLayoutBtn img{height:16px;width:16px;margin-right:10px}.qubely-editor-btn{background:none;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-appearance:button;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.qubely-editor-btn::-moz-focus-inner{border:0;padding:0}.qubely-field-type-switch .qubely-field-switch-button button{border:1px solid #D8D8D8;padding:8px 13px;font-size:13px;color:#333;cursor:pointer}.qubely-field-type-switch .qubely-field-switch-button button .qubely-gradient{position:relative;border:2px solid #333;background-image:linear-gradient(45deg, #333 50%, #fff 50%)}.qubely-field-type-switch .qubely-field-switch-button button .qubely-gradient i{font-style:normal;width:22px;visibility:hidden}.qubely-field-type-switch .qubely-field-switch-button button:not(:last-child){border-right:none}.qubely-field-type-switch .qubely-field-switch-button button:first-child{border-radius:4px 0 0 4px}.qubely-field-type-switch .qubely-field-switch-button button:last-child{border-radius:0 4px 4px 0}.qubely-field-type-switch .qubely-field-switch-button button.active{background:#0085ba;border-color:#006a95;color:#fff}.qubely-field-type-switch .qubely-field-switch-button button.active .qubely-gradient{border-color:#fff;background-image:linear-gradient(45deg, #0085ba 50%, #fff 50%)}.qubely-field-type-switch .qubely-field-switch-button button.active+button{border-left-color:#006a95}.qubely-label-inline-group{display:flex;align-items:center;justify-content:space-between}.qubley-margin-indicator,.qubley-padding-indicator{opacity:0}.qubley-margin-indicator .qubely-indicator-bottom,.qubley-margin-indicator .qubely-indicator-top,.qubley-margin-indicator .qubely-indicator-left,.qubley-margin-indicator .qubely-indicator-right,.qubley-padding-indicator .qubely-indicator-bottom,.qubley-padding-indicator .qubely-indicator-top,.qubley-padding-indicator .qubely-indicator-left,.qubley-padding-indicator .qubely-indicator-right{position:absolute;opacity:.4;background-color:rgba(3,225,109,0.3);display:flex;justify-content:center;align-items:center;font-size:13px;color:#222;z-index:1}.qubley-margin-indicator .qubely-indicator-bottom,.qubley-margin-indicator .qubely-indicator-top,.qubley-padding-indicator .qubely-indicator-bottom,.qubley-padding-indicator .qubely-indicator-top{height:0px;top:0;left:0;right:0}.qubley-margin-indicator .qubely-indicator-bottom,.qubley-padding-indicator .qubely-indicator-bottom{top:auto;bottom:0}.qubley-margin-indicator .qubely-indicator-left,.qubley-margin-indicator .qubely-indicator-right,.qubley-padding-indicator .qubely-indicator-left,.qubley-padding-indicator .qubely-indicator-right{width:0px;top:0;bottom:0;left:0}.qubley-margin-indicator .qubely-indicator-right,.qubley-padding-indicator .qubely-indicator-right{left:auto;right:0}.qubely-section:hover .qubley-margin-indicator,.qubely-section:hover .qubley-padding-indicator,.qubely-section.active .qubley-margin-indicator,.qubely-section.active .qubley-padding-indicator,.padding-input-dragging .qubley-margin-indicator,.padding-input-dragging .qubley-padding-indicator,.margin-input-dragging .qubley-margin-indicator,.margin-input-dragging .qubley-padding-indicator,.padding-dragging .qubley-margin-indicator,.padding-dragging .qubley-padding-indicator,.margin-dragging .qubley-margin-indicator,.margin-dragging .qubley-padding-indicator{opacity:1}.qubley-margin-indicator .qubely-indicator-bottom,.qubley-margin-indicator .qubely-indicator-top,.qubley-margin-indicator .qubely-indicator-left,.qubley-margin-indicator .qubely-indicator-right{background:rgba(0,123,243,0.3)}.qubley-margin-indicator .qubely-indicator-bottom{transform:translateY(100%)}.qubley-margin-indicator .qubely-indicator-top{transform:translateY(-100%)}.qubley-margin-indicator .qubely-indicator-left{transform:translateX(-100%)}.qubley-margin-indicator .qubely-indicator-right{transform:translateX(100%)}.not-clickable,.disable-slide .qubely-field-child{filter:opacity(0.3);pointer-events:none}.block-editor-block-list__block .qubely-row *>[data-type="qubely/column"] .block-list-appender{margin:0}.qubely-row *>[data-type="qubely/column"].has-child-selected{z-index:1}.block-editor-block-list__layout .block-editor-block-list__block[data-type="qubely/row"].is-selected{z-index:inherit}span.block-editor-block-icon img{max-width:24px}.qubely-import-button-wrapper{display:inline-flex;align-items:center}.editor-styles-wrapper .wpfunnels-block-offer-button.rich-text{margin:0}#et-boc .et-l #wpfnl-offerbtn-wrapper,#wpfnl-offerbtn-wrapper{width:100%;max-width:460px}#et-boc .et-l #wpfnl-offerbtn-wrapper .wpfnl-product-variation-title,#wpfnl-offerbtn-wrapper .wpfnl-product-variation-title{background:#FDF4ED;border-radius:10px;font-weight:700;font-size:20px;line-height:1.1;color:#363B4C;padding:15px 27px;margin:0 0 13px 0;border:none;box-shadow:none}#et-boc .et-l #wpfnl-offerbtn-wrapper .wpfnl-product-variation,#wpfnl-offerbtn-wrapper .wpfnl-product-variation{position:relative}#et-boc .et-l #wpfnl-offerbtn-wrapper .has-variation-product,#wpfnl-offerbtn-wrapper .has-variation-product{border:1px solid #F8F8FF;border-radius:10px;padding:4px 24px 24px}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper,#wpfnl-offerbtn-wrapper .offer-variation-wrapper{display:flex;flex-flow:row wrap;width:100%;margin-bottom:10px;position:relative}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation,#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation{margin-right:15px;margin-top:15px;width:calc(100% / 2 - 8px)}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation:nth-child(even),#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation:nth-child(even){margin-right:0}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation .variation-attr-name,#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation .variation-attr-name{font-size:16px;line-height:1.3;color:#363B4C;text-transform:capitalize;margin-bottom:6px;text-align:left;font-weight:500}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation .variation-attr-value,#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation .variation-attr-value{position:relative}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation select,#wpfnl-offerbtn-wrapper .offer-variation-wrapper .offer-product-single-variation select{width:100%;-webkit-appearance:auto;appearance:auto;padding:10px 14px;color:#85868F;border:1px solid #E5E8F3;border-radius:5px;font-size:16px;background:#FFFFFF;outline:none;box-shadow:none}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper .wpfnl-select-variation,#wpfnl-offerbtn-wrapper .offer-variation-wrapper .wpfnl-select-variation{display:block;width:100%;font-size:16px;font-weight:500;color:#ee5f5a;margin-bottom:5px}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper .reset_variations,#wpfnl-offerbtn-wrapper .offer-variation-wrapper .reset_variations{line-height:1;font-size:16px;font-weight:500;color:#000000;text-transform:capitalize;padding:7px 5px;text-align:center;width:100%;margin-top:15px;background:#fdf4ed;border-radius:5px}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper .reset_variations[style*="visibility: hidden"],#wpfnl-offerbtn-wrapper .offer-variation-wrapper .reset_variations[style*="visibility: hidden"]{display:none}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-variation-wrapper .reset_variations[style*="visibility: visible"],#wpfnl-offerbtn-wrapper .offer-variation-wrapper .reset_variations[style*="visibility: visible"]{display:block}#et-boc .et-l #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper .wpfnl-offer-product-price,#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper .wpfnl-offer-product-price{display:inline-flex;align-items:center;flex-flow:row-reverse}#et-boc .et-l #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper del,#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper del{font-weight:500;font-size:20px;line-height:1.1;color:rgba(54,59,76,0.8);margin-left:6px !important;margin-right:8px}#et-boc .et-l #wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper ins,#wpfnl-offerbtn-wrapper .wpfnl-offerbtn-and-price-wrapper ins{color:#E08746;font-weight:700;font-size:32px;line-height:1.18;text-decoration:none}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-btn-loader,#wpfnl-offerbtn-wrapper .offer-btn-loader{display:block;background:#00000036;width:calc(100% + 20px);height:100%;position:absolute;top:8px;left:-10px;border-radius:10px;z-index:2;display:none}#et-boc .et-l #wpfnl-offerbtn-wrapper .offer-btn-loader:before,#wpfnl-offerbtn-wrapper .offer-btn-loader:before{content:"";position:absolute;left:50%;top:50%;width:40px;height:40px;margin-top:-20px;margin-left:-20px;border:3px solid #353535;border-radius:100%;transform:translate(-50%, -50%);border-top-color:#fff;animation:spin 0.7s linear infinite}.wp-block-wpfnl-offer-btn-center #wpfnl-offerbtn-wrapper{margin:0 auto}.wp-block-wpfnl-offer-btn-right #wpfnl-offerbtn-wrapper{margin-left:auto;margin-right:0} includes/core/widgets/block/assets/dist/wpfnl-blocks-style.css000064400000000002147600245720020557 0ustar00 includes/core/widgets/block/block-types/AbstractBlock.php000064400000035740147600245720017545 0ustar00register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_editor_script( $key = null ) { $script = [ 'handle' => 'wpfnl-' . $this->block_name, 'path' => $this->get_block_asset_build_path( $this->block_name ), 'dependencies' => [ 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor', 'wp-api-fetch' ], ]; return $key ? $script[ $key ] : $script; } /** * Register script and style assets for the block type before it is registered. * * This registers the scripts; it does not enqueue them. */ protected function register_block_type_assets() { if ( null !== $this->get_block_type_editor_script() ) { $post_id = isset( $_GET['post'] ) ? intval( $_GET['post'] ) : 0; //phpcs:ignore $handle = $this->get_block_type_editor_script( 'handle' ); $funnel_id = get_post_meta($post_id, '_funnel_id', true); $funnel_type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ); if( ( Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'upsell' ) || Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'downsell' ) ) && $handle === 'wpfnl-offer-button' ) { $this->register_script( $handle, $this->get_block_type_editor_script( 'path' ), $this->get_block_type_editor_script( 'dependencies' ) ); } if( ( Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'upsell' ) || Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'downsell' ) ) && $handle === 'wpfnl-lms-offer-button' && 'lms' === $funnel_type && Wpfnl_functions::is_lms_addon_active() ) { $this->register_script( $handle, $this->get_block_type_editor_script( 'path' ), $this->get_block_type_editor_script( 'dependencies' ) ); } if( ( Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'upsell' ) || Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'downsell' ) ) && $handle === 'wpfnl-offer-title' ) { $this->register_script( $handle, $this->get_block_type_editor_script( 'path' ), $this->get_block_type_editor_script( 'dependencies' ) ); } if( ( Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'upsell' ) || Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'downsell' ) ) && $handle === 'wpfnl-offer-price' ) { $this->register_script( $handle, $this->get_block_type_editor_script( 'path' ), $this->get_block_type_editor_script( 'dependencies' ) ); } if( ( Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'upsell' ) || Wpfnl_functions::check_if_this_is_step_type_by_id( $post_id, 'downsell' ) ) && $handle === 'wpfnl-offer-product' ) { $this->register_script( $handle, $this->get_block_type_editor_script( 'path' ), $this->get_block_type_editor_script( 'dependencies' ) ); } } } public function register_script( $handle, $relative_src, $dependencies = [], $has_i18n = true ) { $src = ''; $version = '1'; if ( $relative_src ) { $src = $this->get_asset_url( $relative_src ); $version = $this->get_file_version( $relative_src ); } wp_register_script( $handle, $src, apply_filters( 'wpfunnels/gutenberg_blocks_register_script_dependencies', $dependencies, $handle ), $version, true ); $step_id = 0; if ( is_admin() && isset( $_REQUEST['action'] ) ) { if ('edit' === $_REQUEST['action'] && isset($_GET['post'])) { $step_id = isset($_GET['post']) ? $_GET['post'] : -1; } elseif (isset($_REQUEST['wpfunnels_gb']) && isset($_POST['post_id'])) { //phpcs:ignore $step_id = intval($_POST['post_id']); //phpcs:ignore } wp_localize_script( $handle, 'wpfnl_pro_block_object', array( 'plugin' => WPFNL_DIR_URL, 'siteUrl' => get_site_url(), 'ajaxUrl' => admin_url('admin-ajax.php'), 'orderBumpImg' => WPFNL_URL . 'admin/assets/images/placeholder.jpg', 'data_post_id' => $step_id, 'variableRender' => $this->render_variable_shortcode( $step_id ), 'wpfnl_ob_data' => get_post_meta( $step_id, 'order-bump-settings', true ), 'nonce' => wp_create_nonce('wp_rest'), 'wpfnl_ajax_nonce' => wp_create_nonce('wpfnl_gb_ajax_nonce'), 'variable_shortcode'=> do_shortcode( '[wpf_variable_offer]' ), 'is_variable_product'=> $this->check_is_variable_product($step_id), 'lmsOfferButtonRender'=> $this->render_lms_pay_button_shortcode($step_id), 'isGbf' => $this->maybe_global_funnel( $step_id ), 'productInfo' => $this->get_dynamic_product_info( $step_id ), ) ); } } /** * Check funnel is global funnel or not. * * @param String * @return String * * @since 2.4.14 * */ public function maybe_global_funnel( $step_id = '' ){ if( $step_id ){ $funnel_id = get_post_meta($step_id, '_funnel_id', true); $is_gbf = get_post_meta( $funnel_id, 'is_global_funnel', true); if( $is_gbf ){ return $is_gbf; } } return 'no'; } /** * Get dynamic product information. * * @param String * @return String * * @since 2.4.14 * */ public function get_dynamic_product_info( $step_id ){ if( $step_id ){ $response = Wpfnl_Pro_functions::get_product_data_for_widget( $step_id ); $offer_product = isset($response['offer_product']) && $response['offer_product'] ? $response['offer_product'] : ''; if( $offer_product ){ $product = [ 'img' => get_the_post_thumbnail_url($offer_product->get_id()), 'title' => $offer_product->get_name(), 'description' => $offer_product->get_short_description(), 'price' => $offer_product->get_price_html(), ]; return $product; } } return [ 'img' => '', 'title' => 'What is Lorem Ipsum?', 'description' => "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.", 'price' => "$120", ]; } /** * Get asset url. * * @param String * @return String * * @since 2.4.14 * */ public function get_asset_url( $relative_url ) { return plugin_dir_url( WPFNL_PRO_DIR . '/includes/core/widgets/block/index.php' ) . $relative_url; } /** * Check product type is variable or not. * * @param String * @return String * * @since 2.4.14 * */ public function check_is_variable_product( $step_id ) { return Wpfnl_Pro_functions::check_is_variable_product($step_id); } /** * Render variable product's shortcode. * * @param String * @return String * * @since 2.4.14 * */ public function render_variable_shortcode( $step_id ) { $data = ''; ob_start(); echo do_shortcode('[wpf_variable_offer post_id="'.$step_id.'" ]'); $data = ob_get_clean(); return $data; } /** * Render LMS pay button shortcode. * * @param String * @return String * * @since 2.4.14 * */ public function render_lms_pay_button_shortcode($step_id){ ob_start(); $funnel_id = get_post_meta($step_id,'_funnel_id',true); $get_learn_dash_setting = get_post_meta($funnel_id,'_wpfnl_funnel_type',true); $lms_button_text = get_option( 'learndash_settings_custom_labels' ); if ($get_learn_dash_setting == 'lms'){ $step_type = get_post_meta($step_id, '_step_type', true); if ($step_type != 'upsell' && $step_type != 'downsell'){ echo __('Sorry, Please place the element in WPFunnels Offer page'); }else{ $button_text = !empty($lms_button_text['button_take_this_course']) ? $lms_button_text['button_take_this_course'] : "Take this course"; echo $button_text; } }else{ echo __('Sorry, Please place the element in WPFunnels when learnDash is active '); } return ob_get_clean(); } /** * Returns the path to the plugin directory. * * @param string $relative_path If provided, the relative path will be * appended to the plugin path. * * @return string */ public function get_path( $relative_path = '' ) { return trailingslashit( WPFNL_PRO_DIR_URL . '/includes/core/Blocks' ) . $relative_path; } /** * Get the file modified time as a cache buster if we're in dev mode. * * @param string $file Local path to the file (relative to the plugin * directory). * @return string The cache buster value to use for the given file. */ protected function get_file_version( $file ) { return '1.0.0'; } /** * Get the editor style handle for this block type. * * @see $this->register_block_type() * @return string|null */ protected function get_block_type_editor_style() { return 'wpfnl-pro-blocks-editor-style'; } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wpfnl-pro-' . $this->block_name . '-frontend', 'path' => $this->get_block_asset_build_path( $this->block_name . '-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * Get the frontend style handle for this block type. * * @see $this->register_block_type() * @return string|null */ protected function get_block_type_style() { return 'wpfnl-pro-blocks-style'; } /** * Get the supports array for this block type. * * @see $this->register_block_type() * @return string; */ protected function get_block_type_supports() { return []; } /** * Get block attributes. * * @return array|null; */ protected function get_block_type_attributes() { return null; } /** * Parses block attributes from the render_callback. * * @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array. * @return array */ protected function parse_render_callback_attributes( $attributes ) { return is_a( $attributes, 'WP_Block' ) ? $attributes->attributes : $attributes; } /** * Render the block. Extended by children. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { return $content; } /** * Enqueue frontend assets for this block, just in time for rendering. * * @internal This prevents the block script being enqueued on all pages. It is only enqueued as needed. Note that * we intentionally do not pass 'script' to register_block_type. * * @param array $attributes Any attributes that currently are available from the block. */ protected function enqueue_assets( array $attributes ) { if ( $this->enqueued_assets ) { return; } $this->enqueue_scripts( $attributes ); $this->enqueued_assets = true; } /** * Injects block attributes into the block. * * @param string $content HTML content to inject into. * @param array $attributes Key value pairs of attributes. * @return string Rendered block with data attributes. */ protected function inject_html_data_attributes( $content, array $attributes ) { return preg_replace( '/
get_html_data_attributes( $attributes ) . ' ', $content, 1 ); } /** * Converts block attributes to HTML data attributes. * * @param array $attributes Key value pairs of attributes. * @return string Rendered HTML attributes. */ protected function get_html_data_attributes( array $attributes ) { $data = []; foreach ( $attributes as $key => $value ) { if ( is_bool( $value ) ) { $value = $value ? 'true' : 'false'; } if ( ! is_scalar( $value ) ) { $value = wp_json_encode( $value ); } $data[] = 'data-' . esc_attr( strtolower( preg_replace( '/(?get_block_type_script() ) { wp_enqueue_script( $this->get_block_type_script( 'handle' ) ); } } } includes/core/widgets/block/block-types/AbstractDynamicBlock.php000064400000003545147600245720021050 0ustar00 'string', 'enum' => array( 'left', 'center', 'right', 'wide', 'full' ), ); } /** * Get the schema for a list of IDs. * * @return array Property definition for a list of numeric ids. */ protected function get_schema_list_ids() { return array( 'type' => 'array', 'items' => array( 'type' => 'number', ), 'default' => array(), ); } /** * Get the schema for a boolean value. * * @param string $default The default value. * @return array Property definition. */ protected function get_schema_boolean( $default = true ) { return array( 'type' => 'boolean', 'default' => $default, ); } /** * Get the schema for a numeric value. * * @param string $default The default value. * @return array Property definition. */ protected function get_schema_number( $default ) { return array( 'type' => 'number', 'default' => $default, ); } /** * Get the schema for a string value. * * @param string $default The default value. * @return array Property definition. */ protected function get_schema_string( $default = '' ) { return array( 'type' => 'string', 'default' => $default, ); } } includes/core/widgets/block/block-types/LmsOfferButton.php000064400000025646147600245720017744 0ustar00 'center', 'buttonTextAlign' => 'center', 'buttonText' => 'I will pass', 'offerAction' => 'accept', 'offerType' => 'upsell', 'buttonColor' => '#39414d', 'buttonTextColor' => '#fff', 'paddingTopBottom' => 14, 'paddingLeftRight' => 25, 'buttonRadius' => 5, ); /** * Block name. * * @var string */ protected $block_name = 'lms-offer-button'; public function __construct( $block_name = '' ) { parent::__construct($block_name); add_action('wp_ajax_wpfnl_offer_variable_shortcode', [$this, 'render_offer_variable_shortcode']); add_action( 'wp_ajax_nopriv_wpfnl_offer_variable_shortcode', [$this, 'render_offer_variable_shortcode'] ); } /** * Render the Featured Product block. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { $attributes = wp_parse_args( $attributes, $this->defaults ); $dynamic_css = $this->generate_assets($attributes); ob_start(); $step_id = get_the_ID(); $funnel_id = get_post_meta($step_id,'_funnel_id',true); $get_learn_dash_setting = Wpfnl_lms_learndash_functions::get_learndash_settings($funnel_id); $lms_button_text = get_option( 'learndash_settings_custom_labels' ); if ($get_learn_dash_setting == 'yes'){ $step_type = get_post_meta($step_id, '_step_type', true); if ($step_type != 'upsell' && $step_type != 'downsell'){ echo __('Sorry, Please place the element in WPFunnels Offer page'); }else{ $button_action = isset($attributes['offerAction']) ? $attributes['offerAction'] : 'accept' ; if ($button_action == 'accept'){ $button_text = !empty($lms_button_text['button_take_this_course']) ? $lms_button_text['button_take_this_course'] : "Take this course"; $lms_product_id = get_post_meta($step_id,'_wpfnl_upsell_products',true); if ($step_type == 'downsell'){ $lms_product_id = get_post_meta($step_id,'_wpfnl_downsell_products',true); } $course_access = sfwd_lms_has_access( $lms_product_id[0]['id'], get_current_user_id() ); $next_step_url = Wpfnl_lms_learndash_functions::get_next_step_url($funnel_id,$step_id).'?wpfnl_ld_payment=free'; if ($course_access ){ echo ''.$button_text.''; echo ''; }else{ if( $this->is_learndash_bilder_mode() ) { echo ''; } else { echo '
'; $course = isset($lms_product_id[0]['id']) ? Wpfnl_lms_learndash_functions::get_course_details_by_id($lms_product_id[0]['id']) : []; if( !empty($course) ){ if( isset($course['type']) && $course['type'] == 'free' ){ $next_step_url = Wpfnl_lms_learndash_functions::get_next_step_url($funnel_id,$step_id); echo ''.$button_text.''; echo ''; } else{ echo do_shortcode('[learndash_payment_buttons course_id='.$lms_product_id[0]['id'].']'); } } $attributes['lmsTypography']['spacing']['unit'] = isset($attributes['lmsTypography']['spacing']['unit']) ? $attributes['lmsTypography']['spacing']['unit'] : ''; $attributes['lmsTypography']['size']['unit'] = isset($attributes['lmsTypography']['size']['unit']) ? $attributes['lmsTypography']['size']['unit'] : ''; $attributes['lmsTypography']['type'] = isset($attributes['lmsTypography']['type']) ? $attributes['lmsTypography']['type'] : ''; $attributes['lmsTypography']['family'] = isset($attributes['lmsTypography']['family']) ? $attributes['lmsTypography']['family'] : ''; $attributes['lmsTypography']['weight'] = isset($attributes['lmsTypography']['weight']) ? $attributes['lmsTypography']['weight'] : ''; $attributes['lmsTypography']['size']['md'] = isset($attributes['lmsTypography']['size']['md']) ? $attributes['lmsTypography']['size']['md'] : ''; $attributes['lmsTypography']['transform'] = isset($attributes['lmsTypography']['transform']) ? $attributes['lmsTypography']['transform'] : ''; $attributes['lmsTypography']['spacing']['md'] = isset($attributes['lmsTypography']['spacing']['md']) ? $attributes['lmsTypography']['spacing']['md'] : ''; echo ''; if( isset($attributes['outline']) ){ echo ''; } echo '
'; } } }else{ echo $content; } } }else{ echo __('Sorry, Please place the element in WPFunnels when learnDash is active '); } return ob_get_clean(); } public function is_learndash_bilder_mode() { if (isset($_GET['action']) && $_GET['action'] == 'edit'){ return true; } return false; } /** * Get the styles for the wrapper element (background image, color). * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_styles( $attributes ) { $style = ''; return $style; } /** * Get class names for the block container. * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_classes( $attributes ) { $classes = array( 'wpfnl-block-' . $this->block_name ); return implode( ' ', $classes ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wpfnl-offer-button-frontend', 'path' => $this->get_block_asset_build_path( 'offer-button-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * render offer product shortcode markup * based on type * */ public function render_offer_variable_shortcode() { check_ajax_referer( 'wpfnl_gb_ajax_nonce', 'nonce' ); $data = ''; ob_start(); echo do_shortcode('[wpf_variable_offer post_id="'.$_POST['id'].'" ]'); $data = ob_get_clean(); wp_send_json_success( $data ); } }includes/core/widgets/block/block-types/OfferButton.php000064400000042113147600245720017254 0ustar00defaults ); $dynamic_css = $this->generate_assets($attributes); $is_variable = Wpfnl_Pro_functions::check_is_variable_product( get_the_id() ); $product_price_html = Wpfnl_Pro_functions::get_offer_product_price( get_the_id() ); $response = Wpfnl_Pro_functions::get_product_data_for_widget( get_the_ID() ); $style_layout = isset($attributes['offerTemplateLayout'])? $attributes['offerTemplateLayout'] : 'style1'; $product_info = $this->get_dynamic_product_info_gutenberg( get_the_id()); $funnel_id = get_post_meta( get_the_id(), '_funnel_id', true); $isGbf = get_post_meta( $funnel_id, 'is_global_funnel', true); ob_start(); if( 'yes' == $isGbf && isset($attributes['showProductData']) && 'yes' == $attributes['showProductData'] ){ if( isset($attributes['templateImageWidth']) && !empty( $attributes['templateImageWidth'] ) ){ $imageWidth = $attributes['templateImageWidth'].'px'; }else{ $imageWidth = '100%'; } ?>
'.$attributes['variationTblTitle'].''; } ?>
'; } echo do_shortcode('[wpf_variable_offer]'); ?>
get_regular_price() * $response['quantity'] : $offer_product->get_regular_price(); if( isset($discount['discountApplyTo'], $discount['discountType']) && 'original' !== $discount['discountType'] ){ if( 'sale' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_sale_price() ? $offer_product->get_sale_price() : $offer_product->get_regular_price(); }elseif( 'regular' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_regular_price() ? $offer_product->get_regular_price() : $offer_product->get_price(); }else{ $sale_price = $offer_product->get_price(); } $product_price = \WPFunnelsPro\Wpfnl_Pro_functions::calculate_discount_price_for_widget( $discount['discountType'] , $discount['discountValue'], $sale_price ); if( $product_price != $total_price ){ echo wc_price(number_format( (float) $product_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $product_price, 2, '.', '' )); } }else{ if( $offer_product->get_sale_price() ){ $sale_price = $offer_product->get_sale_price(); $sale_price = isset($response['quantity']) ? $sale_price * $response['quantity'] : $sale_price; echo wc_price(number_format( (float) $sale_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $total_price, 2, '.', '' )); } } } } } ?>
'; //end ".has-variation-product" } ?>
inject_html_data_attributes( $new_content, $attributes ); return ob_get_clean(); } /** * Get the styles for the wrapper element (background image, color). * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_styles( $attributes ) { $style = ''; return $style; } /** * Get class names for the block container. * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_classes( $attributes ) { $classes = array( 'wpfnl-block-' . $this->block_name ); return implode( ' ', $classes ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wpfnl-offer-button-frontend', 'path' => $this->get_block_asset_build_path( 'offer-button-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * render offer product shortcode markup * based on type * */ public function render_offer_variable_shortcode() { check_ajax_referer( 'wpfnl_gb_ajax_nonce', 'nonce' ); $data = ''; ob_start(); echo do_shortcode('[wpf_variable_offer post_id="'.$_POST['id'].'" ]'); $data = ob_get_clean(); wp_send_json_success( $data ); } /** * Get dynamic product info for Gutenberg. * * @param String * @return String * * @since 2.4.14 * */ public function get_dynamic_product_info_gutenberg( $step_id ){ if( $step_id ){ $response = Wpfnl_Pro_functions::get_product_data_for_widget( $step_id ); $offer_product = isset($response['offer_product']) && $response['offer_product'] ? $response['offer_product'] : ''; if( $offer_product ){ $product = [ 'img' => get_the_post_thumbnail_url($offer_product->get_id()), 'title' => $offer_product->get_name(), 'description' => $offer_product->get_short_description(), 'price' => $offer_product->get_sale_price() ? $offer_product->get_sale_price() : $offer_product->get_regular_price(), 'id' => $offer_product->get_id(), ]; return $product; } } return [ 'img' => '', 'title' => 'What is Lorem Ipsum?', 'description' => "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.", 'price' => "120", ]; } }includes/core/widgets/block/block-types/OfferPrice.php000064400000006230147600245720017043 0ustar00', esc_attr( $this->get_classes( $attributes ) ), esc_attr( $this->get_styles( $attributes ) ) ); $output .= '
'; $output .= do_shortcode('[wpfunnels_offer_product_price]'); $output .= '
'; $output .= '
'; return $output; } /** * Get the styles for the wrapper element (background image, color). * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_styles( $attributes ) { $style = ''; return $style; } /** * Get class names for the block container. * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_classes( $attributes ) { $classes = array( 'wpfnl-block-' . $this->block_name ); return implode( ' ', $classes ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wpfnl-offer-title-frontend', 'path' => $this->get_block_asset_build_path( 'offer-title-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } } includes/core/widgets/block/block-types/OfferProduct.php000064400000010322147600245720017416 0ustar00'; if ($_POST['showTitle'] == 'true'){ $output .= '
'.do_shortcode('[wpfunnels_offer_product_title]').'
'; } if ($_POST['showPrice'] == 'true'){ $output .= '
'.do_shortcode('[wpfunnels_offer_product_price]').'
'; } if ($_POST['showDescription'] == 'true'){ $output .= '
'.do_shortcode('[wpfunnels_offer_product_description]').'
'; } if ($_POST['showImage'] == 'true'){ $output .= '
'.do_shortcode('[wpfunnels_offer_product_image]').'
';; } $output .= '
'; $data['html'] = $output; wp_send_json_success( $data ); } /** * Render the Featured Product block. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { $output = sprintf( '
', esc_attr( $this->get_classes( $attributes ) ), esc_attr( $this->get_styles( $attributes ) ) ); $output .= '
'; $output .=do_shortcode('[wpfunnels_offer_product_title]'); $output .=do_shortcode('[wpfunnels_offer_product_price]'); $output .=do_shortcode('[wpfunnels_offer_product_description]'); $output .=do_shortcode('[wpfunnels_offer_product_image]'); $output .= '
'; $output .= '
'; return $output; } /** * Get the styles for the wrapper element (background image, color). * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_styles( $attributes ) { $style = ''; return $style; } /** * Get class names for the block container. * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_classes( $attributes ) { $classes = array( 'wpfnl-block-' . $this->block_name ); return implode( ' ', $classes ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wpfnl-offer-title-frontend', 'path' => $this->get_block_asset_build_path( 'offer-title-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } } includes/core/widgets/block/block-types/OfferTitle.php000064400000006230147600245720017062 0ustar00', esc_attr( $this->get_classes( $attributes ) ), esc_attr( $this->get_styles( $attributes ) ) ); $output .= '
'; $output .= do_shortcode('[wpfunnels_offer_product_title]'); $output .= '
'; $output .= '
'; return $output; } /** * Get the styles for the wrapper element (background image, color). * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_styles( $attributes ) { $style = ''; return $style; } /** * Get class names for the block container. * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_classes( $attributes ) { $classes = array( 'wpfnl-block-' . $this->block_name ); return implode( ' ', $classes ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wpfnl-offer-title-frontend', 'path' => $this->get_block_asset_build_path( 'offer-title-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } } includes/core/widgets/block/babel.config.js000064400000000372147600245720014722 0ustar00module.exports = { presets: [ [ '@babel/preset-env', { targets: { node: 'current', }, }, ], [ '@babel/preset-typescript' ], ], }; includes/core/widgets/block/Manager.php000064400000010404147600245720014133 0ustar00init(); } /** * Initialize class features. */ protected function init() { add_action( 'init', array( $this, 'register_assets' ) ); add_action( 'init', array( $this, 'register_blocks' ) ); add_action( 'wp_footer', array( $this, 'wpfnl_inline_footer_scripts' ) ); add_action( 'wp_head', array( $this, 'add_block_inline_css' ), 100 ); } /** * Register blocks, hooking up assets and render functions as needed. */ public function register_blocks() { $block_types = $this->get_block_types(); foreach ( $block_types as $block_type ) { $block_type_class = __NAMESPACE__ . '\\BlockTypes\\' . $block_type; $block_type_instance = new $block_type_class(); } } /** * Get list of block types. * * @return array */ protected function get_block_types() { return apply_filters('wpfunnelspro/gutenberg_block_types', array( 'OfferButton', 'LmsOfferButton' // 'OfferTitle', //'OfferPrice', //'OfferProduct', )); } /** * register assets for gutenberg * * @since 2.0.3 */ public function register_assets() { wp_register_style( 'wpfnl-pro-blocks-editor-style', plugins_url( 'block/assets/dist/wpfnl-blocks-editor-style.css', __DIR__ ), [], '1.0.0', 'all' ); wp_register_style( 'wpfnl-pro-blocks-style',plugins_url( 'block/assets/dist/wpfnl-blocks-style.css', __DIR__ ), [], '1.0.0', 'all' ); } /** * Load Inline Footer Script * * @since 1.3.0 */ public function wpfnl_inline_footer_scripts() { global $wp_query; $is_previewing= $wp_query->is_preview(); $can_edit= current_user_can( 'edit_posts' ); if($is_previewing || $can_edit){ ?> ' . $blockCss . ''; } else { echo ''; } } } } Manager::instance(); includes/core/widgets/block/remove-files-webpack-plugin.js000064400000001430147600245720017710 0ustar00/*eslint-env node*/ /** * External dependencies */ const fs = require( 'fs' ); const glob = require( 'glob' ); // This is a simple webpack plugin to delete the JS files generated by MiniCssExtractPlugin. function RemoveFilesPlugin( filePath = '' ) { this.filePath = filePath; } RemoveFilesPlugin.prototype.apply = function ( compiler ) { compiler.hooks.afterEmit.tap( 'afterEmit', () => { const files = glob.sync( this.filePath ); files.forEach( ( f ) => { fs.unlink( f, ( err ) => { if ( err ) { /* eslint-disable-next-line no-console */ console.log( `There was an error removing ${ f }.`, err ); } } ); } ); } ); }; module.exports = RemoveFilesPlugin; includes/core/widgets/block/webpack-entries.js000064400000003355147600245720015500 0ustar00/** * External dependencies */ const { omit } = require( 'lodash' ); const glob = require( 'glob' ); // List of blocks that should be used as webpack entry points. They are expected // to be in `/assets/js/blocks/[BLOCK_NAME]`. If they are not, their relative // path should be defined in the `customDir` property. The scripts below will // take care of looking for `index.js`, `frontend.js` and `*.scss` files in each // block directory. // If a block is experimental, it should be marked with the `isExperimental` // property. const blocks = { 'offer-button': {}, 'lms-offer-button': {}, 'offer-title': {}, 'offer-price': {}, 'offer-product': {}, }; // Returns the entries for each block given a relative path (ie: `index.js`, // `**/*.scss`...). // It also filters out elements with undefined props and experimental blocks. const getBlockEntries = ( relativePath ) => { return Object.fromEntries( Object.entries( blocks ) .map( ( [ blockCode, config ] ) => { const filePaths = glob.sync( `./assets/js/blocks/${ config.customDir || blockCode }/` + relativePath ); if ( filePaths.length > 0 ) { return [ blockCode, filePaths ]; } return null; } ) .filter( Boolean ) ); }; const entries = { styling: { ...getBlockEntries( '**/*.scss' ), }, main: { ...getBlockEntries( 'index.{t,j}s{,x}' ), }, frontend: { ...getBlockEntries( 'frontend.{t,j}s{,x}' ), }, }; const getEntryConfig = ( type = 'main', exclude = [] ) => { return omit( entries[ type ], exclude ); }; module.exports = { getEntryConfig, }; includes/core/widgets/bricks/OfferButton.php000064400000042052147600245720015205 0ustar00controls['offer_button_type'] = [ 'tab' => 'content', 'label' => esc_html__( 'Button Type', 'wpfnl-pro' ), 'type' => 'select', 'options' => [ 'upsell' => esc_html__( 'Upsell', 'wpfnl-pro' ), 'downsell' => esc_html__( 'Downsell', 'wpfnl-pro' ), ], 'inline' => true, 'default' => 'upsell', ]; $this->controls['offer_type'] = [ 'tab' => 'content', 'label' => esc_html__( 'Button action', 'wpfnl-pro' ), 'type' => 'select', 'options' => [ 'accept' => esc_html__( 'Accept', 'wpfnl-pro' ), 'reject' => esc_html__( 'Reject', 'wpfnl-pro' ), ], 'inline' => true, ]; //separator $this->controls['buttonTypeSeparator'] = [ 'type' => 'separator', ]; //---button content--- $this->controls['text'] = [ 'label' => esc_html__( 'Button Title', 'wpfnl-pro' ), 'type' => 'text', 'default' => esc_html__( 'Accept Offer', 'wpfnl-pro' ), 'placeholder' => esc_html__( 'Accept Offer', 'wpfnl-pro' ), ]; // separator $this->controls['gbfSeparator'] = [ 'type' => 'separator', ]; $get_product_type = Wpfnl_Pro_functions::get_offer_product_type( $this->post_id ); if( $get_product_type == 'variable' ){ $this->controls['variation_tbl_title'] = [ 'tab' => 'content', 'label' => esc_html__( 'Variation Table Title', 'wpfnl-pro' ), 'type' => 'textarea', 'spellcheck' => true, 'inlineEditing' => true, 'required' => [ 'offer_type', '=', 'accept' ], ]; } $this->controls['show_product_price'] = [ 'label' => esc_html__( 'Show Product Price', 'wpfnl-pro' ), 'type' => 'checkbox', 'reset' => true, 'required' => [ 'offer_type', '=', 'accept' ], ]; $this->controls['product_price_alignment'] = [ 'tab' => 'content', 'label' => esc_html__( 'Price Alignment', 'wpfnl-pro' ), 'type' => 'select', 'options' => [ '' => esc_html__( 'On The Left Of Button', 'wpfnl-pro' ), 'price-right' => esc_html__( 'On The Right Of Button', 'wpfnl-pro' ), 'price-top' => esc_html__( 'Above The Button', 'wpfnl-pro' ), 'price-bottom' => esc_html__( 'Below The Button', 'wpfnl-pro' ), ], 'inline' => false, 'required' => [ 'show_product_price' ], ]; // separator $this->controls['dynamicDataSeparator'] = [ 'type' => 'separator', 'required' => [ 'offer_type', '=', 'accept' ], ]; $funnel_id = get_post_meta( $this->post_id, '_funnel_id', true ); $is_gbf = get_post_meta( $funnel_id, 'is_global_funnel', true ); if( 'yes' === $is_gbf ){ $this->controls['show_product_data'] = [ 'label' => esc_html__( 'Show Product Data', 'wpfnl-pro' ), 'type' => 'checkbox', 'reset' => true, 'required' => [ 'offer_type', '=', 'accept' ], ]; } $this->controls['dynamic_data_template_layout'] = [ 'tab' => 'content', 'label' => esc_html__( 'Select Template Style', 'wpfnl-pro' ), 'type' => 'select', 'options' => [ 'style1' => esc_html__( 'Left Image Right Content', 'wpfnl-pro' ), 'style2' => esc_html__( 'Left Content Right Image', 'wpfnl-pro' ), 'style3' => esc_html__( 'Top Image Bottom Content', 'wpfnl-pro' ), ], 'default' => 'style1', 'inline' => false, 'required' => [ 'show_product_data' ], ]; //---------offer button style controls--------- $this->register_offer_button_style(); //---------daynamic data template style controls--------- $this->register_dynamic_data_template_style(); } /** * Set builder control groups * * @since 3.1.0 * @access public */ public function set_control_groups(){ $this->control_groups['offer_button_style'] = [ 'title' => esc_html__('Offer Button Style', 'wpfnl-pro'), // Localized control group title 'tab' => 'style', // Set to either "content" or "style" ]; $this->control_groups['dynamic_data_template_style'] = [ 'title' => esc_html__('Dynamic Template Style', 'wpfnl-pro'), // Localized control group title 'tab' => 'style', // Set to either "content" or "style" 'required' => [ 'show_product_data' ], ]; } /** * Register Offer Button Style Control. * * @since 3.1.0 * @access public */ protected function register_offer_button_style(){ $this->controls['offer_button_Typography'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Typography', 'wpfnl-pro' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', 'selector' => '.bricks-button', ] ], ]; $this->controls['offer_button_bg'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Background', 'wpfnl-pro' ), 'type' => 'color', 'css' => [ [ 'property' => 'background-color', 'selector' => '.bricks-button', ] ], ]; $this->controls['offer_button_border'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Border', 'wpfnl-pro' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', 'selector' => '.bricks-button', ], ], ]; $this->controls['offer_button_margin'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Margin', 'wpfnl-pro' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'margin', 'selector' => '.bricks-button', ], ], ]; $this->controls['offer_button_padding'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Padding', 'wpfnl-pro' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'padding', 'selector' => '.bricks-button', ], ], ]; // separator $this->controls['iconSeparator'] = [ 'group' => 'offer_button_style', 'type' => 'separator', ]; // Icon $this->controls['icon'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Icon', 'wpfnl-pro' ), 'type' => 'icon', ]; $this->controls['iconTypography'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Typography', 'bricks' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', 'selector' => 'i', ], ], 'exclude' => [ 'font-family', 'font-weight', 'font-style', 'text-align', 'text-decoration', 'text-transform', 'line-height', 'letter-spacing', ], 'required' => [ 'icon.icon', '!=', '' ], ]; $this->controls['iconPosition'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Position', 'wpfnl-pro' ), 'type' => 'select', 'options' => $this->control_options['iconPosition'], 'inline' => true, 'placeholder' => esc_html__( 'Right', 'wpfnl-pro' ), 'required' => [ 'icon', '!=', '' ], ]; $this->controls['iconGap'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Gap', 'wpfnl-pro' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'gap', 'selector' => '.bricks-button', ], ], 'required' => [ 'icon', '!=', '' ], ]; $this->controls['iconSpace'] = [ 'group' => 'offer_button_style', 'label' => esc_html__( 'Space between', 'wpfnl' ), 'type' => 'checkbox', 'css' => [ [ 'property' => 'justify-content', 'value' => 'space-between', 'selector' => '.bricks-button', ], ], 'required' => [ 'icon', '!=', '' ], ]; } /** * Register Dynamic Data Template Style Control. * * @since 3.1.0 * @access public */ public function register_dynamic_data_template_style(){ // separator $this->controls['layout_separator'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Layout Style', 'wpfnl-pro' ), 'type' => 'separator', ]; $this->controls['template_layout_bg'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Background', 'wpfnl-pro' ), 'type' => 'color', 'css' => [ [ 'property' => 'background-color', 'selector' => '.dynamic-offer-template-default', ] ], ]; $this->controls['template_left_col_width'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Image Column Width', 'wpfnl-pro' ), 'type' => 'number', 'min' => 0, 'inline' => true, 'css' => [ [ 'property' => 'width', 'selector' => '.dynamic-offer-template-default .template-left', ], ], ]; $this->controls['template_right_col_width'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Content Column Width', 'wpfnl-pro' ), 'type' => 'number', 'min' => 0, 'inline' => true, 'css' => [ [ 'property' => 'width', 'selector' => '.dynamic-offer-template-default .template-right', ], ], ]; $this->controls['template_col_gutter_width'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Column Gap', 'wpfnl-pro' ), 'type' => 'number', 'min' => 0, 'inline' => true, 'css' => [ [ 'property' => 'gap', 'selector' => '.dynamic-offer-template-default', ] ], ]; $this->controls['template_layout_border'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Border', 'wpfnl-pro' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', 'selector' => '.dynamic-offer-template-default', ], ], ]; $this->controls['template_layout_shadow'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Box Shadow', 'wpfnl-pro' ), 'type' => 'box-shadow', 'css' => [ [ 'property' => 'box-shadow', 'selector' => '.dynamic-offer-template-default', ] ], 'inline' => true, 'small' => true, 'default' => [ 'values' => [ 'offsetX' => 0, 'offsetY' => 0, 'blur' => 2, 'spread' => 0, ], 'color' => [ 'rgb' => 'rgba(0, 0, 0, .1)', ], ], ]; $this->controls['template_layout_padding'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Padding', 'wpfnl-pro' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'padding', 'selector' => '.dynamic-offer-template-default', ] ], ]; $this->controls['template_layout_margin'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Margin', 'wpfnl-pro' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'margin', 'selector' => '.dynamic-offer-template-default', ], ], ]; // separator $this->controls['template_image_separator'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Template Image Style', 'wpfnl-pro' ), 'type' => 'separator', ]; $this->controls['template_image_radius'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Border Radius', 'wpfnl-pro' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', 'selector' => '.dynamic-offer-template-default .product-img img', ], ], 'exclude' => [ 'width', 'style', 'color', ], ]; // separator $this->controls['template_heading_separator'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Heading Style', 'wpfnl-pro' ), 'type' => 'separator', ]; $this->controls['template_heading_typography'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Typography', 'wpfnl-pro' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', 'selector' => '.dynamic-offer-template-default .template-content .template-product-title', ] ], ]; $this->controls['template_heading_margin'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Margin', 'wpfnl-pro' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'margin', 'selector' => '.dynamic-offer-template-default .template-content .template-product-title', ], ], ]; // separator $this->controls['template_description_style'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Description Style', 'wpfnl-pro' ), 'type' => 'separator', ]; $this->controls['template_description_typography'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Typography', 'wpfnl-pro' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', 'selector' => '.dynamic-offer-template-default .template-content .template-product-description', ] ], ]; $this->controls['template_description_margin'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Margin', 'wpfnl-pro' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'margin', 'selector' => '.dynamic-offer-template-default .template-content .template-product-description', ], ], ]; // separator $this->controls['template_price_style'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Price Style', 'wpfnl-pro' ), 'type' => 'separator', ]; $this->controls['template_discount_price_typography'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Discount Price Typography', 'wpfnl-pro' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', 'selector' => '.dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price bdi', ] ], ]; $this->controls['template_price_typography'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Regular Price Typography', 'wpfnl-pro' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', 'selector' => '.dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del bdi', ], [ 'property' => 'font', 'selector' => '.dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price', ] ], ]; $this->controls['template_price_margin'] = [ 'group' => 'dynamic_data_template_style', 'label' => esc_html__( 'Margin', 'wpfnl-pro' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'margin', 'selector' => '.dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price', ], ], ]; } /** * Render the widget output on the frontend. * * Written in PHP and used to generate the final HTML. * * @since 2.1.0 * * @access public */ public function render() { $settings = $this->settings; // offer button $offer_button_classes[] = 'bricks-button wpfunnels_offer_button'; $this->set_attribute( 'offer-button', 'class', $offer_button_classes ); $response = Wpfnl_Pro_functions::get_product_data_for_widget( $this->post_id ); $offer_product = isset($response['offer_product']) && $response['offer_product'] ? $response['offer_product'] : ''; $get_product_type = isset($response['get_product_type']) && $response['get_product_type'] ? $response['get_product_type'] : ''; $is_gbf = isset($response['is_gbf']) && $response['is_gbf'] ? $response['is_gbf'] : ''; $builder = 'bricks'; echo '
render_attributes( '_root' ) . '>'; if( 'yes' == $is_gbf && isset($settings['show_product_data']) && $settings['show_product_data'] && $settings['offer_type'] == 'accept' ){ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/styles/offer-' . $settings['dynamic_data_template_layout'] . '.php'; }else{ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/bricks/offer-button.php'; } echo '
'; } } includes/core/widgets/divi-modules/includes/fields/Button/Input.jsx000064400000000711147600245720021533 0ustar00// External Dependencies import React, { Component } from 'react'; // Internal Dependencies import './style.css'; class Button extends Component { static slug = 'wpfnl_input'; /** * Handle input value change. * * @param {object} event */ _onChange = (event) => { this.props._onChange(this.props.name, event.target.value); } render() { return( ); } } export default Button; includes/core/widgets/divi-modules/includes/fields/Button/style.css000064400000000000147600245720021547 0ustar00includes/core/widgets/divi-modules/includes/fields/Input/Input.jsx000064400000001240147600245720021355 0ustar00// External Dependencies import React, { Component } from 'react'; // Internal Dependencies import './style.css'; class Input extends Component { static slug = 'wpfnl_input'; /** * Handle input value change. * * @param {object} event */ _onChange = (event) => { this.props._onChange(this.props.name, event.target.value); } render() { return( ); } } export default Input; includes/core/widgets/divi-modules/includes/fields/Input/style.css000064400000001430147600245720021403 0ustar00input.wpfnl-input { background: #f1f5f9; max-height: 30px; border: 0; border-radius: 3px; padding: 7px 10px; box-sizing: border-box; transition: background 200ms ease; color: #4C5866; font-family: 'Open Sans', Helvetica, Roboto, Arial, sans-serif; font-size: 13px; font-weight: 600; line-height: normal; display: block; width: 100%; } input.wpfnl-input:focus { background: #e6ecf2; } input.wpfnl-input::-webkit-input-placeholder { color: #98a7b8; } input.wpfnl-input:-moz-placeholder { color: #98a7b8; } input.wpfnl-input::-moz-placeholder { color: #98a7b8; } input.wpfnl-input:-ms-input-placeholder { color: #98a7b8; } input.wpfnl-input[readonly] { background: #ffffff !important; border: 1px solid #eaedf0 !important; cursor: not-allowed; }includes/core/widgets/divi-modules/includes/fields/ObLayout/ObLayout.jsx000064400000001413147600245720022455 0ustar00// External Dependencies import React, { Component } from 'react'; import $ from 'jquery'; // Internal Dependencies import './style.css'; class ObLayout extends Component { static slug = 'wpfnl_ob_layout'; /** * Handle input value change. * * @param {object} event */ _onChange = (event) => { this.props._onChange(this.props.name, event.target.value); } render() { const { fieldDefinition } = this.props; let layoutList = Object.keys(fieldDefinition.options).map((item, i) => { return ( ) }, this); return( ); } } export default ObLayout; includes/core/widgets/divi-modules/includes/fields/ObLayout/style.css000064400000001430147600245720022042 0ustar00input.wpfnl-input { background: #f1f5f9; max-height: 30px; border: 0; border-radius: 3px; padding: 7px 10px; box-sizing: border-box; transition: background 200ms ease; color: #4C5866; font-family: 'Open Sans', Helvetica, Roboto, Arial, sans-serif; font-size: 13px; font-weight: 600; line-height: normal; display: block; width: 100%; } input.wpfnl-input:focus { background: #e6ecf2; } input.wpfnl-input::-webkit-input-placeholder { color: #98a7b8; } input.wpfnl-input:-moz-placeholder { color: #98a7b8; } input.wpfnl-input::-moz-placeholder { color: #98a7b8; } input.wpfnl-input:-ms-input-placeholder { color: #98a7b8; } input.wpfnl-input[readonly] { background: #ffffff !important; border: 1px solid #eaedf0 !important; cursor: not-allowed; }includes/core/widgets/divi-modules/includes/fields/index.js000064400000000110147600245720020071 0ustar00import ObLayout from "./ObLayout/ObLayout"; export default [ObLayout]; includes/core/widgets/divi-modules/includes/modules/LmsPayButton/LmsPayButton.php000064400000054361147600245720024362 0ustar00 '', 'author' => '', 'author_uri' => '', ); /** * Module properties initialization */ function init() { $this->name = __( 'WPF Lms Pay Button', 'wpfnl-pro' ); $this->icon_path = plugin_dir_path( __FILE__ ) . 'offer_button.svg'; $this->main_css_element = '%%order_class%%'; $this->settings_modal_toggles = array( 'general' => array( 'toggles' => array( 'button' => __( 'Offer Button', 'wpfnl-pro' ), ), ), 'advanced' => array( 'toggles' => array( 'alignment' => __( 'Alignment', 'wpfnl-pro' ), 'text' => array( 'title' => __( 'Alignment', 'wpfnl-pro' ), 'priority' => 49, ), 'variation_alignment' => array( 'title' => __( 'Variation Alignment', 'wpfnl-pro' ), 'priority' => 50, ) ), ), ); $this->wrapper_settings = array( // Flag that indicates that this module's wrapper where order class is declared // has another wrapper (mostly for button alignment purpose). 'order_class_wrapper' => true, ); $this->custom_css_fields = array( 'main_element' => array( 'label' => __( 'Main Element', 'wpfnl-pro' ), 'no_space_before_selector' => true, ), ); $this->advanced_fields = array( 'text' =>array( 'use_text_orientation' => true, // default 'css' => array( 'text_orientation' => '%%order_class%%', ) ), 'borders' => array( 'default' => false, ), 'button' => array( 'button' => array( 'label' => __( 'LMS Reject', 'wpfnl-pro' ), 'css' => array( // 'main' => $this->main_css_element.'.et_pb_button' , // 'main' => "{$this->main_css_element}.et_pb_button" , 'limited_main' => "{$this->main_css_element}.et_pb_button", ), 'box_shadow' => false, 'text_shadow' => false, 'margin_padding' => array( 'css' => array( 'main' => "{$this->main_css_element} .et_pb_button", // 'main' => ".et_pb_button", 'important' => 'all', ), ), ), 'button_offer_lms' => array( 'label' => __( ' LMS Accept Button', 'wpfnl' ), 'css' => array( 'main' => '%%order_class%% .btn-join', 'important' => 'all', ), 'use_alignment' => false, 'border_width' => array( 'default' => '2px', ), 'box_shadow' => array( 'css' => array( 'main' => '%%order_class%% .btn-join', ), ), 'margin_padding' => array( 'css' => array( 'important' => 'all', ), ), 'toggle_priority' => 80, ), ), 'box_shadow' => false, 'margin_padding' => false, 'text_shadow' => array( 'default' => false, ), 'background' => false, 'fonts' => false, 'max_width' => false, 'height' => false, 'link_options' => false, 'position_fields' => array( 'css' => array( 'main' => "{$this->main_css_element}_wrapper,", ), ), 'transform' => array( 'css' => array( 'main' => "{$this->main_css_element}_wrapper,", ), ), ); $this->help_videos = array( array( 'id' => 'XpM2G7tQQIE', 'name' => esc_html__( 'An introduction to the Button module', 'wpfnl' ), ), ); } /** * Module's specific fields * * * The following modules are automatically added regardless being defined or not: * Tabs | Toggles | Fields * --------- ------------------ ------------- * Content | Admin Label | Admin Label * Advanced | CSS ID & Classes | CSS ID * Advanced | CSS ID & Classes | CSS Class * Advanced | Custom CSS | Before * Advanced | Custom CSS | Main Element * Advanced | Custom CSS | After * Advanced | Visibility | Disable On * @return array */ function get_fields() { $basic_fields = array( 'button_text' => array( 'label' => __( 'Button Text', 'wpfnl-pro' ), 'type' => 'text', 'option_category' => 'basic_option', 'description' => __( 'Input your desired button text, or leave blank for no button.', 'wpfnl-pro' ), 'toggle_slug' => 'button', 'default' => 'I will pass', 'computed_affects' => array( '__variationForm' ), 'show_if' => array( 'button_action' => 'reject', ), ), 'button_action' => array( 'label' => __( 'Select Button Action', 'wpfnl-pro' ), 'description' => __( 'Offer Action', 'wpfnl-pro' ), 'type' => 'select', 'options' => array( 'accept' => __( 'Accept Offer', 'wpfnl-pro' ), 'reject' => __( 'Reject Offer', 'wpfnl-pro' ), ), 'priority' => 80, 'default' => 'accept', 'default_on_front' => 'accept', 'toggle_slug' => 'button', 'sub_toggle' => 'ul', 'mobile_options' => true, 'computed_affects' => array( '__variationForm' ), ), 'button_type' => array( 'label' => __( 'Select Button Type', 'wpfnl-pro' ), 'description' => __( 'Offer Type', 'wpfnl-pro' ), 'type' => 'select', 'options' => array( 'upsell' => __( 'Upsell', 'wpfnl-pro' ), 'downsell' => __( 'Downsell', 'wpfnl-pro' ), ), 'priority' => 80, 'default' => 'upsell', 'default_on_front' => 'upsell', 'toggle_slug' => 'button', 'sub_toggle' => 'ul', 'mobile_options' => true, 'computed_affects' => array( '__variationForm' ), ), '__variationForm' => array( 'type' => 'computed', 'computed_callback' => array( 'WPFunnelsPro\Widgets\DiviModules\Modules\WPFNL_Lms_Pay_Button', 'get_variation_form', ), 'computed_depends_on' => array( 'button_action', 'button_type', 'button_text', ) ), ); return $basic_fields; } public static function get_variation_form( $props, $render_slug ) { $offer_obj = new WPFNL_Offer_Button; $multi_view = et_pb_multi_view_options( $offer_obj ); $button_alignment = $offer_obj->get_button_alignment(); $is_button_aligment_responsive = et_pb_responsive_options()->is_responsive_enabled( $props, 'button_alignment' ); $button_alignment_tablet = $is_button_aligment_responsive ? $offer_obj->get_button_alignment( 'tablet' ) : ''; $button_alignment_phone = $is_button_aligment_responsive ? $offer_obj->get_button_alignment( 'phone' ) : ''; $custom_icon_values = et_pb_responsive_options()->get_property_values( $props, 'button_icon' ); $custom_icon = isset( $custom_icon_values['desktop'] ) ? $custom_icon_values['desktop'] : ''; $custom_icon_tablet = isset( $custom_icon_values['tablet'] ) ? $custom_icon_values['tablet'] : ''; $custom_icon_phone = isset( $custom_icon_values['phone'] ) ? $custom_icon_values['phone'] : ''; // Button Alignment. $button_alignments = array(); if ( ! empty( $button_alignment ) ) { array_push( $button_alignments, sprintf( 'et_pb_button_alignment_%1$s', esc_attr( $button_alignment ) ) ); } if ( ! empty( $button_alignment_tablet ) ) { array_push( $button_alignments, sprintf( 'et_pb_button_alignment_tablet_%1$s', esc_attr( $button_alignment_tablet ) ) ); } if ( ! empty( $button_alignment_phone ) ) { array_push( $button_alignments, sprintf( 'et_pb_button_alignment_phone_%1$s', esc_attr( $button_alignment_phone ) ) ); } $button_alignment_classes = join( ' ', $button_alignments ); // Background layout data attributes. $data_background_layout = et_pb_background_layout_options()->get_background_layout_attrs( $props ); // Background layout class names. $background_layout_class_names = et_pb_background_layout_options()->get_background_layout_class( $props ); $offer_obj->add_classname( $background_layout_class_names ); // Module classnames $offer_obj->remove_classname( 'et_pb_module' ); $button_text = $props['button_text']; $button_type = $props['button_type']; $button_action = $props['button_action']; // Render button $button = $offer_obj->render_button( array( 'button_id' => 'wpfunnels_'.$button_type.'_'.$button_action, 'button_classname' => array( 'offer-btn-d-inline-block', 'offer-button ', 'lms-offer-button ', 'wpfunnels_offer_button',$offer_obj->module_classname( $render_slug ) ), 'button_text' => $button_text, 'button_data' => $button_type, ) ); $step_id = isset($_POST['current_page']['id']) ? $_POST['current_page']['id'] : get_the_ID(); $funnel_id = get_post_meta($step_id,'_funnel_id',true); $get_learn_dash_setting = Wpfnl_lms_learndash_functions::get_learndash_settings($funnel_id); ob_start(); $lms_button_text = get_option( 'learndash_settings_custom_labels' ); if ($get_learn_dash_setting == 'yes'){ $step_type = get_post_meta($step_id, '_step_type', true); if ($step_type != 'upsell' && $step_type != 'downsell'){ echo __('Sorry, Please place the element in WPFunnels Offer page'); }else{ if ($button_action == 'accept'){ $button_text = !empty($lms_button_text['button_take_this_course']) ? $lms_button_text['button_take_this_course'] : "Take this course"; $lms_product_id = get_post_meta($step_id,'_wpfnl_upsell_products',true); if ( $step_type == 'downsell'){ $lms_product_id = get_post_meta($step_id,'_wpfnl_down_products',true); } $course_access = sfwd_lms_has_access( $lms_product_id[0]['id'], get_current_user_id() ); $next_step_url = Wpfnl_lms_learndash_functions::get_next_step_url($funnel_id,$step_id).'?wpfnl_ld_payment=free'; if ($course_access ){ echo ''.$button_text.''; echo ''; }else{ if( self::is_buider_mode()) { echo ''; }else { $course = isset($lms_product_id[0]['id']) ? Wpfnl_lms_learndash_functions::get_course_details_by_id($lms_product_id[0]['id']) : []; if( !empty($course) ){ if( isset($course['type']) && $course['type'] == 'free' ){ $next_step_url = Wpfnl_lms_learndash_functions::get_next_step_url($funnel_id,$step_id); echo ''.$button_text.''; echo ''; } else{ echo do_shortcode('[learndash_payment_buttons course_id='.$lms_product_id[0]['id'].']'); } } } } }else{ ?> props[ "button_alignment{$suffix}" ] ) ? $this->props[ "button_alignment{$suffix}" ] : ''; return et_pb_get_alignment( $text_orientation ); } /** * Helper method for rendering button markup which works compatible with advanced options' button * @param array $args button settings. * * @return string rendered button HTML */ public function render_button( $args = array() ) { // Prepare arguments. $defaults = array( 'button_id' => '', 'button_classname' => array(), 'button_custom' => '', 'button_rel' => '', 'button_text' => '', 'button_text_escaped' => false, 'button_url' => '', 'custom_icon' => '', 'custom_icon_tablet' => '', 'custom_icon_phone' => '', 'display_button' => true, 'has_wrapper' => true, 'url_new_window' => '', 'multi_view_data' => '', 'button_data' => '', ); $args = wp_parse_args( $args, $defaults ); // Do not proceed if display_button argument is false. if ( ! $args['display_button'] ) { return ''; } $button_text = $args['button_text_escaped'] ? $args['button_text'] : esc_html( $args['button_text'] ); // Do not proceed if button_text argument is empty and not having multi view value. if ( '' === $button_text && ! $args['multi_view_data'] ) { return ''; } // Button classname. $button_classname = array( 'et_pb_button' ); if ( ( '' !== $args['custom_icon'] || '' !== $args['custom_icon_tablet'] || '' !== $args['custom_icon_phone'] ) && 'on' === $args['button_custom'] ) { $button_classname[] = 'et_pb_custom_button_icon'; } // Add multi view CSS hidden helper class when button text is empty on desktop mode. if ( '' === $button_text && $args['multi_view_data'] ) { $button_classname[] = 'et_multi_view_hidden'; } if ( ! empty( $args['button_classname'] ) ) { $button_classname = array_merge( $button_classname, $args['button_classname'] ); } // Custom icon data attribute. $use_data_icon = '' !== $args['custom_icon'] && 'on' === $args['button_custom']; $data_icon = $use_data_icon ? sprintf( ' data-icon="%1$s"', esc_attr( et_pb_process_font_icon( $args['custom_icon'] ) ) ) : ''; $use_data_icon_tablet = '' !== $args['custom_icon_tablet'] && 'on' === $args['button_custom']; $data_icon_tablet = $use_data_icon_tablet ? sprintf( ' data-icon-tablet="%1$s"', esc_attr( et_pb_process_font_icon( $args['custom_icon_tablet'] ) ) ) : ''; $use_data_icon_phone = '' !== $args['custom_icon_phone'] && 'on' === $args['button_custom']; $data_icon_phone = $use_data_icon_phone ? sprintf( ' data-icon-phone="%1$s"', esc_attr( et_pb_process_font_icon( $args['custom_icon_phone'] ) ) ) : ''; $button_data = '' !== $args['button_data']; $button_data_type = $button_data ? sprintf( ' data-offertype="%1$s"', esc_attr( et_pb_process_font_icon( $args['button_data'] ) ) ) : ''; // Render button. return sprintf( '%7$s%2$s%8$s', esc_url( $args['button_url'] ), et_core_esc_previously( $button_text ), ( 'on' === $args['url_new_window'] ? ' target="_blank"' : '' ), et_core_esc_previously( $data_icon ), esc_attr( implode( ' ', array_unique( $button_classname ) ) ), // #5 et_core_esc_previously( $this->get_rel_attributes( $args['button_rel'] ) ), $args['has_wrapper'] ? '
' : '', $args['has_wrapper'] ? '
' : '', '' !== $args['button_id'] ? sprintf( ' id="%1$s"', esc_attr( $args['button_id'] ) ) : '', et_core_esc_previously( $data_icon_tablet ), // #10 et_core_esc_previously( $data_icon_phone ), et_core_esc_previously( $args['multi_view_data'] ), $button_data_type ); } /** * Render module output * @param array $attrs List of unprocessed attributes * @param string $content Content being processed * @param string $render_slug Slug of module that is used for rendering output * * @return string module's rendered output */ function render( $attrs, $content, $render_slug ) { return self::get_variation_form($this->props, $render_slug ); } public function render_text( $text ){ $html = ''; $html .= ''; $html .= ''.$text.''; $html .= ''; return $html; } /** * Filter multi view value. * * @since 3.27.1 * * @see ET_Builder_Module_Helper_MultiViewOptions::filter_value * * @param mixed $raw_value Props raw value. * @param array $args { * Context data. * * @type string $context Context param: content, attrs, visibility, classes. * @type string $name Module options props name. * @type string $mode Current data mode: desktop, hover, tablet, phone. * @type string $attr_key Attribute key for attrs context data. Example: src, class, etc. * @type string $attr_sub_key Attribute sub key that availabe when passing attrs value as array such as styes. Example: padding-top, margin-botton, etc. * } * @param ET_Builder_Module_Helper_MultiViewOptions $multi_view Multiview object instance. * * @return mixed */ public function multi_view_filter_value( $raw_value, $args, $multi_view ) { $name = isset( $args['name'] ) ? $args['name'] : ''; $mode = isset( $args['mode'] ) ? $args['mode'] : ''; $context = isset( $args['context'] ) ? $args['context'] : ''; $fields_need_escape = array( 'title', ); if ( $raw_value && 'content' === $context && in_array( $name, $fields_need_escape, true ) ) { return $this->_esc_attr( $multi_view->get_name_by_mode( $name, $mode ), 'none', $raw_value ); } return $raw_value; } /** * render variable markup */ private function render_vaiable_markup( $key, $value, $product, $product_id ){ require WPFNL_PRO_DIR . 'includes/core/shortcodes/variable-template/variable-select-box.php'; } } new WPFNL_Lms_Pay_Button; includes/core/widgets/divi-modules/includes/modules/LmsPayButton/offer_button.svg000064400000051510147600245720024456 0ustar00 buy (1) Created with Sketch. includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php000064400000072773147600245720024061 0ustar00 '', 'author' => '', 'author_uri' => '', ); /** * Module properties initialization */ function init() { $this->name = __( 'WPF Offer Button', 'wpfnl-pro' ); $this->icon_path = plugin_dir_path( __FILE__ ) . 'offer_button.svg'; $this->main_css_element = '%%order_class%%'; $this->settings_modal_toggles = array( 'general' => array( 'toggles' => array( 'button' => __( 'Offer Button', 'wpfnl-pro' ), ), ), 'advanced' => array( 'toggles' => array( 'alignment' => __( 'Alignment', 'wpfnl-pro' ), 'text' => array( 'title' => __( 'Alignment', 'wpfnl-pro' ), 'priority' => 49, ), 'variation_alignment' => array( 'title' => __( 'Variation Alignment', 'wpfnl-pro' ), 'priority' => 50, ), 'template_layout_style' => array( 'title' => __( 'Layout Style', 'wpfnl-pro' ), 'priority' => 60, ), 'template_hedding_style' => array( 'title' => __( 'Heading Style', 'wpfnl-pro' ), 'priority' => 65, ), 'template_description_style' => array( 'title' => __( 'Description Style', 'wpfnl-pro' ), 'priority' => 70, ), ), ), ); $this->wrapper_settings = array( // Flag that indicates that this module's wrapper where order class is declared // has another wrapper (mostly for button alignment purpose). 'order_class_wrapper' => true, ); $this->custom_css_fields = array( 'main_element' => array( 'label' => __( 'Main Element', 'wpfnl-pro' ), 'no_space_before_selector' => true, ), ); $this->advanced_fields = array( 'text' =>array( 'use_text_orientation' => true, // default 'css' => array( 'text_orientation' => '%%order_class%%', ) ), 'borders' => array( 'default' => array( 'css' => array( 'main' => array( 'border_radii' => "{$this->main_css_element} .dynamic-offer-template-default", 'border_styles' => "{$this->main_css_element} .dynamic-offer-template-default", ), ), 'defaults' => array( 'border_radii' => 'off|0px|0px|0px|0px', ), ), ), 'button' => array( 'button' => array( 'label' => __( 'Button', 'wpfnl-pro' ), 'css' => array( 'limited_main' => "{$this->main_css_element} .et_pb_button", ), 'box_shadow' => false, 'text_shadow' => false, 'margin_padding' => array( 'css' => array( 'main' => "{$this->main_css_element} .et_pb_button", 'important' => 'all', ), ), ), ), 'box_shadow' => array( 'default' => array( 'css' => array( 'main' => "{$this->main_css_element} .dynamic-offer-template-default", 'important' => 'all', ), ), ), 'margin_padding' => array( 'css' => array( 'main' => "{$this->main_css_element} .dynamic-offer-template-default", 'important' => 'all', ), ), 'text_shadow' => array( 'default' => false, ), 'background' => array( 'css' => array( 'main' => "{$this->main_css_element} .dynamic-offer-template-default", 'important' => 'all', ), ), 'fonts' => array( 'template_hedding_style' => array( 'label' => __( ' Heading', 'wpfnl-pro' ), 'css' => array( 'main' => "{$this->main_css_element} .dynamic-offer-template-default .template-content .template-product-title", ) ), 'template_description_style' => array( 'label' => __( ' Description', 'wpfnl-pro' ), 'css' => array( 'main' => "{$this->main_css_element} .dynamic-offer-template-default .template-content .template-product-description", ) ), 'template_regular_price_style' => array( 'label' => __( ' Regular Price', 'wpfnl-pro' ), 'css' => array( 'main' => "{$this->main_css_element} .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del bdi, .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del", ) ), 'template_discount_price_style' => array( 'label' => __( ' Discount Price', 'wpfnl-pro' ), 'css' => array( 'main' => "{$this->main_css_element} .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price bdi", ) ), ), 'width' =>false, 'height' => false, 'max_width' => false, 'link_options' => false, 'position_fields' => array( 'css' => array( 'main' => "{$this->main_css_element}_wrapper,", ), ), 'transform' => array( 'css' => array( 'main' => "{$this->main_css_element}_wrapper,", ), ), ); $this->help_videos = array( array( 'id' => 'XpM2G7tQQIE', 'name' => esc_html__( 'An introduction to the Button module', 'wpfnl' ), ), ); } /** * Module's specific fields * * * The following modules are automatically added regardless being defined or not: * Tabs | Toggles | Fields * --------- ------------------ ------------- * Content | Admin Label | Admin Label * Advanced | CSS ID & Classes | CSS ID * Advanced | CSS ID & Classes | CSS Class * Advanced | Custom CSS | Before * Advanced | Custom CSS | Main Element * Advanced | Custom CSS | After * Advanced | Visibility | Disable On * @return array */ function get_fields() { $basic_fields = array( 'button_text' => array( 'label' => __( 'Button Text', 'wpfnl-pro' ), 'type' => 'text', 'option_category' => 'basic_option', 'description' => __( 'Input your desired button text, or leave blank for no button.', 'wpfnl-pro' ), 'toggle_slug' => 'button', 'default' => 'Accept Offer', 'computed_affects' => array( '__variationForm' ), ), 'button_action' => array( 'label' => __( 'Select Button Action', 'wpfnl-pro' ), 'description' => __( 'Offer Action', 'wpfnl-pro' ), 'type' => 'select', 'options' => array( 'accept' => __( 'Accept Offer', 'wpfnl-pro' ), 'reject' => __( 'Reject Offer', 'wpfnl-pro' ), ), 'priority' => 80, 'default' => 'accept', 'default_on_front' => 'accept', 'toggle_slug' => 'button', 'sub_toggle' => 'ul', 'mobile_options' => true, 'computed_affects' => array( '__variationForm' ), ), 'button_type' => array( 'label' => __( 'Select Button Type', 'wpfnl-pro' ), 'description' => __( 'Offer Type', 'wpfnl-pro' ), 'type' => 'select', 'options' => array( 'upsell' => __( 'Upsell', 'wpfnl-pro' ), 'downsell' => __( 'Downsell', 'wpfnl-pro' ), ), 'priority' => 80, 'default' => 'upsell', 'default_on_front' => 'upsell', 'toggle_slug' => 'button', 'sub_toggle' => 'ul', 'mobile_options' => true, 'computed_affects' => array( '__variationForm' ), ), 'variation_alignment' => array( 'label' => __( 'Select Variation Align', 'wpfnl-pro' ), 'description' => __( 'Offer Type', 'wpfnl-pro' ), 'type' => 'select', 'options' => array( 'left' => __( 'Left', 'wpfnl-pro' ), 'center' => __( 'Center', 'wpfnl-pro' ), 'right' => __( 'Right', 'wpfnl-pro' ), ), 'priority' => 80, 'default' => 'left', 'default_on_front' => 'left', 'toggle_slug' => 'variation_alignment', 'sub_toggle' => 'ul', 'mobile_options' => true, 'show_if' => array( 'button_action' => 'accept', ), 'computed_affects' => array( '__variationForm' ), ), 'variation_tbl_title' => array( 'label' => __( 'Variation Table Title', 'wpfnl-pro' ), 'type' => 'text', 'option_category' => 'basic_option', 'description' => __( 'Input your Variation Table Title.', 'wpfnl-pro' ), 'toggle_slug' => 'button', 'show_if' => array( 'button_action' => 'accept', ), 'computed_affects' => array( '__variationForm' ), ), 'show_product_price' => array( 'label' => __( 'Show Product Price', 'wpfnl-pro' ), 'type' => 'select', 'options' => array( 'yes' => __( 'Show', 'wpfnl-pro' ), 'no' => __( 'Hide', 'wpfnl-pro' ), ), 'priority' => 80, 'default' => 'no', 'default_on_front' => 'no', 'toggle_slug' => 'button', 'sub_toggle' => 'ul', 'mobile_options' => true, 'show_if' => array( 'button_action' => 'accept', ), 'computed_affects' => array( '__variationForm' ), ), 'product_price_alignment' => array( 'label' => __( 'Product Price Alignment', 'wpfnl-pro' ), 'type' => 'select', 'options' => array( '' => __('On The Left Of Button', 'wpfnl-pro' ), 'price-right' => __('On The Right Of Button', 'wpfnl-pro'), 'price-top' => __('Above The Button', 'wpfnl-pro'), 'price-bottom' => __('Below The Button', 'wpfnl-pro'), ), 'priority' => 85, 'default' => 'no', 'default_on_front' => 'no', 'toggle_slug' => 'button', 'sub_toggle' => 'ul', 'mobile_options' => true, 'show_if' => array( 'show_product_price' => 'yes', ), 'computed_affects' => array( '__variationForm' ), ), 'show_product_data' => array( 'label' => __( 'Show Product Data', 'wpfnl-pro' ), 'type' => 'select', 'options' => array( 'yes' => __( 'Show', 'wpfnl-pro' ), 'no' => __( 'Hide', 'wpfnl-pro' ), ), 'priority' => 90, 'default' => 'no', 'default_on_front' => 'no', 'toggle_slug' => 'button', 'sub_toggle' => 'ul', 'mobile_options' => true, 'show_if' => array( 'button_action' => 'accept', ), 'computed_affects' => array( '__variationForm' ), ), 'dynamic_data_template_layout' => array( 'label' => __( 'Select Template Style', 'wpfnl-pro' ), 'type' => 'select', 'options' => array( 'style1' => __( 'Left Image Right Content', 'wpfnl-pro' ), 'style2' => __( 'Left Content Right Image', 'wpfnl-pro' ), 'style3' => __( 'Top Image Bottom Content', 'wpfnl-pro' ), ), 'priority' => 95, 'default' => 'style1', 'toggle_slug' => 'button', 'sub_toggle' => 'ul', 'mobile_options' => true, 'show_if' => array( 'button_action' => 'accept', 'show_product_data' => 'yes', ), 'computed_affects' => array( '__variationForm' ), ), '__variationForm' => array( 'type' => 'computed', 'computed_callback' => array( 'WPFunnelsPro\Widgets\DiviModules\Modules\WPFNL_Offer_Button', 'get_variation_form', ), 'computed_depends_on' => array( 'button_action', 'button_type', 'button_text', 'variation_alignment', 'variation_tbl_title', 'show_product_price', 'product_price_alignment', 'show_product_data', 'dynamic_data_template_layout', ) ), ); return $basic_fields; } public static function get_variation_form( $props, $render_slug ) { $offer_obj = new WPFNL_Offer_Button; $multi_view = et_pb_multi_view_options( $offer_obj ); $button_alignment = $offer_obj->get_button_alignment(); $is_button_aligment_responsive = et_pb_responsive_options()->is_responsive_enabled( $props, 'button_alignment' ); $button_alignment_tablet = $is_button_aligment_responsive ? $offer_obj->get_button_alignment( 'tablet' ) : ''; $button_alignment_phone = $is_button_aligment_responsive ? $offer_obj->get_button_alignment( 'phone' ) : ''; $custom_icon_values = et_pb_responsive_options()->get_property_values( $props, 'button_icon' ); $custom_icon = isset( $custom_icon_values['desktop'] ) ? $custom_icon_values['desktop'] : ''; $custom_icon_tablet = isset( $custom_icon_values['tablet'] ) ? $custom_icon_values['tablet'] : ''; $custom_icon_phone = isset( $custom_icon_values['phone'] ) ? $custom_icon_values['phone'] : ''; // Button Alignment. $button_alignments = array(); if ( ! empty( $button_alignment ) ) { array_push( $button_alignments, sprintf( 'et_pb_button_alignment_%1$s', esc_attr( $button_alignment ) ) ); } if ( ! empty( $button_alignment_tablet ) ) { array_push( $button_alignments, sprintf( 'et_pb_button_alignment_tablet_%1$s', esc_attr( $button_alignment_tablet ) ) ); } if ( ! empty( $button_alignment_phone ) ) { array_push( $button_alignments, sprintf( 'et_pb_button_alignment_phone_%1$s', esc_attr( $button_alignment_phone ) ) ); } $button_alignment_classes = join( ' ', $button_alignments ); // Background layout data attributes. $data_background_layout = et_pb_background_layout_options()->get_background_layout_attrs( $props ); // Background layout class names. $background_layout_class_names = et_pb_background_layout_options()->get_background_layout_class( $props ); $offer_obj->add_classname( $background_layout_class_names ); // Module classnames $offer_obj->remove_classname( 'et_pb_module' ); $button_text = $props['button_text']; $button_type = $props['button_type']; $button_action = $props['button_action']; // Render button $button = $offer_obj->render_button( array( 'button_id' => 'wpfunnels_'.$button_type.'_'.$button_action, 'button_classname' => array( 'offer-btn-d-inline-block', 'offer-button ', 'wpfunnels_offer_button',$offer_obj->module_classname( $render_slug ) ), 'button_text' => $button_text, 'button_data' => $button_type, ) ); $step_id = isset($_POST['current_page']['id']) ? $_POST['current_page']['id'] : get_the_ID(); ob_start(); if ($props['variation_alignment'] == 'center'){ echo ''; } if($props['variation_alignment'] == 'right'){ echo ''; } if( isset($step_id) && $step_id ){ $step_type = get_post_meta($step_id, '_step_type', true); $offer_product_data = Wpfnl_Pro_functions::get_offer_product( $step_id, $step_type ); $offer_product = null; if( is_array($offer_product_data) ) { foreach ( $offer_product_data as $pr_index => $pr_data ) { $product_id = $pr_data['id']; $offer_product = wc_get_product( $product_id ); break; } } }else{ $offer_product = Wpfnl_Offer_Product::getInstance()->get_offer_product(); } $response = Wpfnl_Pro_functions::get_product_data_for_widget( $step_id ); $offer_product = isset($response['offer_product']) && $response['offer_product'] ? $response['offer_product'] : ''; $get_product_type = isset($response['get_product_type']) && $response['get_product_type'] ? $response['get_product_type'] : ''; $is_gbf = isset($response['is_gbf']) && $response['is_gbf'] ? $response['is_gbf'] : ''; $builder = 'divi'; if( !is_object($offer_product) || null === $offer_product) { return; } if( 'yes' === $is_gbf && isset($props['show_product_data']) && 'yes' === $props['show_product_data'] && 'accept' === $button_action ){ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/styles/offer-'.$props['dynamic_data_template_layout'].'.php'; }else{ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/divi/offer-button.php'; } return ob_get_clean(); } /** * Get button alignment. * * @since 3.23 Add responsive support by adding device parameter. * * @param string $device Current device name. * @return string Alignment value, rtl or not. */ public function get_button_alignment( $device = 'desktop' ) { $suffix = 'desktop' !== $device ? "_{$device}" : ''; $text_orientation = isset( $this->props[ "button_alignment{$suffix}" ] ) ? $this->props[ "button_alignment{$suffix}" ] : ''; return et_pb_get_alignment( $text_orientation ); } /** * Helper method for rendering button markup which works compatible with advanced options' button * @param array $args button settings. * * @return string rendered button HTML */ public function render_button( $args = array() ) { // Prepare arguments. $defaults = array( 'button_id' => '', 'button_classname' => array(), 'button_custom' => '', 'button_rel' => '', 'button_text' => '', 'button_text_escaped' => false, 'button_url' => '', 'custom_icon' => '', 'custom_icon_tablet' => '', 'custom_icon_phone' => '', 'display_button' => true, 'has_wrapper' => true, 'url_new_window' => '', 'multi_view_data' => '', 'button_data' => '', ); $args = wp_parse_args( $args, $defaults ); // Do not proceed if display_button argument is false. if ( ! $args['display_button'] ) { return ''; } $button_text = $args['button_text_escaped'] ? $args['button_text'] : esc_html( $args['button_text'] ); // Do not proceed if button_text argument is empty and not having multi view value. if ( '' === $button_text && ! $args['multi_view_data'] ) { return ''; } // Button classname. $button_classname = array( 'et_pb_button' ); if ( ( '' !== $args['custom_icon'] || '' !== $args['custom_icon_tablet'] || '' !== $args['custom_icon_phone'] ) && 'on' === $args['button_custom'] ) { $button_classname[] = 'et_pb_custom_button_icon'; } // Add multi view CSS hidden helper class when button text is empty on desktop mode. if ( '' === $button_text && $args['multi_view_data'] ) { $button_classname[] = 'et_multi_view_hidden'; } if ( ! empty( $args['button_classname'] ) ) { $button_classname = array_merge( $button_classname, $args['button_classname'] ); } // Custom icon data attribute. $use_data_icon = '' !== $args['custom_icon'] && 'on' === $args['button_custom']; $data_icon = $use_data_icon ? sprintf( ' data-icon="%1$s"', esc_attr( et_pb_process_font_icon( $args['custom_icon'] ) ) ) : ''; $use_data_icon_tablet = '' !== $args['custom_icon_tablet'] && 'on' === $args['button_custom']; $data_icon_tablet = $use_data_icon_tablet ? sprintf( ' data-icon-tablet="%1$s"', esc_attr( et_pb_process_font_icon( $args['custom_icon_tablet'] ) ) ) : ''; $use_data_icon_phone = '' !== $args['custom_icon_phone'] && 'on' === $args['button_custom']; $data_icon_phone = $use_data_icon_phone ? sprintf( ' data-icon-phone="%1$s"', esc_attr( et_pb_process_font_icon( $args['custom_icon_phone'] ) ) ) : ''; $button_data = '' !== $args['button_data']; $button_data_type = $button_data ? sprintf( ' data-offertype="%1$s"', esc_attr( et_pb_process_font_icon( $args['button_data'] ) ) ) : ''; // Render button. return sprintf( '%7$s%2$s%8$s', esc_url( $args['button_url'] ), et_core_esc_previously( $button_text ), ( 'on' === $args['url_new_window'] ? ' target="_blank"' : '' ), et_core_esc_previously( $data_icon ), esc_attr( implode( ' ', array_unique( $button_classname ) ) ), // #5 et_core_esc_previously( $this->get_rel_attributes( $args['button_rel'] ) ), $args['has_wrapper'] ? '
' : '', $args['has_wrapper'] ? '
' : '', '' !== $args['button_id'] ? sprintf( ' id="%1$s"', esc_attr( $args['button_id'] ) ) : '', et_core_esc_previously( $data_icon_tablet ), // #10 et_core_esc_previously( $data_icon_phone ), et_core_esc_previously( $args['multi_view_data'] ), $button_data_type ); } /** * Render module output * @param array $attrs List of unprocessed attributes * @param string $content Content being processed * @param string $render_slug Slug of module that is used for rendering output * * @return string module's rendered output */ function render( $attrs, $content, $render_slug ) { return self::get_variation_form($this->props, $render_slug ); } public function render_text( $text ){ $html = ''; $html .= ''; $html .= ''.$text.''; $html .= ''; return $html; } /** * Filter multi view value. * * @since 3.27.1 * * @see ET_Builder_Module_Helper_MultiViewOptions::filter_value * * @param mixed $raw_value Props raw value. * @param array $args { * Context data. * * @type string $context Context param: content, attrs, visibility, classes. * @type string $name Module options props name. * @type string $mode Current data mode: desktop, hover, tablet, phone. * @type string $attr_key Attribute key for attrs context data. Example: src, class, etc. * @type string $attr_sub_key Attribute sub key that availabe when passing attrs value as array such as styes. Example: padding-top, margin-botton, etc. * } * @param ET_Builder_Module_Helper_MultiViewOptions $multi_view Multiview object instance. * * @return mixed */ public function multi_view_filter_value( $raw_value, $args, $multi_view ) { $name = isset( $args['name'] ) ? $args['name'] : ''; $mode = isset( $args['mode'] ) ? $args['mode'] : ''; $context = isset( $args['context'] ) ? $args['context'] : ''; $fields_need_escape = array( 'title', ); if ( $raw_value && 'content' === $context && in_array( $name, $fields_need_escape, true ) ) { return $this->_esc_attr( $multi_view->get_name_by_mode( $name, $mode ), 'none', $raw_value ); } return $raw_value; } /** * render variable markup */ private function render_vaiable_markup( $key, $value, $product, $product_id ){ require WPFNL_PRO_DIR . 'includes/core/shortcodes/variable-template/variable-select-box.php'; } } if(is_plugin_active('woocommerce/woocommerce.php')){ new WPFNL_Offer_Button; }includes/core/widgets/divi-modules/includes/modules/OfferButton/offer_button.svg000064400000051510147600245720024312 0ustar00 buy (1) Created with Sketch. includes/core/widgets/divi-modules/includes/DiviModules.php000064400000011307147600245720020125 0ustar00get_current_post_id(); $post_type = get_post_type($post_id); $step_type = get_post_meta($post_id, '_step_type', true); if (WPFNL_STEPS_POST_TYPE !== $post_type) { return; } add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts')); add_action('wp_enqueue_scripts', array($this, 'enqueue_builder_scripts')); if ('upsell' === $step_type || 'downsell' === $step_type) { new WPFNL_Offer_Button; new WPFNL_Lms_Pay_Button; } } private function get_current_post_id() { if (wp_doing_ajax() && $this->array_get($_POST, 'current_page.id')) { return absint($this->array_get($_POST, 'current_page.id')); } if (wp_doing_ajax() && isset($_POST['et_post_id'])) { return absint($_POST['et_post_id']); } if (isset($_POST['post'])) { return absint($_POST['post']); } if (self::_should_respect_post_interference()) { return get_the_ID(); } return ET_Post_Stack::get_main_post_id(); } /** * Retrieve a value from a nested array using a dot-separated address. * * @param array $array The array to search. * @param string $address The dot-separated address of the value. * @param mixed $default The default value to return if the address is not found. * * @return mixed The value found at the address, or the default value. * @since 3.5.11 */ public function array_get($array, $address, $default = '') { $keys = is_array($address) ? $address : explode('.', $address); $value = $array; foreach ($keys as $key) { if (! empty($key) && isset($key[0]) && '[' === $key[0]) { $index = substr($key, 1, -1); if (is_numeric($index)) { $key = (int) $index; } } if (! isset($value[$key])) { return $default; } $value = $value[$key]; } return $value; } /** * Determine if post interference should be respected. * * This function checks if the current post ID should be respected * based on the post stack and the current post ID. * * @return bool True if post interference should be respected, false otherwise. * @since 3.5.11 */ protected static function _should_respect_post_interference() { $post = ET_Post_Stack::get(); return null !== $post && get_the_ID() !== $post->ID; } /** * Enqueue frontend scripts and styles for the Divi modules. * * This function enqueues the necessary JavaScript and CSS files * for the frontend of the Divi modules. * * @return void * @since 3.5.11 */ public function enqueue_frontend_scripts() { wp_enqueue_script( 'wpfnl-divi-frontend', WPFNL_DIR_URL . 'includes/core/widgets/divi-modules/scripts/frontend.js', array('jquery'), WPFNL_VERSION, true ); wp_enqueue_script( 'wpfnl-divi-frontend-bundle', WPFNL_DIR_URL . 'includes/core/widgets/divi-modules/scripts/frontend-bundle.min.js', array('jquery'), WPFNL_VERSION, true ); // Modules CSS $styles = et_is_builder_plugin_active() ? 'style-dbp' : 'style'; $styles_url = WPFNL_DIR_URL . "includes/core/widgets/divi-modules/styles/{$styles}.min.css"; wp_enqueue_style( 'wpfnl-divi-frontend-styles', $styles_url, array(), WPFNL_VERSION, 'all' ); } /** * Enqueue builder scripts and styles for the Divi modules. * * This function enqueues the necessary JavaScript files * for the builder interface of the Divi modules. * * @return void * @since 3.5.11 */ public function enqueue_builder_scripts() { $bundle_url = WPFNL_DIR_URL . "includes/core/widgets/divi-modules/scripts/builder-bundle.min.js"; wp_enqueue_script( "{$this->name}-builder-bundle", $bundle_url, array('react-dom', 'wpfnl-divi-frontend-bundle'), WPFNL_VERSION, true ); } } includes/core/widgets/divi-modules/includes/loader.php000064400000000520147600245720017142 0ustar00init(); } /** * initialize divi modules */ private function init() { add_action( 'divi_extensions_init', array( $this, 'wpfnl_initialize_extension' ) ); } public function wpfnl_initialize_extension() { WPFNL_DiviModules::get_instance(); } }includes/core/widgets/elementor/elements/offer-button.php000064400000112712147600245720017714 0ustar00=') ) { $this->register_controls(); } else { $this->_register_controls(); } } /** * Retrieve the widget name. * * @return string Widget name. * @since 1.0.0 * * @access public * */ public function get_name() { return 'wpfnl-offer'; } /** * Retrieve the widget title. * * @return string Widget title. * @since 1.0.0 * * @access public * */ public function get_title() { return __('Offer Button', 'wpfnl'); } /** * Retrieve the widget icon. * * @return string Widget icon. * @since 1.0.0 * * @access public * */ public function get_icon() { return 'icon-wpfnl sell-accept'; } /** * Retrieve the list of categories the widget belongs to. * * Used to determine where to display the widget in the editor. * * Note that currently Elementor supports only one category. * When multiple categories passed, Elementor uses the first one. * * @return array Widget categories. * @since 1.0.0 * * @access public * */ public function get_categories() { return ['wp-funnel']; } /** * Retrieve the list of scripts the widget depended on. * * Used to set scripts dependencies required to run the widget. * * @return array Widget scripts dependencies. * @since 1.0.0 * * @access public * */ public function get_script_depends() { return ['upsell-downsell-widget']; } /** * Get button sizes. * * Retrieve an array of button sizes for the button widget. * * @return array An array containing button sizes. * @since 1.0.0 * @access public * @static * */ public static function get_button_sizes() { return [ 'xs' => __('Extra Small', 'wpfnl-pro'), 'sm' => __('Small', 'wpfnl-pro'), 'md' => __('Medium', 'wpfnl-pro'), 'lg' => __('Large', 'wpfnl-pro'), 'xl' => __('Extra Large', 'wpfnl-pro'), ]; } /** * Register the widget controls. * @since 1.0.0 * * @access protected */ protected function _register_controls() { //----content funtion---- $this->register_dynamic_data_template_content(); $this->register_offer_button_content(); //----style funtion---- $this->register_dynamic_data_template_style(); $this->register_offer_button_style(); } /** * Register the widget controls. * @since 1.0.0 * * @access protected */ protected function register_controls() { //----content funtion---- $this->register_offer_button_content(); $this->register_dynamic_data_template_content(); //----style funtion---- $this->register_dynamic_data_template_style(); $this->register_offer_button_style(); } /** * Register Dynamic Data Template content Control. * * @since x.x.x * @access protected */ protected function register_dynamic_data_template_content(){ $this->start_controls_section( 'dynamic_data_template_content', array( 'label' => __('Template Content', 'wpfnl-pro'), 'condition' => array( 'show_product_data' => 'yes', 'offer_type' => 'accept', ), ) ); $this->add_control( 'dynamic_data_template_layout', [ 'label' => __('Select Template Style ', 'wpfnl-pro'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => 'style1', 'options' => [ 'style1' => __('Left Image Right Content', 'wpfnl-pro'), 'style2' => __('Left Content Right Image', 'wpfnl-pro'), 'style3' => __('Top Image Bottom Content', 'wpfnl-pro'), ], ] ); $this->end_controls_section(); } /** * Register Dynamic Data Template Style Control. * * @since x.x.x * @access protected */ protected function register_dynamic_data_template_style(){ $this->start_controls_section( 'dynamic_data_template_style', [ 'label' => __('Template Style', 'wpfnl-pro'), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => array( 'show_product_data' => 'yes', 'offer_type' => 'accept', ), ] ); // ----Template Layout Style----- $this->add_control( 'template_layout_style', [ 'label' => __('Layout Style', 'wpfnl-pro'), 'type' => Controls_Manager::HEADING, 'label_block' => true, ] ); //-----left column for style-1------ $this->add_responsive_control( 'template_left_col_width', [ 'label' => esc_html__( 'Image Column Width', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => [ 'px', '%' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 1920, 'step' => 1, ], '%' => [ 'min' => 0, 'max' => 100, ], ], 'default' => [ 'unit' => '%', 'size' => '', ], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .template-left' => 'width: {{SIZE}}{{UNIT}};', ], ] ); $this->add_responsive_control( 'template_right_col_width', [ 'label' => esc_html__( 'Content Column Width', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => [ 'px', '%' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 1920, 'step' => 1, ], '%' => [ 'min' => 0, 'max' => 100, ], ], 'default' => [ 'unit' => '%', 'size' => '', ], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .template-right' => 'width: {{SIZE}}{{UNIT}};', ], ] ); $this->add_responsive_control( 'template_col_gutter_width', [ 'label' => esc_html__( 'Column Gutter Width', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => [ 'px', '%' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 100, 'step' => 1, ], '%' => [ 'min' => 0, 'max' => 100, ], ], 'default' => [ 'unit' => 'px', 'size' => '', ], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .template-right' => 'padding-left: {{SIZE}}{{UNIT}};', ], 'condition' => array( 'dynamic_data_template_layout[value]' => 'style1', ), ] ); //--when template style-2 select then this control will show--- $this->add_responsive_control( 'template_col_right_gutter_width', [ 'label' => esc_html__( 'Column Gutter Width', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => [ 'px', '%' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 100, 'step' => 1, ], '%' => [ 'min' => 0, 'max' => 100, ], ], 'default' => [ 'unit' => 'px', 'size' => '', ], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .template-right' => 'padding-right: {{SIZE}}{{UNIT}};', ], 'condition' => array( 'dynamic_data_template_layout[value]' => 'style2', ), ] ); //--when template style-3 select then this control will show--- $this->add_responsive_control( 'template_col_top_gutter_width', [ 'label' => esc_html__( 'Column Gutter Width', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => [ 'px', '%' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 100, 'step' => 1, ], '%' => [ 'min' => 0, 'max' => 100, ], ], 'default' => [ 'unit' => 'px', 'size' => '', ], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .template-right' => 'padding-top: {{SIZE}}{{UNIT}};', ], 'condition' => array( 'dynamic_data_template_layout[value]' => 'style3', ), ] ); $this->add_responsive_control( 'template_layout_radius', [ 'label' => __('Radius', 'wpfnl-pro'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', 'em', '%'], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_group_control( \Elementor\Group_Control_Box_Shadow::get_type(), [ 'name' => 'template_layout_shadow', 'label' => __( 'Box Shadow', 'wpfnl-pro' ), 'selector' => '{{WRAPPER}} .dynamic-offer-template-default', ] ); $this->add_responsive_control( 'template_layout_padding', [ 'label' => __('Padding', 'wpfnl-pro'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', 'em', '%'], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_responsive_control( 'template_layout_margin', [ 'label' => __('Margin', 'wpfnl-pro'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', 'em', '%'], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'after', ] ); // ----Template Content Style----- $this->add_control( 'template_content_heading', [ 'label' => __('Template Image Style', 'wpfnl-pro'), 'type' => Controls_Manager::HEADING, 'label_block' => true, ] ); $this->add_responsive_control( 'template_image_width', [ 'label' => esc_html__( 'Image Width', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => [ 'px', '%' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 1920, 'step' => 1, ], '%' => [ 'min' => 0, 'max' => 100, ], ], 'default' => [ 'unit' => '%', 'size' => '', ], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .product-img img' => 'width: {{SIZE}}{{UNIT}};', ], ] ); $this->add_responsive_control( 'template_image_radius', [ 'label' => __('Image Radius', 'wpfnl-pro'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', 'em', '%'], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .product-img img' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'after', ] ); $this->add_control( 'template_heading_style', [ 'label' => __('Heading Style', 'wpfnl-pro'), 'type' => Controls_Manager::HEADING, 'label_block' => true, ] ); $this->add_control( 'template_heading_color', [ 'label' => __('Color', 'wpfnl-pro'), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .template-content .template-product-title' => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'template_heading_typography', 'label' => 'Typography', 'selector' => '{{WRAPPER}} .dynamic-offer-template-default .template-content .template-product-title', ] ); $this->add_responsive_control( 'template_heading_margin', [ 'label' => __('Margin', 'wpfnl-pro'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', 'em', '%'], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .template-content .template-product-title' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'after', ] ); // ----Template description Style----- $this->add_control( 'template_description_style', [ 'label' => __('Description Style', 'wpfnl-pro'), 'type' => Controls_Manager::HEADING, 'label_block' => true, ] ); $this->add_control( 'template_description_color', [ 'label' => __('Color', 'wpfnl-pro'), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .template-content .template-product-description' => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'template_description_typography', 'label' => 'Typography', 'selector' => '{{WRAPPER}} .dynamic-offer-template-default .template-content .template-product-description', ] ); $this->add_responsive_control( 'template_description_margin', [ 'label' => __('Margin', 'wpfnl-pro'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', 'em', '%'], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default .template-content .template-product-description' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'after', ] ); // ----Template price Style----- $this->add_control( 'template_price_style', [ 'label' => __('Price Style', 'wpfnl-pro'), 'type' => Controls_Manager::HEADING, 'label_block' => true, ] ); $this->add_control( 'template_price_color', [ 'label' => __('Regular Price Color', 'wpfnl-pro'), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del bdi, .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del' => 'color: {{VALUE}};', ], ] ); $this->add_control( 'template_discount_price_color', [ 'label' => __('Discount Price Color', 'wpfnl-pro'), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price bdi' => 'color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'template_discount_price_typography', 'label' => 'Discount Price Typography', 'selector' => '{{WRAPPER}} .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price bdi', ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'template_price_typography', 'label' => 'Regular Price Typography', 'selector' => '{{WRAPPER}} .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del bdi, {{WRAPPER}} .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price', ] ); $this->add_responsive_control( 'template_price_margin', [ 'label' => __('Margin', 'wpfnl-pro'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', 'em', '%'], 'selectors' => [ '{{WRAPPER}} .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'after', ] ); $this->end_controls_section(); } /** * Register Offer Button Content Control. * * @since x.x.x * @access protected */ protected function register_offer_button_content(){ $this->start_controls_section( 'section_button', array( 'label' => __('Upsell/Downsell', 'wpfnl-pro'), ) ); $this->add_control( 'offer_button_type', [ 'label' => __('Select button type', 'wpfnl-pro'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => 'upsell', 'options' => [ 'upsell' => __('Upsell', 'wpfnl-pro'), 'downsell' => __('Downsell', 'wpfnl-pro'), ], ] ); $this->add_control( 'offer_type', [ 'label' => __('Select button action', 'wpfnl-pro'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => 'accept', 'options' => array( 'accept' => __('Accept', 'wpfnl-pro'), 'reject' => __('Reject', 'wpfnl-pro'), ) ] ); $this->add_control( 'text', [ 'label' => __('Text', 'wpfnl-pro'), 'type' => Controls_Manager::TEXT, 'default' => __('Accept Offer', 'wpfnl-pro'), 'placeholder' => __('Accept Offer', 'wpfnl-pro'), 'separator' => 'before', ] ); $this->add_responsive_control( 'align', [ 'label' => __('Alignment', 'wpfnl-pro'), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'left' => [ 'title' => __('Left', 'wpfnl-pro'), 'icon' => 'fa fa-align-left', ], 'center' => [ 'title' => __('Center', 'wpfnl-pro'), 'icon' => 'fa fa-align-center', ], 'right' => [ 'title' => __('Right', 'wpfnl-pro'), 'icon' => 'fa fa-align-right', ], 'justify' => [ 'title' => __('Justified', 'wpfnl-pro'), 'icon' => 'fa fa-align-justify', ], ], 'prefix_class' => 'elementor%s-align-', 'default' => '', ] ); $this->add_responsive_control( 'size', [ 'label' => __('Size', 'wpfnl-pro'), 'type' => Controls_Manager::SELECT, 'default' => 'sm', 'options' => self::get_button_sizes(), ] ); $this->add_control( 'upsell_downsell_button_icon', [ 'label' => __('Icon', 'wpfnl'), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', ] ); $this->add_control( 'upsell_downsell_button_icon_align', [ 'label' => __('Icon Position', 'wpfnl-pro'), 'type' => Controls_Manager::SELECT, 'default' => 'left', 'options' => array( 'left' => __('Before', 'wpfnl-pro'), 'right' => __('After', 'wpfnl-pro'), ), 'condition' => array( 'upsell_downsell_button_icon[value]!' => '', ), ] ); $this->add_control( 'upsell_downsell_button_icon_indent', [ 'label' => __('Icon Spacing', 'wpfnl'), 'type' => \Elementor\Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'max' => 50, ], ], 'condition' => [ 'upsell_downsell_button_icon!' => '', ], 'selectors' => [ '{{WRAPPER}} .elementor-button .elementor-align-icon-right' => 'margin-left: {{SIZE}}{{UNIT}};', '{{WRAPPER}} .elementor-button .elementor-align-icon-left' => 'margin-right: {{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'view', [ 'label' => __('View', 'wpfnl-pro'), 'type' => Controls_Manager::HIDDEN, 'default' => 'traditional', ] ); $get_product_type = Wpfnl_Pro_functions::get_offer_product_type( get_the_ID() ); if( $get_product_type == 'variable' ){ $this->add_control( 'variation_tbl_title', [ 'label' => __('Variation Table Title', 'wpfnl-pro'), 'type' => Controls_Manager::TEXTAREA, 'separator' => 'before', 'condition' => [ 'offer_type' => 'accept', ], ] ); } $this->add_control( 'show_product_price', [ 'label' => esc_html__( 'Show Product Price', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'wpfnl-pro' ), 'label_off' => esc_html__( 'Hide', 'wpfnl-pro' ), 'return_value' => 'yes', 'default' => 'no', 'condition' => [ 'offer_type' => 'accept', ], ] ); $this->add_control( 'product_price_alignment', [ 'label' => __('Price Alignment ', 'wpfnl-pro'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => '', 'options' => [ '' => __('On The Left Of Button', 'wpfnl-pro'), 'price-right' => __('On The Right Of Button', 'wpfnl-pro'), 'price-top' => __('Above The Button', 'wpfnl-pro'), 'price-bottom' => __('Below The Button', 'wpfnl-pro'), ], 'condition' => [ 'show_product_price' => 'yes', ], ] ); $funnel_id = get_post_meta( get_the_ID(), '_funnel_id', true ); $is_gbf = get_post_meta( $funnel_id, 'is_global_funnel', true ); if( 'yes' === $is_gbf ){ $this->add_control( 'show_product_data', [ 'label' => esc_html__( 'Show Product Data', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Show', 'wpfnl-pro' ), 'label_off' => esc_html__( 'Hide', 'wpfnl-pro' ), 'return_value' => 'yes', 'default' => 'off', 'condition' => [ 'offer_type' => 'accept', ], ] ); } $this->end_controls_section(); } /** * Register Offer Button Style Control. * * @since x.x.x * @access protected */ protected function register_offer_button_style(){ $this->start_controls_section( 'button_section_style', [ 'label' => __('Button', 'wpfnl-pro'), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'upsell_downsell_button_typography', 'label' => 'Typography', 'selector' => '{{WRAPPER}} a.elementor-button', ] ); $this->add_group_control( Group_Control_Text_Shadow::get_type(), [ 'name' => 'upsell_downsell_button_text_shadow', 'selector' => '{{WRAPPER}} a.elementor-button', ] ); $this->start_controls_tabs('tabs_button_style'); $this->start_controls_tab( 'tab_button_normal', [ 'label' => __('Normal', 'wpfnl-pro'), ] ); $this->add_control( 'button_text_color', [ 'label' => __('Text Color', 'wpfnl-pro'), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} a.elementor-button, {{WRAPPER}} .elementor-button' => 'color: {{VALUE}};', ], ] ); $this->add_control( 'background_color', [ 'label' => __('Background Color', 'wpfnl-pro'), 'type' => Controls_Manager::COLOR, 'default' => '#61CE70', 'selectors' => [ '{{WRAPPER}} a.elementor-button, {{WRAPPER}} .elementor-button' => 'background-color: {{VALUE}};', ], ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_button_hover', [ 'label' => __('Hover', 'wpfnl-pro'), ] ); $this->add_control( 'hover_color', [ 'label' => __('Text Color', 'wpfnl-pro'), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} a.elementor-button:hover, {{WRAPPER}} .elementor-button:hover' => 'color: {{VALUE}};', ], ] ); $this->add_control( 'button_background_hover_color', [ 'label' => __('Background Color', 'wpfnl-pro'), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} a.elementor-button:hover, {{WRAPPER}} .elementor-button:hover' => 'background-color: {{VALUE}};', ], ] ); $this->add_control( 'button_hover_border_color', [ 'label' => __('Border Color', 'wpfnl-pro'), 'type' => Controls_Manager::COLOR, 'condition' => [ 'border_border!' => '', ], 'selectors' => [ '{{WRAPPER}} a.elementor-button:hover, {{WRAPPER}} .elementor-button:hover' => 'border-color: {{VALUE}};', ], ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'border', 'label' => __('Border', 'wpfnl-pro'), 'placeholder' => '1px', 'default' => '1px', 'selector' => '{{WRAPPER}} .elementor-button', 'separator' => 'before', ] ); $this->add_control( 'border_radius', [ 'label' => __('Border Radius', 'wpfnl-pro'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', '%'], 'selectors' => [ '{{WRAPPER}} a.elementor-button, {{WRAPPER}} .elementor-button' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'button_box_shadow', 'selector' => '{{WRAPPER}} .elementor-button', ] ); $this->add_responsive_control( 'upsell_downsell_padding', [ 'label' => __('Padding', 'wpfnl-pro'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', 'em', '%'], 'selectors' => [ '{{WRAPPER}} a.elementor-button, {{WRAPPER}} .elementor-button' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); $this->end_controls_section(); } public function get_prev_next_link_options() { $associate_funnel_id = get_post_meta(get_the_ID(), '_funnel_id', true); $steps_array = [ 'upsell' => 'Upsell', 'downsell' => 'Downsell', 'thankyou' => 'Thankyou' ]; $option_group = []; foreach ($steps_array as $key => $value) { $args = [ 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC', 'post_type' => WPFNL_STEPS_POST_TYPE, 'post_status' => 'publish', 'post__not_in' => [$this->get_id()], 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_step_type', 'value' => $key, 'compare' => '=', ], [ 'key' => '_funnel_id', 'value' => $associate_funnel_id, 'compare' => '=', ], ], ]; $query = new \WP_Query($args); $steps = $query->posts; if ($steps) { foreach ($steps as $s) { $option_group[$key][] = [ 'id' => $s->ID, 'title' => $s->post_title, ]; } } } return $option_group; } /** * Get all WC products * @since 1.0.0 * * @access protected */ protected function get_products_array() { $products = array(); if (in_array('woocommerce/woocommerce.php', WPFNL_ACTIVE_PLUGINS)) { $ids = wc_get_products(array('return' => 'ids', 'limit' => -1)); if( !empty($ids) ){ foreach ($ids as $id) { $title = get_the_title($id); $products[$id] = $title; } } } return $products; } /** * Get all funnel steps * @since 1.0.0 * * @access protected */ protected function get_steps_array($type = 'upsell') { $options = $this->get_prev_next_link_options(); $response = array(); if (isset($options[$type])) { $prime_data = $options[$type]; foreach ($prime_data as $data) { $response[$data['id']] = $data['title']; } } return $response; } /** * Render the widget output on the frontend. * * Written in PHP and used to generate the final HTML. * * @since 1.0.0 * * @access protected */ protected function render() { $settings = $this->get_settings(); $this->add_render_attribute('wrapper', 'class', 'elementor-button-wrapper'); $this->add_render_attribute('button', 'class', 'elementor-button'); $this->add_render_attribute( 'button', array( 'id' => ['wpfunnels_'.$settings['offer_button_type'].'_'.$settings['offer_type']], 'class' => [ 'wpfunnels-elementor-widget', 'wpfunnels_offer_button' ], ) ); if (!empty($settings['size'])) { $this->add_render_attribute('button', 'class', 'elementor-size-' . $settings['size']); } if ( isset($settings['hover_animation']) && $settings['hover_animation']) { $this->add_render_attribute('button', 'class', 'elementor-animation-' . $settings['hover_animation']); } $response = Wpfnl_Pro_functions::get_product_data_for_widget( get_the_ID() ); $offer_product = isset($response['offer_product']) && $response['offer_product'] ? $response['offer_product'] : ''; $get_product_type = isset($response['get_product_type']) && $response['get_product_type'] ? $response['get_product_type'] : ''; $is_gbf = isset($response['is_gbf']) && $response['is_gbf'] ? $response['is_gbf'] : ''; $builder = 'elementor'; if( 'yes' == $is_gbf && $settings['show_product_data'] == 'yes' && $settings['offer_type'] == 'accept' ){ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/styles/offer-' . $settings['dynamic_data_template_layout'] . '.php'; }else{ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/elementor/offer-button.php'; } } /** * Render button text. * * Render button widget text. * * @since 1.5.0 * @access protected */ protected function render_text() { $settings = $this->get_settings(); $migrated = isset($settings['__fa4_migrated']['upsell_downsell_button_icon']); $is_new = empty($settings['icon']) && Icons_Manager::is_migration_allowed(); if (!$is_new && empty($settings['upsell_downsell_button_icon_align'])) { $settings['upsell_downsell_button_icon_align'] = $this->get_settings('upsell_downsell_button_icon_align'); } $this->add_render_attribute([ 'content-wrapper' => [ 'class' => 'elementor-button-content-wrapper', ], 'icon-align' => [ 'class' => [ 'elementor-button-icon', 'elementor-align-icon-' . $settings['upsell_downsell_button_icon_align'], ], ], 'text' => [ 'class' => 'elementor-button-text', ], ]); $this->add_render_attribute('content-wrapper', 'class', 'elementor-button-content-wrapper'); $this->add_render_attribute('icon-align', 'class', 'elementor-button-icon'); $this->add_render_attribute('text', 'class', 'elementor-button-text'); $this->add_inline_editing_attributes('text', 'none'); ?> get_render_attribute_string('content-wrapper'); ?>> get_render_attribute_string('icon-align'); ?>> 'true']); else : ?> get_render_attribute_string('text'); ?>> =') ) { $this->register_controls(); } else { $this->_register_controls(); } } /** * @inheritDoc */ public function get_name() { // TODO: Implement get_name() method. return 'wpfnl-offer-product-widget'; } /** * widget title * * @return string|void */ public function get_title() { return __('Offer Product Meta', 'wpfnl-pro'); } /** * widget icon * * @return string */ public function get_icon() { return 'icon-wpfnl sell-accept'; } /** * get widget categories * * @return array */ public function get_categories() { return ['wp-funnel']; } /** * register product Widget controls */ protected function _register_controls(){ $this->register_product_layout_meta(); $this->register_product_layout_title(); $this->register_product_layout_price(); $this->register_product_layout_description(); $this->register_product_layout_image(); } /** * register product Widget controls */ protected function register_controls(){ $this->register_product_layout_meta(); $this->register_product_layout_title(); $this->register_product_layout_price(); $this->register_product_layout_description(); $this->register_product_layout_image(); } /** * Register Product Meta Controls. * @access protected */ protected function register_product_layout_meta(){ $this->start_controls_section( 'wpfnl_offer_product_meta', array( 'label' => __( 'Offer Product Meta', 'wpfnl-pro' ), 'tab' => Controls_Manager::TAB_STYLE, ) ); $this->add_control( 'show_title', [ 'label' => __( 'Show title', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => __( 'Show', 'wpfnl-pro' ), 'label_off' => __( 'Hide', 'wpfnl-pro' ), 'return_value' => 'yes', 'default' => 'yes', ] ); $this->add_control( 'show_price', [ 'label' => __( 'Show Price', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => __( 'Show', 'wpfnl-pro' ), 'label_off' => __( 'Hide', 'wpfnl-pro' ), 'return_value' => 'yes', 'default' => 'yes', ] ); $this->add_control( 'show_description', [ 'label' => __( 'Show Description', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => __( 'Show', 'wpfnl-pro' ), 'label_off' => __( 'Hide', 'wpfnl-pro' ), 'return_value' => 'yes', 'default' => 'yes', ] ); $this->add_control( 'show_image', [ 'label' => __( 'Show Image', 'wpfnl-pro' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => __( 'Show', 'wpfnl-pro' ), 'label_off' => __( 'Hide', 'wpfnl-pro' ), 'return_value' => 'yes', 'default' => 'yes', ] ); $this->end_controls_section(); } /** * Register Product Layout Title * @access protected */ protected function register_product_layout_title(){ $this->start_controls_section( 'wpfnl_offer_product_title', array( 'label' => __( 'Offer Product Title', 'wpfnl-pro' ), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => array( 'show_title' => 'yes', ), ) ); $this->add_responsive_control( 'title_align', array( 'label' => __( 'Alignment', 'wpfnl-pro' ), 'type' => Controls_Manager::CHOOSE, 'options' => array( 'left' => array( 'title' => __( 'Left', 'wpfnl-pro' ), 'icon' => 'fa fa-align-left', ), 'center' => array( 'title' => __( 'Center', 'wpfnl-pro' ), 'icon' => 'fa fa-align-center', ), 'right' => array( 'title' => __( 'Right', 'wpfnl-pro' ), 'icon' => 'fa fa-align-right', ) ), 'default' => 'left', 'prefix_class' => 'elementor%s-align-', ) ); $this->add_control( 'title_text_color', array( 'label' => __( 'Text Color', 'wpfnl-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => array( '{{WRAPPER}} .wpfnl-pro-elementor-offer-product-title' => 'color: {{VALUE}};', ), ) ); $this->end_controls_section(); } /** * Register Product Layout Price * @access protected */ protected function register_product_layout_price(){ $this->start_controls_section( 'wpfnl_offer_product_price', array( 'label' => __( 'Offer Product Price', 'wpfnl-pro' ), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => array( 'show_price' => 'yes', ), ) ); $this->add_responsive_control( 'price_align', array( 'label' => __( 'Alignment', 'wpfnl-pro' ), 'type' => Controls_Manager::CHOOSE, 'options' => array( 'left' => array( 'title' => __( 'Left', 'wpfnl-pro' ), 'icon' => 'fa fa-align-left', ), 'center' => array( 'title' => __( 'Center', 'wpfnl-pro' ), 'icon' => 'fa fa-align-center', ), 'right' => array( 'title' => __( 'Right', 'wpfnl-pro' ), 'icon' => 'fa fa-align-right', ) ), 'default' => 'left', 'prefix_class' => 'elementor%s-align-', ) ); $this->add_control( 'price_text_color', array( 'label' => __( 'Text Color', 'wpfnl-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => array( '{{WRAPPER}} .wpfnl-pro-elementor-offer-product-price' => 'color: {{VALUE}};', ), ) ); $this->end_controls_section(); } /** * Register Product Layout Description * @access protected */ protected function register_product_layout_description(){ $this->start_controls_section( 'wpfnl_offer_product_description', array( 'label' => __( 'Offer Product Description', 'wpfnl-pro' ), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => array( 'show_description' => 'yes', ), ) ); $this->add_responsive_control( 'description_align', array( 'label' => __( 'Alignment', 'wpfnl-pro' ), 'type' => Controls_Manager::CHOOSE, 'options' => array( 'left' => array( 'title' => __( 'Left', 'wpfnl-pro' ), 'icon' => 'fa fa-align-left', ), 'center' => array( 'title' => __( 'Center', 'wpfnl-pro' ), 'icon' => 'fa fa-align-center', ), 'right' => array( 'title' => __( 'Right', 'wpfnl-pro' ), 'icon' => 'fa fa-align-right', ) ), 'default' => 'left', 'prefix_class' => 'elementor%s-align-', ) ); $this->add_control( 'description_text_color', array( 'label' => __( 'Text Color', 'wpfnl-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => array( '{{WRAPPER}} .wpfnl-pro-elementor-offer-product-description' => 'color: {{VALUE}};', ), ) ); $this->end_controls_section(); } /** * Register Product Layout Image * @access protected */ protected function register_product_layout_image(){ $this->start_controls_section( 'wpfnl_offer_product_image', array( 'label' => __( 'Offer Product Image', 'wpfnl-pro' ), 'tab' => Controls_Manager::TAB_STYLE, 'condition' => array( 'show_image' => 'yes', ), ) ); $this->add_responsive_control( 'align', array( 'label' => __( 'Alignment', 'wpfnl-pro' ), 'type' => Controls_Manager::CHOOSE, 'options' => array( 'left' => array( 'title' => __( 'Left', 'wpfnl-pro' ), 'icon' => 'fa fa-align-left', ), 'center' => array( 'title' => __( 'Center', 'wpfnl-pro' ), 'icon' => 'fa fa-align-center', ), 'right' => array( 'title' => __( 'Right', 'wpfnl-pro' ), 'icon' => 'fa fa-align-right', ) ), 'default' => 'left', 'prefix_class' => 'elementor%s-align-', ) ); $this->add_control( 'text_color', array( 'label' => __( 'Text Color', 'wpfnl-pro' ), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => array( '{{WRAPPER}} .wpfnl-pro-elementor-offer-product-image' => 'color: {{VALUE}};', ), ) ); $this->end_controls_section(); } /** * Render the Product widget output on the frontend. * @access protected */ protected function render() { $settings = $this->get_settings_for_display(); $offer_product = Wpfnl_Offer_Product::getInstance()->get_offer_product(); if( !is_object($offer_product) || null === $offer_product) { return; } $image = wp_get_attachment_image_src(get_post_thumbnail_id($offer_product->get_id()), 'single-post-thumbnail'); if ( 'yes' === $settings['show_title'] ) { echo '
'.$offer_product->get_title().'
'; } if ( 'yes' === $settings['show_price'] ) { echo '
'.Wpfnl_Offer_Product::getInstance()->get_offer_product_price().'
'; } if ( 'yes' === $settings['show_description'] ) { echo '
'.$offer_product->get_description().'
'; } if ( 'yes' === $settings['show_image'] ) { echo '
'; } } }includes/core/widgets/elementor/elements/upsell-downsell.php000064400000061604147600245720020436 0ustar00=') ) { $this->register_controls(); } else { $this->_register_controls(); } } /** * Retrieve the widget name. * * @since 1.0.0 * * @access public * * @return string Widget name. */ public function get_name() { return 'wpfnl-upsell-downsell'; } /** * Retrieve the widget title. * * @since 1.0.0 * * @access public * * @return string Widget title. */ public function get_title() { return __('Upsell/Downsell', 'wpfnl'); } /** * Retrieve the widget icon. * * @since 1.0.0 * * @access public * * @return string Widget icon. */ public function get_icon() { return 'icon-wpfnl sell-accept'; } /** * Retrieve the list of categories the widget belongs to. * * Used to determine where to display the widget in the editor. * * Note that currently Elementor supports only one category. * When multiple categories passed, Elementor uses the first one. * * @since 1.0.0 * * @access public * * @return array Widget categories. */ public function get_categories() { return [ 'wp-funnel' ]; } /** * Retrieve the list of scripts the widget depended on. * * Used to set scripts dependencies required to run the widget. * * @since 1.0.0 * * @access public * * @return array Widget scripts dependencies. */ public function get_script_depends() { return [ 'upsell-downsell-widget' ]; } /** * Get button sizes. * * Retrieve an array of button sizes for the button widget. * * @since 1.0.0 * @access public * @static * * @return array An array containing button sizes. */ public static function get_button_sizes() { return [ 'xs' => __('Extra Small', 'elementor'), 'sm' => __('Small', 'elementor'), 'md' => __('Medium', 'elementor'), 'lg' => __('Large', 'elementor'), 'xl' => __('Extra Large', 'elementor'), ]; } /** * Register the widget controls. * @since 1.0.0 * * @access protected */ protected function _register_controls() { $this->wpfnl_upsell_downsell_controls(); } /** * Register the widget controls. * @since 1.0.0 * * @access protected */ protected function register_controls() { $this->wpfnl_upsell_downsell_controls(); } /** * Upsell Downsell controls. * @since 1.0.0 * * @access protected */ protected function wpfnl_upsell_downsell_controls(){ $this->start_controls_section( 'section_button', [ 'label' => __('Upsell/Downsell', 'wpfnl'), ] ); $this->add_control( 'upsell_downsell_selector', [ 'label' => __('Select Upsell/Downsell', 'wpfnl'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => '', 'options' => [ 'upsell' => __('Upsell', 'wpfnl'), 'downsell' => __('Downsell', 'wpfnl'), ], ] ); $this->add_control( 'upsell_accept_reject_selector', [ 'label' => __('Select Accept/Reject', 'wpfnl'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => '', 'options' => [ 'accept' => __('Accept', 'wpfnl'), 'reject' => __('Reject', 'wpfnl'), ], 'condition' => [ 'upsell_downsell_selector' => 'upsell', ] ] ); $this->add_control( 'downsell_accept_reject_selector', [ 'label' => __('Select Accept/Reject', 'wpfnl'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => '', 'options' => [ 'accept' => __('Accept', 'wpfnl'), 'reject' => __('Reject', 'wpfnl'), ], 'condition' => [ 'upsell_downsell_selector' => 'downsell', ] ] ); $upsell_product = get_post_meta(get_the_ID(), '_wpfnl_upsell_products', true); // $this->add_control( // 'upsell_product_selector', // [ // 'label' => __( ' Upsell Product', 'wpfnl' ), // 'type' => \WPFunnels\Widgets\Elementor\Controls\Product_Control::ProductSelector, // 'options' => $this->get_products_array(), // 'default' => $upsell_product['id'], // 'condition' => [ // 'upsell_downsell_selector' => 'upsell', // 'upsell_accept_reject_selector' => 'accept', // ] // ] // ); // // $downsell_product = get_post_meta(get_the_ID(), '_wpfnl_downsell_product', true); // // $this->add_control( // 'downsell_product_selector', // [ // 'label' => __( ' Downsell Product', 'wpfnl' ), // 'type' => \WPFunnels\Widgets\Elementor\Controls\Product_Control::ProductSelector, // 'options' => $this->get_products_array(), // 'default' => $downsell_product['id'], // 'condition' => [ // 'upsell_downsell_selector' => 'downsell', // 'downsell_accept_reject_selector' => 'accept', // ] // ] // ); // $this->add_control( // 'upsell_accept_next_step_selector', // [ // 'label' => __( 'Select Next Step if accept', 'wpfnl' ), // 'type' => \Elementor\Controls_Manager::SELECT, // 'groups' => array( // array( // 'label' => __('Downsell', 'wpfnl'), // 'options' => self::get_steps_array('downsell') // ), // array( // 'label' => __('Thank You', 'wpfnl'), // 'options' => self::get_steps_array('thankyou') // ), // ), // 'condition' => [ // 'upsell_downsell_selector' => 'upsell', // 'upsell_accept_reject_selector' => 'accept', // ] // ] // ); // $this->add_control( // 'upsell_reject_next_step_selector', // [ // 'label' => __( 'Select Next Step if reject', 'wpfnl' ), // 'type' => \Elementor\Controls_Manager::SELECT, // 'groups' => array( // array( // 'label' => __('Downsell', 'wpfnl'), // 'options' => self::get_steps_array('downsell') // ), // array( // 'label' => __('Thank You', 'wpfnl'), // 'options' => self::get_steps_array('thankyou') // ), // ), // 'condition' => [ // 'upsell_downsell_selector' => 'upsell', // 'upsell_accept_reject_selector' => 'reject', // ] // ] // ); // $this->add_control( // 'downsell_accept_next_step_selector', // [ // 'label' => __( 'Select Next Step', 'wpfnl' ), // 'type' => \Elementor\Controls_Manager::SELECT, // 'groups' => array( // array( // 'label' => __('Upsell', 'wpfnl'), // 'options' => self::get_steps_array('upsell') // ), // array( // 'label' => __('Thank You', 'wpfnl'), // 'options' => self::get_steps_array('thankyou') // ), // ), // 'condition' => [ // 'upsell_downsell_selector' => 'downsell', // 'downsell_accept_reject_selector' => 'accept', // ] // ] // ); // // $this->add_control( // 'downsell_reject_next_step_selector', // [ // 'label' => __( 'Select Next Step', 'wpfnl' ), // 'type' => \Elementor\Controls_Manager::SELECT, // 'groups' => array( // array( // 'label' => __('Upsell', 'wpfnl'), // 'options' => self::get_steps_array('upsell') // ), // array( // 'label' => __('Thank You', 'wpfnl'), // 'options' => self::get_steps_array('thankyou') // ), // ), // 'condition' => [ // 'upsell_downsell_selector' => 'downsell', // 'downsell_accept_reject_selector' => 'reject', // ] // ] // ); $this->add_control( 'text', [ 'label' => __('Text', 'elementor'), 'type' => Controls_Manager::TEXT, 'default' => __('Buy offer', 'elementor'), 'placeholder' => __('Buy offer', 'elementor'), 'separator' => 'before', ] ); $this->add_responsive_control( 'align', [ 'label' => __('Alignment', 'elementor'), 'type' => Controls_Manager::CHOOSE, 'options' => [ 'left' => [ 'title' => __('Left', 'elementor'), 'icon' => 'fa fa-align-left', ], 'center' => [ 'title' => __('Center', 'elementor'), 'icon' => 'fa fa-align-center', ], 'right' => [ 'title' => __('Right', 'elementor'), 'icon' => 'fa fa-align-right', ], 'justify' => [ 'title' => __('Justified', 'elementor'), 'icon' => 'fa fa-align-justify', ], ], 'prefix_class' => 'elementor%s-align-', 'default' => '', ] ); $this->add_responsive_control( 'size', [ 'label' => __('Size', 'elementor'), 'type' => Controls_Manager::SELECT, 'default' => 'sm', 'options' => self::get_button_sizes(), ] ); $this->add_control( 'upsell_downsell_button_icon', [ 'label' => __( 'Icon', 'wpfnl' ), 'type' => Controls_Manager::ICONS, 'fa4compatibility' => 'icon', ] ); $this->add_control( 'upsell_downsell_button_icon_align', [ 'label' => __( 'Icon Position', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => 'left', 'options' => [ 'left' => __( 'Before', 'elementor' ), 'right' => __( 'After', 'elementor' ), ], 'condition' => [ 'upsell_downsell_button_icon[value]!' => '', ], ] ); $this->add_control( 'upsell_downsell_button_icon_indent', [ 'label' => __( 'Icon Spacing', 'wpfnl' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'max' => 50, ], ], 'condition' => [ 'upsell_downsell_button_icon!' => '', ], 'selectors' => [ '{{WRAPPER}} .elementor-button .elementor-align-icon-right' => 'margin-left: {{SIZE}}{{UNIT}};', '{{WRAPPER}} .elementor-button .elementor-align-icon-left' => 'margin-right: {{SIZE}}{{UNIT}};', ], ] ); $this->add_control( 'view', [ 'label' => __('View', 'elementor'), 'type' => Controls_Manager::HIDDEN, 'default' => 'traditional', ] ); $this->end_controls_section(); $this->start_controls_section( 'section_style', [ 'label' => __('Button', 'elementor'), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'upsell_downsell_button_typography', 'label' => 'Typography', 'selector' => '{{WRAPPER}} a.elementor-button', ] ); $this->add_group_control( Group_Control_Text_Shadow::get_type(), [ 'name' => 'upsell_downsell_button_text_shadow', 'selector' => '{{WRAPPER}} a.elementor-button', ] ); $this->start_controls_tabs('tabs_button_style'); $this->start_controls_tab( 'tab_button_normal', [ 'label' => __('Normal', 'elementor'), ] ); $this->add_control( 'button_text_color', [ 'label' => __('Text Color', 'elementor'), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} a.elementor-button, {{WRAPPER}} .elementor-button' => 'color: {{VALUE}};', ], ] ); $this->add_control( 'background_color', [ 'label' => __('Background Color', 'elementor'), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} a.elementor-button, {{WRAPPER}} .elementor-button' => 'background-color: {{VALUE}};', ], 'default' => '#61CE70', ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_button_hover', [ 'label' => __('Hover', 'elementor'), ] ); $this->add_control( 'hover_color', [ 'label' => __('Text Color', 'elementor'), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} a.elementor-button:hover, {{WRAPPER}} .elementor-button:hover' => 'color: {{VALUE}};', ], ] ); $this->add_control( 'button_background_hover_color', [ 'label' => __('Background Color', 'elementor'), 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} a.elementor-button:hover, {{WRAPPER}} .elementor-button:hover' => 'background-color: {{VALUE}};', ], ] ); $this->add_control( 'button_hover_border_color', [ 'label' => __('Border Color', 'elementor'), 'type' => Controls_Manager::COLOR, 'condition' => [ 'border_border!' => '', ], 'selectors' => [ '{{WRAPPER}} a.elementor-button:hover, {{WRAPPER}} .elementor-button:hover' => 'border-color: {{VALUE}};', ], ] ); $this->add_control( 'hover_animation', [ 'label' => __('Animation', 'elementor'), 'type' => Controls_Manager::HOVER_ANIMATION, ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_group_control( Group_Control_Border::get_type(), [ 'name' => 'border', 'label' => __('Border', 'elementor'), 'placeholder' => '1px', 'default' => '1px', 'selector' => '{{WRAPPER}} .elementor-button', 'separator' => 'before', ] ); $this->add_control( 'border_radius', [ 'label' => __('Border Radius', 'elementor'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%' ], 'selectors' => [ '{{WRAPPER}} a.elementor-button, {{WRAPPER}} .elementor-button' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), [ 'name' => 'button_box_shadow', 'selector' => '{{WRAPPER}} .elementor-button', ] ); $this->add_responsive_control( 'upsell_downsell_padding', [ 'label' => __('Padding', 'elementor'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', 'em', '%' ], 'selectors' => [ '{{WRAPPER}} a.elementor-button, {{WRAPPER}} .elementor-button' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], 'separator' => 'before', ] ); $this->end_controls_section(); } public function get_prev_next_link_options() { $associate_funnel_id = get_post_meta(get_the_ID(), '_funnel_id', true); $steps_array = [ 'upsell' => 'Upsell', 'downsell' => 'Downsell', 'thankyou' => 'Thankyou' ]; $option_group = []; foreach ($steps_array as $key=>$value) { $args = [ 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC', 'post_type' => WPFNL_STEPS_POST_TYPE, 'post_status' => 'publish', 'post__not_in' => [ $this->get_id() ], 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_step_type', 'value' => $key, 'compare' => '=', ], [ 'key' => '_funnel_id', 'value' => $associate_funnel_id, 'compare' => '=', ], ], ]; $query = new \WP_Query($args); $steps = $query->posts; if ($steps) { foreach ($steps as $s) { $option_group[$key][] = [ 'id' => $s->ID, 'title' => $s->post_title, ]; } } } return $option_group; } /** * Get all WC products * @since 1.0.0 * * @access protected */ protected function get_products_array() { $products = array(); if ( in_array( 'woocommerce/woocommerce.php', WPFNL_ACTIVE_PLUGINS ) ) { $ids = wc_get_products( array( 'return' => 'ids', 'limit' => -1 ) ); if( !empty($ids) ){ foreach ($ids as $id) { $title = get_the_title( $id ); $products[$id] = $title; } } } return $products; } /** * Get all funnel steps * @since 1.0.0 * * @access protected */ protected function get_steps_array($type = 'upsell') { $options = $this->get_prev_next_link_options(); $response = array(); if(isset($options[$type])) { $prime_data = $options[$type]; foreach ($prime_data as $data) { $response[$data['id']] = $data['title']; } } return $response; } /** * Render the widget output on the frontend. * * Written in PHP and used to generate the final HTML. * * @since 1.0.0 * * @access protected */ protected function render() { $settings = $this->get_settings(); $this->add_render_attribute('wrapper', 'class', 'elementor-button-wrapper'); $this->add_render_attribute('button', 'class', 'elementor-button'); if (! empty($settings['size'])) { $this->add_render_attribute('button', 'class', 'elementor-size-' . $settings['size']); } if ($settings['hover_animation']) { $this->add_render_attribute('button', 'class', 'elementor-animation-' . $settings['hover_animation']); } if (isset($settings['upsell_accept_reject_selector']) || isset($settings['downsell_accept_reject_selector'])) { if($settings['upsell_accept_reject_selector'] == 'accept' || $settings['downsell_accept_reject_selector'] == 'accept') { ?>
get_render_attribute_string('wrapper'); ?>> get_render_attribute_string('button'); ?>> render_text(); ?>
get_render_attribute_string('wrapper'); ?>> get_render_attribute_string('button'); ?>> render_text(); ?>
get_settings(); $migrated = isset( $settings['__fa4_migrated']['upsell_downsell_button_icon'] ); $is_new = empty( $settings['icon'] ) && Icons_Manager::is_migration_allowed(); if ( ! $is_new && empty( $settings['upsell_downsell_button_icon_align'] ) ) { $settings['upsell_downsell_button_icon_align'] = $this->get_settings( 'upsell_downsell_button_icon_align' ); } $this->add_render_attribute( [ 'content-wrapper' => [ 'class' => 'elementor-button-content-wrapper', ], 'icon-align' => [ 'class' => [ 'elementor-button-icon', 'elementor-align-icon-' . $settings['upsell_downsell_button_icon_align'], ], ], 'text' => [ 'class' => 'elementor-button-text', ], ] ); $this->add_render_attribute( 'content-wrapper', 'class', 'elementor-button-content-wrapper' ); $this->add_render_attribute( 'icon-align', 'class', 'elementor-button-icon' ); $this->add_render_attribute( 'text', 'class', 'elementor-button-text' ); $this->add_inline_editing_attributes( 'text', 'none' ); ?> get_render_attribute_string( 'content-wrapper' ); ?>> get_render_attribute_string( 'icon-align' ); ?>> 'true' ] ); else : ?> get_render_attribute_string( 'text' ); ?>> is_compatible() ) { if ( version_compare(ELEMENTOR_VERSION, '3.5.0', '>=') ) { add_action('elementor/widgets/register', [$this, 'init_widgets']); } else { add_action('elementor/widgets/widgets_registered', [$this, 'init_widgets']); } add_action('elementor/editor/before_enqueue_scripts', [$this, 'enqueue_elementor_custom_style']); } } /** * On Plugins Loaded * * Checks if Elementor has loaded, and performs some compatibility checks. * If All checks pass, inits the plugin. * * Fired by `plugins_loaded` action hook. * * @since 1.0.0 * * @access public */ public function on_plugins_loaded() { if ($this->is_compatible()) { $this->init(); } } /** * Add css file on Elementor admin * * @since 1.0.0 * * @access public */ public function enqueue_elementor_custom_style() { wp_enqueue_style('elementor-icon', WPFNL_URL . 'includes/core/widgets/elementor/assets/css/elemetor-icon-style.css'); } /** * Compatibility Checks * * Checks if the installed version of Elementor meets the plugin's minimum requirement. * Checks if the installed PHP version meets the plugin's minimum requirement. * * @since 1.0.0 * * @access public */ public function is_compatible() { // Check if Elementor installed and activated if (!did_action('elementor/loaded')) { // add_action('admin_notices', [$this, 'admin_notice_missing_main_plugin']); return false; } // Check for required Elementor version if (!version_compare(ELEMENTOR_VERSION, self::MINIMUM_ELEMENTOR_VERSION, '>=')) { // add_action('admin_notices', [$this, 'admin_notice_minimum_elementor_version']); return false; } // Check for required PHP version if (version_compare(PHP_VERSION, self::MINIMUM_PHP_VERSION, '<')) { // add_action('admin_notices', [$this, 'admin_notice_minimum_php_version']); return false; } return true; } /** * Initialize the plugin * * Load the plugin only after Elementor (and other plugins) are loaded. * Load the files required to run the plugin. * * Fired by `plugins_loaded` action hook. * * @since 1.0.0 * * @access public */ public function init() { if ( version_compare(ELEMENTOR_VERSION, '3.5.0', '>=') ) { add_action('elementor/widgets/register', [$this, 'init_widgets']); } else { add_action('elementor/widgets/widgets_registered', [$this, 'init_widgets']); } } /** * Register Category * * @since 1.0.0 * * @access private */ public function add_elementor_widget_categories($elements_manager) { $elements_manager->add_category( 'wp-funnel', [ 'title' => __('WP FUnnels', 'wpfnl'), 'icon' => 'fa fa-plug', ] ); } /** * Init Widgets * * Include widgets files and register them * * @since 1.0.0 * * @access public */ public function init_widgets() { if( wp_doing_ajax() ) { if( isset($_POST['step_id']) ) { $step_id = $_POST['step_id']; } elseif ( isset($_POST['editor_post_id'] ) ) { $step_id = $_POST['editor_post_id']; } else { $step_id = ''; } if( !$step_id && isset($_POST['initial_document_id'] ) ) { $step_id = $_POST['initial_document_id']; } }elseif(wp_get_theme()->get('Name') == 'Woodmart'){ $step_id = isset($_GET['post']) ? $_GET['post'] : get_the_ID(); } else { $step_id = get_the_ID(); } $step_type = get_post_meta($step_id, '_step_type', true); $funnel_id = get_post_meta($step_id,'_funnel_id',true); if (Wpfnl_functions::is_plugin_activated('woocommerce/woocommerce.php')) { if($step_type){ if ($step_type == 'upsell' || $step_type == 'downsell') { if ( version_compare(ELEMENTOR_VERSION, '3.5.0', '>=') ) { Plugin::instance()->widgets_manager->register(new Offer_Button() ); } else { Plugin::instance()->widgets_manager->register_widget_type(new Offer_Button() ); } } }elseif(wp_get_theme()->get('Name') == 'Woodmart'){ if ( version_compare(ELEMENTOR_VERSION, '3.5.0', '>=') ) { Plugin::instance()->widgets_manager->register(new Offer_Button() ); } else { Plugin::instance()->widgets_manager->register_widget_type(new Offer_Button() ); } }else{ if( !$step_id ){ $step_id = isset($_GET['post']) ? $_GET['post'] : get_the_ID(); } $step_type = get_post_meta($step_id, '_step_type', true); $funnel_id = get_post_meta($step_id,'_funnel_id',true); if ($step_type == 'upsell' || $step_type == 'downsell') { if ( version_compare(ELEMENTOR_VERSION, '3.5.0', '>=') ) { Plugin::instance()->widgets_manager->register(new Offer_Button() ); } else { Plugin::instance()->widgets_manager->register_widget_type(new Offer_Button() ); } } } } } /** * Init Controls * * Include controls files and register them * * @since 1.0.0 * * @access public */ public function init_controls() { Plugin::$instance->controls_manager->register_control(\WPFunnels\Widgets\Elementor\Controls\Product_Control::ProductSelector, new Product_Control()); } /** * Admin notice * * Warning when the site doesn't have Elementor installed or activated. * * @since 1.0.0 * * @access public */ public function admin_notice_missing_main_plugin() { if (isset($_GET['activate'])) unset($_GET['activate']); $message = sprintf( /* translators: 1: Plugin name 2: Elementor */ esc_html__('"%1$s" requires "%2$s" to be installed and activated.', 'elementor-test-extension'), '' . esc_html__('Elementor Test Extension', 'elementor-test-extension') . '', '' . esc_html__('Elementor', 'elementor-test-extension') . '' ); $settings = get_option('_wpfunnels_general_settings'); if (isset($settings['builder']) && $settings['builder'] == 'elementor') { printf('

%1$s

', $message); } } /** * Admin notice * * Warning when the site doesn't have a minimum required Elementor version. * * @since 1.0.0 * * @access public */ public function admin_notice_minimum_elementor_version() { if (isset($_GET['activate'])) unset($_GET['activate']); $message = sprintf( /* translators: 1: Plugin name 2: Elementor 3: Required Elementor version */ esc_html__('"%1$s" requires "%2$s" version %3$s or greater.', 'elementor-test-extension'), '' . esc_html__('Elementor Test Extension', 'elementor-test-extension') . '', '' . esc_html__('Elementor', 'elementor-test-extension') . '', self::MINIMUM_ELEMENTOR_VERSION ); printf('

%1$s

', $message); } /** * Admin notice * * Warning when the site doesn't have a minimum required PHP version. * * @since 1.0.0 * * @access public */ public function admin_notice_minimum_php_version() { if (isset($_GET['activate'])) unset($_GET['activate']); $message = sprintf( /* translators: 1: Plugin name 2: PHP 3: Required PHP version */ esc_html__('"%1$s" requires "%2$s" version %3$s or greater.', 'elementor-test-extension'), '' . esc_html__('Elementor Test Extension', 'elementor-test-extension') . '', '' . esc_html__('PHP', 'elementor-test-extension') . '', self::MINIMUM_PHP_VERSION ); printf('

%1$s

', $message); } /** * Widget Registration manager * * @since 1.0.0 * * @access private */ public function widget_registration_manager($page_id) { return get_post_meta($page_id, '_step_type', true); } public function elementor_save_camps($post_id, $editor_data) { $products = array(); foreach ($editor_data as $data) { foreach ($data['elements'] as $data) { foreach ($data['elements'] as $data) { if ($data['widgetType'] == 'wpfnl-sell-accept') { $product = $data['settings']['product']; } elseif ($data['widgetType'] == 'wpfnl-order-detail') { if (isset($data['settings']['enable_order_review'])) { update_post_meta($post_id, '_wpfnl_thankyou_order_overview', 'off'); } else { update_post_meta($post_id, '_wpfnl_thankyou_order_overview', 'on'); } if (isset($data['settings']['enable_order_details'])) { update_post_meta($post_id, '_wpfnl_thankyou_order_details', 'off'); } else { update_post_meta($post_id, '_wpfnl_thankyou_order_details', 'on'); } if (isset($data['settings']['enable_billing_details'])) { update_post_meta($post_id, '_wpfnl_thankyou_billing_details', 'off'); } else { update_post_meta($post_id, '_wpfnl_thankyou_billing_details', 'on'); } if (isset($data['settings']['enable_shipping_details'])) { update_post_meta($post_id, '_wpfnl_thankyou_shipping_details', 'off'); } else { update_post_meta($post_id, '_wpfnl_thankyou_shipping_details', 'on'); } } elseif ($data['widgetType'] == 'wpfnl-upsell-downsell') { if (isset($data['settings']['upsell_downsell_selector']) && $data['settings']['upsell_downsell_selector'] == 'upsell') { $organizer = $this->reorganize_funnel_order($post_id, $data['settings']['upsell_downsell_selector']); update_post_meta($post_id, '_step_type', $data['settings']['upsell_downsell_selector']); if (isset($data['settings']['upsell_accept_reject_selector']) && $data['settings']['upsell_accept_reject_selector'] == 'accept') { if (isset($data['settings']['upsell_product_selector'])) { $product = $data['settings']['upsell_product_selector']; } if (isset($data['settings']['upsell_accept_next_step_selector'])) { update_post_meta($post_id, '_wpfnl_upsell_next_step_yes', $data['settings']['upsell_accept_next_step_selector']); } } else { if (isset($data['settings']['upsell_reject_next_step_selector'])) { update_post_meta($post_id, '_wpfnl_upsell_next_step_no', $data['settings']['upsell_reject_next_step_selector']); } } } elseif (isset($data['settings']['upsell_downsell_selector']) && $data['settings']['upsell_downsell_selector'] == 'downsell') { $organizer = $this->reorganize_funnel_order($post_id, $data['settings']['upsell_downsell_selector']); update_post_meta($post_id, '_step_type', $data['settings']['upsell_downsell_selector']); if (isset($data['settings']['downsell_accept_reject_selector']) && $data['settings']['downsell_accept_reject_selector'] == 'accept') { if (isset($data['settings']['downsell_product_selector'])) { $product = $data['settings']['downsell_product_selector']; } if (isset($data['settings']['downsell_accept_next_step_selector'])) { update_post_meta($post_id, '_wpfnl_downsell_next_step_yes', $data['settings']['downsell_accept_next_step_selector']); } } else { if (isset($data['settings']['downsell_reject_next_step_selector'])) { update_post_meta($post_id, '_wpfnl_downsell_next_step_no', $data['settings']['downsell_reject_next_step_selector']); } } } } } } } // if ($product) { // update_post_meta(get_the_ID(), 'elementor_product_selector', $product); // $products[] = $product = array( // 'id' => $product, // 'quantity' => '1', // ); // // $get_type = get_post_meta($post_id, '_step_type', true); // if ($get_type == 'upsell') { // // update_post_meta($post_id, '_wpfnl_upsell_product', $products); // } elseif ($get_type == 'downsell') { // // update_post_meta($post_id, '_wpfnl_downsell_product', $products); // } // } } public function reorganize_funnel_order($step_id, $step_type) { $funnel_id = get_post_meta($step_id, '_funnel_id', true); $funnel_order = get_post_meta($funnel_id, '_steps_order', true); foreach ($funnel_order as $key => $data) { if ($data['id'] == $step_id) { $funnel_order[$key]['type'] = $step_type; } } update_post_meta($funnel_id, '_steps_order', $funnel_order); } } Manager::instance(); includes/core/widgets/oxygen/elements/OfferButton/icon/offer_button-active.svg000064400000051510147600245720023761 0ustar00 buy (1) Created with Sketch. includes/core/widgets/oxygen/elements/OfferButton/icon/offer_button.svg000064400000051510147600245720022510 0ustar00 buy (1) Created with Sketch. includes/core/widgets/oxygen/elements/OfferButton/OfferButton.php000064400000032020147600245720021304 0ustar00removeApplyParamsButton(); // $this->removeAddButton(); } function name() { return 'Offer Button'; } function slug() { return "offer-button"; } function icon() { return plugin_dir_url(__FILE__) . 'icon/offer_button.svg'; } // function button_place() { // // return "interactive"; // } function button_priority() { // return 9; } function render($options, $defaults, $content) { $step_id = get_the_ID(); $step_type = get_post_meta($step_id, '_step_type', true); if ($step_type != 'upsell' && $step_type != 'downsell'){ echo __('Sorry, Please place the element in WPFunnels Offer page'); }else{ $button_type = $options['button_type']; $button_action = $options['button_action']; $variation_tbl_title = $options['variation_tbl_title']; $show_product_price = $options['show_product_price']; $button_id = 'wpfunnels_'.$button_type.'_'.$button_action; if( $this->is_builder_mode() ) { $id = ''; } else { $id = 'wpfunnels_next_step_controller'; } $response = Wpfnl_Pro_functions::get_product_data_for_widget( get_the_ID() ); $offer_product = isset($response['offer_product']) && $response['offer_product'] ? $response['offer_product'] : ''; $get_product_type = isset($response['get_product_type']) && $response['get_product_type'] ? $response['get_product_type'] : ''; $is_gbf = isset($response['is_gbf']) && $response['is_gbf'] ? $response['is_gbf'] : ''; $builder = 'oxygen'; if( 'yes' == $is_gbf && 'yes' == $options['show_product_data'] && 'accept' == $button_action && $offer_product ){ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/styles/offer-' . $options['dynamic_data_template_layout'] . '.php'; }else{ require WPFNL_PRO_DIR . 'public/modules/dynamic-offer-templates/oxygen/offer-button.php'; } } } function controls() { $offer_button_option = $this->addControlSection("offer_button_option", __("Button Options"), "assets/icon.png", $this); $offer_button_option->addOptionControl( array( "type" => "textfield", "name" => __("Button Text"), "slug" => "title_text", "default" => "Accept Offer" ) )->rebuildElementOnChange(); $offer_button_option->addOptionControl( array( "type" => 'dropdown', "name" => __("Offer Action"), "slug" => 'button_action', "default" => "accept" ) )->setValue(array( 'accept' => __('Accept Offer' ), 'reject' => __('Reject Offer' ), ))->rebuildElementOnChange(); $offer_button_option->addOptionControl( array( "type" => 'dropdown', "name" => __("Offer Type"), "slug" => 'button_type', "default" => "upsell" ) )->setValue(array( 'upsell' => __('Upsell'), 'downsell' => __('Downsell'), ))->rebuildElementOnChange(); $offer_button_option->addOptionControl( array( "type" => "textfield", "name" => __("Variation Table Title"), "slug" => "variation_tbl_title", "default" => "" ) )->rebuildElementOnChange(); $offer_button_option->addOptionControl( array( "type" => 'dropdown', "name" => __("Show Product Price"), "slug" => 'show_product_price', "default" => "no" ) )->setValue(array( 'yes' => __('Show' ), 'no' => __('Hide' ), ))->rebuildElementOnChange(); $offer_button_option->addOptionControl( array( "type" => 'dropdown', "name" => __("Product Price Alignment"), "slug" => 'product_price_alignment', "default" => "" ) )->setValue(array( '' => __('On The Left Of Button', 'wpfnl-pro'), 'price-right' => __('On The Right Of Button', 'wpfnl-pro'), 'price-top' => __('Above The Button', 'wpfnl-pro'), 'price-bottom' => __('Below The Button', 'wpfnl-pro'), ))->rebuildElementOnChange(); $offer_button_option->addOptionControl( array( "type" => 'dropdown', "name" => __("Show Product Data"), "slug" => 'show_product_data', "default" => "no" ) )->setValue(array( 'yes' => __('Show' ), 'no' => __('Hide' ), ))->rebuildElementOnChange(); $icon_selector = '.wpfnl-oxy-offer-btn'; $offer_button_style = $this->addControlSection("offer_button_style", __(" Button Style"), "assets/icon.png", $this); $offer_button_style->addPreset( "padding", "menu_item_padding", __("Button Padding"), $icon_selector )->whiteList(); $offer_button_style->addPreset( "margin", "menu_item_margin", __("Button Margin"), $icon_selector )->whiteList(); $offer_button_style->addOptionControl( array( "type" => 'dropdown', "name" => __("Button Alignment"), "slug" => 'button_alignment', "default" => "left" ) )->setValue(array( 'left' => __('Left' ), 'center' => __('Center' ), 'right' => __('Right' ), ))->rebuildElementOnChange(); $offer_button_style->addStyleControls( array( array( "name" => __('Background Color'), "selector" => $icon_selector."", "property" => 'background-color', ), array( "name" => __('Background Hover Color'), "selector" => $icon_selector.":hover", "property" => 'background-color', ), array( "name" => __('Text Hover Color'), "selector" => $icon_selector.":hover", "property" => 'color', ), ) ); $offer_button_style->borderSection( __("Button Border"), $icon_selector."", $this ); $offer_button_style->typographySection( __("Typography"), ".wpfnl-oxy-offer-btn", $this ); /*---------dynamic offer template content option------- */ $offer_template_option = $this->addControlSection("offer_template_option", __("Offer Template Option"), "assets/icon.png", $this); $offer_template_option->addOptionControl( array( "type" => 'dropdown', "name" => __("Select Template Style"), "slug" => 'dynamic_data_template_layout', "default" => "style1", ) )->setValue(array( 'style1' => __('Left Image Right Content', 'wpfnl-pro' ), 'style2' => __('Left Content Right Image', 'wpfnl-pro' ), 'style3' => __('Top Image Bottom Content', 'wpfnl-pro' ), ))->rebuildElementOnChange(); /*---------end dynamic offer template content option------- */ /*---------dynamic offer template layout style option------- */ $template_layout_style = $this->addControlSection("template_layout_style", __("Template Layout Style"), "assets/icon.png", $this); $template_layout_style->addStyleControls( array( array( "name" => __('Image Column Width', 'wpfnl-pro'), "selector" => '.dynamic-offer-template-default .template-left', "property" => 'width', ), array( "name" => __('Content Column Width', 'wpfnl-pro'), "selector" => '.dynamic-offer-template-default .template-right', "property" => 'width', ), ) ); $template_layout_style->addPreset( "padding", "template_col_gutter_width", __("Column Gutter Width",'wpfnl-pro'), '.dynamic-offer-template-default .template-right' )->whiteList(); $template_layout_style->borderSection( __('Border', 'wpfnl-pro'), ".dynamic-offer-template-default", $this ); $template_layout_style->addPreset( "padding", "template_layout_padding", __("Padding",'wpfnl-pro'), '.dynamic-offer-template-default' )->whiteList(); $template_layout_style->addPreset( "margin", "template_layout_margin", __("Margin",'wpfnl-pro'), '.dynamic-offer-template-default' )->whiteList(); /*---------end dynamic offer template layout style option------- */ /*---------dynamic offer template Image style option------- */ $template_image_style = $this->addControlSection("template_image_style", __("Template Image Style"), "assets/icon.png", $this); $template_image_style->addStyleControls( array( array( "name" => __('Image Width', 'wpfnl-pro'), "selector" => '.dynamic-offer-template-default .product-img img', "property" => 'width', ), ) ); $template_image_style->borderSection( __('Border', 'wpfnl-pro'), ".dynamic-offer-template-default .product-img img", $this ); /*---------end dynamic offer template Image style option------- */ /*---------dynamic offer template Heading style option------- */ $template_heading_style = $this->addControlSection("template_heading_style", __("Template Heading Style"), "assets/icon.png", $this); $template_heading_style->typographySection( __("Typography"), ".dynamic-offer-template-default .template-content .template-product-title", $this ); $template_heading_style->addPreset( "margin", "template_heading_margin", __("Margin",'wpfnl-pro'), '.dynamic-offer-template-default .template-content .template-product-title' )->whiteList(); /*---------end dynamic offer template Image style option------- */ /*---------dynamic offer template Description style option------- */ $template_description_style = $this->addControlSection("template_description_style", __("Template Description Style"), "assets/icon.png", $this); $template_description_style->typographySection( __("Typography"), ".dynamic-offer-template-default .template-content .template-product-description", $this ); $template_description_style->addPreset( "margin", "template_description_margin", __("Margin",'wpfnl-pro'), '.dynamic-offer-template-default .template-content .template-product-description' )->whiteList(); /*---------end dynamic offer template Description style option------- */ /*---------dynamic offer template Price style option------- */ $template_price_style = $this->addControlSection("template_price_style", __("Template Price Style"), "assets/icon.png", $this); $template_price_style->typographySection( __("Discount Price Typography"), ".dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price bdi", $this ); $template_price_style->typographySection( __("Regular Price Typography"), ".dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del bdi, .dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price del", $this ); $template_price_style->addPreset( "margin", "template_price_margin", __("Margin",'wpfnl-pro'), '.dynamic-offer-template-default #wpfnl-offerbtn-wrapper .wpfnl-offer-product-price' )->whiteList(); /*---------end dynamic offer template Price style option------- */ } function defaultCSS() { } }includes/core/widgets/oxygen/elements/abstract-class-wpfnl-oxygen-elements.php000064400000001154147600245720023771 0ustar00post->ID; // $post_type = get_post_type($step_id); // if ( 'wpfunnel_steps' == $post_type){ // if (Wpfnl_functions::check_if_this_is_step_type('landing')){ // new NextStepButton(); // new Optin(); // } // if (Wpfnl_functions::check_if_this_is_step_type('checkout')){ // new Checkout(); // } // if (Wpfnl_functions::check_if_this_is_step_type('thankyou')){ // new OrderDetails(); // } // add_action('oxygen_add_plus_sections', [$this, 'add_plus_sections']); // } // } public function init_elements() { new OfferButton(); } } includes/core/widgets/Manager.php000064400000002554147600245720013050 0ustar00get_namespace_prefix(); $builders = $this->get_builders(); global $post; foreach ( $builders as $builder ) { $class_name = str_replace( '-', ' ', $builder ); $class_name = str_replace( ' ', '', ucwords( $class_name ) ); $class_name = $modules_namespace_prefix . '\\Widgets\\' . $class_name . '\Manager'; $this->widgets[ $builder ] = $class_name::instance(); } } public function get_builders() { $builder = array( 'elementor', 'gutenberg', 'diviModules', 'oxygen' ); if (method_exists(Wpfnl_functions::class,'oxygen_builder_version_capability')){ $oxygen_capability = Wpfnl_functions::oxygen_builder_version_capability(); if ($oxygen_capability){ array_push($builder,'oxygen'); } } return $builder; } public function get_namespace_prefix() { return 'WPFunnelsPro'; } } includes/core/class-wpfnl-pro-addons.php000064400000003006147600245720014316 0ustar00check_plugin_installed( $slug ) ) { return __('Get the add-on', 'wpfnl-pro'); } if ( is_plugin_active( $slug ) ) { return __('Activated', 'wpfnl-pro'); } return __('Please enable', 'wpfnl-pro'); } /** * get wpf pro addons lists * * @return array * * @since 1.3.0 */ public function get_addons() { if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $default_addons = []; return apply_filters( 'wpfunnels/addon-lists', $default_addons ); } }includes/core/class-wpfnl-pro-dependency.php000064400000045715147600245720015201 0ustar00 * */ class Wpfnl_Pro_Dependency { /** * Dependent plugin name, plugin relative path (e.g., 'addthis/addthis_social_widget.php' ) * * @var string|array */ private $plugin = ''; /** * Action being performed on plugins page. * * @var string */ private $action = ''; /** * Minimum version of dependent plugin required. * @var string */ private $min_version; /** * Reference to the current plugin's root. * The full path and filename of the file with symlinks resolved. * * @var string */ private $root_file = __FILE__; /** * Text domain. * * @var string */ public $text_domain = 'wps'; /** * Message to be displayed. * * @var string */ public $message = ''; /** * Plugin data. * * @var array */ public $plugin_data = array(); /** * Transient name. * * @var string */ private $transient = ''; private $allowed = false; /** * Constructor * * @param string $plugin Plugin activation "slug" * @param string $root_file Plugin basename, File reference path to root including filename. * @param string|null $min_version Minimum version allowed. * @param string|null $text_domain Text domain. */ public function __construct( $plugin, $root_file, $min_version = null, $text_domain = null ) { // Setup $this->plugin = $plugin; $this->root_file = $root_file; $this->min_version = $min_version ? $min_version : $this->min_version; $this->text_domain = $text_domain ? $text_domain : $this->text_domain; $this->transient = substr( 'wpsep-' . plugin_basename( $root_file ), 0, 40 ); /* * Cannot add a notice since plugin has been deactivated * Add notice since WP always seems to assume that the plugin was updated. * Cannot use 'deactivate_' . $plugin hook as it does not fire if plugin is silently deactivated (such as during an update) */ if ( 'plugins.php' === basename( $_SERVER['PHP_SELF'] ) && ! ( defined( 'WP_CLI' ) && WP_CLI ) ) { $this->set_action_type(); // Add admin notice add_action( 'admin_notices', array( $this, 'admin_notice' ) ); // Late Deactivation so we can output the notifications add_filter( 'plugin_action_links_' . plugin_basename( $root_file ), array( $this, 'plugin_action_links_maybe_deactivate' ) ); add_filter( 'network_admin_plugin_action_links_' . plugin_basename( $root_file ), array( $this, 'plugin_action_links_maybe_deactivate' ) ); // Fix Current Plugin Action Links add_filter( 'plugin_action_links_' . plugin_basename( $root_file ), array( $this, 'plugin_action_links' ), 10, 4 ); add_filter( 'network_admin_plugin_action_links_' . plugin_basename( $root_file ), array( $this, 'plugin_action_links' ), 10, 4 ); // Add notice on Plugin Row add_action( 'after_plugin_row_' . plugin_basename( $root_file ), array( $this, 'plugin_row' ), 999 ); } else { // Maybe deactivate on update of active_plugins and active_sitewide_plugins options // deactivated_plugin action and deactivate_ . $plugin do not fire if plugin is being deactivated silently add_action( 'update_option_active_sitewide_plugins', array( $this, 'maybe_deactivate' ), 10, 2 ); add_action( 'update_option_active_plugins', array( $this, 'maybe_deactivate' ), 10, 2 ); } } /** * Conditional helper function to determine which generic action is being taken. * * @param string $action Action ('activate' or 'deactivate'). * * @return bool Whether performing an activation action or deactivation action. */ private function is_action( $action ) { if ( 'activate' === $action ) { return ( 'activate' === $this->action || 'activate-multi' === $this->action ); } if ( 'deactivate' === $action ) { return ( 'deactivate' === $this->action || 'deactivate-multi' === $this->action ); } return false; } /** * Sets the action being taken by the plugins.php page. */ private function set_action_type() { if ( isset( $_REQUEST['deactivate-multi'] ) && $_REQUEST['deactivate-multi'] ) { $this->action = 'deactivate-multi'; } elseif ( isset( $_REQUEST['activate-multi'] ) && $_REQUEST['activate-multi'] ) { $this->action = 'activate-multi'; } elseif ( isset( $_REQUEST['deactivate'] ) && $_REQUEST['deactivate'] ) { $this->action = 'deactivate'; } elseif ( isset( $_REQUEST['activate'] ) && $_REQUEST['activate'] ) { $this->action = 'activate'; } } /** * Maybe fix the action links as WordPress believes the plugin is active when it may have been deactivated. * * @param array $actions An array of plugin action links. * @param string $plugin_file Path to the plugin file. * @param array $plugin_data An array of plugin data. * @param string $context The plugin context. Defaults are 'All', 'Active', * 'Inactive', 'Recently Activated', 'Upgrade', * 'Must-Use', 'Drop-ins', 'Search'. * * @return array $actions Maybe an array of modified plugin action links. */ public function plugin_action_links_maybe_deactivate( $actions ) { if ( ! $this->is_active() ) { self::deactivate_self( $this->root_file ); } return $actions; } /** * Maybe fix the action links as WordPress believes the plugin is active when it may have been deactivated. * * @param array $actions An array of plugin action links. * @param string $plugin_file Path to the plugin file. * @param array $plugin_data An array of plugin data. * @param string $context The plugin context. Defaults are 'All', 'Active', * 'Inactive', 'Recently Activated', 'Upgrade', * 'Must-Use', 'Drop-ins', 'Search'. * * @return array $actions Maybe an array of modified plugin action links. */ public function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) { if ( ! $this->is_active() ) { self::deactivate_self( $this->root_file ); if ( isset( $actions['deactivate'] ) ) { $params = self::get_url_params( $actions['deactivate'] ); $params = wp_parse_args( $params, array( 's' => '', ) ); unset( $actions['deactivate'] ); // Change action link deactivate to activate $screen = get_current_screen(); if ( $screen->in_admin( 'network' ) ) { if ( current_user_can( 'manage_network_plugins' ) ) { /* translators: %s: plugin name */ $actions['activate'] = '' . __( 'Network Activate' ) . ''; } if ( current_user_can( 'delete_plugins' ) ) { /* translators: %s: plugin name */ $actions['delete'] = '' . __( 'Delete' ) . ''; } } else { /* translators: %s: plugin name */ $actions['activate'] = '' . __( 'Activate', $this->text_domain ) . ''; if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) { /* translators: %s: plugin name */ $actions['delete'] = '' . __( 'Delete', $this->text_domain ) . ''; } } } } return $actions; } /** * Outputs an admin notice if plugin is trying to be activated when dependent plugin is not activated. */ public function admin_notice() { if ( ! $this->is_active() ) { printf( '

%s

', $this->get_message() ); } } /** * Deactivate ourself if dependent plugin is deactivated. * * @param mixed $old_value The old option value. * @param mixed $value The new option value. */ public function maybe_deactivate( $old_value, $value ) { if ( ! $this->is_active() ) { self::deactivate_self( $this->root_file ); if ( defined( 'WP_CLI' ) && WP_CLI ) { WP_CLI::error( $this->get_message( 'deactivate' ) ); } } } public function recently_activated( $value, $old_value ) { $current = array_diff_key( $value, $old_value ); // Check if our plugin was just now deactivated if ( isset( $current[ $this->plugin ] ) ) { $this->set_transient( 'was_active', 1 ); } return $value; } /** * Returns the message to be displayed. * * @return string Message */ private function get_message() { return $this->message; } /** * Sets the message based on the needed notification type. * * @param string $type Notification type (deactivate, activate, update). */ private function set_message( $type ) { $dependency = $this->get_plugin_data( 'Name' ) ? $this->get_plugin_data( 'Name' ) : $this->plugin; switch ( $type ) { case 'deactivate': $current = $this->get_plugin_data( 'Name', 'current' ) ? $this->get_plugin_data( 'Name', 'current' ) : plugin_basename( $this->root_file ); $this->message = __( 'You need to Install & Activate the latest version of WPFunnels Basic(Free) before activating WPFunnels - Pro.', $this->text_domain ); break; case 'upgrade': case 'update': case 'activate': default: if ( 'update' === $type || 'upgrade' === $type || !$this->is_plugin_at_min_version() ) { $action = 'update'; } else { $action = 'activate'; } $this->message = __( 'You need to Install & Activate the latest version of WPFunnels Basic(Free) before activating WPFunnels - Pro.', $this->text_domain ); break; } } /** * Returns the plugin data. * * @param null|string $attr Specific data to return. * @param null|string $plugin Specific plugin to return plugin_data. * * @return string|array Specific attribute value or all values. */ private function get_plugin_data( $attr = null, $plugin = null ) { $installed_plugins = get_plugins(); if( !array_key_exists( $this->plugin, $installed_plugins ) || !in_array( $this->plugin, $installed_plugins, true ) ){ return array(); } // Default to dependency plugin if ( ! $plugin || 'dependency' === $plugin ) { $plugin = $this->plugin; $plugin_path = trailingslashit( plugin_dir_path( dirname( $this->root_file ) ) ) . $this->plugin; // Allow current plugin_data to be returned } elseif ( 'current' === $plugin ) { $plugin = plugin_basename( $this->root_file ); $plugin_path = plugin_dir_path( dirname( $this->root_file ) ) . plugin_basename( $this->root_file ); // Un-supported plugin request, do nothing } else { return array(); } // Maybe get fresh plugin_data if ( ! isset( $this->plugin_data[ $plugin ] ) || ( isset( $this->plugin_data[ $plugin ] ) && ! $this->plugin_data[ $plugin ] ) ) { require_once(ABSPATH . 'wp-admin/includes/plugin.php'); $this->plugin_data = get_plugin_data( $plugin_path, false, false ); } // Maybe return specific attribute if ( $attr && isset( $this->plugin_data[ $attr ] ) ) { return $this->plugin_data[ $attr ]; } // Return everything return $this->plugin_data; } /** * Returns an array of parameters from HTML markup containing a link. * * @param string $text HTML to be parsed. * * @return array Associative array of parameters and values. */ private static function get_url_params( $text ) { // Capture parameters preg_match( "/]*href=\"([^\"]*)\"[^>]*>(.*)<\/a>/", $text, $output ); if ( $output ) { preg_match_all( '/([^?&=#]+)=([^&#]*)/', html_entity_decode( urldecode( $output[1] ) ), $m ); //combine the keys and values onto an assoc array return array_combine( $m[1], $m[2] ); } return array(); } /** * Deactivates the plugin. * * Function attempts to determine whether to deactivate extension plugin based on whether the depdendent * plugin is active or not. * * @uses deactivate_plugins Deactivate a single plugin or multiple plugins. * * @param string $file Single plugin or list of plugins to deactivate. * @param mixed $network_wide Whether to deactivate the plugin for all sites in the network. * A value of null (the default) will deactivate plugins for both the site and the * network. */ public static function deactivate_self( $file, $network_wide = false ) { if ( is_multisite() && false !== $network_wide ) { $network_wide = is_plugin_active_for_network( $file ); } deactivate_plugins( plugin_basename( $file ), true, $network_wide ); // register_activation_hook($this->root_file, array( $this, 'plugin_action_links_maybe_deactivate' )); } /** * Checks whether the dependent plugin(s) is/are active by checking the active_plugins list. * * @return bool Whether the dependent plugin is active. */ public function is_active() { $active = true; $installed_plugins = get_plugins(); if( !isset($installed_plugins['wpfunnels/wpfnl.php']) && !is_plugin_active( 'wpfunnels/wpfnl.php' ) ){ $plugin_slug = 'wpfunnels'; $plugins = array( array( 'name' => 'wpfunnels', 'path' => 'https://downloads.wordpress.org/plugin/'.$plugin_slug.'.latest-stable.zip', 'install' => 'wpfunnels/wpfnl.php' ) ); return true; } // Check Plugin Active if ( ! is_plugin_active( $this->plugin ) ) { if ( $this->is_action( 'activate' ) ) { $this->set_message( 'activate' ); activate_plugin($this->plugin); Wpfnl_Activator::activate(); } elseif ( $this->is_action( 'deactivate' ) ) { $this->set_message( 'deactivate' ); return false; } // return false; return true; } // All Good! return $active; } /** * Determins whether the given plugin is at the minimum version. * * @return bool Whether the plugin is at the minimum version. */ private function is_plugin_at_min_version() { if ( ! $this->min_version ) { return true; } return ( floatval( $this->get_plugin_data( 'Version' ) ) >= floatval( $this->min_version ) ); } /** * Adds a notice to the plugin row to inform the user of the dependency. */ public function plugin_row() { if ( ! $this->is_active() ) { printf( '
%s
', $this->get_message() ); } } } } includes/core/class-wpfnl-pro-hooks.php000064400000041301147600245720014171 0ustar00clear_pro_update_check_transient(); ob_start(); do_action( 'wpfunnels/check_update_transient' ); ob_get_clean(); } /** * clear pro update check transient data * * @since 1.2.8 */ private function clear_pro_update_check_transient() { global $wpdb; $sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b WHERE a.option_name LIKE %s AND a.option_name NOT LIKE %s AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )"; $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_wpfunnelspro-check_for_plugin_update' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%' ) ); } /** * check if the license is activated or not * * @return bool */ public function is_pro_license_activated() { $status = get_option( 'wpfunnels_pro_license_status' ); return $status === 'active'; } /** * Check if WPFNL pro is activated * * @return bool * @since 1.0.0 */ public function is_wpfnl_pro($is_pro) { return true; } /** * add pro builders * * @param $builders * @return mixed */ public function supported_builders($builders) { $builders['divi-builder'] = 'Divi'; return $builders; } /** * Pro module list * * @param $modules * @return array * @since 1.0.0 */ public function wpfnl_pro_modules( $modules ) { return array( 'upsell', 'downsell' ); } /** * list of available and supported gateways by * WPFunnels * * @param $localize * @return mixed * * @since 1.0.0 */ public function notice_for_payment_gateways( $localize ) { if( !Wpfnl_functions::is_wc_active() ) { return $localize; } $wc_gateways = WC()->payment_gateways->get_available_payment_gateways(); $wc_enabled_gateways = []; $supported_gateways = Payment_Gateways_Factory::getInstance()->get_supported_payment_gateways(); $offer_settings = Wpfnl_functions::get_offer_settings(); $not_supported_gateways = []; $is_supported_activated = false; if( $wc_gateways ) { foreach( $wc_gateways as $key => $gateway ) { if( $gateway->enabled == 'yes' ) { $wc_enabled_gateways[$key] = $gateway->method_title; } if( isset($supported_gateways[$key])){ $is_supported_activated = true; } } } $not_supported_gateways = array_diff_key( $wc_enabled_gateways, $supported_gateways ); $not_supported_gateways_names = is_array( $not_supported_gateways ) ? array_values( $not_supported_gateways ) : array(); if( count( $not_supported_gateways ) ) { $message = wp_sprintf( '%s - %l. %s %s', __( 'WPFunnels Upsell/Downsell does not support the following payment gateways which are enabled on your site', 'wpfnl-pro' ), $not_supported_gateways_names, 'Please check the supported gateways', 'https://getwpfunnels.com/docs/connect-payment-gateways/', 'here.' ); $localize['notices'][] = array( 'type' => 'PaymentGatewaysNotice', 'notice_type' => 'warning', 'notice_texts' => $message ); } if( !$is_supported_activated && $offer_settings['show_supported_payment_gateway'] == 'on' ){ $message = wp_sprintf( '%s - %l. %s %s', __( 'You have selected the option to show only supported payment gateways in the funnel checkouts, but you do not have any supported payment gateways active. Please enable Stripe, Paypal, Mollie, Authorize.net, or Cash On Delivery.', 'wpfnl-pro' ), '', 'Please check the supported gateways', 'https://getwpfunnels.com/docs/connect-payment-gateways/', 'here.' ); $localize['notices'][] = array( 'type' => 'PaymentGatewaysNotice', 'notice_type' => 'warning', 'notice_texts' => $message ); } return $localize; } /** * save offer settings * * @param $settings * * @since 1.0.0 */ public function save_offer_settings( $settings ) { $offer_orders = isset($settings['offer_orders']) ? $settings['offer_orders'] : 'main-order'; $show_supported_payment_gateway = isset($settings['show_supported_payment_gateway']) ? $settings['show_supported_payment_gateway'] : 'off'; $skip_offer_step = isset($settings['skip_offer_step']) ? $settings['skip_offer_step'] : 'off'; $skip_offer_step_for_free = isset($settings['skip_offer_step_for_free']) ? $settings['skip_offer_step_for_free'] : 'off'; $skip_offer_for_recurring_buyer = isset($settings['skip_offer_for_recurring_buyer']) ? $settings['skip_offer_for_recurring_buyer'] : 'off'; $skip_offer_for_recurring_buyer_within_year = isset($settings['skip_offer_for_recurring_buyer_within_year']) ? $settings['skip_offer_for_recurring_buyer_within_year'] : 'off'; $offer_settings = array( 'offer_orders' => $offer_orders, 'show_supported_payment_gateway' => $show_supported_payment_gateway, 'skip_offer_step' => $skip_offer_step, 'skip_offer_step_for_free' => $skip_offer_step_for_free, 'skip_offer_for_recurring_buyer' => $skip_offer_for_recurring_buyer, 'skip_offer_for_recurring_buyer_within_year' => $skip_offer_for_recurring_buyer_within_year, ); Wpfnl_functions::update_admin_settings('_wpfunnels_offer_settings', $offer_settings ); } /** * save Facebook pixel settings * * @param $settings * * @since 1.0.0 */ public function save_facebook_pixel_settings( $settings ) { $fb_pixel_enable = isset($settings['enable_fb_pixel']) ? $settings['enable_fb_pixel'] : 'no'; $fb_pixel_tracking_code = isset($settings['fb_tracking_code']) ? sanitize_text_field($settings['fb_tracking_code']) : ''; $fb_pixel_tracking_events = isset($settings['fb_tracking_events']) ? $settings['fb_tracking_events'] : ''; $fb_pixel_settings = array( 'enable_fb_pixel' => $fb_pixel_enable, 'facebook_pixel_id' => $fb_pixel_tracking_code, 'facebook_tracking_events' => $fb_pixel_tracking_events ); Wpfnl_functions::update_admin_settings('_wpfunnels_facebook_pixel', $fb_pixel_settings ); } /** * */ public function wpfnl_offer_meta($offer_meta){ array_push($offer_meta,'_wpfunnels_upsell'); array_push($offer_meta,'_wpfunnels_downsell'); $this->offer_metas = $offer_meta; return $offer_meta; } /** * save GTM settings * * @param $settings * * @since 1.0.0 */ public function save_gtm_settings( $settings ) { $gtm_enable = isset($settings['gtm_enable']) ? $settings['gtm_enable'] : 'no'; $gtm_container_id = isset($settings['gtm_container_id']) ? sanitize_text_field($settings['gtm_container_id']) : ''; $gtm_events = isset($settings['gtm_events']) ? $settings['gtm_events'] : ''; $gtm_settings = array( 'gtm_enable' => $gtm_enable, 'gtm_container_id' => $gtm_container_id, 'gtm_events' => $gtm_events ); Wpfnl_functions::update_admin_settings('_wpfunnels_gtm', $gtm_settings ); } /** * save UTM settings * * @param $settings * * @since 1.0.0 */ public function save_utm_settings( $settings ) { $utm_enable = isset($settings['utm_enable']) ? $settings['utm_enable'] : 'no'; $utm_source = isset($settings['utm_source']) ? $settings['utm_source'] : ''; $utm_medium = isset($settings['utm_medium']) ? $settings['utm_medium'] : ''; $utm_campaign = isset($settings['utm_campaign']) ? $settings['utm_campaign'] : ''; $utm_content = isset($settings['utm_content']) ? $settings['utm_content'] : ''; $utm_settings = array( 'utm_enable' => $utm_enable, 'utm_source' => $utm_source, 'utm_medium' => $utm_medium, 'utm_campaign' => $utm_campaign, 'utm_content' => $utm_content ); Wpfnl_functions::update_admin_settings('_wpfunnels_utm_params', $utm_settings ); } /** * beautify item meta on order * * @param $display_key, $meta, $item * * @return $display_key */ public function wpfnl_beautify_item_meta_on_order($display_key, $meta, $item){ $offer_meta = ['_wpfunnels_order_bump','_wpfunnels_upsell','_wpfunnels_downsell']; if( is_admin() && $item->get_type() === 'line_item' && ( $meta->key === $offer_meta[0] || $meta->key === $offer_meta[1] || $meta->key === $offer_meta[2]) ) { $display_key = __("Offer Type", "woocommerce" ); } return $display_key; } /** * Display customize meta value * * @param $display_key, $meta, $item * * @return $meta */ public function wpfnl_update_order_item_display_meta_value($display_key, $meta, $item){ if( isset($item['order_id']) && $item['order_id'] ){ $order = wc_get_order( $item['order_id'] ); if ( $order && Wpfnl_functions::check_if_funnel_order( $order ) ) { $offer_metas = $this->offer_metas; if( is_admin() && ($item->get_type() === 'line_item')) { foreach($offer_metas as $offer_meta){ if($meta->key == $offer_meta){ $key = substr($offer_meta,11); $meta = ucfirst(str_replace('_',' ',$key)); return $meta; } } }elseif( is_admin() && $item->get_type() === 'shipping' && $meta->key === 'Items') { $meta = $item->get_meta('Items'); return $meta; } }else{ $display_value = $meta->value; return $display_value; } }else{ $display_value = $meta->value; return $display_value; } } /** * Hidden order item meta * * @param $meta * * @return $meta */ public function wpfnl_woocommerce_hidden_order_itemmeta($meta) { $meta[] = '_wpfunnels_step_id'; $meta[] = '_wpfnl_upsell'; $meta[] = '_wpfnl_downsell'; $meta[] = '_wpfnl_step_id'; $meta[] = '_wpfunnels_offer_txn_id'; $meta[] = '_wpfunnels_offer_refunded'; return $meta; } /** * Update supported step for Pro * Add Upsell, Downsell and Custom steps for Pro plugin * * @param Array $types * @return Array $types * @since 1.6.28 */ public function update_supported_step_type( $types ){ if( Wpfnl_functions::is_pro_license_activated() ){ $new_types = [ [ 'type' => 'custom', 'name' => 'Custom', ], [ 'type' => 'upsell', 'name' => 'Upsell', ], [ 'type' => 'downsell', 'name' => 'Downsell', ], ]; $types = array_merge( $types, $new_types ); } return $types; } /** * Supported step keys * * @return array $steps */ public function supported_steps_key( $meta_keys ){ $meta_keys['addTag'] = []; $meta_keys['addList'] = []; $meta_keys['delay'] = []; $meta_keys['sendMail'] = []; $meta_keys['removeTag'] = []; $meta_keys['removeList'] = []; $meta_keys['wpf_optin_submit'] = []; $meta_keys['wpf_order_placed'] = []; $meta_keys['wpf_cta_triggered'] = []; $meta_keys['wpf_order_bump_accepted'] = []; $meta_keys['wpf_offer_trigger'] = []; return $meta_keys; } /** * After delete a funnel step * * @param string $funnel_id * @param string $step_id * * @return void */ public function before_delete_step( $step_id ){ if( $step_id ){ try { $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); Wpfnl_Pro_functions::delete_automation_by_funnel_id( $funnel_id ); }catch( \Exception $e ){ } } } /** * This method is used to determine whether all page templates should be displayed or not for funnel's step. * It takes an argument $is_allow and sets it to true, indicating that all page templates are allowed. * Finally, it returns the updated value of $is_allow. * * @param bool $is_allow A variable indicating whether all page templates are allowed or not. * * @since 1.8.7 * @return bool */ public function show_all_page_templates( $is_allow ){ return true; } }includes/core/class-wpfnl-pro-module-manager.php000064400000006544147600245720015755 0ustar00get_namespace_prefix(); $modules = $this->get_modules_names(); $admin_modules = $modules['admin']; $frontend_modules = $modules['frontend']; foreach ( $admin_modules as $key => $module_name ) { if( $key === 'steps') { $class_name = str_replace('-', ' ', $key); $class_name = str_replace(' ', '', ucwords($class_name)); $class_name = 'WPFunnels\\Admin\\Modules\\' . $class_name . '\Module'; $this->admin_modules[$key] = $class_name::instance(); $this->admin_modules[$key]->init_ajax(); foreach ($module_name as $step) { $class_name = str_replace('-', ' ', $step); $class_name = str_replace(' ', '', ucwords($class_name)); $class_name = $modules_namespace_prefix . '\\Admin\\Modules\\Steps\\' . $class_name . '\Module'; $this->admin_modules[$step] = $class_name::instance(); $this->admin_modules[$step]->init_ajax(); } }else { $class_name = str_replace('-', ' ', $module_name); $class_name = str_replace(' ', '', ucwords($class_name)); $class_name = $modules_namespace_prefix . '\\Admin\\Modules\\' . $class_name . '\Module'; $this->admin_modules[$module_name] = $class_name::instance(); $this->admin_modules[$module_name]->init_ajax(); } } foreach ( $frontend_modules as $module_name ) { $class_name = str_replace( '-', ' ', $module_name ); $class_name = str_replace( ' ', '', ucwords( $class_name ) ); $class_name = $modules_namespace_prefix . '\\Frontend\\Modules\\' . $class_name . '\Module'; $this->frontend_modules[ $module_name ] = $class_name::instance(); } } /** * get_modules_names */ public function get_modules_names() { return [ 'admin' => array( 'steps' => array( 'upsell', 'downsell', ), ), 'frontend' => array( 'upsell', 'checkout', 'thankyou', ) ]; } /** * get_admin_modules */ public function get_admin_modules( $module_name ) { if ( $module_name ) { if ( isset( $this->admin_modules[ $module_name ] ) ) { return $this->admin_modules[ $module_name ]; } return null; } return $this->admin_modules; } /** * get_frontend_modules */ public function get_frontend_modules( $module_name ) { if ( $module_name ) { if ( isset( $this->frontend_modules[ $module_name ] ) ) { return $this->frontend_modules[ $module_name ]; } return null; } return $this->frontend_modules; } /** * get_namespace_prefix */ public function get_namespace_prefix() { return 'WPFunnelsPro'; } }includes/core/class-wpfnl-pro-sessions.php000064400000017374147600245720014731 0ustar00get_cookiepath(); setcookie( WPFUNNELS_SESSION_COOKIE . $funnel_id, $key, time() + $expiration_time * MINUTE_IN_SECONDS, $cookiepath, COOKIE_DOMAIN ); set_transient( 'wpfunnels_data_' . $key, $transient, $expiration_time * MINUTE_IN_SECONDS ); wp_cache_set( 'wpfunnels_data_' . $key, $transient ); } /** * Set session data * * @param $funnel_id * @param array $data * @since 1.0.0 */ public function set_session( $funnel_id, $data = array() ) { if ( isset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ) { $key = sanitize_text_field( wp_unslash( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ); } else { $key = $funnel_id . '_' . md5( time() . wp_rand() ); } $transient = $data; $this->set_transient( $key, $transient, $funnel_id ); } /** * update session data * * @param $funnel_id * @param array $data * @since 1.0.0 */ public function update_session( $funnel_id, $data = array() ) { if ( ! isset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ) { $this->set_session( $funnel_id, $data ); } $key = sanitize_text_field( wp_unslash( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ); $transient = get_transient( 'wpfunnels_data_' . $key ); $this->set_transient( $key, $transient, $funnel_id ); } /** * Destroy session data * * @param $funnel_id */ public function destroy_session( $funnel_id ) { if ( isset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ) { $key = sanitize_text_field( wp_unslash( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ); $cookiepath = $this->get_cookiepath(); delete_transient( 'wpfunnels_data_' . $key ); wp_cache_delete( 'wpfunnels_data_' . $key ); unset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ); setcookie( WPFUNNELS_SESSION_COOKIE . $funnel_id, $key, time() - 3600, $cookiepath, COOKIE_DOMAIN ); } } /** * get session * * @param $funnel_id */ public function get_session($funnel_id) { if ( isset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ) { $key = sanitize_text_field( wp_unslash( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ); $data = get_transient( 'wpfunnels_data_' . $key ); return $data; } } /** * update transient data for funnel * * @param $funnel_id * @param array $data */ public function update_data( $funnel_id, $data = array() ) { if ( isset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ) { $key = sanitize_text_field( wp_unslash( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ); $transient = get_transient( 'wpfunnels_data_' . $key ); if ( ! is_array( $transient ) ) { $transient = array(); } $transient = array_merge( $transient, $data ); $expiration_time = WPFUNNELS_SESSION_EXPIRE_TIME; set_transient( 'wpfunnels_data_' . $key, $transient, $expiration_time * MINUTE_IN_SECONDS ); wp_cache_set( 'wpfunnels_data_' . $key, $transient ); } } /** * get transient data * * @param $funnel_id * @return array|false * * @since 1.0.0 */ public function get_data( $funnel_id ) { if ( isset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ) { $key = sanitize_text_field( wp_unslash( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ); // Try to grab the transient from the database, if it exists. $transient = get_transient( 'wpfunnels_data_' . $key ); if ( is_array( $transient ) ) { return $transient; } } return false; } /** * check if session is active * * @param $funnel_id * @return mixed|void */ public function is_session_active( $funnel_id ) { $is_active = false; if ( isset( $_GET['wcf-sk'] ) && isset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ) { $session_key = sanitize_text_field( wp_unslash( $_GET['wpfnl-sk'] ) ); $key = sanitize_text_field( wp_unslash( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ); if ( $session_key === $key ) { if ( isset( $_GET['wpfnl-order'] ) && isset( $_GET['wpfnl-key'] ) ) { $order_id = empty( $_GET['wpfnl-order'] ) ? 0 : absint( $_GET['wpfnl-order'] ); $order_key = empty( $_GET['wpfnl-key'] ) ? '' : sanitize_text_field( wp_unslash( $_GET['wpfnl-key'] ) ); if ( $order_id > 0 ) { $order = wc_get_order( $order_id ); if ( $order && $order->get_order_key() === $order_key ) { $is_active = true; } } } } } return $is_active; } /** * check if funnel session is active * * @param $funnel_id * @return mixed|void * * @since 1.0.0 */ public function is_active_session( $funnel_id ) { $is_active = false; if ( isset( $_GET['wpfnl-sk'] ) && isset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ) { $sk = sanitize_text_field( wp_unslash( $_GET['wpfnl-sk'] ) ); $key = sanitize_text_field( wp_unslash( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ); if ( $sk === $key ) { if ( isset( $_GET['wpfnl-order'] ) && isset( $_GET['wpfnl-key'] ) ) { // Get the order. $order_id = empty( $_GET['wpfnl-order'] ) ? 0 : absint( $_GET['wpfnl-order'] ); $order_key = empty( $_GET['wpfnl-key'] ) ? '' : sanitize_text_field( wp_unslash( $_GET['wpfnl-key'] ) ); if ( $order_id > 0 ) { $order = wc_get_order( $order_id ); if ( $order && $order->get_order_key() === $order_key ) { $is_active = true; } } } } } return $is_active; } /** * Get session key for funnel * * @param $funnel_id * @return false|string */ public function get_session_key( $funnel_id ) { if ( isset( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ) { $key = sanitize_text_field( wp_unslash( $_COOKIE[ WPFUNNELS_SESSION_COOKIE . $funnel_id ] ) ); return $key; } return false; } } includes/utils/class-wpfnl-pro-activator.php000064400000003162147600245720015255 0ustar00 */ class Wpfnl_Pro_Activator { protected static $wpfnl_pro_db; protected static $db_version = '1.0.0'; /** * Short Description. (use period) * * Long Description. * * @since 1.0.0 */ public static function activate() { add_option('wpfunnels_pro_do_activation_redirect', true); if ( is_plugin_active('wpfunnels/wpfnl.php') ){ // Wpfnl_Pro_functions::update_encrypt_key(); self::$wpfnl_pro_db = new Wpfnl_Pro_Db( self::$db_version ); self::$wpfnl_pro_db->create_wpfnl_pro_tables(); // save security key $security_key = get_option( WPFNL_SECURITY_KEY, ''); if(!isset($security_key)) update_option( WPFNL_SECURITY_KEY, md5( uniqid( wp_rand(), true ) ) ); if (class_exists('ActionScheduler_StoreSchema') && class_exists('ActionScheduler_LoggerSchema')){ $store_schema = new ActionScheduler_StoreSchema(); $logger_schema = new ActionScheduler_LoggerSchema(); $store_schema->register_tables( true ); $logger_schema->register_tables( true ); } } } } includes/utils/class-wpfnl-pro-deactivator.php000064400000001137147600245720015566 0ustar00 */ class Wpfnl_Pro_Deactivator { /** * Short Description. (use period) * * Long Description. * * @since 1.0.0 */ public static function deactivate() { } } includes/utils/class-wpfnl-pro-functions.php000064400000242703147600245720015277 0ustar00ID, '_step_type', true ); if ( 'upsell' === $step_type || 'downsell' === $step_type ) { $is_offer_type = true; } } return $is_offer_type; } /** * Checks if the current page is the edit post page and if the post belongs to a funnel. * * @return bool True if the current page is the edit post page and the post belongs to a funnel, false otherwise. * * @since 1.3.4 */ public static function maybe_admin_on_edit_page() { $is_edit_mode = false; global $pagenow; if ( isset( $_REQUEST['post'] ) && $_REQUEST['post'] ) { $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $_REQUEST['post'] ); if ( ( 'post.php' === $pagenow ) && $funnel_id ) { $is_edit_mode = true; } } return $is_edit_mode; } /** * Retrieves the checkout ID from the provided post data. * * @param array $post_data The post data array. * * @return int|false The checkout ID if found, false otherwise. * * @since 1.0.0 */ public static function get_checkout_id_from_post_data( $post_data ) { if ( isset( $post_data['_wpfunnels_checkout_id'] ) ) { $checkout_id = filter_var( wp_unslash( $post_data['_wpfunnels_checkout_id'] ), FILTER_SANITIZE_NUMBER_INT ); return intval( $checkout_id ); } return false; } /** * Get funnel if from post data * * @param $post_data array $_POST data. * @return false|int */ public static function get_funnel_id_from_post_data( $post_data ) { if ( isset( $post_data['_wpfunnels_funnel_id'] ) ) { $funnel_id = filter_var( wp_unslash( $post_data['_wpfunnels_funnel_id'] ), FILTER_SANITIZE_NUMBER_INT ); return intval( $funnel_id ); } return false; } /** * Retrieves the offer product for a given step ID. * * @param int $step_id The ID of the funnel step. * @param string $type The type of offer product (default is 'upsell'). * @param mixed $default The default value to return if no offer product is found. * * @return mixed The offer product data or the default value if not found. * * @since 1.0.0 */ public static function get_offer_product( $step_id, $type = 'upsell', $default = false ) { if ( $default ) { return $default; } $value = get_post_meta( $step_id, "_wpfnl_{$type}_products", true ); if ( ! $value ) { return array(); } return $value; } /** * Checks if the current funnel step is an upsell or downsell step. * * @param int $step_id The ID of the funnel step to check. If not provided, the current step ID will be used. * * @return bool True if the step is an upsell or downsell step, false otherwise. */ public static function is_upsell_downsell_step( $step_id = false ) { $is_upsell_downsell = false; if ( Wpfnl_functions::is_funnel_step_page() ) { if ( ! $step_id ) { global $post; $step_id = $post->ID; } $step_type = get_post_meta( $step_id, '_step_type', true ); if ( $step_type === 'upsell' || $step_type === 'downsell' ) { $is_upsell_downsell = true; } } return $is_upsell_downsell; } /** * Checks if the current page is an offer page (upsell or downsell). * * @param int $step_id The ID of the step to check. * * @return bool True if the step is an upsell or downsell, false otherwise. */ public static function is_offer_page( $step_id ) { if ( ! $step_id ) { return false; } return Wpfnl_functions::check_if_this_is_step_type_by_id( $step_id, 'upsell' ) || Wpfnl_functions::check_if_this_is_step_type_by_id( $step_id, 'downsell' ); } /** * Checks if an offer exists in a funnel. * * @param int $funnel_id The ID of the funnel to check. * * @return bool True if an offer exists, false otherwise. * * @since 2.0.5 */ public static function is_offer_exists_in_funnel( $funnel_id ) { $is_offer_exists = false; $steps = Wpfnl_functions::get_steps( $funnel_id ); if ( is_array( $steps ) ) { foreach ( $steps as $index => $step ) { $step_type = $step['step_type']; if ( in_array( $step_type, array( 'upsell', 'downsell' ) ) ) { $is_offer_exists = true; break; } } } return $is_offer_exists; } /** * Get upsell/downsell offer product information * * @param $step_id * @param string $selected_product_id * @param string $input_qty * @param int $order_id * @return array * * @since 1.0.0 */ public static function get_offer_product_data( $step_id, $selected_product_id = '', $input_qty = '', $order_id = 0 ) { $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); return apply_filters( 'wpfunnels/offer_product_data', self::get_offer_data( $step_id, $selected_product_id, $input_qty, $order_id = 0 ), $funnel_id, $step_id ); } /** * Retrieves offer data based on the provided parameters. * * @param int $step_id The ID of the step. * @param string $selected_product_id The ID of the selected product. * @param string $input_qty The quantity input. * @param int $order_id The ID of the order. * * @return mixed The offer data. * * @since 1.0.0 */ public static function get_offer_data( $step_id, $selected_product_id = '', $input_qty = '', $order_id = 0 ) { $data = array(); $amount_diff = 0; $cancel_main_order = false; $product_id = 0; $product_qty = 0; $step_type = get_post_meta( $step_id, '_step_type', true ); $offer_product = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_products', true ); $discount = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_discount', true ); if ( isset( $offer_product[0] ) && ! $selected_product_id ) { $product_id = $offer_product[0]['id']; $product_qty = $offer_product[0]['quantity']; } else { $product_id = $selected_product_id; $product_qty = ! empty( $input_qty ) ? intval( $input_qty ) : ( isset( $offer_product[0]['quantity'] ) ? $offer_product[0]['quantity'] : 1 ); } if ( $product_id ) { $product = wc_get_product( $product_id ); if ( $product ) { $main_qty = $product->get_stock_quantity(); if ( $main_qty <= 0 && $product->get_manage_stock() == true ) { $product_qty = 0; } $product_qty = intval( $product_qty ); $order = wc_get_order( $order_id ); $product_type = $product->get_type(); $original_price = $product->get_price( 'edit' ); $custom_price = $original_price; if ( is_a( $product, 'WC_Product_Bundle' ) ) { $custom_price = $product->get_bundle_price( 'min' ); } if ( 'composite' === $product->get_type() ) { $custom_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); } $custom_price = apply_filters( 'wpfunnels/modify_offer_product_price_data_without_discount', $custom_price ); if ( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) { if ( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ) { $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $custom_price = $custom_price + $signUpFee; } } $unit_price = $custom_price; $unit_price_tax = $custom_price; $product_price = floatval( $custom_price ) * intval( $product_qty ); /** tax calculation */ $tax_enabled = get_option( 'woocommerce_calc_taxes' ); $shipping_fee = 0; $shipping_incl_tax = 0; $shipping_excl_tax = 0; $shipping_method_name = 0; if ( $order ) { $shipping_fee = $order->get_shipping_total(); $shipping_method_name = $order->get_shipping_method(); } if ( 'yes' === $tax_enabled ) { if ( ! wc_prices_include_tax() ) { $product_price = wc_get_price_including_tax( $product, array( 'price' => $custom_price ) ) * floatval( $product_qty ); $shipping_excl_tax = wc_get_price_including_tax( $product, array( 'price' => $shipping_fee ) ); } else { $custom_price = wc_get_price_excluding_tax( $product, array( 'price' => $custom_price ) ) * floatval( $product_qty ); $shipping_incl_tax = wc_get_price_excluding_tax( $product, array( 'price' => $shipping_fee ) ); } $unit_price_tax = $custom_price; } $shipping_incl_tax = $shipping_incl_tax ? $shipping_incl_tax : $shipping_fee; $shipping_excl_tax = $shipping_excl_tax ? $shipping_excl_tax : $shipping_fee; /** if offer product has discount */ if ( is_array( $discount ) ) { $discount_instance = new WpfnlDiscount(); if ( ! $discount_instance->maybe_time_bound_discount( $step_id ) || ( $discount_instance->maybe_time_bound_discount( $step_id ) && $discount_instance->maybe_validate_discount_time( $step_id ) ) ) { $discount_type = $discount['discountType']; $discount_apply_to = $discount['discountApplyTo']; $discount_value = $discount['discountValue']; if ( 'discount-percentage' === $discount_type || 'discount-price' === $discount_type ) { $regular_price = $product->get_regular_price(); if ( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) { if ( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ) { $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $regular_price = $regular_price + $signUpFee; } } $product_price = $discount_apply_to === 'sale' ? $product->get_sale_price() : $regular_price; $product_price = $product_price ? $product_price : $product->get_price(); if ( is_a( $product, 'WC_Product_Bundle' ) ) { $custom_price = $discount_apply_to === 'sale' ? $product->get_bundle_price( 'min' ) : $product->get_bundle_regular_price( 'max' ); } if ( 'composite' === $product->get_type() ) { $custom_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); } $product_price = self::calculate_discount_price_for_widget( $discount_type, $discount_value, floatval( $product_price ) * floatval( $product_qty ) ); $product_price = apply_filters( 'wpfunnels/modify_offer_product_price_data_with_discount', $product_price ); $custom_price = $product_price; $unit_price = $product_price; $unit_price_tax = $product_price; } } } $data = array( 'step_id' => $step_id, 'id' => $product_id, 'name' => $product->get_title(), 'desc' => self::get_item_description( $product ), 'qty' => $product_qty, 'original_price' => $original_price, 'regular_price' => $product->get_regular_price(), 'sale_price' => $product->get_sale_price(), 'unit_price' => $unit_price, 'unit_price_tax' => $unit_price_tax, 'args' => array( 'subtotal' => $custom_price, 'total' => $custom_price, ), 'shipping_fee' => $shipping_excl_tax, 'shipping_fee_incl_tax' => $shipping_incl_tax, 'shipping_method_name' => $shipping_method_name, 'price' => wc_prices_include_tax() ? $custom_price : $product_price, 'url' => $product->get_permalink(), 'total_unit_price_amount' => floatval( preg_replace( '/[^\d.]/', '', $unit_price_tax ) ) * floatval( $product_qty ), 'total' => wc_prices_include_tax() ? $custom_price : $product_price, 'cancel_main_order' => $cancel_main_order, 'amount_diff' => $amount_diff, 'discount' => $discount ? true : false, 'discount_type' => isset( $discount['discountType'] ) ? $discount['discountType'] : '', 'discount_apply_to' => isset( $discount['discountApplyTo'] ) ? $discount['discountApplyTo'] : '', 'discount_value' => isset( $discount['discountValue'] ) ? $discount['discountValue'] : '', ); } } return $data; } /** * Helper method to return the item description, which is composed of item * meta flattened into a comma-separated string, if available. Otherwise the * product SKU is included. * * The description is automatically truncated to the 127 char limit. * * @param array $item cart or order item * @param \WC_Product $product product data * * @return string * @since 1.0.0 */ public static function get_item_description( $product ) { if ( is_string( $product ) ) { $str = $product; } else { $str = $product->get_short_description(); } $item_desc = wp_strip_all_tags( wp_specialchars_decode( wp_staticize_emoji( $str ) ) ); $item_desc = preg_replace( '/[\x00-\x1F\x80-\xFF]/', '', $item_desc ); $item_desc = str_replace( "\n", ', ', rtrim( $item_desc ) ); if ( strlen( $item_desc ) > 127 ) { $item_desc = substr( $item_desc, 0, 124 ) . '...'; } return html_entity_decode( $item_desc, ENT_NOQUOTES, 'UTF-8' ); } /** * Returns a string with all non-ASCII characters removed. This is useful for any string functions that expect only * ASCII chars and can't safely handle UTF-8 * * Based on the SV_WC_Helper::str_to_ascii() method developed by the masterful SkyVerge team * * Note: We must do a strict false check on the iconv() output due to a bug in PHP/glibc {@link https://bugs.php.net/bug.php?id=63450} * * @param string $string string to make ASCII * * @return string|null ASCII string or null if error occurred */ public static function str_to_ascii( $string ) { $ascii = false; if ( function_exists( 'iconv' ) ) { $ascii = iconv( 'UTF-8', 'ASCII//IGNORE', $string ); } return false === $ascii ? preg_replace( '/[^a-zA-Z0-9_\-]/', '', $string ) : $ascii; } /** * get product amount/price * * @param $total * @return int */ public static function get_amount_for_comparisons( $total ) { return absint( wc_format_decimal( ( (float) $total * 100 ), wc_get_price_decimals() ) ); } /** * Actions after offer charge completes. * * @param $step_id * @param $order_id * @param $order_key * @param bool $is_charge_success * @param string $variation_id * @param string $input_qty * @param $offer_product * @return array * @throws \Exception */ public static function after_offer_charged( $funnel_id, $step_id, $order_id, $order_key, $offer_product, $is_charge_success = false, $attr = null, $variation_id = '', $input_qty = '', $shipping_data = array() ) { $offer_settings = Wpfnl_functions::get_offer_settings(); $result = array(); if ( $is_charge_success ) { $order = wc_get_order( $order_id ); $transaction_id = $order->get_meta( '_wpfunnels_offer_txn_resp_' . $offer_product['step_id'] ); $stripe_balance_transaction = $order->get_meta( '_stripe_balance_transaction_' . $offer_product['step_id'] ); $step_type = get_post_meta( $step_id, '_step_type', true ); if ( $offer_settings['offer_orders'] == 'main-order' ) { if ( $attr ) { $offer_product['args']['variation'] = $attr; } $product = wc_get_product( $offer_product['id'] ); if ( $product ) { $product->set_price( ( $offer_product['args']['total'] / $offer_product['qty'] ) ); } $_order_total = $order->get_total() + $offer_product['total']; $cart_discount = $order->get_meta( '_cart_discount' ); $order_shipping = $order->get_meta( '_order_shipping' ); $order_shipping_tax = $order->get_meta( '_order_shipping_tax' ); $item_id = $order->add_product( $product, $offer_product['qty'] ); if ( ! $item_id ) { return $result; } $order->update_meta_data( '_wpfunnels_order', 'yes' ); if ( Wpfnl_functions::is_wc_active() ) { $chained_product_class_instance = new \WPFunnelsPro\Compatibility\ChainedProduct(); $chained_products = $chained_product_class_instance->get_chain_product_details( $offer_product['id'] ); $response = $chained_product_class_instance->update_order_item( $order, $chained_products, $offer_product['id'] ); if ( isset( $response['override_amount'], $response['total_chained_product_price'] ) && $response['override_amount'] ) { $_order_total = $_order_total + $response['total_chained_product_price']; } } if ( $item_id ) { wc_add_order_item_meta( $item_id, "_wpfunnels_{$step_type}", 'yes' ); wc_add_order_item_meta( $item_id, '_wpfunnels_step_id', $offer_product['step_id'] ); wc_add_order_item_meta( $item_id, '_wpfunnels_offer_txn_id', $transaction_id ); $order->update_meta_data( '_wpfunnels_offer_' . $step_id, $step_id ); } $tax_enabled = get_option( 'woocommerce_calc_taxes' ); $prev_tax = 0; $tax = 0; if ( 'yes' === $tax_enabled ) { if ( ! wc_prices_include_tax() ) { foreach ( $order->get_items( array( 'tax' ) ) as $item_id => $line_item ) { $order_product_detail = $line_item->get_data(); if ( isset( $order_product_detail['tax_total'] ) && isset( $order_product_detail['rate_percent'] ) ) { $prev_tax = $order_product_detail['tax_total']; $tax = ( $offer_product['args']['total'] * $order_product_detail['rate_percent'] ) / 100; } } } } $order = self::update_offer_order_shipping( $order, $shipping_data ); ob_start(); do_action( 'woocommerce_order_before_calculate_totals', true, $order ); ob_get_clean(); ob_start(); do_action( 'woocommerce_order_after_calculate_totals', true, $order ); ob_get_clean(); if ( $cart_discount ) { $order->update_meta_data( '_order_total', $_order_total ); $order->update_meta_data( '_cart_discount', $cart_discount ); if ( 'yes' === $tax_enabled ) { if ( ! wc_prices_include_tax() ) { foreach ( $order->get_items( array( 'tax' ) ) as $item_id => $line_item ) { $order_product_detail = $line_item->get_data(); if ( isset( $order_product_detail['tax_total'] ) ) { wc_update_order_item_meta( $item_id, 'tax_total', $prev_tax + $tax ); wc_update_order_item_meta( $item_id, 'tax_amount', $prev_tax + $tax ); $order->update_meta_data( '_order_tax', $prev_tax + $tax ); } } } } } if ( $order_shipping ) { $order->update_meta_data( '_order_shipping', $order_shipping ); if ( $order_shipping_tax ) { $order->update_meta_data( '_order_shipping_tax', $order_shipping_tax ); } } $order->save(); } else { $offer_product['transaction_id'] = $transaction_id; $child_order = self::create_child_order( $order, $offer_product, $step_type, $attr, $shipping_data ); if ( $stripe_balance_transaction ) { \WPFunnelsPro\Frontend\Gateways\Wpfnl_Stripe_payment_process::update_stripe_fees( $child_order, $stripe_balance_transaction ); } $user_id = $child_order->get_user_id(); $child_order->update_meta_data( '_wpfunnels_offer_' . $step_id, $step_id ); $child_order->update_meta_data( '_wpfunnels_parent_funnel_id', $funnel_id ); $child_order->save(); $replaceSettings = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_replacement_settings', true ); if ( $replaceSettings == 'true' ) { $isOfferReplace = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_replacement', true ); $order_ids = WC()->session->get( 'wpfnl_orders_' . $user_id . '_' . $funnel_id ); if ( ! empty( $isOfferReplace['value'] ) && $isOfferReplace['value'] == 'true' ) { $child_order_items = $child_order->get_items(); /** check any physcial product is avialable in child order item */ $any_physical_item = false; foreach ( $child_order_items as $id => $child_order_item ) { $product = $child_order_item->get_product(); if ( ! $product->is_virtual() || ! $product->is_downloadable() ) { $any_physical_item = true; } } foreach ( $order_ids as $order_id ) { $order = wc_get_order( $order_id ); if ( $order->get_meta( '_wpfunnels_offer_parent_id' ) && ( $order->get_id() != $order->get_meta( '_wpfunnels_offer_parent_id' ) ) ) { foreach ( $order->get_items() as $id => $_order_item ) { $product = $_order_item->get_product(); if ( ! $product->is_virtual() || ! $product->is_downloadable() ) { $any_physical_item = true; } } } } if ( $isOfferReplace['replacement_type'] == 'all_prior_order' ) { foreach ( $order_ids as $order_id ) { $shipping_cost_with_tax = 0; $order = wc_get_order( $order_id ); if ( $order->get_shipping_total() && $any_physical_item ) { $shipping_cost_with_tax = $order->get_shipping_tax() + $order->get_shipping_total(); } if ( $order->get_status() != 'cancelled' ) { self::replace_order( $order, $isOfferReplace['replacement_type'], $shipping_cost_with_tax ); } } } elseif ( $isOfferReplace['replacement_type'] == 'previous_step' ) { $step_info = Wpfnl_functions::get_prev_step( $funnel_id, $step_id ); if ( isset( $order_ids[ $step_info['step_id'] ] ) ) { $shipping_cost_with_tax = 0; $order = wc_get_order( $order_ids[ $step_info['step_id'] ] ); if ( $order->get_shipping_total() && $any_physical_item ) { $shipping_cost_with_tax = $order->get_shipping_tax() + $order->get_shipping_total(); } if ( $order->get_status() != 'cancelled' ) { self::replace_order( $order, $isOfferReplace['replacement_type'], $shipping_cost_with_tax ); } } } elseif ( $isOfferReplace['replacement_type'] == 'main_order_with_order_bump' || $isOfferReplace['replacement_type'] == 'main_order_without_order_bump' ) { ob_start(); do_action( 'wpfunnels/before_main_order_cancelled', $order ); ob_get_clean(); $shipping_cost_with_tax = 0; $parent_order_id = $child_order->get_meta( '_wpfunnels_offer_parent_id', true ); if ( $parent_order_id ) { $order = wc_get_order( $parent_order_id ); if ( $order ) { if ( $order->get_shipping_total() && $any_physical_item ) { $shipping_cost_with_tax = $order->get_shipping_tax() + $order->get_shipping_total(); } if ( $order->get_status() != 'cancelled' ) { self::replace_order( $order, $isOfferReplace['replacement_type'], $shipping_cost_with_tax ); } } } } } } $order = $child_order; if ( is_plugin_active( 'woocommerce-software-license/software-license.php' ) ) { $license_obj = new \WOO_SL_functions(); self::create_license( $order->get_id(), $license_obj ); $license_obj->generate_license_keys( $order->get_id() ); } // start set session for replace order $orders = WC()->session->get( 'wpfnl_orders_' . $user_id . '_' . $funnel_id ); $orders[ $step_id ] = $child_order->get_id(); WC()->session->set( 'wpfnl_orders_' . $user_id . '_' . $funnel_id, $orders ); // end set session for replace order // start destroy session for replace order if next step is thankyou $step_info = Wpfnl_functions::get_next_step( $funnel_id, $step_id ); if ( $step_info['step_type'] == 'thankyou' ) { if ( session_status() === PHP_SESSION_NONE ) { session_start(); } if ( isset( $_SESSION ) || $_SESSION ) { unset( $_SESSION[ 'wpfnl_orders_' . $user_id . '_' . $funnel_id ] ); } } // end destroy session for replace order if next step is thankyou } $result = array( 'status' => 'success', 'message' => __( 'Product Added Successfully.', 'wpfnl-pro' ), ); $offer_settings['offer_type'] = $step_type; /** * $order /WC_Order object * $step_type String * $offer_product Array * $offer_product['step_id'] String represent the associate step id * $offer_product['id'] String represent the id of the product * $offer_product['name'] String represent the name of the product * $offer_product['desc'] String represent the description of the product * $offer_product['qty'] String represent the quantity of the product * $offer_product['original_price'] String represent the price of the product * $offer_product['unit_price'] String represent the unit_price of the product * $offer_product['unit_price_tax'] String represent the unit_price with tax of the product * $offer_product['args'] Array represent the extra arguments of the product * $offer_product['args']['subtotal'] String represent the subtotal of the product * $offer_product['args']['total'] String represent the total of the product * $offer_product['price'] String represent the price of the product * $offer_product['url'] String represent the url of the product * $offer_product['total_unit_price_amount'] String represent the $unit_price_tax * $product_qty of the product * $offer_product['total'] String represent the $custom_price of the product if any * $offer_product['cancel_main_order'] Bool checker if cancel main order is enabled/disabled from funnel settings */ ob_start(); do_action( 'wpfunnels/offer_accepted', $order, $offer_product ); ob_get_clean(); } return $result; } /** * Replaces the order with a new order based on the specified replacement type. * * @param mixed $order The original order to be replaced. * @param string $replacement_type The type of replacement to be performed. * @param float $shipping_cost The cost of shipping for the new order (optional, default: 0). * @return mixed The replaced order. */ private static function replace_order( $order, $replacement_type, $shipping_cost = 0 ) { if ( false === is_a( $order, 'WC_Order' ) ) { return false; } $refunded_item_id = ''; $refunds = $order->get_refunds(); if ( ! empty( $refunds ) ) { foreach ( $refunds as $refund ) { foreach ( $refund->get_items() as $item_id => $item ) { $refunded_quantity = $item->get_quantity(); // Quantity: zero or negative integer $refunded_line_subtotal = $item->get_subtotal(); // line subtotal: zero or negative number $refunded_item_id = $item->get_meta( '_refunded_item_id' ); // line subtotal: zero or negative number } } } $order_items = $order->get_items(); $refund_amount = 0; $line_items = array(); $order_fee_total = 0; foreach ( $order->get_fees() as $fee_id => $fee ) { $order_fee_total += $fee->get_total(); } if ( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) { \WC_Subscriptions_Manager::cancel_subscriptions_for_order( $order ); } if ( $order_items ) { $ob_item_ids = array(); foreach ( $order_items as $item_id => $item ) { if ( $replacement_type !== 'main_order_without_order_bump' ) { if ( $refunded_item_id && $refunded_item_id == $item_id ) { continue; } $refund_tax = wc_get_order_item_meta( $item_id, '_line_tax', true ); $line_items[ $item_id ] = array( 'qty' => $item->get_quantity(), 'refund_total' => wc_format_decimal( wc_get_order_item_meta( $item_id, '_line_total', true ) ), 'refund_tax' => $refund_tax, ); } else { $ob_meta = wc_get_order_item_meta( $item_id, '_wpfunnels_order_bump', true ); if ( $refunded_item_id && $refunded_item_id == $item_id ) { continue; } if ( $ob_meta !== 'yes' ) { $refund_tax = wc_get_order_item_meta( $item_id, '_line_tax', true ); $refund_tax = wc_get_order_item_meta( $item_id, '_line_tax', true ); $amount = wc_get_order_item_meta( $item_id, '_line_total', true ); $refund_amount = $refund_amount + ( wc_format_decimal( $amount ) + wc_format_decimal( $refund_tax ) ); $refund_amount = filter_var( number_format( $refund_amount, 2 ), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION ); $line_items[ $item_id ] = array( 'qty' => $item->get_quantity(), 'refund_total' => wc_format_decimal( wc_get_order_item_meta( $item_id, '_line_total', true ) ), 'refund_tax' => $refund_tax, ); } else { array_push( $ob_item_ids, $item_id ); } } } } $refund_reason = ''; if ( $replacement_type == 'main_order_without_order_bump' ) { $refund_reason = __( 'The main product is replaced by the offer product', 'wpfnl_pro' ); } elseif ( $replacement_type == 'main_order_with_order_bump' ) { $refund_reason = __( 'The main product and the order bump are replaced by the offer product', 'wpfnl_pro' ); } elseif ( $replacement_type == 'all_prior_order' ) { $refund_reason = __( 'All products are replaced by the offer product', 'wpfnl_pro' ); } elseif ( $replacement_type == 'previous_step' ) { $refund_reason = __( 'The product is replaced by the next offer product', 'wpfnl_pro' ); } else { $refund_reason = __( 'The main product is replaced by the offer product', 'wpfnl_pro' ); } if ( $replacement_type !== 'main_order_without_order_bump' ) { $refund_amount = $order->get_total() - $shipping_cost; } elseif ( empty( $ob_item_ids ) ) { $refund_amount = $order->get_total() - $shipping_cost; } $refund_amount = $refund_amount - abs( $order_fee_total ); $refund = wc_create_refund( array( 'amount' => $refund_amount, 'reason' => $refund_reason ? $refund_reason : '', 'order_id' => $order->get_id(), 'line_items' => $line_items, 'refund_payment' => false, ) ); $all_gateways = WC()->payment_gateways->payment_gateways(); $payment_method = $order->get_payment_method(); $gateway = isset( $all_gateways[ $payment_method ] ) ? $all_gateways[ $payment_method ] : false; if ( $gateway ) { $result = $gateway->process_refund( $order->get_id(), $refund_amount, $refund_reason ); } if ( $replacement_type !== 'main_order_without_order_bump' ) { $order->update_status( 'cancelled' ); } } /** * Adds a shipping fee to the specified order. * * @param \WC_Order $order The WooCommerce order object. * @param mixed $offer_product The product to which the shipping fee should be added. */ public static function add_shipping_fee_to_order( \WC_Order $order, $offer_product ) { $item = new \WC_Order_Item_Shipping(); $item->set_method_title( $offer_product['shipping_method_name'] ); $item->set_method_id( '' ); $item->set_total( $offer_product['shipping_fee'] ); $item->save(); $order->add_item( $item ); // Show product details in shipping rates section of order data. $product_name = $offer_product['name'] . ' × ' . $offer_product['qty']; $offer_shipping_items = array( $product_name ); $item_id = $item->get_id(); $offer_itmes = implode( ',', $offer_shipping_items ); wc_add_order_item_meta( $item_id, 'Items', $offer_itmes ); $order->calculate_totals(); $order->save(); } /** * Creates a child order. * * @param mixed $parent_order The parent order. * @param array $product_data The product data. * @param string $type The type of order (upsell, cross-sell, etc.). * @param array $attr Additional attributes for the child order. * @param array $shipping_data The shipping data for the child order. * @return void */ public static function create_child_order( $parent_order, $product_data, $type = 'upsell', $attr = array(), $shipping_data = array() ) { $order = false; if ( ! empty( $parent_order ) ) { $parent_order->update_meta_data( '_wpfunnels_order', 'yes' ); $parent_order_id = $parent_order->get_id(); $parent_order_billing = $parent_order->get_address( 'billing' ); $funnel_id = $parent_order->get_meta( '_wpfunnels_funnel_id' ); if ( ! $funnel_id && isset( $product_data['step_id'] ) ) { $funnel_id = get_post_meta( $product_data['step_id'], '_funnel_id', true ); } if ( ! empty( $parent_order_billing['email'] ) ) { $customer_id = $parent_order->get_customer_id(); $order = wc_create_order( array( 'customer_id' => $customer_id, 'status' => 'wc-pending', 'parent' => $parent_order_id, ) ); $currency = $parent_order->get_currency(); $order->set_currency( $currency ); $order->update_meta_data( '_wpfunnels_offer', 'yes' ); $order->update_meta_data( '_wpfunnels_order', 'yes' ); $order->update_meta_data( '_wpfunnels_offer_type', $type ); $order->update_meta_data( '_wpfunnels_parent_funnel_id', $funnel_id ); $order->update_meta_data( '_wpfunnels_funnel_id', $funnel_id ); $parent_order->update_meta_data( '_wpfunnels_funnel_id', $funnel_id ); $order->update_meta_data( '_wpfunnels_offer_step_id', $product_data['step_id'] ); $order->update_meta_data( '_wpfunnels_offer_parent_id', $parent_order_id ); if ( $attr ) { $product_data['args']['variation'] = $attr; } $new_item_id = $order->add_product( wc_get_product( $product_data['id'] ), $product_data['qty'], $product_data['args'] ); if ( $new_item_id ) { wc_add_order_item_meta( $new_item_id, '_wpfnl_' . $type, 'yes' ); wc_add_order_item_meta( $new_item_id, '_wpfunnels_' . $type, 'yes' ); wc_add_order_item_meta( $new_item_id, '_wpfunnels_step_id', $product_data['step_id'] ); } $chained_product_class_instance = new \WPFunnelsPro\Compatibility\ChainedProduct(); $response = array(); if ( Wpfnl_functions::is_wc_active() ) { $chained_products = $chained_product_class_instance->get_chain_product_details( $product_data['id'] ); $response = $chained_product_class_instance->update_order_item( $order, $chained_products, $product_data['id'] ); if ( isset( $response['override_amount'], $response['total_chained_product_price'] ) && $response['override_amount'] ) { $product_data['args']['total'] = $product_data['args']['total'] + $response['total_chained_product_price']; } } $order->set_address( $parent_order->get_address( 'billing' ), 'billing' ); $order->set_address( $parent_order->get_address( 'shipping' ), 'shipping' ); $order->set_payment_method( $parent_order->get_payment_method() ); $order->set_payment_method_title( $parent_order->get_payment_method_title() ); if ( ! wc_tax_enabled() ) { $order->set_shipping_tax( 0 ); $order->set_cart_tax( 0 ); $total = $product_data['total']; } elseif ( ! wc_prices_include_tax() ) { $tax = 0; foreach ( $order->get_items( array( 'tax' ) ) as $item_id => $line_item ) { $order_product_detail = $line_item->get_data(); if ( isset( $order_product_detail['tax_total'] ) && isset( $order_product_detail['rate_percent'] ) ) { $tax = ( $offer_product['total'] * $order_product_detail['rate_percent'] ) / 100; wc_update_order_item_meta( $item_id, 'tax_total', $tax ); wc_update_order_item_meta( $item_id, 'tax_amount', $tax ); } } $total = $product_data['total'] + $tax; } else { $total = $product_data['total']; } $order->calculate_taxes(); $order->set_total( $total ); $offer_orders_meta = $parent_order->get_meta( '_wpfunnels_offer_child_orders' ); if ( ! is_array( $offer_orders_meta ) ) { $offer_orders_meta = array(); } $offer_orders_meta[ $order->get_id() ] = array( 'type' => $type ); $parent_order->update_meta_data( '_wpfunnels_offer_child_orders', $offer_orders_meta ); // Save the order. $parent_order->save(); $order->save(); if ( $parent_order && ( $parent_order->get_total_tax() > 0 || count( $parent_order->get_tax_totals() ) > 0 ) ) { if ( wc_tax_enabled() ) { if ( wc_prices_include_tax() ) { $tax_rate = 0; $tax = 0; foreach ( $order->get_items( array( 'tax' ) ) as $item_id => $line_item ) { $order_product_detail = $line_item->get_data(); if ( isset( $order_product_detail['rate_percent'] ) ) { $tax_rate = $order_product_detail['rate_percent']; } } $tax = $product_data['args']['total'] - ( $product_data['args']['total'] / ( ( $tax_rate / 100 ) + 1 ) ); $rate_id = 0; foreach ( $order->get_items( array( 'tax' ) ) as $item_id => $line_item ) { $order_product_detail = $line_item->get_data(); if ( isset( $order_product_detail['rate_id'] ) ) { $rate_id = $order_product_detail['rate_id']; } wc_update_order_item_meta( $item_id, 'tax_amount', $tax ); } $line_tax = array( 'total' => array( $rate_id => $tax, ), 'subtotal' => array( $rate_id => $tax, ), ); $total_without_tax = $product_data['args']['total'] - $tax; wc_update_order_item_meta( $new_item_id, '_line_tax_data', $line_tax ); wc_update_order_item_meta( $new_item_id, '_line_tax_data', $line_tax ); wc_update_order_item_meta( $new_item_id, '_line_subtotal_tax', $tax ); wc_update_order_item_meta( $new_item_id, '_line_tax', $tax ); wc_update_order_item_meta( $new_item_id, '_line_total', $total_without_tax ); wc_update_order_item_meta( $new_item_id, '_line_subtotal', $total_without_tax ); if ( isset( $response['chain_item_details'] ) ) { $chained_product_class_instance->update_tax_ammount( $response['chain_item_details'] ); } $order->update_meta_data( '_order_total', $product_data['args']['total'] ); } } } $order->save(); } } if ( $order ) { $transaction_id = $product_data['transaction_id']; ob_start(); do_action( 'wpfunnels/before_offer_new_child_order_before_completed', $order, $product_data, $parent_order ); ob_get_clean(); $order->set_transaction_id( $transaction_id ); $order->save(); $transaction_id_note = ''; if ( ! empty( $transaction_id ) ) { $transaction_id_note = sprintf( ' (Transaction ID: %s)', $transaction_id ); } $order->add_order_note( 'Offer Accepted | ' . $type . ' | Step ID - ' . $product_data['step_id'] . ' | ' . $transaction_id_note ); ob_start(); do_action( 'wpfunnels/child_order_created', $parent_order, $order, $transaction_id ); ob_get_clean(); ob_start(); do_action( 'wpfunnels/child_order_created_' . $parent_order->get_payment_method(), $parent_order, $order, $transaction_id ); ob_get_clean(); self::payment_complete( $order, $transaction_id ); return $order; } return false; } /** * Updates the total for a bundle parent item. * * @param int $bundle_parent_item_id The ID of the bundle parent item. * @param object $order The order object. * @param array $product_data The product data. */ public static function update_bundle_total( $bundle_parent_item_id, $order, $product_data ) { $order_items = $order->get_items(); $bundle_item = $order_items[ $bundle_parent_item_id ]; if ( $bundle_item && is_a( $bundle_item, 'WC_Order_Item_Product' ) ) { $bundle_id = $bundle_item->get_product_id(); // Get the product ID $bundle = wc_get_product( $bundle_id ); // Get the product object $bundled_items = $bundle->get_bundled_items(); if ( ! empty( $bundled_items ) ) { $discount_type = isset( $product_data['discount_apply_to'] ) && 'regular' === $product_data['discount_apply_to'] ? 'regular' : 'original'; foreach ( $bundled_items as $bundled_item_id => $bundled_item ) { $order_items = $order->get_items( 'line_item' ); $single_item = wc_get_product( $bundled_item->get_product_id() ); $total = $discount_type === 'regular' ? $single_item->get_regular_price() : $single_item->get_price(); if ( wc_tax_enabled() ) { if ( wc_prices_include_tax() ) { $tax_rate = 0; $tax = 0; foreach ( $order->get_items( array( 'tax' ) ) as $item_id => $line_item ) { $order_product_detail = $line_item->get_data(); if ( isset( $order_product_detail['rate_percent'] ) ) { $tax_rate = $order_product_detail['rate_percent']; } } $tax = $total - ( $total / ( ( $tax_rate / 100 ) + 1 ) ); $rate_id = 0; foreach ( $order->get_items( array( 'tax' ) ) as $item_id => $line_item ) { $order_product_detail = $line_item->get_data(); if ( isset( $order_product_detail['rate_id'] ) ) { $rate_id = $order_product_detail['rate_id']; } wc_update_order_item_meta( $item_id, 'tax_amount', $tax ); } $line_tax = array( 'total' => array( $rate_id => $tax, ), 'subtotal' => array( $rate_id => $tax, ), ); wc_update_order_item_meta( $bundled_item_id, '_line_tax_data', $line_tax ); wc_update_order_item_meta( $bundled_item_id, '_line_tax_data', $line_tax ); wc_update_order_item_meta( $bundled_item_id, '_line_subtotal_tax', $tax ); wc_update_order_item_meta( $bundled_item_id, '_line_tax', $tax ); wc_update_order_item_meta( $bundled_item_id, '_line_total', $total ); wc_update_order_item_meta( $bundled_item_id, '_line_subtotal', $total ); } } } } } } /** * Check if offer will be added to parent order or will create a separate one * * @return bool */ public static function is_separate_offer() { $offer_settings = Wpfnl_functions::get_offer_settings(); return $offer_settings['offer_orders'] !== 'main-order'; } /** * After payment completed action * * @param $order * @param string $transaction_id * * @since 1.0.0 */ public static function payment_complete( $order, $transaction_id = '' ) { $payment_method = $order->get_payment_method(); if ( 'cod' === $payment_method ) { $order->set_status( 'processing' ); wc_reduce_stock_levels( $order ); } elseif ( 'bacs' === $payment_method ) { $order->set_status( 'on-hold' ); wc_reduce_stock_levels( $order ); } else { $order->payment_complete( $transaction_id ); } } /** * Check if any upsell/downsell step is added on that funnel * * @param $order * @return bool */ public static function is_offer_exists( $order ) { $funnel_id = Wpfnl_functions::get_funnel_id_from_order( $order->get_id() ); $is_offer_exists = false; if ( $funnel_id ) { $steps = get_post_meta( $funnel_id, '_steps_order', true ); if ( $steps ) { foreach ( $steps as $step ) { if ( 'upsell' === $step['step_type'] || 'downsell' === $step['step_type'] ) { $is_offer_exists = true; break; } } } } return $is_offer_exists; } /** * Check if there is any upsell/downsell in the funnel * * @param $order * @return bool */ public static function check_if_offer_exists( $order ) { $exists = false; $funnel_id = Wpfnl_functions::get_funnel_id_from_order( $order->get_id() ); if ( $funnel_id ) { $steps = Wpfnl_functions::get_steps( $funnel_id ); if ( $steps ) { foreach ( $steps as $step ) { if ( 'upsell' === $step['step_type'] || 'downsell' === $steps['step_type'] ) { $exists = true; break; } } } } return $exists; } /** * Get all order from funnel id * * @param $funnel_id * * @return $orders */ public static function get_orders_from_funnel_id( $funnel_id ) { global $wpdb; $where = ''; $where .= " WHERE ( (( wpft2.meta_key = '_wpfunnels_funnel_id' AND wpft2.meta_value = $funnel_id ) OR ( wpft2.meta_key = '_wpfunnels_parent_funnel_id' AND wpft2.meta_value = $funnel_id )) "; $where .= " AND wpft1.post_status IN ( 'wc-completed', 'wc-processing'))"; $query = 'SELECT wpft1.ID FROM ' . $wpdb->prefix . 'posts wpft1 INNER JOIN ' . $wpdb->prefix . 'postmeta wpft2 ON wpft1.ID = wpft2.post_id ' . $where; return $wpdb->get_results( $query ); } /** * check if doing ajax * * @return bool */ public static function is_doing_ajax() { if ( wp_doing_ajax() || isset( $_GET['wc-ajax'] ) ) { if ( isset( $_GET['wc-ajax'] ) && isset( $_POST['_wpfunnels_checkout_id'] ) ) { return true; } } return false; } /** * Covert to xml from array * * @param $array */ public static function array_to_xml( $array, $rootElement = null, $xml = null ) { $_xml = $xml; // If there is no Root Element then insert root if ( $_xml === null ) { $_xml = new \SimpleXMLElement( $rootElement !== null ? $rootElement : '' ); } // Visit all key value pair foreach ( $array as $k => $v ) { // If there is nested array then if ( is_array( $v ) ) { // Call function for nested array self::array_to_xml( $v, $k, $_xml->addChild( $k ) ); } else { // Simply add child element. $_xml->addChild( $k, $v ); } } return $_xml->asXML(); } /** * Prepare common request body for webhook * * @param Array $settings, $content_type, $request_body * @return Array */ public static function prepare_common_request_args( $settings, $content_type, $request_body, $event_name, $url ) { if ( $settings['request']['method'] !== 'GET' ) { if ( isset( $settings['request']['format'] ) ) { if ( $settings['request']['format'] == 'FORM' ) { $content_type = 'application/x-www-form-urlencoded'; } elseif ( $settings['request']['format'] == 'JSON' ) { $content_type = 'application/json'; $request_body = json_encode( $request_body ); } else { $content_type = 'text/xml'; $request_body = self::array_to_xml( $request_body ); } } } $request_args = array( 'body' => $request_body, 'method' => $settings['request']['method'], 'headers' => array( 'Content-Type' => $content_type, 'X-WC-Webhook-Source' => $url, 'X-WC-Webhook-Event' => $event_name, 'X-WC-Webhook-ID' => $settings['id'], ), 'sslverify' => 1, ); return $request_args; } /** * Updates the variation product details. * * @param int $step_id The ID of the step. * @param int $selected_product_id The ID of the selected product. * @param int $input_qty The quantity input by the user. * @param int $order_id The ID of the order. */ public static function update_variation_product_details( $step_id, $selected_product_id, $input_qty, $order_id ) { $product_id = $selected_product_id; $product_qty = $input_qty; $step_type = get_post_meta( $step_id, '_step_type', true ); $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); $discount = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_discount', true ); $cancel_main_order = false; $amount_diff = 0; $data = ''; if ( $product_id ) { $data = self::prepare_offer_data( $step_id, $product_id, $product_qty, $step_type, $discount, $cancel_main_order, $amount_diff, $order_id ); } return apply_filters( 'wpfunnels/offer_product_data', $data, $funnel_id, $step_id ); } /** * Prepares offer data for processing. * * @param int $step_id The ID of the step. * @param int $product_id The ID of the product. * @param int $product_qty The quantity of the product. * @param string $step_type The type of the step. * @param float $discount The discount amount. * @param bool $cancel_main_order Whether to cancel the main order. * @param float $amount_diff The difference in amount. * @param int $order_id The ID of the order. */ public static function prepare_offer_data( $step_id, $product_id, $product_qty, $step_type, $discount, $cancel_main_order, $amount_diff, $order_id ) { $product = wc_get_product( $product_id ); if ( $product ) { $main_qty = $product->get_stock_quantity(); if ( $main_qty <= 0 && $product->get_manage_stock() == true ) { $product_qty = 0; } $product_qty = intval( $product_qty ); $order = wc_get_order( $order_id ); $product_type = $product->get_type(); $original_price = $product->get_price( 'edit' ); $custom_price = $original_price; if ( is_a( $product, 'WC_Product_Bundle' ) ) { $custom_price = $product->get_bundle_price( 'min' ); } if ( 'composite' === $product->get_type() ) { $custom_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); } $custom_price = apply_filters( 'wpfunnels/modify_offer_product_price_data_without_discount', $custom_price, $product_id ); if ( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) { if ( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ) { $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $custom_price = $custom_price + $signUpFee; } } $unit_price = $custom_price; $unit_price_tax = $custom_price; $custom_price = floatval( $custom_price ); $product_price = floatval( $custom_price ) * intval( $product_qty ); /** tax calculation */ $tax_enabled = get_option( 'woocommerce_calc_taxes' ); $shipping_fee = 0; $shipping_incl_tax = 0; $shipping_excl_tax = 0; $shipping_method_name = 0; if ( $order ) { $shipping_fee = $order->get_shipping_total(); $shipping_method_name = $order->get_shipping_method(); } if ( 'yes' === $tax_enabled ) { if ( ! wc_prices_include_tax() ) { $product_price = wc_get_price_including_tax( $product, array( 'price' => $product_price ) ); $shipping_excl_tax = wc_get_price_including_tax( $product, array( 'price' => $shipping_fee ) ); } else { $product_price = wc_get_price_excluding_tax( $product, array( 'price' => $product_price ) ); $shipping_incl_tax = wc_get_price_excluding_tax( $product, array( 'price' => $shipping_fee ) ); } $unit_price_tax = $custom_price; } $shipping_incl_tax = $shipping_incl_tax ? $shipping_incl_tax : $shipping_fee; $shipping_excl_tax = $shipping_excl_tax ? $shipping_excl_tax : $shipping_fee; /** if offer product has discount */ if ( is_array( $discount ) ) { $discount_instance = new WpfnlDiscount(); if ( ! $discount_instance->maybe_time_bound_discount( $step_id ) || ( $discount_instance->maybe_time_bound_discount( $step_id ) && $discount_instance->maybe_validate_discount_time( $step_id ) ) ) { $discount_type = $discount['discountType']; $discount_apply_to = $discount['discountApplyTo']; $discount_value = $discount['discountValue']; if ( 'discount-percentage' === $discount_type || 'discount-price' === $discount_type ) { $regular_price = $product->get_type() == 'variable' ? $product->get_price() : $product->get_regular_price(); $sale_price = $product->get_type() == 'variable' ? $product->get_price() : $product->get_sale_price(); if ( is_a( $product, 'WC_Product_Bundle' ) ) { $sale_price = $product->get_bundle_price( 'min' ); $regular_price = $product->get_bundle_regular_price( 'min' ) ? $product->get_bundle_regular_price( 'min' ) : $product->get_bundle_price( 'min' ); } if ( 'composite' === $product->get_type() ) { $sale_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false ); $regular_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), true ); } if ( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) { if ( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ) { $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product ); $regular_price = $regular_price + $signUpFee; } } $product_price = $discount_apply_to === 'sale' ? $sale_price : $regular_price; $product_price = $product_price ? $product_price : $regular_price; $product_price = $product_price ? $product_price : $product->get_price(); $product_price = floatval( $product_price ); $product_price = self::calculate_discount_price_for_widget( $discount_type, $discount_value, $product_price * $product_qty ); $product_price = apply_filters( 'wpfunnels/modify_offer_product_price_data_with_discount', $product_price, $product_id, $product_qty ); $custom_price = $product_price; $unit_price = $product_price; $unit_price_tax = $product_price; } } } $data = array( 'step_id' => $step_id, 'id' => $product_id, 'name' => $product->get_type() == 'variable' ? Wpfnl_functions::get_formated_product_name( $product ) : $product->get_title(), 'desc' => self::get_item_description( $product ), 'qty' => $product_qty, 'original_price' => $original_price, 'regular_price' => $product->get_regular_price(), 'sale_price' => $product->get_sale_price(), 'unit_price' => $unit_price, 'unit_price_tax' => $unit_price_tax, 'args' => array( 'subtotal' => $custom_price, 'total' => $custom_price, ), 'shipping_fee' => $shipping_excl_tax, 'shipping_fee_incl_tax' => $shipping_incl_tax, 'shipping_method_name' => $shipping_method_name, 'price' => wc_prices_include_tax() ? $custom_price : $product_price, 'url' => $product->get_permalink(), 'total_unit_price_amount' => preg_replace( '/[^\d.]/', '', $unit_price_tax ) * $product_qty, 'total' => wc_prices_include_tax() ? $custom_price : $product_price, 'cancel_main_order' => $cancel_main_order, 'amount_diff' => $amount_diff, 'discount' => $discount ? true : false, 'discount_type' => isset( $discount['discountType'] ) ? $discount['discountType'] : '', 'discount_apply_to' => isset( $discount['discountApplyTo'] ) ? $discount['discountApplyTo'] : '', 'discount_value' => isset( $discount['discountValue'] ) ? $discount['discountValue'] : '', 'offer_button' => 'yes', ); return $data; } return false; } /** * Retrieves the product type of an offer. * * @param string $step_id The ID of the step. * @return string The product type of the offer. */ public static function get_offer_product_type( $step_id = '' ) { if ( $step_id ) { $step_type = get_post_meta( $step_id, '_step_type', true ); if ( $step_type == 'upsell' || $step_type == 'downsell' ) { $offer_product = self::get_offer_product( $step_id, $step_type ); if ( is_array( $offer_product ) ) { $product_type = ''; foreach ( $offer_product as $pr_index => $pr_data ) { $product_id = $pr_data['id']; $product = wc_get_product( $product_id ); if ( $product ) { $product_type = $product->get_type(); break; } } return $product_type; } } } return false; } /** * Checks if a product is a variable product. * * @param int $step_id The ID of the step. * @return bool True if the product is a variable product, false otherwise. */ public static function check_is_variable_product( $step_id ) { $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); $type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ); if ( $type == 'lms' ) { return false; } $step_type = get_post_meta( $step_id, '_step_type', true ); $products = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_products', true ); if ( is_array( $products ) && ! empty( $products ) ) { foreach ( $products as $product ) { $get_product = wc_get_product( $product['id'] ); if ( $get_product ) { if ( $get_product->get_type() == 'variable' ) { return true; } } } } return false; } /** * Retrieves the price of an offer product. * * @param int $step_id The ID of the step. * @return float The price of the offer product. */ public static function get_offer_product_price( $step_id ) { $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); $type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ); if ( $type == 'lms' ) { return false; } $step_type = get_post_meta( $step_id, '_step_type', true ); $products = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_products', true ); $product_price = ''; if ( $products ) { foreach ( $products as $product ) { $get_product = wc_get_product( $product['id'] ); if ( $get_product ) { $product_price = $get_product->get_price_html(); } } } return $product_price; } /** * Encrypts a given key. * * @param string $key The key to be encrypted. * @return string The encrypted key. */ public static function encrypt_key( $key ) { $encrypted_key = Wpfunnels_Aes_Ctr::encrypt( $key, WPFNL_SECURITY_KEY, 256 ); return $encrypted_key; } /** * Decrypt a key with AES * * @param $key * @return string */ public static function decrypt_key( $key ) { $encrypted_key = Wpfunnels_Aes_Ctr::decrypt( $key, WPFNL_SECURITY_KEY, 256 ); return $encrypted_key; } /** * Senitize request data * * @param array $data * * @return array */ public static function get_sanitized_get_post( $data = array() ) { if ( is_array( $data ) && ! empty( $data ) ) { return filter_var_array( $data, FILTER_SANITIZE_FULL_SPECIAL_CHARS ); } return array( 'get' => filter_input_array( INPUT_GET, FILTER_SANITIZE_FULL_SPECIAL_CHARS ), 'post' => filter_input_array( INPUT_POST, FILTER_SANITIZE_FULL_SPECIAL_CHARS ), 'request' => filter_var_array( $_REQUEST, FILTER_SANITIZE_FULL_SPECIAL_CHARS ), ); } /** * Retrieve dynamic offer product for GBF * * @return int|mixed * * @since 1.6.9 */ public static function get_gbf_product_from_cookie() { return WC()->session->get( 'wpfunnels_global_funnel_specific_product' ) ? WC()->session->get( 'wpfunnels_global_funnel_specific_product' ) : array(); } /** * Get random product for showing dummy data in editor backend * * @return Array || Bool * * @since 1.6.9 */ public static function get_random_product() { if ( Wpfnl_functions::is_wc_active() ) { global $post; // setup_postdata will not work without this being set (outside of the foreach loop) $args = array( 'posts_per_page' => 1, 'orderby' => 'rand', 'post_type' => 'product', ); $random_products = get_posts( $args ); if ( is_array( $random_products ) ) { if ( isset( $random_products[0]->ID ) ) { return $random_products[0]->ID; } } } return false; } /** * Get product data for widget * * @param String * * @return Array * @since 1.6.8 */ public static function get_product_data_for_widget( $step_id = '' ) { if ( $step_id && Wpfnl_functions::is_wc_active() ) { $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); $step_type = get_post_meta( $step_id, '_step_type', true ); $is_gbf = get_post_meta( $funnel_id, 'is_global_funnel', true ); $offer_product = ''; if ( 'yes' === $is_gbf && ( 'upsell' === $step_type || 'downsell' === $step_type ) ) { if ( is_plugin_active( 'wpfunnels-pro-gbf/wpfnl-pro-gb.php' ) ) { $instance = new Wpfnl_Pro_GB_Functions(); $offer_rules = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true ); $quantity = isset( $offer_rules['quantity'] ) ? $offer_rules['quantity'] : 1; $rules_type = isset( $offer_rules['type'] ) ? $offer_rules['type'] : ''; if ( 'moreQuantity' == $rules_type ) { $rand_product_id = self::get_random_product(); $offer_product = wc_get_product( $rand_product_id ); $get_product_type = $offer_product ? $offer_product->get_type() : ''; $quantity = isset( $gbf_product[0]['quantity'] ) ? $quantity + $gbf_product[0]['quantity'] : $quantity; } elseif ( 'specificProduct' == $rules_type ) { $rules = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true ); $offer_product = ''; if ( is_array( $rules ) ) { if ( isset( $rules['show'] ) && $rules['show'] ) { $offer_product = wc_get_product( $rules['show'] ); } } $get_product_type = $offer_product ? $offer_product->get_type() : ''; } elseif ( 'randomProduct' == $rules_type ) { $offer_mappings = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true ); if ( $offer_mappings ) { $category = isset( $offer_mappings['category'] ) ? $offer_mappings['category'] : null; $function_exist = is_callable( array( $instance, 'get_random_product_in_category_for_offer' ) ); if ( $function_exist ) { $id = ''; if ( is_admin() ) { $dynamic_product = Wpfnl_Pro_GB_Functions::get_random_product_in_shop_for_widget( 1 ); if ( isset( $dynamic_product[0]->ID ) ) { $id = $dynamic_product[0]->ID; } } else { $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true ); if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) { $id = $dynamic_product[0]['ID']; } } if ( $id ) { $offer_product = wc_get_product( $id ); $get_product_type = $offer_product ? $offer_product->get_type() : ''; } } } } elseif ( 'randomProductInsideCategoryWithinPrice' == $rules_type ) { $offer_mappings = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true ); if ( $offer_mappings ) { $category = isset( $offer_mappings['category'] ) ? $offer_mappings['category'] : null; $function_exist = is_callable( array( $instance, 'get_random_product_in_category_within_price_for_offer_for_widget' ) ); if ( $function_exist ) { $id = ''; if ( is_admin() ) { $dynamic_product = Wpfnl_Pro_GB_Functions::get_random_product_in_category_within_price_for_offer_for_widget( 1 ); if ( isset( $dynamic_product[0]->ID ) ) { $id = $dynamic_product[0]->ID; } } else { $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true ); if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) { $id = $dynamic_product[0]['ID']; } } if ( $id ) { $offer_product = wc_get_product( $id ); $get_product_type = $offer_product ? $offer_product->get_type() : ''; } } } } elseif ( 'highestSoldInsideCategory' == $rules_type ) { $offer_mappings = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true ); if ( $offer_mappings ) { $category = isset( $offer_mappings['category'] ) ? $offer_mappings['category'] : null; $function_exist = is_callable( array( $instance, 'get_highest_sold_product_in_category_for_offer' ) ); if ( $function_exist ) { $highestSoldInsideCategory = Wpfnl_Pro_GB_Functions::get_highest_sold_product_in_category_for_offer( 1, $category ); if ( is_array( $highestSoldInsideCategory ) && isset( $highestSoldInsideCategory['posts'][0]->ID ) ) { $id = $highestSoldInsideCategory['posts'][0]->ID; $offer_product = wc_get_product( $id ); $get_product_type = $offer_product ? $offer_product->get_type() : ''; } } } } elseif ( 'highestSoldInsideTag' == $rules_type ) { $offer_mappings = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true ); if ( $offer_mappings ) { $tag = isset( $offer_mappings['tag'] ) ? $offer_mappings['tag'] : null; $function_exist = is_callable( array( $instance, 'get_highest_sold_product_in_tag_for_offer' ) ); if ( $function_exist ) { $highestSoldInsideTag = Wpfnl_Pro_GB_Functions::get_highest_sold_product_in_tag_for_offer( $tag, 1 ); if ( is_array( $highestSoldInsideTag ) && isset( $highestSoldInsideTag['posts'][0]->ID ) ) { $id = $highestSoldInsideTag['posts'][0]->ID; $offer_product = wc_get_product( $id ); $get_product_type = $offer_product ? $offer_product->get_type() : ''; } } } } elseif ( 'highestSold' == $rules_type ) { $function_exist = is_callable( array( $instance, 'get_highest_sold_product_for_offer' ) ); if ( $function_exist ) { $highestSold = Wpfnl_Pro_GB_Functions::get_highest_sold_product_for_offer( 1 ); if ( is_array( $highestSold ) && isset( $highestSold['posts'][0]->ID ) ) { $id = $highestSold['posts'][0]->ID; $offer_product = wc_get_product( $id ); $get_product_type = $offer_product ? $offer_product->get_type() : ''; } } } elseif ( 'randomProductInTag' == $rules_type ) { $function_exist = is_callable( array( $instance, 'get_random_product_in_tag_for_offer_for_widget' ) ); if ( $function_exist ) { $id = ''; if ( is_admin() ) { $dynamic_product = Wpfnl_Pro_GB_Functions::get_random_product_in_tag_for_offer_for_widget( 1 ); if ( isset( $dynamic_product[0]->ID ) ) { $id = $dynamic_product[0]->ID; } } else { $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true ); if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) { $id = $dynamic_product[0]['ID']; } } if ( $id ) { $offer_product = wc_get_product( $id ); $get_product_type = $offer_product ? $offer_product->get_type() : ''; } } } elseif ( 'randomProductInsideTagWithinPrice' == $rules_type ) { $function_exist = is_callable( array( $instance, 'get_random_product_in_tag_within_price_for_offer_for_widget' ) ); if ( $function_exist ) { $id = ''; if ( is_admin() ) { $dynamic_product = Wpfnl_Pro_GB_Functions::get_random_product_in_tag_within_price_for_offer_for_widget( 1 ); if ( isset( $dynamic_product[0]->ID ) ) { $id = $dynamic_product[0]->ID; } } else { $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true ); if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) { $id = $dynamic_product[0]['ID']; } } if ( $id ) { $offer_product = wc_get_product( $id ); $get_product_type = $offer_product ? $offer_product->get_type() : ''; } } } elseif ( 'randomInShop' == $rules_type ) { $id = ''; if ( is_admin() ) { $dynamic_product = Wpfnl_Pro_GB_Functions::get_random_product_in_shop_for_widget( 1 ); if ( isset( $dynamic_product[0]->ID ) ) { $id = $dynamic_product[0]->ID; } } else { $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true ); if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) { $id = $dynamic_product[0]['ID']; } } if ( $id ) { $offer_product = wc_get_product( $id ); $get_product_type = $offer_product ? $offer_product->get_type() : ''; } } } } else { $offer_product_data = self::get_offer_product( $step_id, $step_type ); $quantity = isset( $offer_product_data['quantity'] ) ? $offer_product_data['quantity'] : 1; $product = null; if ( is_array( $offer_product_data ) ) { foreach ( $offer_product_data as $pr_index => $pr_data ) { $product_id = $pr_data['id']; $product = wc_get_product( $product_id ); break; } } $offer_product = $product; $get_product_type = $offer_product ? $offer_product->get_type() : ''; } if ( $offer_product && $get_product_type ) { return array( 'offer_product' => $offer_product, 'get_product_type' => $get_product_type, 'is_gbf' => $is_gbf, 'quantity' => $quantity, ); } } return array( 'offer_product' => '', 'get_product_type' => '', 'is_gbf' => 'no', 'quantity' => 1, ); } /** * @desc update order shipping values with offer order shipping * @since 1.6.21 * @param \WC_Order $order * @param $offer_shipping * @param $update * @return \WC_Order * @throws \WC_Data_Exception */ private static function update_offer_order_shipping( \WC_Order $order, $offer_shipping, $update = true ) { $order->calculate_shipping(); $order->calculate_totals(); return $order; } /** * Get supported CRM * * @return Array $integrations * @since 1.6.27 */ public static function get_supported_crm() { $integrations = array( 'fluent_crm' => array( 'class_name' => 'FluentCRM', ), ); return apply_filters( 'wpfunnels/supported_crm_integrations', $integrations ); } /** * Modify offer product if type is variable * * @param array $payload * * @return mix * @since 1.7.6 */ public static function modify_offer_product( $payload ) { if ( isset( $payload['product_id'], $payload['data'] ) ) { $variation_id = ( new \WC_Product_Data_Store_CPT() )->find_matching_product_variation( new \WC_Product( $payload['product_id'] ), $payload['data'] ); return $variation_id; } return false; } /** * Delete automation by funnel id * * @param int $funnel_id */ public static function delete_automation_by_funnel_id( $funnel_id ) { if ( Wpfnl_functions::is_mint_mrm_active() && class_exists( 'Mint\\MRM\\DataBase\\Tables\\AutomationMetaSchema' ) && class_exists( 'MintMail\\App\\Internal\\Automation\\AutomationModel' ) && class_exists( 'Mint\\MRM\\DataBase\\Tables\\AutomationSchema' ) ) { $automationSchema = 'Mint\\MRM\\DataBase\\Tables\\AutomationSchema'; $automationMetaSchema = 'Mint\\MRM\\DataBase\\Tables\\AutomationMetaSchema'; $automationModel = 'MintMail\\App\\Internal\\Automation\\AutomationModel'; global $wpdb; $automation_table = $wpdb->prefix . $automationSchema::$table_name; $automation_meta_table = $wpdb->prefix . $automationMetaSchema::$table_name; $automations = $wpdb->get_results( $wpdb->prepare( "SELECT automation.id as id FROM $automation_table as automation INNER JOIN $automation_meta_table as automation_meta ON automation.id = automation_meta.automation_id WHERE automation_meta.meta_key = %s AND automation_meta.meta_value = %s", array( 'funnel_id', $funnel_id ) ), ARRAY_A ); // db call ok. ; no-cache ok. $wpdb->query( $wpdb->prepare( "DELETE automation FROM $automation_table as automation INNER JOIN $automation_meta_table as automation_meta ON automation.id = automation_meta.automation_id WHERE automation_meta.meta_key = %s AND automation_meta.meta_value = %s", array( 'funnel_id', $funnel_id ) ) ); if ( $automations && is_array( $automations ) ) { foreach ( $automations as $automation ) { if ( isset( $automation['id'] ) ) { $function_exist = is_callable( array( $automationModel, 'delete_child_row_by_autoamtion_id' ) ); if ( $function_exist ) { $automationModel::delete_child_row_by_autoamtion_id( $automation['id'] ); } } } } } } /** * Create licenses for products in an order that contain licensed products. * * @param int $order_id The ID of the order. * @param object $license_obj The license object. * * @since 1.8.8 * @return void */ public static function create_license( $order_id, $license_obj ) { // Check if the order contains any licensed products $order_data = new \WC_Order( $order_id ); $order_products = $order_data->get_items(); $found_licensed_product = false; foreach ( $order_products as $key => $order_product ) { if ( \WOO_SL_functions::is_product_licensed( $order_product->get_product_id() ) ) { $found_licensed_product = true; break; } } if ( false === $found_licensed_product ) { return; } // Iterate through the order items and create licenses for licensed products $_woo_sl = array(); foreach ( $order_products as $key => $order_product ) { if ( ! $license_obj->is_product_licensed( $order_product->get_product_id() ) ) { continue; } $is_licence_extend = false; $_woo_sl_extend = wc_get_order_item_meta( $key, '_woo_sl_extend', true ); if ( ! empty( $_woo_sl_extend ) ) { $is_licence_extend = true; } // Skip processing if it is a license extend if ( true === $is_licence_extend ) { continue; } // Check against the variation if it is assigned a license group if ( $order_product->get_variation_id() > 0 ) { $variation_license_group_id = get_post_meta( $order_product->get_variation_id(), '_sl_license_group_id', true ); if ( '' === $variation_license_group_id ) { continue; } } // Get product licensing details $product_sl_groups = \WOO_SL_functions::get_product_licensing_groups( $order_product->get_product_id() ); // If it is a variation, filter out the license groups if ( $order_product->get_variation_id() > 0 ) { if ( isset( $product_sl_groups[ $variation_license_group_id ] ) ) { $_product_sl_groups = $product_sl_groups; $product_sl_groups = array(); $product_sl_groups[ $variation_license_group_id ] = $_product_sl_groups[ $variation_license_group_id ]; } else { $product_sl_groups = array(); } } // Prepare data arrays for each licensing group $_group_title = array(); $_licence_prefix = array(); $_max_keys = array(); $_max_instances_per_key = array(); $_use_predefined_keys = array(); $_product_use_expire = array(); $_product_expire_renew_price = array(); $_product_expire_units = array(); $_product_expire_time = array(); $_product_expire_starts_on_activate = array(); $_product_expire_disable_update_link = array(); $_product_expire_limit_api_usage = array(); $_product_expire_notice = array(); foreach ( $product_sl_groups as $product_sl_group ) { $_group_title[] = $product_sl_group['group_title']; $_licence_prefix[] = $product_sl_group['licence_prefix']; $_max_keys[] = $product_sl_group['max_keys']; $_max_instances_per_key[] = $product_sl_group['max_instances_per_key']; $_use_predefined_keys[] = $product_sl_group['use_predefined_keys']; $_product_use_expire[] = $product_sl_group['product_use_expire']; $_product_expire_renew_price[] = $product_sl_group['product_expire_renew_price']; $_product_expire_units[] = $product_sl_group['product_expire_units']; $_product_expire_time[] = $product_sl_group['product_expire_time']; $_product_expire_starts_on_activate[] = $product_sl_group['product_expire_starts_on_activate']; $_product_expire_disable_update_link[] = $product_sl_group['product_expire_disable_update_link']; $_product_expire_limit_api_usage[] = $product_sl_group['product_expire_limit_api_usage']; $_product_expire_notice[] = $product_sl_group['product_expire_notice']; } // Prepare the license data array $data['group_title'] = $_group_title; $data['licence_prefix'] = $_licence_prefix; $data['max_keys'] = $_max_keys; $data['max_instances_per_key'] = $_max_instances_per_key; $data['use_predefined_keys'] = $_use_predefined_keys; $data['product_use_expire'] = $_product_use_expire; $data['product_expire_renew_price'] = $_product_expire_renew_price; $data['product_expire_units'] = $_product_expire_units; $data['product_expire_time'] = $_product_expire_time; $data['product_expire_starts_on_activate'] = $_product_expire_starts_on_activate; $data['product_expire_disable_update_link'] = $_product_expire_disable_update_link; $data['product_expire_limit_api_usage'] = $_product_expire_limit_api_usage; $data['product_expire_notice'] = $_product_expire_notice; // Apply filters to the license data $data = apply_filters( 'woo_sl/order_processed/product_sl', $data, $order_product, $order_id ); // Update order item meta with the license data wc_update_order_item_meta( $key, '_woo_sl', $data ); // Set the licensing status as active wc_update_order_item_meta( $key, '_woo_sl_licensing_status', 'active' ); // Process the licensing expiration for each data block foreach ( $data['product_use_expire'] as $data_key => $data_block_value ) { if ( 'no' !== $data_block_value ) { wc_update_order_item_meta( $key, '_woo_sl_licensing_using_expire', $data_block_value ); // Continue only if expire_starts_on_activate is not set to yes $expire_starts_on_activate = $data['product_expire_starts_on_activate'][ $data_key ]; if ( 'yes' === $expire_starts_on_activate ) { // Set the licensing status as not-activated wc_update_order_item_meta( $key, '_woo_sl_licensing_status', 'not-activated' ); continue; } if ( 'yes' === $data_block_value ) { $today = date( 'Y-m-d', current_time( 'timestamp' ) ); $start_at = strtotime( $today ); wc_update_order_item_meta( $key, '_woo_sl_licensing_start', $start_at ); $_sl_product_expire_units = $data['product_expire_units'][ $data_key ]; $_sl_product_expire_time = $data['product_expire_time'][ $data_key ]; $expire_at = strtotime( '+ ' . $_sl_product_expire_units . ' ' . $_sl_product_expire_time, $start_at ); wc_update_order_item_meta( $key, '_woo_sl_licensing_expire_at', $expire_at ); } } } } } /** * Check if woocommerce payment is activated or not * * @return bool * @since 1.9.0 */ public static function is_wc_payment_active() { if ( defined( 'WCPAY_PLUGIN_FILE' ) ) { return true; } return false; } /** * Calculate discount price * * @param $discount_type * @param $discount_value * @param $product_price * * @return string */ public static function calculate_discount_price_for_widget( $discount_type, $discount_value, $product_price ) { $custom_price = $product_price; if ( ! empty( $discount_type ) ) { if ( 'discount-percentage' === $discount_type ) { if ( $discount_value > 0 && $discount_value <= 100 ) { $custom_price = (float) $product_price - ( (float) ( $product_price * $discount_value ) / 100 ); } } elseif ( 'discount-price' === $discount_type ) { if ( $discount_value > 0 ) { $custom_price = $product_price - $discount_value; } } } return $custom_price; } /** * Retrieves an array of order IDs associated with a specific funnel ID. * * This function queries the WordPress database to retrieve order IDs linked to a given funnel ID. * * @param int $funnel_id The ID of the funnel to retrieve order IDs for. * @return array An array of order IDs linked to the provided funnel ID. * @since 1.9.6 */ public static function get_order_ids_by_funnel_id( $funnel_id ) { global $wpdb; // Determine the appropriate order meta table and column based on WooCommerce settings. $order_meta_table = $wpdb->postmeta; $column = 'post_id'; if ( 'yes' === get_option( 'woocommerce_custom_orders_table_enabled', 'no' ) ) { $order_meta_table = "{$wpdb->prefix}wc_orders_meta"; $column = 'order_id'; } // Query the database to retrieve order IDs associated with the provided funnel ID. $order_ids = $wpdb->get_results( $wpdb->prepare( "SELECT %i FROM {$order_meta_table} WHERE `meta_key`=%s AND `meta_value`=%d", array( $column, '_wpfunnels_funnel_id', $funnel_id ) ), ARRAY_A ); // Extract and return an array of order IDs. return is_array( $order_ids ) && ! empty( $order_ids ) ? array_column( $order_ids, $column ) : array(); } /** * Checks if a payment gateway is potentially unsupported. * * @return void * @since 2.0.5 */ public static function maybe_unsupported_payment_gateway() { if ( ! Wpfnl_functions::is_wc_active() || ! isset( $_GET['wpfnl-order'] ) ) { return false; } $order_id = ( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0; $order = wc_get_order( $order_id ); if ( false === is_a( $order, 'WC_Order' ) ) { return false; } $payment_method = $order->get_payment_method(); if ( $payment_method ) { $payment_gateways = Payment_Gateways_Factory::getInstance()->get_supported_payment_gateways(); if ( ! isset( $payment_gateways[ $payment_method ] ) ) { return true; } else { return false; } } return true; } /** * get_available_payment_methods: fetched available payment methods * * @since 2.1.1 * @return array */ public static function get_available_payment_methods() { if ( Wpfnl_functions::is_wc_active() ) { $gateways = WC()->payment_gateways->get_available_payment_gateways(); $enabled_gateways = array(); if ( $gateways ) { foreach ( $gateways as $key => $gateway ) { if ( $gateway->enabled == 'yes' ) { $enabled_gateways[ $key ] = $gateway->method_title; } } } return $enabled_gateways; } return array(); } /** * Generates a globally unique identifier (GUID). * * @return string The generated GUID. * * @since 2.1.1 */ public static function generate_guid() { $unique_id = uniqid( mt_rand(), true ); $md5_hash = md5( $unique_id ); $guid = substr( $md5_hash, 0, 32 ); return $guid; } } includes/utils/class-wpfnl-pro-i18n.php000064400000001565147600245720014045 0ustar00 */ class Wpfnl_Pro_i18n { /** * Load the plugin text domain for translation. * * @since 1.0.0 */ public function load_plugin_textdomain() { load_plugin_textdomain( 'wpfnl-pro', false, dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/' ); } } includes/utils/class-wpfnl-pro-licensing.php000064400000017476147600245720015251 0ustar00' ) && version_compare( WPFNL_PRO_VERSION, '1.2.9', '>' ) ) { add_action('admin_menu', array( $this, 'add_license_sub_menu' ) ); } add_action('admin_init', array( $this, 'licence_management_operations' )); add_action( 'admin_notices', array( $this, 'licensing_admin_notices' )); } /** * add license submenu page * * @since 1.3.0 */ public function add_license_sub_menu() { add_submenu_page( WPFNL_MAIN_PAGE_SLUG, __('License', 'wpfnl-pro'), __('License', 'wpfnl-pro'), 'wpf_manage_funnels', 'wpf-license', [$this, 'render_license'] ); } /** * render license page * * @since 1.3.0 */ public function render_license() { require WPFNL_PRO_DIR . '/admin/partials/license.php'; } /** * license management operations */ public function licence_management_operations() { if( isset( $_POST['wpfunnels_pro_license_activate'] ) ) { $this->do_license_operation('activate'); } elseif (isset( $_POST['wpfunnels_pro_license_deactivate'] ) ) { $this->do_license_operation('deactivate'); } } /** * license checking * * @param string $operation_name */ private function do_license_operation( $operation_name = 'activate' ) { if (!check_admin_referer('wpfunnels_pro_licensing_nonce', 'wpfunnels_pro_licensing_nonce')) return; $license_key = isset( $_POST['wpfunnels_license_key'] ) ? sanitize_key( trim($_POST['wpfunnels_license_key']) ) : ''; if ( strlen($license_key) > 40 ) { $license_key = get_option( 'wpfunnels_pro_license_key', '' ); $decrypt_license = Wpfnl_Pro_functions::decrypt_key($_POST['wpfunnels_license_key']); if( $license_key == $decrypt_license ){ $license_key = get_option( 'wpfunnels_pro_license_key', '' ); } } $args = array( 'woo_sl_action' => $operation_name, 'licence_key' => $license_key, 'product_unique_id' => WPFNL_PRO_PRODUCT_ID, 'domain' => WPFNL_PRO_INSTANCE, 'license_url' => WPFNL_PRO_API_URL ); //check if license url is working $_status_data = wp_remote_get( WPFNL_PRO_API_URL ); if(is_wp_error( $_status_data ) || $_status_data['response']['code'] != 200) { $request_uri = WPFNL_PRO_LICENSE_URL . '?' . http_build_query( $args ); } else { $request_uri = WPFNL_PRO_API_URL . '?' . http_build_query( $args ); } $data = wp_remote_get( $request_uri ); if(is_wp_error( $data ) || $data['response']['code'] != 200) { $message = __( 'An error occurred, please try again.' ); $base_url = admin_url( 'admin.php?page=wpf-license' ); $redirect = add_query_arg( array( 'wpfnl_activation_pro' => 'false', 'message' => urlencode( $message ) ), $base_url ); wp_redirect( $redirect ); exit(); } $data_body = json_decode($data['body']); foreach ($data_body as $data) { if ( isset($data->status) && $data->status == 'success') { if($data->status_code == 's100' || $data->status_code == 's101') { $message = $data->message; $license_status = $data->licence_status; $license_start = isset($data->licence_start) ? $data->licence_start: ''; $license_end = isset($data->licence_expire) ? $data->licence_expire : ''; //save the license $licence_data = array( 'key' => $license_key, 'last_check' => time(), 'start_date' => $license_start, 'end_date' => $license_end, ); update_option('wpfunnels_pro_license_key', $license_key); update_option('wpfunnels_pro_licence_data', $licence_data ); update_option('wpfunnels_pro_is_premium', 'yes' ); update_option('wpfunnels_pro_license_status', $license_status ); } elseif ( $data->status_code == 's201' ) { $message = $data->message; $license_status = $data->status; //save the license $licence_data = array( 'key' => '', 'last_check' => time(), 'start_date' => '', 'end_date' => '', ); update_option('wpfunnels_pro_licence_data', $licence_data ); update_option('wpfunnels_pro_is_premium', 'no' ); update_option('wpfunnels_pro_license_status', 'deactivate' ); } elseif ( $data->status_code == 'e002' || $data->status_code == 'e104' || $data->status_code == 'e211' ) { $message = $data->message; $license_status = $data->licence_status; //save the license $licence_data = array( 'key' => '', 'last_check' => time(), 'start_date' => '', 'end_date' => '', ); update_option('wpfunnels_pro_licence_data', $licence_data); update_option('wpfunnels_pro_is_premium', 'no'); update_option('wpfunnels_pro_license_status', 'deactivate' ); } } else { $message = $data->message; $licence_data = array( 'key' => '', 'last_check' => time(), 'start_date' => '', 'end_date' => '', ); update_option('wpfunnels_pro_licence_data', $licence_data); update_option('wpfunnels_pro_is_premium', 'no'); update_option('wpfunnels_pro_license_status', 'deactivate' ); } } $base_url = admin_url( 'admin.php?page=wpf-license' ); $redirect = add_query_arg( array( 'wpfnl_activation_pro' => 'true', 'message' => urlencode( $message ) ), $base_url ); wp_redirect( $redirect ); exit(); } /** * show notice on license activation */ public function licensing_admin_notices() { $data = Wpfnl_functions::get_sanitized_get_post(); if ( isset( $data['get']['wpfnl_activation_pro'] ) && ! empty( $_GET['message'] ) ) { switch( $data['get']['wpfnl_activation_pro'] ) { case 'false': $message = urldecode( $_GET['message'] ); ?>

*/ class Wpfnl_Pro_Loader { /** * The array of actions registered with WordPress. * * @since 1.0.0 * @access protected * @var array $actions The actions registered with WordPress to fire when the plugin loads. */ protected $actions; /** * The array of filters registered with WordPress. * * @since 1.0.0 * @access protected * @var array $filters The filters registered with WordPress to fire when the plugin loads. */ protected $filters; /** * Initialize the collections used to maintain the actions and filters. * * @since 1.0.0 */ public function __construct() { $this->actions = array(); $this->filters = array(); } /** * Add a new action to the collection to be registered with WordPress. * * @since 1.0.0 * @param string $hook The name of the WordPress action that is being registered. * @param object $component A reference to the instance of the object on which the action is defined. * @param string $callback The name of the function definition on the $component. * @param int $priority Optional. The priority at which the function should be fired. Default is 10. * @param int $accepted_args Optional. The number of arguments that should be passed to the $callback. Default is 1. */ public function add_action( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) { $this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args ); } /** * Add a new filter to the collection to be registered with WordPress. * * @since 1.0.0 * @param string $hook The name of the WordPress filter that is being registered. * @param object $component A reference to the instance of the object on which the filter is defined. * @param string $callback The name of the function definition on the $component. * @param int $priority Optional. The priority at which the function should be fired. Default is 10. * @param int $accepted_args Optional. The number of arguments that should be passed to the $callback. Default is 1 */ public function add_filter( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) { $this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args ); } /** * A utility function that is used to register the actions and hooks into a single * collection. * * @since 1.0.0 * @access private * @param array $hooks The collection of hooks that is being registered (that is, actions or filters). * @param string $hook The name of the WordPress filter that is being registered. * @param object $component A reference to the instance of the object on which the filter is defined. * @param string $callback The name of the function definition on the $component. * @param int $priority The priority at which the function should be fired. * @param int $accepted_args The number of arguments that should be passed to the $callback. * @return array The collection of actions and filters registered with WordPress. */ private function add( $hooks, $hook, $component, $callback, $priority, $accepted_args ) { $hooks[] = array( 'hook' => $hook, 'component' => $component, 'callback' => $callback, 'priority' => $priority, 'accepted_args' => $accepted_args ); return $hooks; } /** * Register the filters and actions with WordPress. * * @since 1.0.0 */ public function run() { foreach ( $this->filters as $hook ) { add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] ); } foreach ( $this->actions as $hook ) { add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] ); } } } includes/utils/class-wpfnl-pro-updater.php000064400000013177147600245720014734 0ustar00api_url = $api_url; $this->slug = $slug; $this->plugin = $plugin; $this->API_VERSION = 1.1; // Take over the update check add_filter('pre_set_site_transient_update_plugins', array( $this, 'check_for_plugin_update' )); // Take over the Plugin info screen add_filter('plugins_api', array($this, 'plugins_api_call'), 10, 3); } /** * check if any update is pending * * @param $checked_data * @return mixed */ public function check_for_plugin_update( $checked_data ) { if (!is_object($checked_data) || !isset ($checked_data->response)) { return $checked_data; } $request_string = $this->prepare_request('plugin_update'); if ($request_string === FALSE) return $checked_data; global $wp_version; // Start checking for an update $request_uri = $this->api_url . '?' . http_build_query($request_string, '', '&'); //check if cached $data = get_site_transient('wpfunnelspro-check_for_plugin_update_' . md5($request_uri)); if (isset ($_GET['force-check']) && $_GET['force-check'] == '1') $data = FALSE; if ($data === FALSE) { $data = wp_remote_get($request_uri, array( 'timeout' => 20, 'user-agent' => 'WordPress/' . $wp_version . '; wpfunnelspro/' . WPFNL_PRO_VERSION . '; ' . WPFNL_PRO_INSTANCE, )); if (is_wp_error($data) || $data['response']['code'] != 200) return $checked_data; set_site_transient('wpfunnelspro-check_for_plugin_update_' . md5($request_uri), $data, 60 * 60 * 4); } $response_block = json_decode($data['body']); if (!is_array($response_block) || count($response_block) < 1) return $checked_data; //retrieve the last message within the $response_block $response_block = $response_block[count($response_block) - 1]; $response = isset($response_block->message) ? $response_block->message : ''; if (is_object($response) && !empty($response)) // Feed the update data into WP updater { $response = $this->postprocess_response( $response ); $checked_data->response[$this->plugin] = $response; } return $checked_data; } /** * API call for plugin update notice * * @param $def * @param $action * @param $args * @return WP_Error */ public function plugins_api_call($def, $action, $args) { if (!is_object($args) || !isset($args->slug) || $args->slug != $this->slug) return $def; $request_string = $this->prepare_request($action, $args); if ($request_string === FALSE) return new WP_Error('plugins_api_failed', __('An error occour when try to identify the pluguin.', 'software-license') . '</p> <p><a href="?" onclick="document.location.reload(); return false;">' . __('Try again', 'software-license') . '</a>');; $request_uri = $this->api_url . '?' . http_build_query($request_string, '', '&'); $data = wp_remote_get($request_uri); if (is_wp_error($data) || $data['response']['code'] != 200) return new WP_Error('plugins_api_failed', __('An Unexpected HTTP Error occurred during the API request.', 'software-license') . '</p> <p><a href="?" onclick="document.location.reload(); return false;">' . __('Try again', 'software-license') . '</a>', $data->get_error_message()); $response_block = json_decode($data['body']); //retrieve the last message within the $response_block $response_block = $response_block[count($response_block) - 1]; $response = $response_block->message; if (is_object($response) && !empty($response)) { $response = $this->postprocess_response( $response ); return $response; } } /** * post process response of the api * response * * @param $response * @return mixed * */ private function postprocess_response( $response ) { //include slug and plugin data $response->slug = $this->slug; $response->plugin = $this->plugin; //if sections are being set if ( isset ( $response->sections ) ) $response->sections = (array)$response->sections; //if banners are being set if ( isset ( $response->banners ) ) $response->banners = (array)$response->banners; //if icons being set, convert to array if ( isset ( $response->icons ) ) $response->icons = (array)$response->icons; return $response; } /** * prepare param for API request * * @param $action * @param array $args * @return array */ public function prepare_request($action, $args = array()) { global $wp_version; $licence_key = get_site_option('wpfunnels_pro_license_key'); return array( 'woo_sl_action' => $action, 'version' => WPFNL_PRO_VERSION, 'product_unique_id' => WPFNL_PRO_PRODUCT_ID, 'licence_key' => $licence_key, 'domain' => WPFNL_PRO_INSTANCE, 'wp-version' => $wp_version, 'api_version' => $this->API_VERSION, ); } } ?>includes/class-wpfnl-pro.php000064400000034352147600245720012130 0ustar00 */ class Wpfnl_Pro { /** * @var Wpfnl_Pro_Admin Instance. */ public $admin; /** * Instance. * * Holds the plugin instance. * * @since 1.0.0 * @access public * @static * * @var Wpfnl_Pro */ public static $instance = null; /** * Holds the list of all registered modules * * @var Widget_Manager $widget_manager * @since 1.0.0 */ public $widget_manager; /** * Holds the list of all registered modules * * @var Module_Manager $module_manager * @since 1.0.0 */ public $module_manager; /** * Holds the list of all integrations * * @var integration_manager $integration_manager * @since 1.0.0 */ public $integration_manager; /** * factory class for payment gateways * * @var Payment_Gateways_Factory $payment_gateways * @since 1.0.0 */ public $payment_gateways; protected $license; /** * * * @var integration_manager $integration_manager * @since 1.0.1 */ public $checkout_fields; /** * The loader that's responsible for maintaining and registering all hooks that power * the plugin. * * @since 1.0.0 * @access protected * @var Wpfnl_Pro_Loader $loader Maintains and registers all hooks for the plugin. */ protected $loader; /** * The unique identifier of this plugin. * * @since 1.0.0 * @access protected * @var string $plugin_name The string used to uniquely identify this plugin. */ protected $plugin_name; /** * The current version of the plugin. * * @since 1.0.0 * @access protected * @var string $version The current version of the plugin. */ protected $version; /** * The database version. * * @since 1.0.0 * @access protected * @var string */ protected $db_version; public $wpfnl_pro_db; public $session; /** * frontend module of funnel pro * * @var $frontend */ public $frontend; public $analytics; public $webhook; public $order_meta; public $order_refund; /** * @var $orders */ public $orders; public $notices; public $offer_subscription; /** * crm integrations * * @var $crm_integrations string * @access public */ public $crm_integrations; public $gtm_integration; public $facebook_pixel; public $gtm; public $shortcodes; public $wp_affiliate; public $ab_testing; public $mint; public $export_funnel; public $import_funnel; /** * Reporting object * * @var $reporting * * @since 3.2.0 */ protected $reporting; /** * Define the core functionality of the plugin. * * Set the plugin name and the plugin version that can be used throughout the plugin. * Load the dependencies, define the locale, and set the hooks for the admin area and * the public-facing side of the site. * * @since 1.0.0 */ public function __construct() { $this->version = '1.0.0'; $this->db_version = '1.0.0'; if (defined('WPFNL_PRO_VERSION')) { $this->version = WPFNL_PRO_VERSION; } if (defined('WPFNL_PRO_DB_VERSION')) { $this->db_version = WPFNL_PRO_DB_VERSION; } $this->plugin_name = 'wpfnl-pro'; $this->load_dependencies(); $this->set_locale(); $this->initialize(); add_action( 'plugins_loaded', array( $this, 'init' ), 100 ); } public function initialize() { Wpfnl_Pro_Hooks::getInstance()->init(); } /** * Load the required dependencies for this plugin. * * Include the following files that make up the plugin: * * - Wpfnl_Pro_Loader. Orchestrates the hooks of the plugin. * - Wpfnl_Pro_i18n. Defines internationalization functionality. * - Wpfnl_Pro_Admin. Defines all hooks for the admin area. * - Wpfnl_Pro_Public. Defines all hooks for the public side of the site. * * Create an instance of the loader which will be used to register the hooks * with WordPress. * * @since 1.0.0 * @access private */ private function load_dependencies() { /** * The class responsible for auto loading all files of the * core plugin. */ require_once plugin_dir_path(dirname(__FILE__)) . 'vendor/autoload.php'; /** * The class responsible for orchestrating the actions and filters of the * core plugin. */ require_once plugin_dir_path(dirname(__FILE__)) . 'includes/utils/class-wpfnl-pro-loader.php'; /** * The class responsible for defining internationalization functionality * of the plugin. */ require_once plugin_dir_path(dirname(__FILE__)) . 'includes/utils/class-wpfnl-pro-i18n.php'; /** * The class responsible for defining all actions that occur in the admin area. */ require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-wpfnl-pro-admin.php'; /** * The class responsible for defining all actions that occur in the public-facing * side of the site. */ require_once plugin_dir_path(dirname(__FILE__)) . 'public/class-wpfnl-pro-public.php'; $this->loader = new Wpfnl_Pro_Loader(); } /** * Define the locale for this plugin for internationalization. * * Uses the Wpfnl_Pro_i18n class in order to set the domain and to register the hook * with WordPress. * * @since 1.0.0 * @access private */ private function set_locale() { $plugin_i18n = new Wpfnl_Pro_i18n(); $this->loader->add_action('init', $plugin_i18n, 'load_plugin_textdomain'); add_filter( 'wpfunels/bricks_elements', array($this, 'add_bricks_elements'), 10 ); } /** * Initialize the core of the plugin * * @since 1.0.0 */ public function init() { if ( ! did_action( 'wpfunnels/init' ) ) { return; } if ( ! version_compare( WPFNL_VERSION, WPFNL_REQUIRED_VERSION, '>=' ) ) { add_action( 'admin_notices', array( $this, 'may_be_failed_to_load_pro_plugin_notice' ) ); return; } $this->admin = new Admin_Pro( $this->get_plugin_name(), $this->get_version() ); $this->frontend = Frontend::getInstance(); $this->module_manager = new Module_Manager(); $this->checkout_fields = new CheckoutFields(); $this->session = new Wpfnl_Pro_Session(); $this->payment_gateways = Payment_Gateways_Factory::getInstance(); $this->analytics = Analytics::getInstance(); $this->webhook = Wpfnl_Pro_Webhook::getInstance(); $this->license = Wpfnl_Pro_Licensing::getInstance(); $this->widget_manager = Widget_Manager::getInstance()->init(); $this->order_meta = OrderMeta::getInstance(); $this->order_refund = Wpfnl_Order_Refund::getInstance(); $this->orders = Orders::getInstance(); $this->notices = Notices::getInstance(); $this->offer_subscription = Offer_Subscription::getInstance(); $this->gtm = GTM::getInstance()->init_actions(); $this->facebook_pixel = Facebook_Pixel_Integration::getInstance()->init_actions(); $this->shortcodes = Wpfnl_Pro_Shortcodes::getInstance()->init(); $this->wp_affiliate = Wpfnl_Pro_Integration_WPAffiliate::getInstance(); if ( !version_compare( WPFNL_VERSION, '3.0.0', '>=' ) ) { $this->ab_testing = Backup_Ab_Testing_Hook::getInstance()->init(); }else{ $this->ab_testing = Wpfnl_Ab_Testing_Hook::getInstance()->init(); } $instance = new Wpfnl_functions(); if( method_exists($instance,'is_mint_mrm_active') && Wpfnl_functions::is_mint_mrm_active() ){ if ( !version_compare( WPFNL_VERSION, '3.0.0', '>=' ) ) { $this->mint = Backup_Mint_Hook::getInstance()->init(); }else{ $this->mint = Wpfnl_Mint_Hook::getInstance()->init(); } } $this->export_funnel = Wpfnl_Export::getInstance()->init_ajax(); $this->import_funnel = new Wpfnl_Import(); $this->frontend->set_name($this->get_plugin_name()); $this->frontend->set_version($this->get_version()); $this->checkout_fields->init(); $this->reporting = Reporting::get_instance(); $this->reporting->init(); do_action( 'wpfunnels/pro_init' ); } /** * fires admin notice if * WPF version is not up to date * * @since 1.2.0 */ public function may_be_failed_to_load_pro_plugin_notice() { if ( ! current_user_can( 'update_plugins' ) ) { return; } $class = 'notice notice-error'; $message = __("It appears you have an older version of WPFunnels (Basic) . This may cause some issues with the plugin's functionality. Please update WPFunnels (Basic) to v". WPFNL_REQUIRED_VERSION ." and above.",'wpfnl-pro'); printf( '

%2$s

', $class, $message ); } public function run_plugin_updater() { new Wpfnl_Pro_Updater(WPFNL_PRO_API_URL, 'wpfunnels-pro', 'wpfunnels-pro/wpfnl-pro.php'); } /** * Run the loader to execute all of the hooks with WordPress. * * @since 1.0.0 */ public function run() { $this->loader->run(); } /** * The name of the plugin used to uniquely identify it within the context of * WordPress and to define internationalization functionality. * * @return string The name of the plugin. * @since 1.0.0 */ public function get_plugin_name() { return $this->plugin_name; } /** * The reference to the class that orchestrates the hooks with the plugin. * * @return Wpfnl_Pro_Loader Orchestrates the hooks of the plugin. * @since 1.0.0 */ public function get_loader() { return $this->loader; } /** * Retrieve the version number of the plugin. * * @return string The version number of the plugin. * @since 1.0.0 */ public function get_version() { return $this->version; } /** * Get the Database Version of the plugin. * * @return string * @since 1.0.0 */ public function get_db_version() { return $this->db_version; } /** * Instance. * * Ensures only one instance of the plugin class is loaded or can be loaded. * * @return Wpfnl_Pro An instance of the class. * @since 1.0.0 * @access public * @static * */ public static function instance() { if (is_null(self::$instance)) { self::$instance = new self(); } return self::$instance; } /** * Adds bricks elements to the existing elements array. * * @param array $elements The array of elements to add bricks elements to. * * @since 2.1.0 */ public function add_bricks_elements( $elements ){ $element = [ [ 'file' => WPFNL_PRO_DIR. 'includes/core/widgets/bricks/OfferButton.php', 'class' => '\WPFunnelsPro\Widgets\Bricks\OfferButton', ], ]; $elements = array_merge($elements, $element); return $elements; } } Wpfnl_Pro::instance(); if ( ! function_exists( '_is_wpfunnels_installed' ) ) { /** * check if wpfunnels free plugin is installed * * @return bool */ function _is_wpfunnels_installed() { $path = 'wpfunnels/wpfnl.php'; $plugins = get_plugins(); return isset( $plugins[ $path ] ); } } includes/index.php000064400000000032147600245720010174 0ustar00\n" "Language-Team: \n" "Language: \n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Loco https://localise.biz/\n" "X-Loco-Version: 2.5.8; wp-5.9\n" "X-Domain: wpfnl-pro" #: wpfnl-pro.php:171 wpfnl-pro.php:184 build/wpfnl-pro.php:171 #: build/wpfnl-pro.php:184 msgid " and above." msgstr "" #: public/gateways/class-wpfnl-pro-gateway-authorize-net.php:331 #: build/public/gateways/class-wpfnl-pro-gateway-authorize-net.php:331 #, php-format msgid "%1$s %2$s Release Payment Approved: %3$s ending in %4$s (expires %5$s)" msgstr "" #: public/gateways/class-wpfnl-pro-gateway-authorize-net.php:308 #: build/public/gateways/class-wpfnl-pro-gateway-authorize-net.php:308 #, php-format msgid "%1$s - Release Payment for Order %2$s" msgstr "" #: public/gateways/class-wpfnl-pro-gateway-authorize-net.php:335 #: build/public/gateways/class-wpfnl-pro-gateway-authorize-net.php:335 #, php-format msgid "(Transaction ID %s)" msgstr "" #: admin/partials/refund-metabox.php:158 #: build/admin/partials/refund-metabox.php:158 msgid "" "**P.S. When handling refunds, always refund upsell/dowsell offers first, and " "then refund the main order." msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:171 #: includes/core/widgets/elementor/elements/offer-button.php:172 #: build/includes/core/widgets/elementor/elements/offer-button.php:171 #: build/includes/core/widgets/elementor/elements/offer-button.php:172 #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:147 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:147 msgid "Accept Offer" msgstr "" #: admin/partials/refund-metabox.php:61 #: build/admin/partials/refund-metabox.php:61 msgid "Action" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:232 #: build/includes/core/widgets/elementor/elements/offer-button.php:232 msgid "After" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:40 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:40 msgid "Alignemnt" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:145 #: includes/core/widgets/elementor/elements/offer-product-widget.php:196 #: includes/core/widgets/elementor/elements/offer-product-widget.php:247 #: includes/core/widgets/elementor/elements/offer-product-widget.php:301 #: includes/core/widgets/elementor/elements/offer-button.php:180 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:145 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:196 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:247 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:301 #: build/includes/core/widgets/elementor/elements/offer-button.php:180 msgid "Alignment" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:381 #: build/includes/core/widgets/elementor/elements/offer-button.php:381 msgid "Animation" msgstr "" #: public/gateways/class-wpfnl-pro-gateway-authorize-net.php:350 #: build/public/gateways/class-wpfnl-pro-gateway-authorize-net.php:350 msgid "Authorization only transaction" msgstr "" #: public/gateways/class-wpfnl-pro-gateway-authorize-net.php:230 #: public/gateways/class-wpfnl-pro-gateway-authorize-net.php:237 #: build/public/gateways/class-wpfnl-pro-gateway-authorize-net.php:230 #: build/public/gateways/class-wpfnl-pro-gateway-authorize-net.php:237 #, php-format msgid "Authorize.net CIM Transaction Failed (%s)" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:320 #: includes/core/widgets/elementor/elements/offer-button.php:356 #: build/includes/core/widgets/elementor/elements/offer-button.php:320 #: build/includes/core/widgets/elementor/elements/offer-button.php:356 msgid "Background Color" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:231 #: build/includes/core/widgets/elementor/elements/offer-button.php:231 msgid "Before" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:394 #: build/includes/core/widgets/elementor/elements/offer-button.php:394 msgid "Border" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:367 #: build/includes/core/widgets/elementor/elements/offer-button.php:367 msgid "Border Color" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:405 #: build/includes/core/widgets/elementor/elements/offer-button.php:405 msgid "Border Radius" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:274 #: build/includes/core/widgets/elementor/elements/offer-button.php:274 #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:71 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:71 msgid "Button" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:135 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:135 msgid "Button Text" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:153 #: includes/core/widgets/elementor/elements/offer-product-widget.php:204 #: includes/core/widgets/elementor/elements/offer-product-widget.php:255 #: includes/core/widgets/elementor/elements/offer-product-widget.php:309 #: includes/core/widgets/elementor/elements/offer-button.php:188 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:153 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:204 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:255 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:309 #: build/includes/core/widgets/elementor/elements/offer-button.php:188 msgid "Center" msgstr "" #: admin/partials/refund-metabox.php:58 #: build/admin/partials/refund-metabox.php:58 msgid "Cost" msgstr "" #. placeholder is blogname #: public/gateways/class-wpfnl-pro-gateway-paypal-express.php:690 #: public/gateways/class-wpfnl-pro-gateway-paypal-standard.php:643 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-express.php:690 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-standard.php:643 #, php-format msgctxt "data sent to paypal" msgid "Orders with %s" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:163 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:163 msgid "Downsell" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:121 #: build/includes/core/widgets/elementor/elements/offer-button.php:121 msgid "Extra Large" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:117 #: build/includes/core/widgets/elementor/elements/offer-button.php:117 msgid "Extra Small" msgstr "" #: includes/core/rest-api/controllers/AutomationController.php:109 #: includes/core/rest-api/controllers/AutomationController.php:125 #: includes/core/rest-api/controllers/AutomationController.php:141 #: includes/core/rest-api/controllers/AutomationController.php:157 #: includes/core/rest-api/controllers/AutomationController.php:173 #: includes/core/rest-api/controllers/AutomationController.php:200 #: build/includes/core/rest-api/controllers/AutomationController.php:109 #: build/includes/core/rest-api/controllers/AutomationController.php:125 #: build/includes/core/rest-api/controllers/AutomationController.php:141 #: build/includes/core/rest-api/controllers/AutomationController.php:157 #: build/includes/core/rest-api/controllers/AutomationController.php:173 #: build/includes/core/rest-api/controllers/AutomationController.php:200 msgid "Funnel ID." msgstr "" #: includes/core/classes/class-wpfnl-pro-orders.php:47 #: build/includes/core/classes/class-wpfnl-pro-orders.php:47 #, php-format msgid "Funnel Order Accepted (%s)" msgid_plural "Funnel Order Accepted (%s)" msgstr[0] "" msgstr[1] "" #. Description of the plugin msgid "" "Get advanced funnel features such as Upsell, Downsell, and more premium " "funnel templates in WPFunnels." msgstr "" #: public/gateways/class-wpfnl-pro-gateway-paypal-express.php:878 #: public/gateways/class-wpfnl-pro-gateway-paypal-standard.php:829 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-express.php:878 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-standard.php:829 msgctxt "" "hash before the order number. Used as a character to remove from the actual " "order number" msgid "#" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:82 #: includes/core/widgets/elementor/elements/offer-product-widget.php:94 #: includes/core/widgets/elementor/elements/offer-product-widget.php:105 #: includes/core/widgets/elementor/elements/offer-product-widget.php:116 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:82 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:94 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:105 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:116 msgid "Hide" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:338 #: build/includes/core/widgets/elementor/elements/offer-button.php:338 msgid "Hover" msgstr "" #. URI of the plugin #. Author URI of the plugin msgid "https://getwpfunnels.com" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:227 #: build/includes/core/widgets/elementor/elements/offer-button.php:227 msgid "Icon Position" msgstr "" #: admin/partials/refund-metabox.php:156 #: build/admin/partials/refund-metabox.php:156 msgid "" "In this section, you may refund the orders that you sold as Upsell /Downsell." " To refund other orders, use the default refund feature in WooCommerce." msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:138 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:138 msgid "Input your desired button text, or leave blank for no button." msgstr "" #: admin/partials/refund-metabox.php:56 #: build/admin/partials/refund-metabox.php:56 msgid "Item" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:196 #: build/includes/core/widgets/elementor/elements/offer-button.php:196 msgid "Justified" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:120 #: build/includes/core/widgets/elementor/elements/offer-button.php:120 msgid "Large" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:149 #: includes/core/widgets/elementor/elements/offer-product-widget.php:200 #: includes/core/widgets/elementor/elements/offer-product-widget.php:251 #: includes/core/widgets/elementor/elements/offer-product-widget.php:305 #: includes/core/widgets/elementor/elements/offer-button.php:184 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:149 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:200 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:251 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:305 #: build/includes/core/widgets/elementor/elements/offer-button.php:184 msgid "Left" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:54 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:54 msgid "Main Element" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:119 #: build/includes/core/widgets/elementor/elements/offer-button.php:119 msgid "Medium" msgstr "" #: public/modules/upsell/class-wpfnl-upsell.php:515 #: build/public/modules/upsell/class-wpfnl-upsell.php:515 msgid "No payment gateway found" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:301 #: build/includes/core/widgets/elementor/elements/offer-button.php:301 msgid "Normal" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:144 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:144 msgid "Offer Action" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:33 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:33 msgid "Offer Button" msgstr "" #: public/modules/thank-you/templates/child-orders.php:53 #: build/public/modules/thank-you/templates/child-orders.php:53 msgid "Offer Order Details" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:237 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:237 msgid "Offer Product Description" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:289 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:289 msgid "Offer Product Image" msgstr "" #: public/modules/upsell/class-wpfnl-upsell.php:503 #: build/public/modules/upsell/class-wpfnl-upsell.php:503 msgid "Offer product is added successfully" msgstr "" #: public/modules/upsell/class-wpfnl-upsell.php:508 #: build/public/modules/upsell/class-wpfnl-upsell.php:508 msgid "Offer product is not added" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:28 #: includes/core/widgets/elementor/elements/offer-product-widget.php:72 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:28 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:72 msgid "Offer Product Meta" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:186 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:186 msgid "Offer Product Price" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:134 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:134 msgid "Offer Product Title" msgstr "" #: admin/partials/refund-metabox.php:57 #: build/admin/partials/refund-metabox.php:57 #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:159 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:159 msgid "Offer Type" msgstr "" #: public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php:246 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php:246 msgid "Order Captured successfully" msgstr "" #: public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php:177 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php:177 msgid "Order created successfully" msgstr "" #: public/modules/upsell/class-wpfnl-upsell.php:320 #: public/modules/upsell/class-wpfnl-upsell.php:375 #: build/public/modules/upsell/class-wpfnl-upsell.php:320 #: build/public/modules/upsell/class-wpfnl-upsell.php:375 msgid "Order does not exist" msgstr "" #: includes/core/classes/class-wpfnl-pro-orders.php:38 #: build/includes/core/classes/class-wpfnl-pro-orders.php:38 msgctxt "Order status" msgid "Funnel Order Accepted" msgstr "" #: includes/core/classes/class-wpfnl-pro-orders.php:39 #: build/includes/core/classes/class-wpfnl-pro-orders.php:39 msgctxt "Order status" msgid "Funnel Partially Refunded" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:425 #: build/includes/core/widgets/elementor/elements/offer-button.php:425 msgid "Padding" msgstr "" #: public/gateways/class-wpfnl-pro-gateway-mollie-credit-card.php:190 #: build/public/gateways/class-wpfnl-pro-gateway-mollie-credit-card.php:190 msgid "Payment processed for mollie credit card." msgstr "" #: public/gateways/class-wpfnl-pro-gateway-mollie-ideal.php:190 #: build/public/gateways/class-wpfnl-pro-gateway-mollie-ideal.php:190 msgid "Payment processed for mollie ideal payment." msgstr "" #: public/gateways/class-wpfnl-pro-gateway-authorize-net.php:312 #: build/public/gateways/class-wpfnl-pro-gateway-authorize-net.php:312 msgid "Payment token missing/invalid." msgstr "" #: public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php:168 #: public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php:234 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php:168 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php:234 msgid "PayPal order is not created" msgstr "" #: public/class-wpfnl-pro-public.php:164 #: build/public/class-wpfnl-pro-public.php:164 msgid "Please wait while processing your order" msgstr "" #: public/modules/thank-you/templates/child-orders.php:59 #: build/public/modules/thank-you/templates/child-orders.php:59 msgid "Product" msgstr "" #: includes/utils/class-wpfnl-pro-functions.php:427 #: build/includes/utils/class-wpfnl-pro-functions.php:427 msgid "Product Added Successfully." msgstr "" #: public/modules/upsell/class-wpfnl-upsell.php:476 #: build/public/modules/upsell/class-wpfnl-upsell.php:476 msgid "Product is out of stock" msgstr "" #: public/modules/checkout/templates/variable-product/product-title.php:4 #: build/public/modules/checkout/templates/variable-product/product-title.php:4 msgid "Product name: " msgstr "" #: public/gateways/class-wpfnl-pro-gateway-mollie-ideal.php:69 #: public/gateways/class-wpfnl-pro-gateway-mollie-credit-card.php:90 #: public/gateways/class-wpfnl-pro-gateway-stripe.php:188 #: build/public/gateways/class-wpfnl-pro-gateway-mollie-ideal.php:69 #: build/public/gateways/class-wpfnl-pro-gateway-mollie-credit-card.php:90 #: build/public/gateways/class-wpfnl-pro-gateway-stripe.php:188 msgid "Product price is less than 0" msgstr "" #: admin/partials/refund-metabox.php:59 #: build/admin/partials/refund-metabox.php:59 msgid "Quantity" msgstr "" #: public/modules/checkout/templates/variable-product/product-qty.php:5 #: build/public/modules/checkout/templates/variable-product/product-qty.php:5 msgid "Quantity: " msgstr "" #: admin/partials/refund-metabox.php:138 #: build/admin/partials/refund-metabox.php:138 msgid "Refund" msgstr "" #: admin/classes/class-wpfnl-refund.php:207 #: build/admin/classes/class-wpfnl-refund.php:207 msgid "Refund Successful" msgstr "" #: admin/classes/class-wpfnl-refund.php:203 #: build/admin/classes/class-wpfnl-refund.php:203 msgid "Refund Unsuccessful" msgstr "" #: admin/classes/class-wpfnl-refund.php:162 #: build/admin/classes/class-wpfnl-refund.php:162 msgid "Refund unsuccessful" msgstr "" #: admin/partials/refund-metabox.php:141 #: build/admin/partials/refund-metabox.php:141 msgid "Refunded" msgstr "" #: admin/partials/refund-metabox.php:162 #: build/admin/partials/refund-metabox.php:162 msgid "Refunds are not available for any offer(s) against this order." msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:148 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:148 msgid "Reject Offer" msgstr "" #: public/gateways/class-wpfnl-pro-gateway-authorize-net.php:362 #: public/gateways/class-wpfnl-pro-gateway-authorize-net.php:364 #: build/public/gateways/class-wpfnl-pro-gateway-authorize-net.php:362 #: build/public/gateways/class-wpfnl-pro-gateway-authorize-net.php:364 #, php-format msgid "Release Payment Failed: %s" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:157 #: includes/core/widgets/elementor/elements/offer-product-widget.php:208 #: includes/core/widgets/elementor/elements/offer-product-widget.php:259 #: includes/core/widgets/elementor/elements/offer-product-widget.php:313 #: includes/core/widgets/elementor/elements/offer-button.php:192 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:157 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:208 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:259 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:313 #: build/includes/core/widgets/elementor/elements/offer-button.php:192 msgid "Right" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:143 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:143 msgid "Select Button Action" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:158 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:158 msgid "Select Button Type" msgstr "" #: public/modules/checkout/templates/variable-product/header.php:2 #: build/public/modules/checkout/templates/variable-product/header.php:2 msgid "Select your variation" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:81 #: includes/core/widgets/elementor/elements/offer-product-widget.php:93 #: includes/core/widgets/elementor/elements/offer-product-widget.php:104 #: includes/core/widgets/elementor/elements/offer-product-widget.php:115 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:81 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:93 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:104 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:115 msgid "Show" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:102 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:102 msgid "Show Description" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:113 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:113 msgid "Show Image" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:91 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:91 msgid "Show Price" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:79 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:79 msgid "Show title" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:208 #: build/includes/core/widgets/elementor/elements/offer-button.php:208 msgid "Size" msgstr "" #: admin/partials/refund-metabox.php:92 #: build/admin/partials/refund-metabox.php:92 msgid "SKU" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:118 #: build/includes/core/widgets/elementor/elements/offer-button.php:118 msgid "Small" msgstr "" #: includes/core/classes/class-wpfnl-pro-subscription.php:245 #: build/includes/core/classes/class-wpfnl-pro-subscription.php:245 msgid "Subscription replaced by the Offer Product" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:169 #: build/includes/core/widgets/elementor/elements/offer-button.php:169 msgid "Text" msgstr "" #: includes/core/widgets/elementor/elements/offer-product-widget.php:168 #: includes/core/widgets/elementor/elements/offer-product-widget.php:219 #: includes/core/widgets/elementor/elements/offer-product-widget.php:270 #: includes/core/widgets/elementor/elements/offer-product-widget.php:325 #: includes/core/widgets/elementor/elements/offer-button.php:308 #: includes/core/widgets/elementor/elements/offer-button.php:345 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:168 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:219 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:270 #: build/includes/core/widgets/elementor/elements/offer-product-widget.php:325 #: build/includes/core/widgets/elementor/elements/offer-button.php:308 #: build/includes/core/widgets/elementor/elements/offer-button.php:345 msgid "Text Color" msgstr "" #: admin/partials/refund-metabox.php:60 #: build/admin/partials/refund-metabox.php:60 #: public/modules/thank-you/templates/child-orders.php:60 #: build/public/modules/thank-you/templates/child-orders.php:60 msgid "Total" msgstr "" #: public/gateways/class-wpfnl-pro-gateway-paypal-express.php:1394 #: public/gateways/class-wpfnl-pro-gateway-paypal-standard.php:1344 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-express.php:1394 #: build/public/gateways/class-wpfnl-pro-gateway-paypal-standard.php:1344 msgid "Unable to find order for PayPal billing agreement." msgstr "" #: public/modules/upsell/class-wpfnl-upsell.php:200 #: build/public/modules/upsell/class-wpfnl-upsell.php:200 msgid "Unauthorized request" msgstr "" #: public/modules/checkout/templates/variable-product/product-qty.php:11 #: build/public/modules/checkout/templates/variable-product/product-qty.php:11 msgid "Update cart" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:162 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:162 msgid "Upsell" msgstr "" #: includes/core/widgets/elementor/elements/offer-button.php:263 #: build/includes/core/widgets/elementor/elements/offer-button.php:263 msgid "View" msgstr "" #: includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:24 #: build/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php:24 msgid "WPF Offer Button" msgstr "" #. Name of the plugin msgid "WPFunnels Pro" msgstr "" #: admin/classes/class-wpfnl-refund.php:47 #: build/admin/classes/class-wpfnl-refund.php:47 msgid "WPFunnels Refund Offer" msgstr "" #. Author of the plugin msgid "WPFunnels Team" msgstr "" #: includes/core/class-wpfnl-pro-hooks.php:154 #: build/includes/core/class-wpfnl-pro-hooks.php:154 msgid "" "WPFunnels Upsell/Downsell does not support the following payment gateways " "which are enabled on your site" msgstr "" #: admin/classes/class-wpfnl-pro-notices.php:29 #: build/admin/classes/class-wpfnl-pro-notices.php:29 #, php-format msgid "" "Your WPFunnels Pro License is not activate. Please, go to " "the WPFunnels > License menu and activate the license to use all the pro " "features of WPFunnels Pro . Activate now." "" msgstr "" #: public/class-wpfnl-pro-public.php:202 #: build/public/class-wpfnl-pro-public.php:202 msgid "Your session is expired" msgstr "" public/assets/css/wpfnl-pro-public.css000064400000012751147600245720014043 0ustar00/** * All of the CSS for your public-facing functionality should be * included in this file. */ .offer-btn-d-inline-block{ display: inline-block; } .wpfunnels-offer-loader .wpfnl-loader { border: 3px solid #d6d6d6; border-top: 3px solid #6E42D3; width: 50px; height: 50px; } .wpfunnels-offer-loader { background: #6e42d326; position: fixed; top: 0; left: 0; z-index: 99; width: 100%; height: 100%; display: none; } .wpfunnels-offer-loader-wrapper { display: flex; flex-flow: column; align-items: center; justify-content: center; height: 210px; width: 360px; background: #fff; box-shadow: 0px 10px 60px rgba(186, 159, 254, 0.2); position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); border-radius: 15px; padding: 20px; text-align: center; } .wpfunnels-offer-loader .wpfnl-loader { display: block; } .wpfunnels-offer-loader .loader-ittle { font-size: 22px; font-weight: 600; margin: 10px 0 5px; } .wpfunnels-offer-loader p { font-size: 16px; } .dot-wrapper span { color: #000; font-size: 16px; margin-left: -1px; animation-duration: 1.5s; animation-iteration-count: infinite; } .dot-wrapper span.dot-one { animation-name: flashOne; } .dot-wrapper span.dot-two { animation-name: flashTwo; } .dot-wrapper span.dot-three { animation-name: flashThree; } @keyframes flashOne { 0% { opacity: 0; } 25% { opacity: 1; } 50% { opacity: 1; } 100% { opacity: 1; } } @keyframes flashTwo { 0% { opacity: 0; } 25% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 1; } } @keyframes flashThree { 0% { opacity: 0; } 50% { opacity: 0; } 75% { opacity: 1; } 100% { opacity: 1; } } .checkout-radio .radio { display: inline!important; margin-left: 3px; margin-right: 10px; } /* --------- unsupported payment modal-------------- */ .wpfnl-offer-payment-form { position: fixed; inset: 0; z-index: 99; padding: 30px 20px; background: #3333336b; } .wpfnl-offer-payment-form .wpfnl-offer-payment-form-inner { width: 100%; height: 100%; display: flex; flex-flow: column; align-items: center; justify-content: center; } .wpfnl-offer-payment-form .wpfnl-offer-payment-form-wrapper { max-width: 890px; width: 100%; max-height: calc(100% - 60px); min-height: 490px; position: relative; background: #fff; border-radius: 20px; } .wpfnl-offer-payment-form .wpfnl-offer-payment-form-wrapper .wpfnl-offer-payment-title { font-size: 22px; font-weight: 600; padding: 18px 30px; border-bottom: 1px solid #E5E8F3; border-radius: 20px 20px 0 0; } .wpfnl-offer-payment-form .wpfnl-offer-payment-form-wrapper .modal-overflow { width: 100%; height: calc(100% - 66px); overflow: auto; border-radius: 0 0 19px 19px; } .wpfnl-offer-payment-form .close-modal { border: none; background: #fff!important; position: absolute; right: -12px; top: -12px; padding: 0; line-height: 1; height: 40px; width: 40px; display: flex; align-items: center; justify-content: center; border-radius: 100%; z-index: 2; cursor: pointer; } .wpfnl-offer-payment-form .woocommerce-checkout #payment { background: transparent; border-radius: 0; border: none; } .wpfnl-offer-payment-form .modal-loader { padding: 30px; text-align: center; height: calc(100% - 50px); display: flex; align-items: center; flex-flow: column; justify-content: center; } .wpfnl-offer-payment-form .modal-loader p { font-size: 14px; } .wpfnl-offer-payment-form .modal-loader h4 { font-size: 20px; font-weight: 700; display: flex; align-items: center; gap: 8px; } .wpfnl-offer-payment-form .wpfnl-loader { display: block; margin:0; width: 30px; height: 30px; margin-bottom: 10px; } /* ----place oder button's checkbox styel------- */ .wpfnl-offer-payment-form .place-order > label.woocommerce-form__label-for-checkbox { margin-bottom: 15px!important; display: block!important; } .wpfnl-offer-payment-form .place-order > label.woocommerce-form__label-for-checkbox input[type="checkbox"] { display: none; } .wpfnl-offer-payment-form .place-order > label.woocommerce-form__label-for-checkbox > span { font-size: 16px; line-height : 1.2; position: relative; cursor: pointer; padding-left: 30px; width: 100%; display: block !important; min-height: 14px; rgba(54, 59, 78, 0.8) } .wpfnl-offer-payment-form .place-order > label.woocommerce-form__label-for-checkbox > span:before { content: ""; position: absolute; top: 2px; left: 0; width: 18px; height: 18px; border: 2px solid #72737e; border-radius: 3px; transition: all 0.3s ease; } .wpfnl-offer-payment-form .place-order > label.woocommerce-form__label-for-checkbox > span:after { content: ""; position: absolute; top: 4px; left: 6px; width: 7px; height: 11px; background: transparent; border-radius: 2px; opacity: 0; transform: rotate(45deg) skewX(15deg); transition: all 0.3s ease; border: 3px solid #fff; border-left: none; border-top: none; } .wpfnl-offer-payment-form .place-order > label.woocommerce-form__label-for-checkbox input[type="checkbox"]:checked + span:before { border-color: #6E42D3; background-color: #6E42D3; } .wpfnl-offer-payment-form .place-order > label.woocommerce-form__label-for-checkbox input[type="checkbox"]:checked + span:after { opacity: 1; }public/assets/js/wpfnl-pro-public.js000064400000112473147600245720013515 0ustar00(function ($) { "use strict"; /** * All of the code for your public-facing JavaScript source * should reside in this file. * * Note: It has been assumed you will write jQuery code here, so the * $ function reference has been prepared for usage within the scope * of this function. * * This enables you to define handlers, for when the DOM is ready: * * $(function() { * * }); * * When the window is loaded: * * $( window ).load(function() { * * }); * * ...and/or other possibilities. * * Ideally, it is not considered best practise to attach more than a * single DOM-ready or window-load handler for a particular page. * Although scripts in the WordPress core, Plugins and Themes may be * practising this, we should strive to set a better example in our own work. */ /** * process offer order * * @param data */ var wpfunnels_process_offer = function (data) { var ajaxurl = wpfnl_obj.ajaxurl; $.ajax({ type: "POST", url: ajaxurl, data: data, success: function (response) { $(".wpfunnels-offer-loader p.description").text(response.message); if (response.status === "success") { $("#wpfnl-alert-accept").addClass("wpfnl-success"); if (response.redirect_url != undefined) { setTimeout(function () { $(".wpfunnels-offer-loader p.description").text("Redirecting..."); window.location.href = response.redirect_url; }, 1000); } } else { $(".wpfunnels-offer-loader p.description").text(response.message); setTimeout(function () { $(".wpfunnels-offer-loader").hide(); }, 1500); } }, }); }; /** * get query param from url * * @param sParam * @returns {boolean|string} */ function getUrlParameter(sParam) { var sPageURL = window.location.search.substring(1), sURLVariables = sPageURL.split("&"), sParameterName, i; for (i = 0; i < sURLVariables.length; i++) { sParameterName = sURLVariables[i].split("="); if (sParameterName[0] === sParam) { return sParameterName[1] === undefined ? true : decodeURIComponent(sParameterName[1]); } } } function wpfunnels_capture_woocommerce_paypal_order() { if ("undefined" !== typeof window.WPFunnelsOfferVars) { if ("upsell" === window.WPFunnelsOfferVars.offer_type || "downsell" === window.WPFunnelsOfferVars.offer_type) { var is_paypal_return = getUrlParameter("wpfnl-paypal-return"), security = ""; if (is_paypal_return) { security = window.WPFunnelsOfferVars.wpfnl_capture_paypal_order_nonce; var ajax_data = { order_id: window.WPFunnelsOfferVars.order_id, step_id: window.WPFunnelsOfferVars.step_id, action: "wpfnl_capture_paypal_order", step_type: window.WPFunnelsOfferVars.offer_type, security: security, }; $(".wpfunnels-offer-loader").show(); $.ajax({ url: window.WPFunnelsOfferVars.ajaxUrl, data: ajax_data, dataType: "json", type: "POST", success: function (response) { var ajax_data = { action: "", step_id: window.WPFunnelsOfferVars.step_id, funnel_id: window.WPFunnelsOfferVars.funnel_id, order_id: window.WPFunnelsOfferVars.order_id, order_key: window.WPFunnelsOfferVars.order_key, offer_type: window.WPFunnelsOfferVars.offer_type, offer_action: "yes", product_id: window.WPFunnelsOfferVars.product_id, quantity: window.WPFunnelsOfferVars.quantity, attr: "", is_variable: window.WPFunnelsOfferVars.is_variable, }; if (window.WPFunnelsOfferVars.is_variable) { ajax_data.variation_id = getUrlParameter("wpfnl-variation-id"); } var offer_type = window.WPFunnelsOfferVars.offer_type; if ("upsell" === ajax_data.offer_type) { ajax_data.action = "wpfnl_" + offer_type + "_accepted"; ajax_data.security = window.WPFunnelsOfferVars.wpfnl_upsell_accepted_nonce; } else if ("downsell" === ajax_data.offer_type) { ajax_data.action = "wpfnl_" + offer_type + "_accepted"; ajax_data.security = window.WPFunnelsOfferVars.wpfnl_downsell_accepted_nonce; } wpfunnels_process_offer(ajax_data); }, }); } } } } /** * capture mollie payment from url */ var wpfunnels_capture_woocommerce_mollie_order = function () { let is_mollie_return = getUrlParameter("wpfnl-mollie-return"), nonce = ""; if (is_mollie_return) { var offer_type = window.WPFunnelsOfferVars.offer_type; var ajax_data = { action: "", step_id: window.WPFunnelsOfferVars.step_id, funnel_id: window.WPFunnelsOfferVars.funnel_id, order_id: window.WPFunnelsOfferVars.order_id, order_key: window.WPFunnelsOfferVars.order_key, offer_type: window.WPFunnelsOfferVars.offer_type, offer_action: "yes", product_id: window.WPFunnelsOfferVars.product_id, quantity: window.WPFunnelsOfferVars.quantity, security: nonce, }; if ("upsell" === offer_type) { ajax_data.action = "wpfnl_" + offer_type + "_accepted"; ajax_data.security = window.WPFunnelsOfferVars.wpfnl_upsell_accepted_nonce; } else if ("downsell" === offer_type) { ajax_data.action = "wpfnl_" + offer_type + "_accepted"; ajax_data.security = window.WPFunnelsOfferVars.wpfnl_downsell_accepted_nonce; } $(".wpfunnels-offer-loader").show(); wpfunnels_process_offer(ajax_data); } }; /** * process mollie payment with * ajax * * @param ajax_data * @param gateway */ function wpfunnels_process_mollie_payments(ajax_data, gateway) { if ("mollie_wc_gateway_creditcard" === gateway) { ajax_data.action = "wpf_mollie_credit_card_payment_process"; ajax_data.security = window.WPFunnelsOfferVars.wpfnl_mollie_cc_process_nonce; } else if ("mollie_wc_gateway_creditcard" === gateway) { ajax_data.action = "wpf_mollie_ideal_payment_process"; ajax_data.security = window.WPFunnelsOfferVars.wpfnl_mollie_ideal_process_nonce; } $.ajax({ url: window.WPFunnelsOfferVars.ajaxUrl, data: ajax_data, dataType: "json", type: "POST", success: function (response) { if ("success" === response.result) { window.location.href = response.redirect; } }, }); } /** * create paypal order for WC paypal * @param ajax_data */ function wpfunnels_create_paypal_order(ajax_data) { $.ajax({ url: window.WPFunnelsOfferVars.ajaxUrl, data: ajax_data, dataType: "json", type: "POST", success: function (response) { if ("success" === response.status) { window.location.href = response.redirect; } else { ajax_data.action = "wpfnl_" + ajax_data.offer_type + "_accepted"; if ("upsell" === ajax_data.offer_type) { ajax_data.security = window.WPFunnelsOfferVars.wpfnl_upsell_accepted_nonce; } else if ("downsell" === ajax_data.offer_type) { ajax_data.security = window.WPFunnelsOfferVars.wpfnl_downsell_accepted_nonce; } wpfunnels_process_offer(ajax_data); } }, }); } /** * Get selected variation * * @returns Object */ function getSelectedVariationID() { let attr = {}, is_empty = false; $(".wpfnl-variable-attribute-offer").each(function (index) { if ($(this).val()) { attr["attribute_" + $(this).attr("id")] = $(this).val(); } else { is_empty = true; } }); let response = { is_empty: is_empty, attr: attr, }; return response; } $(".wpfnl-variable-attribute-offer").change(function () { let response = getSelectedVariationID(); if (!response.is_empty) { let payload = { product_id: window.WPFunnelsOfferVars.product_id, attr: response.attr, }; $(".offer-btn-loader").show(); wpAjaxHelperRequest("wpfnl-get-variation-price", payload) .success(function (response) { if (response.success) { $("#wpfnl-offer-product-price").empty(); $("#wpfnl-offer-product-price").html(response.data); $(".offer-btn-loader").hide(); } }) .error(function (response) {}); } else { $("#wpfnl-offer-product-price").empty(); } }); /** * offer action ajax */ $(document).on("click", ".wpfunnels_offer_button, .wpfunnels-block-offer-button", function (e) { e.preventDefault(); let supportedPaymentGateway = [ // 'stripe', 'paypal', 'ppcp-gateway', 'cod', 'mollie_wc_gateway_creditcard', 'mollie_wc_gateway_ideal', 'authorize_net_cim_credit_card', 'officeguy', 'payfast', 'square_credit_card', 'bacs', 'cheque', 'woocommerce_payments' ]; let ajaxurl = window.WPFunnelsOfferVars.ajaxUrl, step_id = window.WPFunnelsOfferVars.step_id, funnel_id = window.WPFunnelsOfferVars.funnel_id, is_lms = window.WPFunnelsOfferVars.is_lms, order_id = window.WPFunnelsOfferVars.order_id, order_key = window.WPFunnelsOfferVars.order_key, product_id = window.WPFunnelsOfferVars.product_id, quantity = window.WPFunnelsOfferVars.quantity, payment_method = window.WPFunnelsOfferVars.payment_method, skip_offer = window.WPFunnelsOfferVars.skip_offer, id = $(this).attr("id"), offer_action = "yes", offer_type = "upsell", action = "wpfnl_upsell_accept", security = "", products = $(this).attr("data-products"), next_step = $(this).attr("data-next-step"); let response = getSelectedVariationID(); /** * check if upsell accepted or rejected */ if (id.indexOf("wpfunnels_upsell") !== -1) { offer_type = "upsell"; if (id.indexOf("wpfunnels_upsell_accept") !== -1) { offer_action = "yes"; security = window.WPFunnelsOfferVars.wpfnl_upsell_accepted_nonce; } else { offer_action = "no"; security = window.WPFunnelsOfferVars.wpfnl_upsell_rejected_nonce; } } /** * check if downsell accepted or rejected */ if (id.indexOf("wpfunnels_downsell") !== -1) { offer_type = "downsell"; if (id.indexOf("wpfunnels_downsell_accept") !== -1) { offer_action = "yes"; security = window.WPFunnelsOfferVars.wpfnl_downsell_accepted_nonce; } else { offer_action = "no"; security = window.WPFunnelsOfferVars.wpfnl_downsell_rejected_nonce; } } if( 'yes' === offer_action && 'yes' === window.WPFunnelsOfferVars.maybe_unsupported_payment ){ var ajax_data = { action: 'wpfnl_load_payment', product_id: product_id, step_id : window.WPFunnelsOfferVars.step_id, quantity: quantity, attr: undefined !== response.attr && response.attr ? response.attr : "", is_variable: window.WPFunnelsOfferVars.is_variable, }; $('.wpfnl-offer-payment-form').show(); $.ajax({ type: "POST", url: ajaxurl, data: ajax_data, success: function (response) { jQuery(document.body).trigger('update_checkout'); $('.wpfnl-offer-payment-form .modal-loader').hide(); $('.wpfnl-offer-payment-form form.woocommerce-checkout').show(); }, }); }else{ /** * define action of ajax */ if ("yes" === offer_action) { action = "wpfnl_" + offer_type + "_accepted"; $(".wpfunnels-offer-loader p.description").text("Please wait while processing your order"); } else { action = "wpfnl_" + offer_type + "_rejected"; $(".wpfunnels-offer-loader p.description").text("Please wait..."); } if ("yes" === skip_offer && "yes" === offer_action) { return false; } if (offer_action == "yes" && response.is_empty && window.WPFunnelsOfferVars.is_variable) { $(".wpfnl-select-variation").text("Please select the variation"); return false; } $(".wpfunnels-offer-loader").show(); if (is_lms === "yes" || (undefined !== order_id && undefined !== order_key)) { var ajax_data = { action: action, step_id: step_id, funnel_id: funnel_id, order_id: order_id, order_key: order_key, offer_type: offer_type, offer_action: offer_action, products: products, product_id: product_id, quantity: quantity, next_step: next_step, stripe_sca_payment: false, stripe_intent_id: "", woop_intent_id: "", attr: "", is_variable: window.WPFunnelsOfferVars.is_variable, }; if ("yes" === offer_action) { if (window.WPFunnelsOfferVars.is_variable) { ajax_data.attr = response.attr; } if ("stripe" === window.WPFunnelsOfferVars.payment_gateway) { ajax_data.security = window.WPFunnelsOfferVars.wpfnl_stripe_sca_check_nonce; ajax_data.action = "wpfunnels_stripe_sca_check"; $.ajax({ type: "POST", url: ajaxurl, data: ajax_data, success: function (response) { if (response.hasOwnProperty("intent_secret")) { var stripe = Stripe(response.stripe_pk); stripe .handleCardPayment(response.intent_secret) .then(function (response) { if (response.error) { throw response.error; } if ("requires_capture" !== response.paymentIntent.status && "succeeded" !== response.paymentIntent.status) { return; } ajax_data.action = action; ajax_data.stripe_sca_payment = true; ajax_data.security = security; ajax_data.stripe_intent_id = response.paymentIntent.id; wpfunnels_process_offer(ajax_data); }) .catch(function (error) { window.location.reload(); }); } else { ajax_data.action = action; ajax_data.security = security; wpfunnels_process_offer(ajax_data); } }, }); } else if ("woocommerce_payments" === window.WPFunnelsOfferVars.payment_gateway) { ajax_data.security = window.WPFunnelsOfferVars.wpfunnels_woop_create_payment_intent; ajax_data.action = "wpfunnels_woop_create_payment_intent"; $.ajax({ url: ajaxurl, data: ajax_data, dataType: "json", type: "POST", success(response) { if (response.hasOwnProperty("client_secret") && "" !== response.client_secret && "succeeded" !== response.status) { const stripe = Stripe(response.client_public); stripe .confirmCardPayment(response.client_secret) .then(function (resp) { if (resp.error) { throw resp.error; } if ("requires_capture" !== resp.paymentIntent.status && "succeeded" !== resp.paymentIntent.status) { console.log("Order not complete. Received status: " + resp.paymentIntent.status); return; } ajax_data.action = action; ajax_data.woop_intent_id = resp.paymentIntent.id; ajax_data.woop_payment_method = resp.paymentIntent.payment_method; wpfunnels_process_offer(ajax_data); }) .catch(function () { window.location.reload(); }); } else { ajax_data.action = action; ajax_data.security = security; ajax_data.woop_intent_id = response.client_intend; wpfunnels_process_offer(ajax_data); } }, }); } else if ("mollie_wc_gateway_creditcard" === window.WPFunnelsOfferVars.payment_gateway) { wpfunnels_process_mollie_payments(ajax_data, window.WPFunnelsOfferVars.payment_gateway); } else if ("mollie_wc_gateway_ideal" === window.WPFunnelsOfferVars.payment_gateway) { wpfunnels_process_mollie_payments(ajax_data, window.WPFunnelsOfferVars.payment_gateway); } else if ("ppcp-gateway" === window.WPFunnelsOfferVars.payment_gateway) { ajax_data.security = window.WPFunnelsOfferVars.wpfnl_create_paypal_order_nonce; ajax_data.action = "wpfnl_create_paypal_order"; wpfunnels_create_paypal_order(ajax_data); } else { ajax_data.security = security; wpfunnels_process_offer(ajax_data); } } else { ajax_data.action = action; ajax_data.security = security; wpfunnels_process_offer(ajax_data); } } else { $("#wpfnl-loader-accept").hide(); $("#wpfnl-alert-accept").addClass("wpfnl-error"); $("#wpfnl-alert-accept").text("No order found to process"); } } }); $(document).on("click", ".wpfnl-offer-payment-form .close-modal", function (e) { $('.wpfnl-offer-payment-form').hide(); }); //=== Upsell Reject ajax called==// $(document).on("click", "#wpfunnels_upsell_reject", function (e) { e.preventDefault(); $("#wpfnl-loader-reject").css("display", "inline-block"); $("#wpfnl-alert-reject").show(); var ajaxurl = wpfnl_obj.ajaxurl; var step_id = $(this).attr("data-id"); var order_id = getUrlParameter("order_id"); var order_key = getUrlParameter("order_key"); var next_step = $(this).attr("data-next-step"); if (order_id != undefined && order_key != undefined) { jQuery.ajax({ type: "POST", url: ajaxurl, data: { action: "wpfnl_upsell_reject_ajax", step_id: step_id, order_id: order_id, next_step: next_step, order_key: order_key, }, success: function (response) { $("#wpfnl-loader-reject").hide(); setTimeout(function () { window.location.href = response.redirect_url; }, 500); }, }); } else { $("#wpfnl-loader-reject").hide(); $("#wpfnl-alert-reject").addClass("wpfnl-error"); $("#wpfnl-alert-reject").text("No order found to process"); } }); wpfunnels_capture_woocommerce_paypal_order(); wpfunnels_capture_woocommerce_mollie_order(); /** * facebook pixel for order bump added to * cart */ var wpfunnels_facebook_pixel = function () { jQuery(document).ajaxComplete(function (event, xhr, settings) { if (!xhr.hasOwnProperty("responseJSON")) { return; } let fragments = xhr.responseJSON.hasOwnProperty("fragments") ? xhr.responseJSON.fragments : null; if (fragments && fragments.hasOwnProperty("product_added_to_cart")) { fbq("track", "AddToCart", fragments.product_added_to_cart.product_added_to_cart); } }); }; //== Variable product and single product add in checkout page ==// $(document).ready(function () { $(document).on("change", ".wpfnl-product-variation .input-checkbox", function (e) { e.preventDefault(); var ajaxurl = window.wpfnl_obj.ajaxurl; var step_id = window.wpfnl_obj.step_id; var product_id = $(this).parent().data("id"); // var quantity = $(this).parent().data('qty'); var quantity = $(this).parent().parent().parent().parent().parent().find(".set-quantity").val(); let checker = false; if (quantity == undefined) { var quantity = $(this).parent().data("qty"); } if ($(this).prop("checked") == true) { checker = true; } else if ($(this).prop("checked") == false) { checker = false; } jQuery.ajax({ type: "POST", url: ajaxurl, data: { action: "wpfnl_variable_ajax", checker: checker, quantity: quantity, step_id: step_id, product_id: product_id, type: "checkbox", }, success: function (response) { if (response.success) { if (response.data.status == "success") { $("body").trigger("update_checkout"); } else { console.log(response); } } else { console.log(response); } }, }); }); $(document).on("change keyup", ".product-quantity .set-quantity", function (e) { e.preventDefault(); var ajaxurl = window.wpfnl_obj.ajaxurl; var step_id = window.wpfnl_obj.step_id; var product_id = $(this).attr("product-id"); var quantity = $(this).val(); if (quantity < 1) { return false; } let checker = false; let isQuantity = true; if ($(this).parent().parent().find(".input-checkbox").prop("checked") == undefined) { if ($(this).parent().parent().find(".input-radio").prop("checked") == true) { checker = true; } else if ($(this).parent().parent().find(".input-radio").prop("checked") == false) { checker = false; return false; } } if ($(this).parent().parent().find(".input-radio").prop("checked") == undefined) { if ($(this).parent().parent().find(".input-checkbox").prop("checked") == true) { checker = true; } else if ($(this).parent().parent().find(".input-checkbox").prop("checked") == false) { checker = false; return false; } } jQuery.ajax({ type: "POST", url: ajaxurl, data: { action: "wpfnl_variable_ajax", checker: checker, quantity: quantity, step_id: step_id, product_id: product_id, isQuantity: isQuantity, type: "checkbox", }, success: function (response) { if (response.success) { if (response.data.status == "success") { $("body").trigger("update_checkout"); } else { console.log(response); } } else { console.log(response); } }, }); }); $(document).on("change keyup", ".single-product .product-quantity .set-quantity-single", function (e) { e.preventDefault(); var ajaxurl = window.wpfnl_obj.ajaxurl; var step_id = window.wpfnl_obj.step_id; var product_id = $(this).attr("product-id"); var quantity = $(this).val(); if (quantity < 1) { return false; } let isQuantity = true; jQuery.ajax({ type: "POST", url: ajaxurl, data: { action: "wpfnl_single_product_quantity_ajax", quantity: quantity, step_id: step_id, product_id: product_id, isQuantity: isQuantity, }, success: function (response) { if (response.success) { if (response.data.status == "success") { $("body").trigger("update_checkout"); } else { console.log(response); } } else { console.log(response); } }, }); }); $(document).on("change", ".wpfnl-product-variation .input-radio", function (e) { e.preventDefault(); var ajaxurl = window.wpfnl_obj.ajaxurl; var step_id = window.wpfnl_obj.step_id; var product_id = $(this).data("id"); var quantity = $(this).parent().parent().parent().parent().find(".set-quantity").val(); if (quantity == undefined) { var quantity = $(this).parent().data("qty"); } let checker = true; if (quantity < 1) { return false; } if ($(this).prop("checked") == true) { checker = true; } else if ($(this).prop("checked") == false) { checker = false; } jQuery.ajax({ type: "POST", url: ajaxurl, data: { action: "wpfnl_variable_ajax", checker: checker, quantity: quantity, step_id: step_id, product_id: product_id, type: "radio", }, success: function (response) { if (response.success) { if (response.data.status == "success") { $("body").trigger("update_checkout"); } else { console.log(response); } } else { console.log(response); } }, }); }); $(document).on("change", ".wpfnl-variation", function (e) { e.preventDefault(); let ajaxurl = window.wpfnl_obj.ajaxurl, step_id = window.wpfnl_obj.step_id, attrs = [], quantity = $(this).parents(".offer-variation-wrapper").find(".wpfnl-varition-qty").val(), product_id = $(this).parents(".offer-variation-wrapper").find(".wpfnl-product-id").val(), load_ajax = true; $(this) .parents(".offer-variation-wrapper") .find(".wpfnl-variable-attribute-offer") .each(function () { var title = $(this).attr("id"); var value = $(this).val(); if (value) { attrs.push({ [title]: value }); } else { load_ajax = false; } }); if (load_ajax) { $("input[name='_wpfunnels_variable_product']").val('selected'); jQuery.ajax({ type: "POST", url: ajaxurl, data: { action: "wpfnl_update_variable_ajax", quantity: quantity, step_id: step_id, product_id: product_id, attrs: attrs, }, success: function (response) { if (response.success) { if (response.data.status == "success") { $("body").trigger("update_checkout"); } } else { console.log(response); } }, }); }else{ $("input[name='_wpfunnels_variable_product']").val('unselected'); } }); $(document).on("keyup change input", ".wpfnl-quantity-setect", function (e) { e.preventDefault(); var ajaxurl = window.wpfnl_obj.ajaxurl; var step_id = window.wpfnl_obj.step_id; var product_id = $(this).data("product-id"); var variation_id = $(this).data("variation-id"); var variation = $(this).data("variation"); var set_quantity = $(this).data("set-quantity"); var quantity = parseInt($(this).val()); if( set_quantity == 'yes' ){ var limit = parseInt($(this).data("quantity-limit")); if( quantity > limit ){ quantity = limit; $(this).val(quantity) } } var quantity = $(this).val(); $("input[name='_wpfunnels_select_quantity']").val('yes'); if (!quantity) { return false; } if (quantity < 1 ) { quantity = 1; $(this).val(quantity) } let that = this; jQuery.ajax({ type: "POST", url: ajaxurl, data: { action: "wpfnl_update_quantity_ajax", quantity: quantity, step_id: step_id, product_id: product_id, variation_id: variation_id, variation: variation, }, success: function (response) { if (response.success) { $(that).val(quantity); // Clear existing notices only within the checkout form $('.woocommerce-checkout .woocommerce-error, .woocommerce-checkout .woocommerce-message').remove(); var checkoutForm = $('.woocommerce-checkout'); if (response.data.status == "success") { $("body").trigger("update_checkout"); }else{ if (checkoutForm.find('.woocommerce-error').length === 0) { checkoutForm.prepend('
' + response.data.message + '
'); } } } }, }); }); if ("1" !== window?.wpfnl_obj?.is_builder_preview) { wpfunnels_facebook_pixel(); } }); // trigger ajax if any user abandon the funnel var doAjaxBeforeUnloadEnabled = true; var doAjaxBeforeUnload = function (evt) { if (!doAjaxBeforeUnloadEnabled) { return; } doAjaxBeforeUnloadEnabled = false; var ajaxurl = window.wpfnl_obj.ajaxurl; jQuery.ajax({ type: "POST", url: ajaxurl, data: { action: "maybe_abandoned_funnel", funnel_id: window.wpfnl_obj.funnel_id, step_id: window.wpfnl_obj.step_id, security: window.wpfnl_obj.abandoned_ajax_nonce, }, success: function (response) {}, }); }; // $(document).ready( function () { // window.onbeforeunload = doAjaxBeforeUnload; // checkStepBouncing(); // }); const checkStepBouncing = () => { const wpfnlBody = $( 'body.single-wpfunnel_steps' )?.length; if ( wpfnlBody ) { let time = 0; const bounceTimeInterval = setInterval( function () { time++; if ( 10 <= time ) { setStepBounce(time, bounceTimeInterval); } }, 1000 ); $( 'input, button, a, span' ).on( 'click change', () => { if ( 10 >= time ) { setStepBounce( time, bounceTimeInterval ) } } ); } } const setStepBounce = ( time, bounceTimeInterval ) => { const ajaxurl = window?.wpfnl_pro_obj?.ajaxurl; const data = { action: 'wpfnl_set_bounce_rate', security: window?.wpfnl_pro_obj?.ajaxNonce, funnel_id: window?.wpfnl_pro_obj?.funnel_id, step_id: window?.wpfnl_pro_obj?.step_id, user_id: window?.wpfnl_pro_obj?.user_id }; $.ajax({ url: ajaxurl, type: "POST", data: data, dataType: "json", success: function (data) { clearInterval( bounceTimeInterval ); }, error: function (error) { clearInterval( bounceTimeInterval ); } }); } })(jQuery); public/classes/integrations/crm-integrations/crm/abstract-class-wpfnl-pro-crm.php000064400000001035147600245720024361 0ustar00get_user_info_for_lms( $user_info ); }elseif( 'wc' === $type ){ $user = $this->get_user_info_for_woo( $order_id, $user_info); }elseif( 'lead' === $type ){ $user = $this->get_user_info_for_lead( $$user_info ); } }else{ $user = $this->get_user_info_for_woo( $order_id, $user_info ); } $user['status'] = 'pending'; return $user; } /** * Get user for woocommerce * @param Int $order_id * @param Array $user_info * @return Array */ private function get_user_info_for_woo( $order_id, $user_info ){ $user = []; if( $order_id ){ $order = wc_get_order( $order_id ); if( $order ){ $customer_id = $order->get_customer_id(); $user = array(); if( 0 != $customer_id ) { $customer = new \WC_Customer( $customer_id ); $user['email'] = $customer->get_email(); $user['first_name'] = $customer->get_first_name(); $user['last_name'] = $customer->get_last_name(); } else { $user['email'] = $order->get_billing_email(); $user['first_name'] = $order->get_billing_first_name(); $user['last_name'] = $order->get_billing_last_name(); } }else{ $user['email'] = isset($user_info['email']) ? $user_info['email'] : ''; $user['first_name'] = isset($user_info['first_name']) ? $user_info['first_name'] : ''; $user['last_name'] = isset($user_info['last_name']) ? $user_info['last_name'] : ''; } }else{ $user['email'] = isset($user_info['email']) ? $user_info['email'] : ''; $user['first_name'] = isset($user_info['first_name']) ? $user_info['first_name'] : ''; $user['last_name'] = isset($user_info['last_name']) ? $user_info['last_name'] : ''; } return $user; } /** * Get user for woocommerce */ private function get_user_info_for_lms( $user_info ){ $user = []; $user['email'] = isset($user_info['email']) ? $user_info['email'] : ''; $user['first_name'] = isset($user_info['first_name']) ? $user_info['first_name'] : ''; $user['last_name'] = isset($user_info['last_name']) ? $user_info['last_name'] : ''; return $user; } /** * Get user for woocommerce * * @param Array * @return Array */ private function get_user_info_for_lead( $user_info ){ $user = []; $user['email'] = isset($user_info['email']) ? $user_info['email'] : ''; $user['first_name'] = isset($user_info['first_name']) ? $user_info['first_name'] : ''; $user['last_name'] = isset($user_info['last_name']) ? $user_info['last_name'] : ''; return $user; } /** * Sending data to Fluent CRM to create or update contact, contact lists and tags * * @param data * @return array */ public function send_or_update_data( $data ) { if (isset( $data['source'] )){ json_encode($data['source']); } $contactApi = FluentCrmApi('contacts'); $contact = $contactApi->createOrUpdate($data); if( $contact && 'pending' == $contact->status ) { $contact->sendDoubleOptinEmail(); } } public function deleteData($id) { // TODO: Implement deleteData() method. } /** * check if fluent crm is activated or not * * @return bool */ function is_connected() { if ( is_plugin_active( 'fluent-crm/fluent-crm.php' ) ) { return true; } return false; } /** * Get Fluent CRM Contact lists * * @param null * @return array */ public function get_crm_contact_lists(){ $listApi = FluentCrmApi('lists'); $allLists = $listApi->all(); foreach($allLists as $list) { $response[$list->id] = [ 'value' => $list->title ]; } return $response; } /** * Get Fluent CRM Contact tags * * @param null * @return array */ public function get_crm_contact_tags( $list = [] ){ $tagApi = FluentCrmApi('tags'); $allTags = $tagApi->all(); foreach($allTags as $tag) { $response[$tag->id] = $tag->title; } return $response; } }public/classes/integrations/crm-integrations/data/class-wpfnl-pro-automation-cookie-data.php000064400000000704147600245720026467 0ustar00data = $cookie_data; } }public/classes/integrations/crm-integrations/events/class-wpfnl-cta-events.php000064400000002017147600245720023776 0ustar00crm->is_connected()) { if( isset( $this->data['cta_clicked']) && $this->data['cta_clicked'] ) { $order_id = isset( $this->data['main_order_id'] ) ? $this->data['main_order_id'] : null; $user_info = $this->crm->get_user_info_from_order( $order_id , $this->data['user_info']); $user_info['tags'] = [$this->triggerObject->get_tag_id()]; $user_info['lists'] = [$this->triggerObject->get_list_id()]; $user_info['source'] = "trigger_cta"; $this->crm->send_or_update_data($user_info); } } } }public/classes/integrations/crm-integrations/events/class-wpfnl-mainorder-events.php000064400000003032147600245720025205 0ustar00crm->is_connected()) { if( (isset($this->data['main_order_id']) && $this->data['main_order_id'] && 'main_order_accepted_enrolled' !== $this->event ) || ( 'main_order_accepted' !== $this->event && isset($this->data['already_enrolled']) && in_array('checkout',$this->data['already_enrolled']) ) ) { $type = isset($this->data['funnel_id']) ? get_post_meta($this->data['funnel_id'],'_wpfnl_funnel_type',true) : ''; if( (isset($this->data['main_order_id']) && $this->data['main_order_id']) && 'lms' !== $type ){ $user_info = $this->crm->get_user_info_from_order( $this->data['main_order_id'] ); }else{ $user_info = $this->crm->get_user_info_from_order( null, $this->data['user_info'] ); } $user_info['tags'] = [$this->triggerObject->get_tag_id()]; $user_info['lists'] = [$this->triggerObject->get_list_id()]; $user_info['source'] = "main_order"; $this->crm->send_or_update_data($user_info); } } } }public/classes/integrations/crm-integrations/events/class-wpfnl-offer-events.php000064400000007632147600245720024340 0ustar00crm->is_connected()) { $offer_data = $this->get_offer_data(); $event_status = ''; if( (strpos($this->triggerObject->get_event_name(), 'upsell_accepted_enrolled' ) !== false && isset($this->data['already_enrolled']) && in_array('upsell',$this->data['already_enrolled'])) || (strpos($this->triggerObject->get_event_name(), 'downsell_accepted_enrolled' ) !== false && isset($this->data['already_enrolled']) && in_array('downsell',$this->data['already_enrolled']))){ $event_status = 'accepted'; } $step_type = get_post_meta( $this->triggerObject->get_step_id(), '_step_type', true ); if( $step_type === 'upsell' ){ if( !isset($this->data['already_enrolled']) || (!in_array('upsell',$this->data['already_enrolled']) )){ if( strpos($this->triggerObject->get_event_name(), 'upsell_accepted') !== false || strpos($this->triggerObject->get_event_name(), 'downsell_accepted') !== false ){ $event_status = 'accepted'; }elseif(strpos($this->triggerObject->get_event_name(), 'upsell_rejected') !== false || strpos($this->triggerObject->get_event_name(), 'downsell_rejected') !== false ){ $event_status = 'rejected'; } } } if( $step_type === 'downsell' ){ if( !isset($this->data['already_enrolled']) || !in_array('downsell',$this->data['already_enrolled']) ){ if( strpos($this->triggerObject->get_event_name(), 'upsell_accepted') !== false || strpos($this->triggerObject->get_event_name(), 'downsell_accepted') !== false ){ $event_status = 'accepted'; }elseif(strpos($this->triggerObject->get_event_name(), 'upsell_rejected') !== false || strpos($this->triggerObject->get_event_name(), 'downsell_rejected') !== false ){ $event_status = 'rejected'; } } } if( isset( $offer_data['status'] ) && $offer_data['status'] == $event_status ){ $order_id = null; if( isset($this->data['main_order_id']) ){ $order_id = $offer_data['status'] === 'rejected' ? $this->data['main_order_id'] : $offer_data['order_id']; }else{ if( isset($this->data['already_enrolled']) ){ $order_id = null; } } $_user = isset($this->data['user_info']) ? $this->data['user_info'] : null; $user_info = $this->crm->get_user_info_from_order( $order_id , $_user ); $user_info['tags'] = [$this->triggerObject->get_tag_id()]; $user_info['lists'] = [$this->triggerObject->get_list_id()]; $src = ''; if ( isset( $offer_data['type'] ) && isset( $offer_data['status'] )){ $src = $offer_data['type'].'_'.$offer_data['status']; } $user_info['source'] = $src; $this->crm->send_or_update_data($user_info); } } } private function get_offer_data() { if( isset( $this->data['offer'] ) && isset($this->data['offer'][$this->triggerObject->get_step_id()]) ) { return $this->data['offer'][$this->triggerObject->get_step_id()]; } return false; } }public/classes/integrations/crm-integrations/events/class-wpfnl-optin-submit.php000064400000003076147600245720024365 0ustar00crm->is_connected()) { if( isset($this->data['after_optin_submit']['email']) && $this->data['after_optin_submit'] ) { $user_info['email'] = $this->data['after_optin_submit']['email']; $user_info['first_name'] = ( isset($this->data['after_optin_submit']['first_name']) && $this->data['after_optin_submit']['first_name'] ) ? $this->data['after_optin_submit']['first_name'] : ''; $user_info['last_name'] = ( isset($this->data['after_optin_submit']['last_name']) && $this->data['after_optin_submit']['last_name'] ) ? $this->data['after_optin_submit']['last_name'] : ''; $user_info['phone'] = ( isset($this->data['after_optin_submit']['phone']) && $this->data['after_optin_submit']['phone'] ) ? $this->data['after_optin_submit']['phone'] : ''; $user_info['tags'] = [$this->triggerObject->get_tag_id()]; $user_info['lists'] = [$this->triggerObject->get_list_id()]; $user_info['source'] = "optin_submit"; $this->crm->send_or_update_data($user_info); } } } }public/classes/integrations/crm-integrations/events/class-wpfnl-orderbump-events.php000064400000010573147600245720025234 0ustar00crm->is_connected()) { // if( isset( $this->data['main_order_id'] )) { // if( isset($this->data['orderbump_accepted']) && ( ($this->data['orderbump_accepted'] && $this->triggerObject->get_event_name() == 'orderbump_accepted') || (!$this->data['orderbump_accepted'] && $this->triggerObject->get_event_name() == 'orderbump_not_accepted')) ){ if( isset($this->data['orderbump_accepted']) && $this->is_allow_to_send() ){ $order_id = isset($this->data['main_order_id']) ? $this->data['main_order_id'] : null; $_user = isset($this->data['user_info']) ? $this->data['user_info'] : null; $user_info = $this->crm->get_user_info_from_order( $order_id, $_user ); $user_info['tags'] = [$this->triggerObject->get_tag_id()]; $user_info['lists'] = [$this->triggerObject->get_list_id()]; $user_info['source'] = "order_bump"; $this->crm->send_or_update_data($user_info); } // } } } private function is_allow_to_send(){ $funnel_id = isset($this->data['funnel_id']) ? $this->data['funnel_id'] : ''; if( $funnel_id ){ $steps = get_post_meta( $funnel_id, '_steps_order', true ); $key = array_search('checkout', array_column($steps, 'step_type')); if( false !== $key ){ $step_id = isset($steps[$key]['id']) ? $steps[$key]['id'] : ''; if( $step_id ){ $ob_settings = get_post_meta( $step_id, 'order-bump-settings', true ); if( !empty($ob_settings) ){ if(strpos($this->event, '_orderbump_accepted') !== false){ if(strpos($this->event, '_enrolled_orderbump_accepted') !== false){ $index = str_replace('_enrolled_orderbump_accepted','',$this->event); }else{ $index = str_replace('_orderbump_accepted','',$this->event); } if( 'any' == $index ){ return true; }else{ if(strpos($index, '_enrolled') !== false){ $index = str_replace('_enrolled','',$index); } if(isset($ob_settings[(int)($index-1)])){ $product_id = $ob_settings[(int)($index-1)]['product']; if( isset($this->data['ob_accepetd_products']) ){ if( in_array($product_id,$this->data['ob_accepetd_products'])){ return true; } } } } }elseif(strpos($this->event, '_orderbump_not_accepted') !== false){ $index = str_replace('_orderbump_not_accepted','',$this->event); if(isset($ob_settings[(int)($index-1)])){ $product_id = $ob_settings[(int)($index-1)]['product']; if( isset($this->data['ob_accepetd_products']) ){ if( !in_array($product_id,$this->data['ob_accepetd_products'])){ return true; } } } } } } } } return false; } }public/classes/integrations/crm-integrations/events/class-wpfnl-pro-automation-events.php000064400000001617147600245720026212 0ustar00triggerObject = $triggerObject; $this->data = $data; $this->crm = $crm; $this->event = $event; } }public/classes/integrations/crm-integrations/handlers/AutomationTriggerObject.php000064400000002240147600245720024563 0ustar00event_name = isset($trigger['event']) ? $trigger['event'] : ''; $this->list_id = isset($trigger['list']) ? $trigger['list'] : ''; $this->tag_id = isset($trigger['tag']) ? $trigger['tag'] : ''; $this->step_id = isset($trigger['stepID']) ? $trigger['stepID'] : ''; } public function get_step_id() { return $this->step_id; } public function get_event_name() { return $this->event_name; } public function get_tag_id() { return $this->tag_id; } public function get_list_id() { return $this->list_id; } }public/classes/integrations/crm-integrations/handlers/class-wpfnl-pro-cookie.php000064400000032717147600245720024302 0ustar00is_automation_enabled($funnel_id)) { return; } /** Define Cookie Key */ $cookie_name = 'wpfunnels_automation_data'; /** Un-setting Cookie Data with Key. */ setcookie( $cookie_name, '', time() + 3600 * 6, '/', COOKIE_DOMAIN ); /** Checking and Initialize Cookie with Key */ $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); if($funnel_id) { $cookie['funnel_id'] = $funnel_id; $current_user = wp_get_current_user(); $cookie['user_info'] = [ 'id' => get_current_user_id(), 'first_name' => $current_user->user_firstname, 'last_name' => $current_user->user_lastname, 'email' => $current_user->user_email ]; } if( !isset($cookie['ob_accepetd_products']) ){ $cookie['ob_accepetd_products'] = []; } $cookie['funnel_status'] = 'processing'; /** Set Cookies. */ setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); } /** * Get optin data * * @param $step_id, $record */ public function get_optin_data( $step_id, $post_action, $action_type, $record ){ $funnel_id = Wpfnl_functions::get_funnel_id_from_step($step_id); if('false' === $this->is_automation_enabled($funnel_id)) { return; } /** Set Cookie Data */ $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $cookie['after_optin_submit'] = ( isset($record->form_data['email']) && $record->form_data['email'] ) ? $record->form_data : false; setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); if(!isset($cookie['funnel_id'])) { $cookie['funnel_id'] = $funnel_id; } if( !isset($cookie['ob_accepetd_products']) ){ $cookie['ob_accepetd_products'] = []; } $cookie['funnel_status'] = 'successful'; // setcookie( $cookie_name, null, strtotime( '-1 days' ), '/', COOKIE_DOMAIN ); if(isset( $_COOKIE[$cookie_name] )){ ob_start(); do_action( 'wpfunnels/trigger_automation', $cookie ); ob_get_clean(); } } /** * End Journey hook if funnel journey end * * @param $step_id * @param $funnel_id */ public function end_journey( $step_id, $funnel_id ) { // if('false' === $this->is_automation_enabled($funnel_id)) { // return; // } Wpfnl_functions::unset_site_cookie( $step_id, 'wpfunnels_automation_data', 'wpfunnels/trigger_automation', $funnel_id ); } /** * Action if user clicks on the cta * * @param $step_id * @param $funnel_id */ public function trigger_cta( $step_id, $funnel_id ) { if('false' === $this->is_automation_enabled($funnel_id)) { return; } /** Set Cookie Data */ $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $cookie['cta_clicked'] = true; if(!isset($cookie['funnel_id'])) { $cookie['funnel_id'] = $funnel_id; } setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); if(isset( $_COOKIE[$cookie_name] )){ ob_start(); do_action( 'wpfunnels/trigger_automation', $cookie ); ob_get_clean(); } } /** * Order bump accepted hook * * @param $step_id * @param $product_id */ public function order_bump_accepted( $step_id, $product_id ) { /** Set Cookie Data */ $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $cookie['orderbump_accepted'] = true; if( !isset($cookie['ob_accepetd_products']) ){ $cookie['ob_accepetd_products'] = []; } $funnel_id = get_post_meta($step_id,'_funnel_id',true); $is_lms = get_post_meta($funnel_id,'_wpfnl_funnel_type',true); if( $is_lms !== 'lms' ){ if( !in_array($product_id,$cookie['ob_accepetd_products']) ){ array_push($cookie['ob_accepetd_products'],$product_id); } }else{ $cookie['ob_accepetd_products'] = []; array_push($cookie['ob_accepetd_products'],$product_id); } if(!isset($cookie['funnel_id'])) { $cookie['funnel_id'] = $funnel_id; } setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); } /** * Order bump accepted hook * * @param $step_id * @param $product_id */ public function order_bump_rejected( $step_id, $product_id ) { /** Set Cookie Data */ $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); if( !isset($cookie['ob_accepetd_products']) ){ $cookie['ob_accepetd_products'] = []; if(!isset($cookie['orderbump_accepted'])){ $cookie['orderbump_accepted'] = false; } } if( in_array($product_id,$cookie['ob_accepetd_products']) ){ if (($key = array_search($product_id, $cookie['ob_accepetd_products'])) !== false) { unset($cookie['ob_accepetd_products'][$key]); } } $funnel_id = get_post_meta($step_id,'_funnel_id',true); if(!isset($cookie['funnel_id'])) { $cookie['funnel_id'] = $funnel_id; } setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); } /** * Main Order Tracking hook * * @param $order_id * @param $funnel_id */ public function funnel_order_placed( $order_id, $funnel_id, $step_id) { // if('false' === $this->is_automation_enabled($funnel_id)) { // return; // } if($funnel_id){ /** Set Cookie Data */ $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $cookie['main_order_id'] = $order_id; if(!isset($cookie['funnel_id'])) { $cookie['funnel_id'] = $funnel_id; } if( !isset($cookie['ob_accepetd_products']) ){ $cookie['ob_accepetd_products'] = []; } if(!isset($cookie['orderbump_accepted'])){ $cookie['orderbump_accepted'] = false; } setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); if(isset( $_COOKIE[$cookie_name] )){ ob_start(); do_action( 'wpfunnels/trigger_automation', $cookie ); ob_get_clean(); } } } /** * This will trigger after user accept the offer * * @param $order * @param $offer_product */ public function offer_accepted( $order, $offer_product ) { $step_id = $offer_product['step_id']; $funnel_id = get_post_meta($step_id, '_funnel_id', true); $step_type = get_post_meta($step_id, '_step_type', true); $order_id = $order->get_id(); if('false' === $this->is_automation_enabled($funnel_id)) { return; } if(!isset($cookie['funnel_id'])) { $cookie['funnel_id'] = $funnel_id; } $offer_settings = \WPFunnels\Wpfnl_functions::get_offer_settings(); $product_id = $offer_product['id']; $product_name = $offer_product['name']; $product_qty = $offer_product['qty']; $product_price = $offer_product['price']; /** Set Cookie Data */ $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $cookie['child_order'] = $offer_settings['offer_orders'] == 'child-order' ? true : false; $cookie['offer'][$step_id] = array( 'id' => $step_id, 'type' => $step_type, 'order_id' => $order_id, 'status' => 'accepted' ); setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); if(isset( $_COOKIE[$cookie_name] )){ ob_start(); do_action( 'wpfunnels/trigger_automation', $cookie ); ob_get_clean(); } } /** * This will trigger after user rejected the offer * * @param $order * @param $offer_product */ public function offer_rejected( $order, $offer_product ) { $step_id = isset($offer_product['step_id']) ? $offer_product['step_id'] : ''; $funnel_id = get_post_meta($step_id, '_funnel_id', true); $step_type = get_post_meta($step_id, '_step_type', true); if('false' === $this->is_automation_enabled($funnel_id)) { return; } if(!isset($cookie['funnel_id'])) { $cookie['funnel_id'] = $funnel_id; } $offer_settings = \WPFunnels\Wpfnl_functions::get_offer_settings(); $product_id = $offer_product['id']; /** Set Cookie Data */ $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $cookie['child_order'] = $offer_settings['offer_orders'] == 'child-order' ? true : false; $cookie['offer'][$step_id] = array( 'id' => $step_id, 'type' => $step_type, 'status' => 'rejected' ); setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); if(isset( $_COOKIE[$cookie_name] )){ ob_start(); do_action( 'wpfunnels/trigger_automation', $cookie ); ob_get_clean(); } } /** * this will trigger after any funnel is abandoned * * @param $step_id * @param $funnel_id */ public function funnel_abandoned( $step_id, $funnel_id ) { if('false' === $this->is_automation_enabled($funnel_id)) { return; } /** Set Cookie Data */ $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $cookie['funnel_status'] = 'abandoned'; if(!isset($cookie['funnel_id'])) { $cookie['funnel_id'] = $funnel_id; } setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); } }public/classes/integrations/crm-integrations/handlers/class-wpfnl-pro-trigger.php000064400000011041147600245720024457 0ustar00trigger = $trigger; $this->data = $data; $this->crm = $crm; $this->event = $this->get_event(); } /** * Automation Event Initialize * * @param null * @return object */ private function get_event() { if( strpos($this->trigger->get_event_name(), 'upsell_accepted') !== false ){ $event_name = 'upsell_accepted'; }elseif(strpos($this->trigger->get_event_name(), 'upsell_rejected') !== false ){ $event_name = 'upsell_rejected'; }elseif(strpos($this->trigger->get_event_name(), 'downsell_accepted') !== false ){ $event_name = 'downsell_accepted'; }elseif(strpos($this->trigger->get_event_name(), 'downsell_rejected') !== false ){ $event_name = 'downsell_rejected'; }elseif( strpos($this->trigger->get_event_name(), 'any_orderbump_accepted') === false && strpos($this->trigger->get_event_name(), '_orderbump_accepted') !== false ){ $event_name = 'orderbump_accepted'; }elseif(strpos($this->trigger->get_event_name(), '_orderbump_not_accepted') !== false ){ $event_name = 'orderbump_not_accepted'; }elseif(strpos($this->trigger->get_event_name(), 'any_orderbump_not_accepted') !== false ){ $event_name = 'orderbump_not_accepted'; }elseif(strpos($this->trigger->get_event_name(), 'any_orderbump_accepted') !== false ){ if( isset($this->data['ob_accepetd_products']) && !empty($this->data['ob_accepetd_products']) ){ $event_name = 'orderbump_accepted'; }else{ $event_name = $this->trigger->get_event_name(); } }elseif( false !== strpos($this->trigger->get_event_name(), 'cta_clicked') ){ $event_name = 'cta_clicked'; }elseif( false !== strpos($this->trigger->get_event_name(), 'after_optin_submit') ){ $event_name = 'after_optin_submit'; }else{ $event_name = $this->trigger->get_event_name(); } switch ( $event_name ) { case 'upsell_accepted' : case 'downsell_accepted': case 'upsell_rejected': case 'downsell_rejected': return new OfferEvent( $this->trigger, $this->data, $this->crm, $this->trigger->get_event_name() ); case 'cta_clicked': return new TriggerCTA( $this->trigger, $this->data, $this->crm, $this->trigger->get_event_name()); case 'main_order_accepted': case 'main_order_accepted_enrolled': return new MainOrder( $this->trigger, $this->data, $this->crm, $this->trigger->get_event_name() ); case 'orderbump_accepted': case 'orderbump_not_accepted': return new OrderBump( $this->trigger, $this->data, $this->crm, $this->trigger->get_event_name() ); case 'after_optin_submit': return new OptinSubmit( $this->trigger, $this->data, $this->crm, $this->trigger->get_event_name() ); default : return false; } } /** * Automation Event Start * * @param null * @return void */ public function start() { if($this->event){ $this->event->run(); } } }public/classes/skip-offer/class-wpfnl-skip-offer.php000064400000036743147600245720016555 0ustar00 $displayable_variation_id, 'step_type' => get_post_meta( $displayable_variation_id, '_step_type', true ) ]; return $data; } /** * Skip the next offer step(s) * if the assigned product is out of stock. * @param Array $next_step * * @since 1.8.5 * @return Array */ public function modify_next_step_data( $next_step ) { if( isset( $next_step['step_type'] ) && in_array($next_step['step_type'], ['upsell', 'downsell']) ) { $next_step_type = $next_step[ 'step_type' ]; $next_step_id = $next_step[ 'step_id' ] ?? false; if( $next_step_id ) { $funnel_id = get_post_meta( $next_step_id, '_funnel_id', true ); $is_gbf = get_post_meta( $funnel_id, 'is_global_funnel', true ); if( 'yes' !== $is_gbf ){ $offer_products = Wpfnl_Pro_functions::get_offer_product( $next_step_id, $next_step_type ); $product_ids = array_column( $offer_products, 'id' ); if( !empty( $product_ids ) ) { foreach( $product_ids as $id ) { $product = ''; if( is_plugin_active('wpfunnels-pro-gbf/wpfnl-pro-gb.php' )){ $product = wc_get_product( $id ); } if( $product && ( $product->is_on_backorder() || $product->is_in_stock() || $product->get_stock_quantity() ) ) { continue; } $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $next_step_id ); $next_step = Wpfnl_functions::get_next_step( $funnel_id, $next_step_id ); return $this->modify_next_step_data( $next_step ); } }else{ $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $next_step_id ); $next_step = Wpfnl_functions::get_next_step( $funnel_id, $next_step_id ); return $this->modify_next_step_data( $next_step ); } }else{ if( is_plugin_active('wpfunnels-pro-gbf/wpfnl-pro-gb.php' )){ $offer_product = $this->skip_next_step_for_gbf( $funnel_id, $next_step_id ); if( isset($offer_product['id']) && $offer_product['id'] ){ $product = wc_get_product( $offer_product['id'] ); if( $product && ( $product->is_on_backorder() || $product->is_in_stock() || $product->get_stock_quantity() ) ) { return $next_step; } $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $next_step_id ); $next_step = Wpfnl_functions::get_next_step( $funnel_id, $next_step_id ); return $this->modify_next_step_data( $next_step ); }else{ $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $next_step_id ); $next_step = Wpfnl_functions::get_next_step( $funnel_id, $next_step_id ); return $this->modify_next_step_data( $next_step ); } } } } } return $next_step; } /** * get next step url * * @param string $thankyou_step_url * @param string $next_step_url * @param object $order * * @since 1.8.5 * @return string */ public function get_next_step_url( $thankyou_step_url, $next_step_url, $order ) { // check if payment gateway is supported or not $offer_settings = Wpfnl_functions::get_offer_settings(); if( $offer_settings['skip_offer_step_for_free'] === 'on' ){ if( $order && $order->get_total() <= 0 ){ return $thankyou_step_url; } } if( $offer_settings['skip_offer_step'] === 'on' ){ $supported_gateways = \WPFunnelsPro\Frontend\Modules\Gateways\Payment_Gateways_Factory::getInstance()->get_supported_payment_gateways(); if( $order && isset($supported_gateways[$order->get_payment_method()]) ){ return $next_step_url; }else{ return $thankyou_step_url; } } return $next_step_url; } /** * Modify next step based on order * * @param array $next_step next step data. * @param object $order order details. * * @return array * @since 1.8.5 */ public function modify_next_step_based_on_order( $next_step, $order ){ if( $order && $next_step ){ $offer_steps = [ 'upsell', 'downsell', 'conditional' ]; if( isset($next_step['step_type']) && in_array( $next_step['step_type'], $offer_steps ) ){ $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $next_step['step_id'] ); $offer_settings = Wpfnl_functions::get_offer_settings(); $maybe_skip = get_post_meta($funnel_id,'_wpfunnels_skip_recurring_offer',true); $maybe_skip_within_days = get_post_meta($funnel_id,'_wpfunnels_skip_recurring_offer_within_days',true); if( 'yes' == $maybe_skip ){ $email = $order->get_billing_email(); if( $email ) { $order_arg = array( 'customer' => $email, 'limit' => -1 ); $is_gbf = get_post_meta( $funnel_id, 'is_global_funnel', true ); if( 'yes' !== $is_gbf ){ $offer_product = Wpfnl_Pro_functions::get_offer_product( $next_step['step_id'], $next_step['step_type'] ); }else{ if( is_plugin_active('wpfunnels-pro-gbf/wpfnl-pro-gb.php' )){ $offer_product = $this->skip_next_step_for_gbf( $funnel_id, $next_step['step_id'] ); } } $order_arg = array( 'customer' => $email, 'limit' => -1 ); $orders = wc_get_orders($order_arg); if( is_array($orders) ) { $is_product_purchaged = false; foreach( $orders as $customer_order ){ if( $is_product_purchaged ){ break; } $order_date = $customer_order->get_date_created(); $date = $order_date->date('Y-m-d'); $current_date = date("Y-m-d"); $diff = date_diff(date_create($date),date_create($current_date)); $days = $diff->format("%R%a"); $days = str_replace('+','',$days); foreach ($customer_order->get_items() as $item_id => $item) { $product = $item->get_product(); if( $product ){ $product_id = 'variation' == $product->get_type() ? $product->get_parent_id() : $product->get_id(); if( $product_id ){ if( 'yes' == $is_gbf ){ $product = wc_get_product( isset($offer_product['id']) ? $offer_product['id'] : '' ); if( $product && 'variation' == $product->get_type() ){ $offer_id =$product->get_parent_id(); }elseif( $product ){ $offer_id =$product->get_id(); } if( $product_id == $offer_id ){ if( $maybe_skip_within_days ){ if( $maybe_skip_within_days >= $days ){ $is_product_purchaged = true; } }else{ $is_product_purchaged = true; } break; } }else{ $is_available = false; foreach( $offer_product as $_product ){ $product = wc_get_product( $_product['id'] ); if( $product && 'variation' == $product->get_type() ){ $offer_id =$product->get_parent_id(); }elseif( $product ){ $offer_id =$product->get_id(); } if( $product_id == $offer_id ){ if( $maybe_skip_within_days ){ if( $maybe_skip_within_days >= $days ){ $is_product_purchaged = true; } }else{ $is_product_purchaged = true; } break; } } if( $is_product_purchaged ){ break; } } } } } } if( $is_product_purchaged ){ $next_step = Wpfnl_functions::get_next_step( $funnel_id, $next_step['step_id'] ); return $this->modify_next_step_based_on_order( $next_step, $order ); } } } } } } return $next_step; } /** * @desc Skip the next offer step(s) for gbf * if the assigned product is out of stock. * @since 1.6.21 * @param $next_step * @return mixed */ private function skip_next_step_for_gbf( $funnel_id, $step_id ){ $step_type = get_post_meta($step_id, '_step_type', true); $offer_product = []; $order_id = ( isset( $_POST['order_id'] ) ) ? intval( $_POST['order_id'] ) : (( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0); $gbf_product = $this->get_gbf_product_from_cookie(); $gb_funnel_settings = wpfnl()->meta->get_funnel_meta( $funnel_id, 'global_funnel_start_condition' ); if( $gb_funnel_settings && is_array( $gb_funnel_settings ) ) { $offer_mappings = wpfnl()->meta->get_funnel_meta( $step_id, "global_funnel_{$step_type}_rules" ); if( isset($offer_mappings['type']) ){ if( 'specificProduct' == $offer_mappings['type'] ){ $product = isset( $offer_mappings['show'] ) && $offer_mappings['show'] ? wc_get_product( $offer_mappings['show'] ) : ''; if( $product ){ if( $product->get_type() == 'variable' ){ return $offer_product; } } } $param_type = Wpfnl_Pro_GBF_Offer_Conditions_Factory::build($offer_mappings['type']); $offer_product = $param_type->get_offer_product( $offer_mappings, $order_id, $step_id, $gbf_product ); } } return $offer_product; } /** * Get GBF product from cookie data * * @return Array */ private function get_gbf_product_from_cookie() { if( Wpfnl_functions::is_wc_active() ){ return WC()->session->get('wpfunnels_global_funnel_specific_product') ? WC()->session->get('wpfunnels_global_funnel_specific_product') : []; } return []; } } public/classes/tracking/gtm/class-wpfnl-pro-gtm.php000064400000042064147600245720016412 0ustar00settings = $this->get_settings(); if( $this->is_gtm_enabled() ) { $this->add_gtm_header_script(); add_action( 'wp_body_open', array( $this, 'add_gtm_body_script' ), 100 ); add_action( 'wpfunnels/order_bump_accepted', array( $this, 'order_bump_accepted' ), 10, 2 ); add_action( 'wpfunnels/funnel_order_placed', array($this, 'funnel_order_placed'), 10, 3 ); add_action( 'wpfunnels/offer_accepted', array( $this, 'offer_accepted' ), 10, 2 ); add_action( 'woocommerce_add_to_cart', array( $this, 'add_to_cart' ) ); } } /** * Load dataLayer Events and GTM Snippets for * * @return void */ public function add_gtm_header_script() { $compatibility = Wpfnl_Theme_Compatibility::getInstance(); if ( $compatibility->is_builder_preview() ) { return; } $gtm_container_id = isset($this->settings['gtm_container_id']) ? $this->settings['gtm_container_id']: '' ; if( $gtm_container_id != '' ) { $gtm_active_events = $this->get_active_gtm_events(); $setp_id = get_the_ID(); if( 'checkout' === get_post_meta($setp_id, '_step_type', true) ){ if($gtm_active_events['orderbump_accept']) { $this->trigger_gtm_order_bump(); } $this->trigger_gtm_purchase(); }elseif( 'upsell' === get_post_meta($setp_id, '_step_type', true) ){ if($gtm_active_events['upsell']) { $this->trigger_gtm_upsell(); } }elseif( 'downsell' === get_post_meta($setp_id, '_step_type', true) ){ if($gtm_active_events['downsell']) { $this->trigger_gtm_downsell(); } } $gtm_head_snippet = " "; echo $gtm_head_snippet; } } /** * Load GTM Snippets for * * @return void */ public function add_gtm_body_script () { $compatibility = Wpfnl_Theme_Compatibility::getInstance(); if ( $compatibility->is_builder_preview() ) { return; } $gtm_container_id = isset($this->settings['gtm_container_id']) ? $this->settings['gtm_container_id']: '' ; if( $gtm_container_id != '') { $gtm_body_snippet = ' '; echo $gtm_body_snippet; } } /** * Process upsell trigger data for dataLayer * * @return void */ public function trigger_gtm_upsell() { $key = get_current_user_id(); $upsell_data = get_transient( 'wpfunnels-upsell-info-' . $key ); if($upsell_data) { $this->load_purchage_gtm_script($event = 'upsell', $upsell_data); delete_transient( 'wpfunnels-upsell-info-' . $key ); } } /** * Process downsell trigger data for dataLayer * * @return void */ public function trigger_gtm_downsell() { $key = get_current_user_id(); $downsell_data = get_transient( 'wpfunnels-downsell-info-' . $key ); if($downsell_data) { $this->load_purchage_gtm_script($event = 'downsell', $downsell_data); delete_transient( 'wpfunnels-downsell-info-' . $key ); } } /** * Process purchase trigger data for dataLayer * * @return void */ public function trigger_gtm_purchase() { $key = get_current_user_id(); $payment_data = get_transient( 'wpfunnels-payment-info-' . $key ); $purchase_data = get_transient( 'wpfunnels-purchase-info-' . $key ); $shipping_data = get_transient( 'wpfunnels-shipping-info-' . $key ); $gtm_active_events = $this->get_active_gtm_events(); if( isset($gtm_active_events['add_payment_info']) && $gtm_active_events['add_payment_info'] && $payment_data) { $this->load_purchage_gtm_script($event = 'add_payment_info', $payment_data); delete_transient( 'wpfunnels-payment-info-' . $key ); } if( isset($gtm_active_events['add_shipping_info']) && $gtm_active_events['add_shipping_info'] && $shipping_data) { $this->load_purchage_gtm_script($event = 'add_shipping_info', $shipping_data); delete_transient( 'wpfunnels-shipping-info-' . $key ); } if( isset($gtm_active_events['purchase']) && $gtm_active_events['purchase'] ) { $this->load_purchage_gtm_script($event = 'purchase', $purchase_data); delete_transient( 'wpfunnels-purchase-info-' . $key ); } } /** * Process order bump trigger data for dataLayer * * @return void */ public function trigger_gtm_order_bump() { $key = get_current_user_id(); $orderbump_data = get_transient( 'wpfunnels-order-bump-accept-' . $key ); if($orderbump_data) { $orderbump_response = $this->prepare_gtm_order_bump_response( $orderbump_data ); $orderbump_response_data = wp_json_encode( $orderbump_response ); delete_transient( 'wpfunnels-order-bump-accept-' . $key ); delete_transient( 'wpfunnels-cart-item-key-' . $key ); $dataLayer_script = ' '; echo $dataLayer_script; } } /** * Prepare order bump event data * * @return array */ public function prepare_gtm_order_bump_response( $orderbump_data ) { return $orderbump_data; } /** * WooCommerce woocommerce_ajax_add_to_cart hook * * @param $step_id * @param $product_id */ public function add_to_cart() { $compatibility = Wpfnl_Theme_Compatibility::getInstance(); if ( $compatibility->is_builder_preview() ) { return; } $key = get_current_user_id(); $cart = WC()->cart->get_cart(); $cart_data = array(); foreach( $cart as $cart_item_key => $cart_item ){ $is_order_bump = isset($cart_item['wpfnl_order_bump']) ? $cart_item['wpfnl_order_bump']: '' ; $cart_item_key_exist = get_transient( 'wpfunnels-cart-item-key-' . $key ); if($is_order_bump == '' && !$cart_item_key_exist) { $product = $cart_item['data']; $cart_data['item_name'] = $product->get_name(); $cart_data['item_id'] = $product->get_id(); $cart_data['price'] = $product->get_price(); $cart_data['index'] = $product->get_id(); $cart_data['quantity'] = $cart_item['quantity']; set_transient( 'wpfunnels-cart-item-key-' . $key, $cart_item_key ); } } if(empty($cart_data)) { return; } $this->trigger_gtm_add_to_cart($cart_data); } /** * Process order bump trigger data for dataLayer * * @return void */ public function trigger_gtm_add_to_cart($cart_data) { $compatibility = Wpfnl_Theme_Compatibility::getInstance(); if ( $compatibility->is_builder_preview() ) { return; } $gtm_active_events = $this->get_active_gtm_events(); if($gtm_active_events['add_to_cart']) { $this->load_pre_purchage_gtm_script($event = 'add_to_cart', $cart_data); } if($gtm_active_events['begin_checkout']) { $this->load_pre_purchage_gtm_script($event = 'begin_checkout', $cart_data); } } /** * Load purchase event GTM Script * * @return array */ public function load_purchage_gtm_script($event, $data) { if (!$event || !$data ) { return; } $gtm_event_data = array( 'event' => $event, 'ecommerce' => array( 'items' => array( $data, ) ) ); $dataLayer_script = ' '; echo $dataLayer_script; } /** * Load pre purchase event GTM Script * * @return array */ public function load_pre_purchage_gtm_script($event, $data) { if (!$event) { return; } $gtm_event_data = array( 'event' => $event, 'ecommerce' => array( 'items' => array( $data, ) ) ); $dataLayer_script = ' '; echo $dataLayer_script; } /** * Order bump accepted hook * * @param $step_id * @param $product_id */ public function order_bump_accepted( $step_id, $product_id ) { $product = wc_get_product($product_id); if( $product ){ $key = get_current_user_id(); $data = array( 'event' => 'orderbump_accept', 'ecommerce' => array( 'items' => array( array( 'item_name' => $product->get_name(), 'item_id' => $product->get_id(), 'price' => $product->get_price(), 'index' => $product->get_id(), 'quantity' => 1, ), ) ) ); $gtm_active_events = $this->get_active_gtm_events(); if($gtm_active_events['orderbump_accept']) { set_transient( 'wpfunnels-order-bump-accept-' . $key, $data ); } } } /** * Main Order Tracking hook * * @param $order_id * @param $funnel_id */ public function funnel_order_placed( $order_id, $funnel_id, $step_id ) { $order = wc_get_order( $order_id ); $coupon_codes = $order->get_coupon_codes(); $item_data = array(); foreach ( $order->get_items() as $item_id => $item ) { $item_data['item_name'] = $item->get_name(); $item_data['item_id'] = $item->get_product_id(); $item_data['affiliation'] = get_bloginfo( 'name' ); $item_data['price'] = $item->get_total(); $item_data['index'] = $item->get_product_id(); $item_data['quantity'] = $item->get_quantity(); } $key = get_current_user_id(); $payment_info = array( 'currency' => $order->get_currency(), 'value' => $order->get_total(), 'coupon' => '', 'payment_type' => $order->get_payment_method_title(), 'items' => array( $item_data ) ); $shipping_info = array( 'currency' => $order->get_currency(), 'value' => $order->get_total(), 'coupon' => '', 'shipping_tier' => '', 'items' => array( $item_data ) ); $gtm_active_events = $this->get_active_gtm_events(); if($gtm_active_events['add_payment_info']) { set_transient( 'wpfunnels-payment-info-' . $key, $payment_info ); } if($gtm_active_events['purchase']) { set_transient( 'wpfunnels-purchase-info-' . $key, $payment_info ); } if($gtm_active_events['add_shipping_info']) { set_transient( 'wpfunnels-shipping-info-' . $key, $shipping_info ); } } /** * This will trigger after user accept the offer * * @param $order * @param $offer_product */ public function offer_accepted( $order, $offer_product ) { $step_id = $offer_product['step_id']; $step_type = $this->get_offer_step_type( $step_id ); $item_data = array(); $item_data['item_name'] = $offer_product['name']; $item_data['item_id'] = $offer_product['id']; $item_data['affiliation'] = get_bloginfo( 'name' ); $item_data['price'] = $offer_product['total']; $item_data['index'] = $offer_product['id']; $item_data['quantity'] = $offer_product['qty']; $key = get_current_user_id(); $offer_info = array( 'currency' => $order->get_currency(), 'value' => $order->get_total(), 'coupon' => '', 'payment_type' => $order->get_payment_method_title(), 'items' => array( $item_data ) ); $gtm_active_events = $this->get_active_gtm_events(); if($step_type == 'upsell' && $gtm_active_events['upsell']) { set_transient( 'wpfunnels-upsell-info-' . $key, $offer_info ); } if($step_type == 'downsell' && $gtm_active_events['downsell']) { set_transient( 'wpfunnels-downsell-info-' . $key, $offer_info ); } } /** * Get GTM Settings * @return array */ public function get_settings() { $default = array( 'gtm_enable' => 'off', 'gtm_container_id' => '', 'gtm_events' => array( 'view_item' => 'true', 'view_item_list' => 'true', 'select_item' => 'true', 'add_to_cart' => 'true', 'remove_from_cart' => 'true', 'view_cart' => 'true', 'begin_checkout' => 'true', 'add_payment_info' => 'true', 'add_shipping_info' => 'true', 'purchase' => 'true', ), ); $settings = get_option('_wpfunnels_gtm', $default); return wp_parse_args($settings, $default); } /** * check if gtm is enabled or not * * @return bool */ private function is_gtm_enabled() { if ( 'on' == $this->settings['gtm_enable'] ) { $step_id = get_the_id(); $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); if( !$funnel_id ) { return false; } $is_disabled = get_post_meta( $funnel_id, '_wpfunnels_disabled_gtm', true ); if( !$is_disabled || ($is_disabled && 'no' == $is_disabled) ) { return true; } } return false; } /** * Get active GTM events * @return array|bool */ public function get_active_gtm_events(){ if (isset($this->settings['gtm_events'])){ return $this->settings['gtm_events']; } return false; } /** * Check Funnel Page * @return bool */ public function is_funnel_page(){ if(get_post_type() == 'wpfunnel_steps'){ return true; } return false; } /** * check if the current step is upsell/downsell * @return string */ public function get_offer_step_type( $step_id ) { $is_upsell_downsell = false; if( !$step_id ) { global $post; $step_id = $post->ID; } $step_type = get_post_meta( $step_id, '_step_type', true ); if ( $step_type === 'upsell' || $step_type === 'downsell' || $step_type === 'landing' ) { return $step_type; } return $is_upsell_downsell; } }public/classes/tracking/pixel/class-facebook-pixel-integration.php000064400000045714147600245720021451 0ustar00is_facebook_pixel_enable() && $this->is_edit_page() != 'yes') { $this->load_facebook_pixel_public_script(); add_action( 'woocommerce_thankyou', array($this, 'load_facebook_pixel_public_script') ); add_action( 'woocommerce_add_to_cart', array($this, 'added_to_cart'),10,2 ); // AddToCart while AJAX is enabled add_action( 'woocommerce_ajax_added_to_cart', array( $this, 'ajax_added_to_cart' ) ); } } /**get_customer_id * Trigger Facebook tracking events 'ViewContent','Purchase','AddPaymentInfo','InitiateCheckout' * @param false $order_id */ public function load_facebook_pixel_public_script( $order_id = false ) { // if( $order_id ){ $compatibility = Wpfnl_Theme_Compatibility::getInstance(); if ( $compatibility->is_builder_preview() ) { return; } $fb_events = $this->get_active_facebook_tacking_event(); $pixel_id = $this->get_facebook_pixel_id(); if ( !$fb_events && !$pixel_id ) { return; } if ($pixel_id != '' && $this->is_funnel_page() && $this->is_edit_page() != 'yes' ){ $is_landing = $this->get_offer_step_type(get_the_ID()); if ($is_landing == 'landing' && isset($fb_events['ViewContent'])){ echo $this->load_script( $pixel_id, 'ViewContent', array( 'content_ids' => $this->product_id, 'content_type' => 'product', 'plugin' => 'WPFunnel-Landing' )); } if( Wpfnl_functions::is_wc_active() && is_checkout() && isset($fb_events['InitiateCheckout']) && strpos( $_SERVER['REQUEST_URI'], 'order-received' ) === false ) { echo $this->load_script( $pixel_id, 'InitiateCheckout' ); } echo $this->load_script( $pixel_id, 'PageView' ); do_action( 'wpfunnels/facebook_pixel_events',$pixel_id,$fb_events ); } // } } /** * Load facebook Pixel code * @param $pixel_id * @param false $event * @param string $params * @return string */ public function load_script( $pixel_id, $event = false, $params='' ) { $http_params = ''; if($params) { $http_params = '&' . http_build_query($params); $params = ', ' . json_encode($params); } return " "; } /** * Order bump accepted hook * * @param $step_id * @param $product_id */ public function order_bump_accepted( $step_id, $product_id ) { add_filter( 'woocommerce_update_order_review_fragments', function( $data ) use ( $product_id ) { $data['product_added_to_cart'] = $this->prepare_fb_pixel_response( $product_id ); return $data; } ); } /** * Check facebook Pixel * @return string */ public function is_facebook_pixel_enable() { $facebook_pixel_setting = get_option( '_wpfunnels_facebook_pixel' ); if ( isset($facebook_pixel_setting['enable_fb_pixel']) && 'on' == $facebook_pixel_setting['enable_fb_pixel'] ) { $step_id = get_the_id(); $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); $is_disabled = get_post_meta( $funnel_id, '_wpfunnels_disabled_fb_pixel', true ); if( !$is_disabled || ($is_disabled && $is_disabled == 'no') ){ return true; } } return false; } /** * Get facebook pixel ID * @return string */ public function get_facebook_pixel_id(){ $facebook_pixel_setting = get_option('_wpfunnels_facebook_pixel'); if (isset($facebook_pixel_setting['facebook_pixel_id'])){ return $facebook_pixel_setting['facebook_pixel_id']; } } /** * Get active facebook tracking events * @return mixed|void */ public function get_active_facebook_tacking_event(){ $facebook_pixel_setting = get_option('_wpfunnels_facebook_pixel'); if (isset($facebook_pixel_setting['facebook_tracking_events'])){ return $facebook_pixel_setting['facebook_tracking_events']; } } /** * Load Facebook Pixel code for ajax (add_to_cart) */ public function load_script_for_ajax() { ?> add_to_cart = true; $this->product_id = $product_id; $fb_events = $this->get_active_facebook_tacking_event(); $pixel_id = $this->get_facebook_pixel_id(); if ($pixel_id != '' && $this->is_funnel_page() && $this->is_edit_page() != 'yes'){ if( $this->add_to_cart && isset($fb_events['AddToCart']) ) { echo $this->load_script( $pixel_id, 'AddToCart', array( 'content_ids' => $this->product_id, 'content_type' => 'product', )); } } } public function ajax_added_to_cart() { if( get_option( 'woocommerce_enable_ajax_add_to_cart' ) == 'yes' && isset($fb_events['AddToCart'])) { $this->load_script_for_ajax(); } } /** * Get Facebook data for event Purchase in Thank you page * @param $order * @param array $offer_data * @return array */ public function get_fb_data($order, $offer_data = array()) { $value = $order->get_total(); $currency = $order->get_currency(); $data = array( 'content_type' => 'product', 'value' => $value, 'currency' => $currency, ); if( $order ){ foreach ( $order->get_items() as $item_key => $item ) { $product = $item->get_product(); if( $product ){ $data['content_ids'][] = (string) $product->get_id(); $data['content_names'][] = $product->get_name(); $data['content_category'][] = wp_strip_all_tags( wc_get_product_category_list( $product->get_id() ) ); $data['plugin'] = 'WPFunnels-Main-Order'; } } $data['transaction_id'] = $order->get_id(); } return $data; } /** * Prepare Order bump product info for fb event * @param $product_id * @return array */ public function prepare_fb_pixel_response($product_id) { $response = array(); if( Wpfnl_functions::is_wc_active() ){ $product_details = array(); $product = wc_get_product( $product_id ); $items = WC()->cart->get_cart(); foreach ( $items as $index => $item ) { if ( $item['product_id'] === $product_id ) { $product_details = $item; break; } } if ( ! empty( $product_details ) ) { $add_to_cart['content_type'] = 'product'; $add_to_cart['plugin'] = 'WPFunnels-OrderBump'; $add_to_cart['content_category'][] = wp_strip_all_tags( wc_get_product_category_list( $product ? $product->get_id() : '' ) ); $add_to_cart['currency'] = get_woocommerce_currency(); $add_to_cart['value'] = $product_details['line_subtotal'] + $product_details['line_subtotal_tax']; $add_to_cart['content_name'] = $product->get_title(); $add_to_cart['content_ids'][] = (string) $item['product_id']; $add_to_cart['contents'] = wp_json_encode( array( array( 'id' => $product_details['product_id'], 'name' => $product->get_title(), 'quantity' => $product_details['quantity'], 'item_price' => $product_details['line_subtotal'] + $product_details['line_subtotal_tax'] ), ) ); $response['product_added_to_cart'] = $add_to_cart; } } return $response; } /** * Main Order Tracking hook * Add payment,Purchase set in transient * @param $order_id * @param $funnel_id */ public function funnel_order_placed( $order_id, $funnel_id, $step_id ) { if( Wpfnl_functions::is_wc_active() ){ $order = wc_get_order($order_id); $payment_info = array( 'content_ids' => $order_id, 'value' => $order->get_total(), 'currency' => $order->get_currency(), 'payment_method' => $order->get_payment_method_title(), ); $user_key = WC()->session->get_customer_id(); $payment_data = array( 'order_id' => $order_id, 'payment_info' => $payment_info, ); $purchase_data = $this->get_fb_data($order); set_transient( 'wpfnl-payment-method-details-for-fbp-' . $user_key, $payment_data ); set_transient( 'wpfnl-main-order-purchase-details-for-fbp-' . $user_key, $purchase_data ); } } /** * This will trigger after user accept the offer * * @param $order * @param $offer_product */ public function offer_accepted( $order, $offer_product ) { if( Wpfnl_functions::is_wc_active() ){ $user_key = WC()->session->get_customer_id(); $data = array( 'order_id' => $order->get_id(), 'offer_product' => $offer_product, 'accept_offer' => $this->get_offer_step_type($offer_product['step_id']) ); set_transient( 'wpfnl-offer-details-for-fbp-' . $user_key, $data ); } } /** * Prepare Facebook response for upsell/downsell offer * @param $order_id * @param $offer_details * @return array */ public function prepare_offer_fb_pixel_response( $order_id, $offer_details ) { $order = wc_get_order($order_id); if( !$order ) { return; } $purchase_info= array(); $product_details = $offer_details['offer_product']; if ( empty( $product_details ) ) { return $purchase_info; } $purchase_info['content_type'] = 'product'; $purchase_info['currency'] = $order->get_currency(); $purchase_info['userAgent'] = $order->get_customer_user_agent(); $purchase_info['plugin'] = 'WPFunnel-Offer'; $purchase_info['content_ids'][] = (string) $product_details['id']; $purchase_info['content_names'][] = $product_details['name']; $purchase_info['content_category'][] = wp_strip_all_tags( wc_get_product_category_list( $product_details['id'] ) ); $purchase_info['value'] = $product_details['total']; $purchase_info['transaction_id'] = $offer_details['order_id']; $purchase_info['accepted_offer'] = $offer_details['accept_offer']; return $purchase_info; } /** * Track offer Event for facebook * @param $pixel_id * @param $fb_events */ public function track_offer_event_for_fb( $pixel_id, $fb_events ) { if( Wpfnl_functions::is_wc_active() ){ $user_key = Wpfnl_functions::is_wc_active() && isset(WC()->session) ? WC()->session->get_customer_id() : '' ; $offer_product = get_transient( 'wpfnl-offer-details-for-fbp-' . $user_key ); if ( empty( $offer_product ) ) { return; } $order_id = $offer_product['order_id']; $purchase_details = $this->prepare_offer_fb_pixel_response( $order_id, $offer_product ); delete_transient( 'wpfnl-offer-details-for-fbp-' . $user_key ); if ( ! empty( $purchase_details ) ) { if ($pixel_id != '' && $this->is_funnel_page()){ if( $order_id != false && isset($fb_events['Purchase']) ) { echo $this->load_script( $pixel_id, 'Purchase', $purchase_details ); } } } } } /** * Prepare Payment info for facebook response * @param $order_id * @param $payment_info * @return array|mixed */ public function prepare_payment_info_fb_pixel_response($order_id, $payment_info) { $add_payment_info = array(); $payments_details = $payment_info['payment_info']; if ( empty( $payments_details ) ) { return $payment_info; } $add_payment_info['content_ids'] = $payments_details['content_ids']; $add_payment_info['value'] = $payments_details['value']; $add_payment_info['currency'] = $payments_details['currency']; $add_payment_info['payment_method'] = $payments_details['payment_method']; $add_payment_info['plugin'] = 'WPFunnel-PaymentInfo'; return $add_payment_info; } /** * Track AddPaymentInfo Event for facebook * @param $pixel_id * @param $fb_events */ public function track_payment_event_for_fb( $pixel_id, $fb_events ) { if( Wpfnl_functions::is_wc_active() ){ $user_key = isset(WC()->session) ? WC()->session->get_customer_id() : '' ; $payment_info = get_transient( 'wpfnl-payment-method-details-for-fbp-' . $user_key ); if ( empty( $payment_info ) ) { return; } $order_id = $payment_info['order_id']; $purchase_details = $this->prepare_payment_info_fb_pixel_response( $order_id, $payment_info ); delete_transient( 'wpfnl-payment-method-details-for-fbp-' . $user_key ); if ( ! empty( $purchase_details ) ) { if ($pixel_id != '' && $this->is_funnel_page()) { if ($order_id != false && isset($fb_events['AddPaymentInfo'])) { echo $this->load_script($pixel_id, 'AddPaymentInfo', $purchase_details); } } } } } public function track_main_order_purchase_event_for_fb( $pixel_id, $fb_events ) { if( Wpfnl_functions::is_wc_active() ){ $user_key = isset(WC()->session) ? WC()->session->get_customer_id() : '' ; $purchase_info = get_transient( 'wpfnl-main-order-purchase-details-for-fbp-' . $user_key ); delete_transient( 'wpfnl-main-order-purchase-details-for-fbp-' . $user_key ); if ( ! empty( $purchase_info ) ) { if ($pixel_id != '' && $this->is_funnel_page()) { if ($purchase_info['transaction_id'] != false && isset($fb_events['Purchase'])) { echo $this->load_script($pixel_id, 'Purchase', $purchase_info); } } } } } /** * check if the current step is upsell/downsell * @return string */ public function get_offer_step_type( $step_id ) { $is_upsell_downsell = false; if( !$step_id ) { global $post; $step_id = $post->ID; } $step_type = get_post_meta( $step_id, '_step_type', true ); if ( $step_type === 'upsell' || $step_type === 'downsell' || $step_type === 'landing' ) { return $step_type; } return $is_upsell_downsell; } }public/gateways/class-wpfnl-pro-api-base.php000064400000043023147600245720015076 0ustar00 $value ) { $this->add_parameter( $key, $value ); } } /** * Add a parameter * * @param string $key * @param string|int $value * * @since 2.0 */ public function add_parameter( $key, $value ) { $this->parameters[ $key ] = $value; } public function clean_params() { $this->parameters = array(); } public function get_parameters() { $this->parameters = apply_filters( 'wpfnl_api_args', $this->parameters, $this ); // validate parameters foreach ( $this->parameters as $key => $value ) { // remove unused params if ( '' === $value || is_null( $value ) ) { unset( $this->parameters[ $key ] ); } } return $this->parameters; } /** * Perform the request and return the parsed response * * @param object $request class instance which implements \SV_WC_API_Request * * @return object class instance which implements \SV_WC_API_Response * @throws Exception * @since 2.2.0 * */ protected function perform_request( $request ) { // ensure API is in its default state $this->reset_response(); // save the request object $this->request = $request; $start_time = microtime( true ); // perform the request $response = $this->do_remote_request( $this->get_request_uri(), $this->get_request_args() ); // calculate request duration $this->request_duration = round( microtime( true ) - $start_time, 5 ); try { // parse & validate response $response = $this->handle_response( $response ); } catch ( \Exception $e ) { // alert other actors that a request has been made $this->broadcast_request(); throw $e; } return $response; } /** * Reset the API response members to their * * @since 1.0.0 */ protected function reset_response() { $this->response_code = null; $this->response_message = null; $this->response_headers = null; $this->raw_response_body = null; $this->response = null; $this->request_duration = null; } /** * Simple wrapper for wp_remote_request() so child classes can override this * and provide their own transport mechanism if needed, e.g. a custom * cURL implementation * * @param string $request_uri * @param string $request_args * * @return array|WP_Error * @since 2.2.0 * */ protected function do_remote_request( $request_uri, $request_args ) { return wp_safe_remote_request( $request_uri, $request_args ); } /** * Get the request URI * * @return string * @since 2.2.0 */ protected function get_request_uri() { // API base request URI + any request-specific path $uri = $this->request_uri . ( $this->get_request() ? $this->get_request()->path : '' ); /** * Request URI Filter. * * Allow actors to filter the request URI. Note that child classes can override * this method, which means this filter may be invoked prior to the overridden * method. * * @param string $uri current request URI * @param \Wpfnl_Pro_API_Base class instance * * @since 4.1.0 * */ return apply_filters( 'wc_' . $this->get_api_id() . '_api_request_uri', $uri, $this ); } /** * Returns the most recent request object * * @return object the most recent request object * @see \SV_WC_API_Request * @since 2.2.0 */ public function get_request() { return $this->request; } /** * Get the ID for the API, used primarily to namespace the action name * for broadcasting requests * * @return string * @since 2.2.0 */ protected function get_api_id() { return 'wpfunnels-pro'; } /** * Get the request arguments in the format required by wp_remote_request() * * @return mixed|void * @since 2.2.0 */ protected function get_request_args() { $args = array( 'method' => $this->get_request_method(), 'timeout' => MINUTE_IN_SECONDS, 'redirection' => 0, 'httpversion' => $this->get_request_http_version(), 'sslverify' => true, 'blocking' => true, 'user-agent' => $this->get_request_user_agent(), 'headers' => $this->get_request_headers(), 'body' => $this->get_request()->body, 'cookies' => array(), ); /** * Request arguments. * * Allow other actors to filter the request arguments. Note that * child classes can override this method, which means this filter may * not be invoked, or may be invoked prior to the overridden method * * @param array $args request arguments * @param \Wpfnl_Pro_API_Base class instance * * @since 2.2.0 * */ return apply_filters( 'wc_' . $this->get_api_id() . '_http_request_args', $args, $this ); } /** * Get the request method, POST by default * * @return string * @since 2.2.0 */ protected function get_request_method() { // if the request object specifies the method to use, use that, otherwise use the API default return $this->get_request() && $this->get_request()->method ? $this->get_request()->method : $this->request_method; } /** Request Getters *******************************************************/ /** * Get the request HTTP version, 1.1 by default * * @return string * @since 2.2.0 */ protected function get_request_http_version() { return $this->request_http_version; } /** * Get the request user agent, defaults to: * * Dasherized-Plugin-Name/Plugin-Version (WooCommerce/WC-Version; WordPress/WP-Version) * * @return string * @since 2.2.0 */ protected function get_request_user_agent() { return ''; return sprintf( '%s/%s (WooCommerce/%s; WordPress/%s)', str_replace( ' ', '-', $this->get_plugin()->get_plugin_name() ), $this->get_plugin()->get_version(), WC_VERSION, $GLOBALS['wp_version'] ); } /** * Get the request headers * * @return array * @since 2.2.0 */ protected function get_request_headers() { return $this->request_headers; } /** * Handle and parse the response * * @param array|WP_Error $response response data * * @return object request class instance that implements SV_WC_API_Request * @throws Exception network issues, timeouts, API errors, etc * @since 2.2.0 * */ protected function handle_response( $response ) { // check for WP HTTP API specific errors (network timeout, etc) if ( is_wp_error( $response ) ) { throw new \Exception( $response->get_error_message(), (int) $response->get_error_code() ); } // set response data $this->response_code = wp_remote_retrieve_response_code( $response ); $this->response_message = wp_remote_retrieve_response_message( $response ); $this->response_headers = wp_remote_retrieve_headers( $response ); $this->raw_response_body = wp_remote_retrieve_body( $response ); // allow child classes to validate response prior to parsing -- this is useful // for checking HTTP status codes, etc. $this->do_pre_parse_response_validation(); // parse the response body and tie it to the request $this->response = $this->get_parsed_response( $this->raw_response_body ); // allow child classes to validate response after parsing -- this is useful // for checking error codes/messages included in a parsed response $this->do_post_parse_response_validation(); // fire do_action() so other actors can act on request/response data, // primarily used for logging $this->broadcast_request(); return $this->response; } /** * Allow child classes to validate a response prior to instantiating the * response object. Useful for checking response codes or messages, e.g. * throw an exception if the response code is not 200. * * A child class implementing this method should simply return true if the response * processing should continue, or throw a \SV_WC_API_Exception with a * relevant error message & code to stop processing. * * Note: Child classes *must* sanitize the raw response body before throwing * an exception, as it will be included in the broadcast_request() method * which is typically used to log requests. * * @since 2.2.0 */ protected function do_pre_parse_response_validation() { // stub method } /** * Return the parsed response object for the request * * @param string $raw_response_body * * @return object response class instance which implements SV_WC_API_Request * @since 2.2.0 * */ protected function get_parsed_response( $raw_response_body ) { /** * do parsing if necessary */ return $raw_response_body; } /** * Allow child classes to validate a response after it has been parsed * and instantiated. This is useful for check error codes or messages that * exist in the parsed response. * * A child class implementing this method should simply return true if the response * processing should continue, or throw an Exception with a * relevant error message & code to stop processing. * * Note: Response body sanitization is handled automatically * * @since 2.2.0 */ protected function do_post_parse_response_validation() { // stub method } /** * Alert other actors that a request has been performed. This is primarily used * for request logging. * * @since 2.2.0 */ protected function broadcast_request() { $request_data = array( 'method' => $this->get_request_method(), 'uri' => $this->get_request_uri(), 'user-agent' => $this->get_request_user_agent(), 'headers' => $this->get_sanitized_request_headers(), 'body' => $this->request->body, 'duration' => $this->get_request_duration() . 's', // seconds ); $response_data = array( 'code' => $this->get_response_code(), 'message' => $this->get_response_message(), 'headers' => $this->get_response_headers(), 'body' => $this->get_sanitized_response_body() ? $this->get_sanitized_response_body() : $this->get_raw_response_body(), ); do_action( 'wc_' . $this->get_api_id() . '_api_request_performed', $request_data, $response_data, $this ); } /** Response Getters ******************************************************/ /** * Get sanitized request headers suitable for logging, stripped of any * confidential information * * The `Authorization` header is sanitized automatically. * * Child classes that implement any custom authorization headers should * override this method to perform sanitization. * * @return array * @since 2.2.0 */ protected function get_sanitized_request_headers() { $headers = $this->get_request_headers(); if ( ! empty( $headers['Authorization'] ) ) { $headers['Authorization'] = str_repeat( '*', strlen( $headers['Authorization'] ) ); } return $headers; } /** * Get the request duration in seconds, rounded to the 5th decimal place * * @return string * @since 2.2.0 */ protected function get_request_duration() { return $this->request_duration; } /** * Get the response code * * @return string * @since 2.2.0 */ protected function get_response_code() { return $this->response_code; } /** * Get the response message * * @return string * @since 2.2.0 */ protected function get_response_message() { return $this->response_message; } /** * Get the response headers * * @return array * @since 2.2.0 */ protected function get_response_headers() { return $this->response_headers; } /** * Get the sanitized response body, provided by the response class * to_string_safe() method * * @return string|null * @since 2.2.0 */ protected function get_sanitized_response_body() { return is_callable( array( $this->get_response(), 'to_string_safe' ) ) ? $this->get_response()->to_string_safe() : null; } /** Misc Getters ******************************************************/ /** * Returns the most recent response object * * @return object the most recent response object * @see \SV_WC_API_Response * @since 2.2.0 */ public function get_response() { return $this->response; } /** * Get the raw response body, prior to any parsing or sanitization * * @return string * @since 2.2.0 */ protected function get_raw_response_body() { return $this->raw_response_body; } /** * Get the response handler class name * * @return string * @since 2.2.0 */ protected function get_response_handler() { return $this->response_handler; } /** Setters ***************************************************************/ /** * Set the response handler class name. This class will be instantiated * to parse the response for the request. * * Note the class should implement SV_WC_API * * @param string $handler handle class name * * @return array * @since 2.2.0 * */ protected function set_response_handler( $handler ) { $this->response_handler = $handler; } /** * Set a header request * * @param string $name header name * @param string $value header value * * @return string * @since 2.2.0 * */ protected function set_request_header( $name, $value ) { $this->request_headers[ $name ] = $value; } /** * Set HTTP basic auth for the request * * Since 2.2.0 * * @param string $username * @param string $password */ protected function set_http_basic_auth( $username, $password ) { $this->request_headers['Authorization'] = sprintf( 'Basic %s', base64_encode( "{$username}:{$password}" ) ); } /** * Set the Content-Type request header * * @param string $content_type * * @since 2.2.0 * */ protected function set_request_content_type_header( $content_type ) { $this->request_headers['content-type'] = $content_type; } /** * Set the Accept request header * * @param string $type the request accept type * * @since 2.2.0 * */ protected function set_request_accept_header( $type ) { $this->request_headers['accept'] = $type; } }public/gateways/class-wpfnl-pro-gateway-authorize-net.php000064400000107417147600245720017662 0ustar00refund_support = true; /** * force tokenization and do not ask user for an option */ add_filter( 'wc_payment_gateway_' . $this->key . '_tokenization_forced', array( $this, 'maybe_force_tokenization' ) ); /** * create token for non logged in user and accept js is turned on. * force tokenization for guest when needed */ add_filter( 'wc_payment_gateway_' . $this->key . '_process_payment', array( $this, 'create_token_process_payment' ), 10, 3 ); /** * modify refund request data */ add_filter( 'wc_authorize_net_cim_api_request_data', array( $this, 'modify_offer_refund_request_data' ), 10, 3 ); add_action( 'wpfunnels/subscription_created', array( $this, 'add_offer_subscription_meta' ), 9999, 3 ); } /** * force tokenization for upsell/downsell * * @param $force_tokenization * @return mixed */ public function maybe_force_tokenization( $force_tokenization ) { if( isset( $_POST['post_data'] ) ) { $post_data = array(); parse_str( $_POST['post_data'],$post_data ); $checkout_id = Wpfnl_Pro_functions::get_checkout_id_from_post_data( $post_data ); $funnel_id = Wpfnl_Pro_functions::get_funnel_id_from_post_data( $post_data ); if ( $checkout_id && $funnel_id ) { if ( Wpfnl_Pro_functions::is_offer_exists_in_funnel($funnel_id) ) { $force_tokenization = true; } } } return $force_tokenization; } /** * create token and process payment for non-logged in user * * @param $result * @param $order_id * @param $auth_net_obj * @return mixed */ public function create_token_process_payment( $result, $order_id, $auth_net_obj ) { $create_token = false; $checkout_id = Wpfnl_functions::get_checkout_id_from_post_data(); $funnel_id = Wpfnl_functions::get_funnel_id_from_post_data(); if ( $checkout_id && $funnel_id ) { if ( Wpfnl_Pro_functions::is_offer_exists_in_funnel($funnel_id) ) { $create_token = true; } } $order = $this->get_wc_gateway()->get_order( $order_id ); if( $create_token && empty( $order->get_user_id() ) ) { try { // check if token is already exists for the order if( isset( $order->payment->token ) && $order->payment->token ) { $this->get_wc_gateway()->add_transaction_data( $order ); } else { // create new token $order_for_shipping = $order; try { $order = $this->get_wc_gateway()->get_payment_tokens_handler()->create_token($order); } catch ( \Exception $e ) { $re = '/[0-9]+/'; $str = $e->getMessage(); preg_match_all( $re, $str, $matches, PREG_SET_ORDER, 0 ); if ( $matches && is_array( $matches ) && isset( $matches[0][0] ) && '00039' === $matches[0][0] ) { $get_order_by_meta = new \WP_Query( array( 'post_type' => 'shop_order', 'post_status' => 'any', 'meta_query' => array( array( 'key' => '_wc_authorize_net_cim_credit_card_customer_id', 'value' => $matches[1][0], 'compare' => '=', ), ), 'fields' => 'ids', 'order' => 'ASC', ) ); if ( is_array( $get_order_by_meta->posts ) && count( $get_order_by_meta->posts ) > 0 ) { $this->extra_data['authorize_net_cim_order_id'] = $get_order_by_meta->posts[0]; $order_for_shipping = $this->get_wc_gateway()->get_order( $get_order_by_meta->posts[0] ); $this->extra_data['authorize_net_cim_customer_id'] = $matches[1][0]; } } } $this->unset_opaque_value = true; $order = $this->get_order( $order ); $this->get_wc_gateway()->add_transaction_data( $order ); /** * We need to create shipping ID for the current user on Authorize.Net CIM API * As ShippingAddressID is important for the cases when business owner has shipping-filters enabled in their merchant account. * */ try { /** * When we are in a case when there is a returning user & not logged in then in this case there are chances that shipping API request might fail. * In this case we need to try and get shipping ID from the order meta and set this up for further. */ $response = $this->get_wc_gateway()->get_api()->create_shipping_address( $order ); } catch ( \Exception $e) { $response = intval( $order_for_shipping->get_meta( '_authorize_cim_shipping_address_id', true ) ); } $shipping_address_id = is_numeric( $response ) ? $response : $response->get_shipping_address_id(); $order->payment->shipping_address_id = $shipping_address_id; WC()->session->set( 'authorize_net_cim_shipping_id', $order->payment->shipping_address_id ); $this->get_wc_gateway()->add_transaction_data( $order ); $this->do_main_transaction( $order ); } $result = array( 'result' => 'success', 'redirect' => $this->get_wc_gateway()->get_return_url( $order ), ); } catch ( \Exception $e ) { $result = array( 'result' => 'failure', 'message' => $e->getMessage(), ); } } return $result; } /** * process the offer payment * * @param $order * @param $offer_product * @return array */ public function process_payment( $order, $offer_product ) { $_response = array( 'is_success' => false, 'message' => '' ); $this->offer_product = $offer_product; try { $gateway = $this->get_wc_gateway(); $api = $gateway->get_api(); $environment = $gateway->get_environment(); $url = ( 'production' === $environment ) ? $api::PRODUCTION_ENDPOINT : $api::TEST_ENDPOINT; /** * modify order object and populate it as per the scenario (offer transaction) */ add_filter( 'wc_payment_gateway_' . $this->key . '_get_order', array( $this, 'get_modified_order' ), 999 ); $new_order = $gateway->get_order( $order ); $request = $this->create_transaction_request( 'capture', $new_order ); $response = wp_safe_remote_request( $url, $this->get_request_attributes( $request ) ); $body = wp_remote_retrieve_body( $response ); $body = preg_replace( '/[\x00-\x1F\x80-\xFF]/', '', $body ); $result = json_decode( $body, true ); if ( is_wp_error( $response ) ) { $result['is_success'] = false; } else { if ( isset( $result['messages'] ) && isset( $result['messages']['message'][0]['code'] ) && 'I00001' === $result['messages']['message'][0]['code'] ) { $_response['is_success'] = true; $transaction_id = $this->get_transaction_id( $result['directResponse'] ); $response_data = array( 'id' => $transaction_id, ); $this->store_offer_transaction( $order, $response_data, $offer_product ); } else { $order_note = sprintf( __( 'Authorize.net CIM Transaction Failed (%s)', 'wpfnl-pro' ), $result['messages']['message'][0]['text'] ); $_response['is_success'] = false; $_response['message'] = $order_note; $order->add_order_note( $order_note ); } } } catch (\Exception $e) { $order_note = sprintf( __( 'Authorize.net CIM Transaction Failed (%s)', 'wpfnl-pro' ), $e->getMessage() ); $_response['is_success'] = false; $_response['message'] = $order_note; $order->add_order_note( $order_note ); } return $_response; } /** * create transaction request for offer * product * * @param $type * @param $new_order * @return array[] */ private function create_transaction_request( $type, $new_order ) { $order = $new_order; $transaction_type = ( 'auth_only' === $type ) ? 'profileTransAuthOnly' : 'profileTransAuthCapture'; $offer_product = $this->offer_product; /** * We need to create shipping ID for the current user on Authorize.Net CIM API * As ShippingAddressID is important for the cases when business owner has shipping-filters enabled in their merchant account. */ $maybe_get_shipping_id_from_session = WC()->session->get( 'authorize_net_cim_shipping_id' ); if ( isset( $order->payment ) && isset( $order->payment->shipping_address_id ) && ! empty( $order->payment->shipping_address_id ) ) { $shipping_address_id = $order->payment->shipping_address_id; } elseif ( ! empty( $maybe_get_shipping_id_from_session ) ) { $shipping_address_id = $maybe_get_shipping_id_from_session; } else { $response = $this->get_wc_gateway()->get_api()->create_shipping_address( $order ); $shipping_address_id = is_numeric( $response ) ? $response : $response->get_shipping_address_id(); } return array( 'createCustomerProfileTransactionRequest' => array( 'merchantAuthentication' => array( 'name' => wc_clean( $this->get_wc_gateway()->get_api_login_id() ), 'transactionKey' => wc_clean( $this->get_wc_gateway()->get_api_transaction_key() ), ), 'refId' => $order->get_id() . '_' . $offer_product['step_id'], 'transaction' => array( $transaction_type => array( 'amount' => $offer_product['total'], 'tax' => array(), 'shipping' => array(), 'lineItems' => $this->get_line_items(), 'customerProfileId' => $this->get_customer_id( $order ), 'customerPaymentProfileId' => $this->get_token( $order ), 'customerShippingAddressId' => $shipping_address_id, 'order' => array( 'invoiceNumber' => $order->get_id() . '_' . $offer_product['step_id'], 'description' => $this->string_truncate( $offer_product['desc'], 255 ), 'purchaseOrderNumber' => $this->string_truncate( preg_replace( '/\W/', '', $order->payment->po_number ), 25 ), ), ), ), ) ); } /** * do main transaction * * @param \WC_Order $order */ private function do_main_transaction( \WC_Order $order ) { try{ $order->description = sprintf( __( '%1$s - Release Payment for Order %2$s', 'wpfnl-pro' ), esc_html( $this->get_current_site_name() ), $order->get_order_number() ); // token is required. if ( ! $order->payment->token ) { throw new Exception( __( 'Payment token missing/invalid.', 'wpfnl-pro' ) ); } // perform the main transaction if ( $this->get_wc_gateway()->is_credit_card_gateway() ) { if ( $this->get_wc_gateway()->perform_credit_card_charge( $order ) ) { $response = $this->get_wc_gateway()->get_api()->credit_card_charge( $order ); } else { $response = $this->get_wc_gateway()->get_api()->credit_card_authorization( $order ); } } elseif ( $this->get_wc_gateway()->is_echeck_gateway() ) { $response = $this->get_wc_gateway()->get_api()->check_debit( $order ); } // success! update order record if ( $response->transaction_approved() ) { $last_four = substr( $order->payment->account_number, - 4 ); // order note based on gateway type if ( $this->get_wc_gateway()->is_credit_card_gateway() ) { $message = sprintf( __( '%1$s %2$s Release Payment Approved: %3$s ending in %4$s (expires %5$s)', 'wpfnl-pro' ), $this->get_wc_gateway()->get_method_title(), $this->get_wc_gateway()->perform_credit_card_authorization( $order ) ? 'Authorization' : 'Charge', ! isset( $order->payment->card_type ) ? $order->payment->card_type : 'card', $last_four, ( isset( $order->payment->exp_month ) && isset( $order->payment->exp_year ) ? $order->payment->exp_month . '/' . substr( $order->payment->exp_year, - 2 ) : 'n/a' ) ); } // adds the transaction id (if any) to the order note if ( $response->get_transaction_id() ) { $message .= ' ' . sprintf( __( '(Transaction ID %s)', 'wpfnl-pro' ), $response->get_transaction_id() ); } $order->add_order_note( $message ); } if ( $response->transaction_approved() || $response->transaction_held() ) { // add the standard transaction data. $this->get_wc_gateway()->add_transaction_data( $order, $response ); // allow the concrete class to add any gateway-specific transaction data to the order. $this->get_wc_gateway()->add_payment_gateway_transaction_data( $order, $response ); // if the transaction was held (ie fraud validation failure) mark it as such. if ( $response->transaction_held() || ( $this->get_wc_gateway()->supports( 'authorization' ) && $this->get_wc_gateway()->perform_credit_card_authorization( $order ) ) ) { $this->get_wc_gateway()->mark_order_as_held( $order, $this->get_wc_gateway()->supports( 'authorization' ) && $this->get_wc_gateway()->perform_credit_card_authorization( $order ) ? __( 'Authorization only transaction', 'wpfnl-pro' ) : $response->get_status_message(), $response ); wc_reduce_stock_levels( $order->get_id() ); } else { // otherwise complete the order. $order->payment_complete(); } } else { // failure. throw new Exception( sprintf( '%s: %s', $response->get_status_code(), $response->get_status_message() ) ); } } catch ( \Exception $e ) { if ( isset( $response ) ) { $this->get_wc_gateway()->mark_order_as_failed( $order, sprintf( __( 'Release Payment Failed: %s', 'wpfnl-pro' ), $e->getMessage() ), $response ); } else { $this->get_wc_gateway()->mark_order_as_failed( $order, sprintf( __( 'Release Payment Failed: %s', 'wpfnl-pro' ), $e->getMessage() ) ); } } } /** * get modified order * * @param $order * @return mixed|\WC_Order */ public function get_modified_order( $order ) { if ( $order instanceof \WC_Order && $this->key === $order->get_payment_method() ) { if ( $this->has_token( $order ) && ! is_checkout_pay_page() ) { $order_id = $order->get_id(); // retrieve the payment token. $order->payment->token = $this->get_wc_gateway()->get_order_meta( $order_id, 'payment_token' ); $token_from_gateway = $this->get_token( $order ); if ( empty( $order->payment->token ) && ! empty( $token_from_gateway ) ) { $order->payment->token = $token_from_gateway; } // retrieve the optional customer id. $order->customer_id = $this->get_wc_gateway()->get_order_meta( $order_id, 'customer_id' ); // set token data on order. if ( $this->get_wc_gateway()->get_payment_tokens_handler()->user_has_token( $order->get_user_id(), $order->payment->token ) ) { // an existing registered user with a saved payment token. $token = $this->get_wc_gateway()->get_payment_tokens_handler()->get_token( $order->get_user_id(), $order->payment->token ); // account last four. $order->payment->account_number = $token->get_last_four(); if ( $this->get_wc_gateway()->is_credit_card_gateway() ) { // card type. $order->payment->card_type = $token->get_card_type(); // exp month/year. $order->payment->exp_month = $token->get_exp_month(); $order->payment->exp_year = $token->get_exp_year(); } elseif ( $this->get_wc_gateway()->is_echeck_gateway() ) { // account type (checking/savings). $order->payment->account_type = $token->get_account_type(); } } else { // a guest user means that token data must be set from the original order. // account number. $order->payment->account_number = $this->get_wc_gateway()->get_order_meta( $order_id, 'account_four' ); if ( $this->get_wc_gateway()->is_credit_card_gateway() ) { // card type. $order->payment->card_type = $this->get_wc_gateway()->get_order_meta( $order_id, 'card_type' ); // expiry date. $expiry_date = $this->get_wc_gateway()->get_order_meta( $order_id, 'card_expiry_date' ); if ( ! empty( $expiry_date ) ) { list( $exp_year, $exp_month ) = explode( '-', $expiry_date ); $order->payment->exp_month = $exp_month; $order->payment->exp_year = $exp_year; } } elseif ( $this->get_wc_gateway()->is_echeck_gateway() ) { // account type. $order->payment->account_type = $this->get_wc_gateway()->get_order_meta( $order_id, 'account_type' ); } } } $response = intval( $order->get_meta( '_authorize_cim_shipping_address_id', true ) ); if ( ! empty( $response ) ) { $order->payment->shipping_address_id = $response; } if ( true === $this->unset_opaque_value && isset( $order->payment->opaque_value ) ) { unset( $order->payment->opaque_value ); } } return $order; } /** * modify offer refund request data * * @param $request_data * @param $order * @param $gateway * @return mixed */ public function modify_offer_refund_request_data( $request_data, $order, $gateway ) { if ( isset( $_POST['wpfnl_refund'] ) ) { $refund_data = $_POST; $order_id = $order->get_id(); $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; if ( isset( $request_data['createCustomerProfileTransactionRequest'] ) && isset( $request_data['createCustomerProfileTransactionRequest']['refId'] ) ) { $request_data['createCustomerProfileTransactionRequest']['refId'] = $order_id . '_' . $step_id; } if ( isset( $request_data['createCustomerProfileTransactionRequest'] ) && isset( $request_data['createCustomerProfileTransactionRequest']['transaction'] ) && isset( $request_data['createCustomerProfileTransactionRequest']['transaction']['profileTransRefund'] ) && isset( $request_data['createCustomerProfileTransactionRequest']['transaction']['profileTransRefund']['order'] ) && isset( $request_data['createCustomerProfileTransactionRequest']['transaction']['profileTransRefund']['order']['invoiceNumber'] ) ) { $request_data['createCustomerProfileTransactionRequest']['transaction']['profileTransRefund']['order']['invoiceNumber'] = $order_id . '_' . $step_id; } } return $request_data; } /** * @param $order * @param $data * @return mixed */ public function process_refund_offer( $order, $data ) { $transaction_id = $data['transaction_id']; $refund_amount = $data['refund_amount']; $refund_reason = $data['refund_reason']; $gateway = $this->get_wc_gateway(); $api = $gateway->get_api(); $order->refund = new \stdClass(); $order->refund->trans_id = $transaction_id; $order->refund->amount = number_format( $refund_amount, 2, '.', '' ); $order->refund->reason = $refund_reason; $order->refund->customer_profile_id = $gateway->get_order_meta( $order, 'customer_id' ); $order->refund->customer_payment_profile_id = $gateway->get_order_meta( $order, 'payment_token' ); $response = $api->refund( $order ); $response_id = $response->get_transaction_id(); if ( ! $response_id ) { $response = $api->void( $order ); $response_id = $response->get_transaction_id(); } return $response_id; } /** * update the meta data for WooCommerce subscription * * @param $subscription * @param $offer_product * @param $order */ public function add_offer_subscription_meta( $subscription, $offer_product, $order ) { if ( 'authorize_net_cim_credit_card' === $order->get_payment_method() ) { $subscription_id = $subscription->get_id(); update_post_meta( $subscription_id, '_wc_authorize_net_cim_credit_card_customer_id', $order->get_meta( '_wc_authorize_net_cim_credit_card_customer_id', true ) ); update_post_meta( $subscription_id, '_wc_authorize_net_cim_credit_card_payment_token', $order->get_meta( '_wc_authorize_net_cim_credit_card_payment_token', true ) ); } } /** * gert order * * @param $order * @return mixed */ public function get_order( $order ) { if ( $order instanceof \WC_Order && $this->key === $order->get_payment_method() ) { if ( $this->has_token( $order ) && ! is_checkout_pay_page() ) { $order_id = $order->get_id(); // retrieve the payment token. $order->payment->token = $this->get_wc_gateway()->get_order_meta( $order_id, 'payment_token' ); $token_from_gateway = $this->get_token( $order ); if ( empty( $order->payment->token ) && ! empty( $token_from_gateway ) ) { $order->payment->token = $token_from_gateway; } // retrieve the optional customer id. $order->customer_id = $this->get_wc_gateway()->get_order_meta( $order_id, 'customer_id' ); /* May be we need customer id from session */ $customer_id_from_session = isset( $this->extra_data['authorize_net_cim_customer_id'] ) ? $this->extra_data['authorize_net_cim_customer_id'] : ''; if ( empty( $order->customer_id ) && ! empty( $customer_id_from_session ) ) { $order->customer_id = $customer_id_from_session; } // set token data on order. if ( $this->get_wc_gateway()->get_payment_tokens_handler()->user_has_token( $order->get_user_id(), $order->payment->token ) ) { // an existing registered user with a saved payment token. $token = $this->get_wc_gateway()->get_payment_tokens_handler()->get_token( $order->get_user_id(), $order->payment->token ); // account last four. $order->payment->account_number = $token->get_last_four(); if ( $this->get_wc_gateway()->is_credit_card_gateway() ) { // card type. $order->payment->card_type = $token->get_card_type(); // exp month/year. $order->payment->exp_month = $token->get_exp_month(); $order->payment->exp_year = $token->get_exp_year(); } elseif ( $this->get_wc_gateway()->is_echeck_gateway() ) { // account type (checking/savings). $order->payment->account_type = $token->get_account_type(); } } else { // a guest user means that token data must be set from the original order. // account number. $order->payment->account_number = $this->get_wc_gateway()->get_order_meta( $order_id, 'account_four' ); if ( $this->get_wc_gateway()->is_credit_card_gateway() ) { // card type. $order->payment->card_type = $this->get_wc_gateway()->get_order_meta( $order_id, 'card_type' ); // expiry date. $expiry_date = $this->get_wc_gateway()->get_order_meta( $order_id, 'card_expiry_date' ); if ( ! empty( $expiry_date ) ) { list( $exp_year, $exp_month ) = explode( '-', $expiry_date ); $order->payment->exp_month = $exp_month; $order->payment->exp_year = $exp_year; } } elseif ( $this->get_wc_gateway()->is_echeck_gateway() ) { // account type. $order->payment->account_type = $this->get_wc_gateway()->get_order_meta( $order_id, 'account_type' ); } } } $response = intval( $order->get_meta( '_authorize_cim_shipping_address_id' ) ); if ( ! empty( $response ) ) { $order->payment->shipping_address_id = $response; } if ( true === $this->unset_opaque_value && isset( $order->payment->opaque_value ) ) { unset( $order->payment->opaque_value ); } } return $order; } /** * check if the order has any payment * token * * @param $order * @return bool */ private function has_token( $order ) { $order_id = $order->get_id(); $token = $order->get_meta('_wc_' . $this->key . '_payment_token' ); if ( ! empty( $token ) ) { return true; } /** * if token is not present in order fallback */ if ( isset( $this->extra_data['authorize_net_cim_order_id'] ) ) { $fallback_order_id = $this->extra_data['authorize_net_cim_order_id']; $token = $order->get_meta('_wc_' . $this->key . '_payment_token' ); if ( ! empty( $token ) ) { update_post_meta( $order_id, '_wc_' . $this->key . '_payment_token', $token ); return true; } } return false; } /** * get token from order * * @param $order * @return array|false|mixed|string */ private function get_token( $order ) { if( false === is_a( $order, 'WC_Order' ) ){ return false; } $token = $order->get_meta('_wc_' . $this->key . '_payment_token' ); if ( ! empty( $token ) ) { return $token; } return false; } /** * get next step id * * @param $funnel_id * @param $step_id * @return false|int */ public function get_next_step( $funnel_id, $step_id ) { $next_step_id = false; if( $step_id ) { $steps = Wpfnl_functions::get_steps($funnel_id); if ( is_array( $steps ) ) { foreach ( $steps as $index => $step ) { if ( intval( $step['id'] ) === $step_id ) { $next_step_index = $index + 1; if ( isset( $steps[ $next_step_index ] ) ) { $next_step_id = intval( $steps[ $next_step_index ]['id'] ); } break; } } } } return $next_step_id; } /** * get wc payment gateway * * @return mixed */ private function get_wc_gateway() { global $woocommerce; $gateways = $woocommerce->payment_gateways->payment_gateways(); return $gateways[ $this->key ]; } /** * get line items of the order * * @return array */ private function get_line_items() { $line_items = array(); $offer_product = $this->offer_product; if ( isset( $offer_product['id'] ) && $offer_product['id'] > 0 ) { $line_items[] = array( 'itemId' => $this->string_truncate( $offer_product['id'], 31 ), 'name' => $this->string_truncate( $offer_product['name'], 31 ), 'description' => $this->string_truncate( $offer_product['desc'], 255 ), 'quantity' => $offer_product['qty'], 'unitPrice' => number_format( (float) $offer_product['total'], 2, '.', '' ), ); } return $line_items; } /** * Truncates a given string. The total length of return string will not exceed the given length. * The last characters will be replaced with the $omission string * for a total length not exceeding $length * * @param $string * @param $length * @param string $omission * @return mixed|string */ private function string_truncate( $string, $length, $omission = '...' ) { if ( extension_loaded( 'mbstring' ) ) { if ( mb_strlen( $string, self::MB_ENCODING ) <= $length ) { return $string; } $length -= mb_strlen( $omission, self::MB_ENCODING ); return mb_substr( $string, 0, $length, self::MB_ENCODING ) . $omission; } else { $string = $this->str_to_ascii( $string ); if ( strlen( $string ) <= $length ) { return $string; } $length -= strlen( $omission ); return substr( $string, 0, $length ) . $omission; } } /** * returns a string with all non ascii characters * removed * * @param $string * @return mixed */ private function str_to_ascii( $string ) { // strip ASCII chars 32 and under $string = filter_var( $string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW ); // strip ASCII chars 127 and higher return filter_var( $string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH ); } /** * get customer id from order * * @param $order * @return array|false|mixed|string */ private function get_customer_id( $order ) { $customer_id = $order->get_meta('_wc_' . $this->key . '_customer_id', ); if ( ! empty( $customer_id ) ) { return $customer_id; } return ''; } /** * get request attributes * * @param $request * @return array */ public function get_request_attributes( $request ) { return array( 'method' => 'POST', 'timeout' => MINUTE_IN_SECONDS, 'redirection' => 0, 'httpversion' => '1.0', 'sslverify' => true, 'blocking' => true, 'headers' => array( 'content-type' => 'application/json', 'accept' => 'application/json', ), 'body' => wp_json_encode( $request ), 'cookies' => array(), ); } /** * get transaction id * * @param $response * @return mixed|string */ private function get_transaction_id( $response ) { // parse response $response = explode( ',', $response ); if ( empty( $response ) ) { return ''; } // offset array by 1 to match Authorize.Net's order, mainly for readability array_unshift( $response, null ); $new_direct_response = array(); // direct response fields are URL encoded, but we currently do not use any fields // (e.g. billing/shipping details) that would be affected by that $response_fields = array( 'response_code' => 1, 'response_subcode' => 2, 'response_reason_code' => 3, 'response_reason_text' => 4, 'authorization_code' => 5, 'avs_response' => 6, 'transaction_id' => 7, 'amount' => 10, 'account_type' => 11, // CC or ECHECK 'transaction_type' => 12, // AUTH_ONLY or AUTH_CAPTUREVOID probably 'csc_response' => 39, 'cavv_response' => 40, 'account_last_four' => 51, 'card_type' => 52, ); foreach ( $response_fields as $field => $order ) { $new_direct_response[ $field ] = ( isset( $response[ $order ] ) ) ? $response[ $order ] : ''; } return isset( $new_direct_response['transaction_id'] ) && '' !== $new_direct_response['transaction_id'] ? $new_direct_response['transaction_id'] : ''; } /** * store offer transaction data * * @param $order * @param $response * @param $product */ public function store_offer_transaction( $order, $response, $product ) { $order->update_meta_data( '_wpfunnels_offer_txn_resp_' . $product['step_id'], $response['id'] ); $order->save(); } /** * Get current site name. * * @return string */ public function get_current_site_name() { return ( is_multisite() ) ? get_blog_details()->blogname : get_bloginfo( 'name' ); } }public/gateways/class-wpfnl-pro-gateway-bacs.php000064400000002316147600245720015764 0ustar00token = true; } /** * Try and get the payment token saved by the gateway * * @param WC_Order $order * * @return true * @since 1.6.7 */ public function has_token( $order ) { return $this->token; } /** * Process payment for one-click offer product * * @param mixed $order * * @return array * @since 1.6.7 */ public function process_payment( $order ) { if($this->key === $order->get_payment_method() && $this->has_token($order)){ return array( 'is_success' => true, 'message' => 'Success' ); } } }public/gateways/class-wpfnl-pro-gateway-cheque.php000064400000002317147600245720016327 0ustar00token = true; } /** * Try and get the payment token saved by the gateway * * @param WC_Order $order * * @return true * @since 1.6.7 */ public function has_token( $order ) { return $this->token; } /** * Process payment for one-click offer product * * @param mixed $order * * @return array * @since 1.6.7 */ public function process_payment( $order ) { if($this->key === $order->get_payment_method() && $this->has_token($order)){ return array( 'is_success' => true, 'message' => 'Success' ); } } }public/gateways/class-wpfnl-pro-gateway-cod.php000064400000003010147600245720015611 0ustar00refund_support = false; add_filter( 'woocommerce_cod_process_payment_order_status', array( $this, 'maybe_setup_offer_for_cod' ), 9999, 2 ); } /** * set new status for orders based on offer * * @param $order_status * @param $order * @return string */ public function maybe_setup_offer_for_cod( $order_status, $order ) { $payment_method = $order->get_payment_method(); $funnel_id = Wpfnl_functions::get_funnel_id_from_order( $order->get_id() ); $offer_settings = Wpfnl_functions::get_offer_settings(); if ( $offer_settings['offer_orders'] == 'main-order' && $funnel_id ) { return 'wc-wpfnl-main-order'; } return $order_status; } /** * process payment for offer * * @param $order * @param $offer_product * @return bool */ public function process_payment( $order, $offer_product ) { $result['is_success'] = true; return $result; } }public/gateways/class-wpfnl-pro-gateway-exception.php000064400000000660147600245720017052 0ustar00payment_gateways()->get_available_payment_gateways(); if ( false === is_array( $available_gateways ) ) { return $available_gateways; } $supported = array_keys( $available_gateways ); foreach ( $supported as $key ) { $this->build_gateway( $key ); } return $available_gateways; } /** * build payment gateway object * based on user selection * * @param $gateway * @return bool * * @since 1.0.0 */ public function build_gateway( $gateway ){ $gateways = $this->get_supported_payment_gateways(); if(isset($gateways[$gateway])) { $gateway_class = "WPFunnelsPro\\Frontend\\Gateways\\".$gateways[$gateway]; $this->payment_gateway = new $gateway_class(); return $this->payment_gateway; } return false; } /** * get all supported payment gateways of WPFunnels PRO * * @return array * * @since 1.0.0 */ public function get_supported_payment_gateways() { $gateways = array( 'stripe' => 'Wpfnl_Stripe_payment_process', 'paypal' => 'Wpfnl_Pro_Gateway_Paypal', 'ppcp-gateway' => 'Wpfnl_Pro_Gateway_Paypal_WooCommerce', 'cod' => 'Wpfnl_Pro_Gateway_Cod', 'mollie_wc_gateway_creditcard' => 'Wpfnl_Pro_Gateway_Mollie', 'mollie_wc_gateway_ideal' => 'Wpfnl_Pro_Gateway_Mollie_Idea', 'authorize_net_cim_credit_card' => 'Wpfnl_Pro_Gateway_Authorize_Net', 'officeguy' => 'Wpfnl_Pro_Gateway_OfficeGuy', 'payfast' => 'Wpfnl_Pro_Gateway_PayFast', 'square_credit_card' => 'Wpfnl_Pro_Gateway_Square', 'bacs' => 'Wpfnl_Pro_Gateway_Bacs', 'cheque' => 'Wpfnl_Pro_Gateway_Cheque', 'woocommerce_payments' => 'Wpfnl_Pro_Woocommerce_Payments' ); return apply_filters('wpfunnels/supported_payment_gateways', $gateways); } /** * create paypal token * @since 1.0.0 */ public function create_wc_express_checkout_token() { $this->build_gateway( 'paypal' )->create_express_checkout_token(); } /** * create paypal express token * @since 1.0.0 */ public function create_wc_paypal_express_checkout_token() { $this->build_gateway( 'ppec_paypal' )->create_express_checkout_token(); } /** * */ public function maybe_handle_paypal_api_call() { $this->build_gateway( 'paypal' )->maybe_create_billing(); $this->build_gateway( 'paypal' )->handle_api_calls(); } /** * */ public function maybe_handle_paypal_express_api_call() { $this->build_gateway( 'ppec_paypal' )->maybe_create_billing(); $this->build_gateway( 'ppec_paypal' )->handle_api_calls(); } /** * Show supported payment gateway in funnel checkout page * * @param Array $available_gateways * * @return Array $available_gateways */ public function wpfnl_show_supported_payment_gateway( $available_gateways ){ $is_checkout = Wpfnl_functions::is_funnel_checkout_page(); if( $is_checkout['status'] ){ $is_funnel_checkout = Wpfnl_functions::check_if_this_is_step_type_by_id( $is_checkout['id'], 'checkout' ); if( $is_funnel_checkout ){ $offer_settings = Wpfnl_functions::get_offer_settings(); if( 'on' === $offer_settings['show_supported_payment_gateway'] ){ $supported_gateways = $this->get_supported_payment_gateways(); foreach( $available_gateways as $key=>$gateway ){ if( !isset( $supported_gateways[$key] ) ){ unset( $available_gateways[$key] ); } } } } } return $available_gateways; } }public/gateways/class-wpfnl-pro-gateway-mollie-credit-card.php000064400000003046147600245720020515 0ustar00refund_support = true; add_filter( 'woocommerce_mollie_wc_gateway_creditcard_args', array( $this, 'maybe_create_mollie_customer_id' ), 10, 2 ); add_action( 'wp_ajax_wpf_mollie_credit_card_payment_process', array( $this, 'process_credit_card' ) ); add_action( 'wp_ajax_nopriv_wpf_mollie_credit_card_payment_process', array( $this, 'process_credit_card' ) ); // run this action after successful payment to mollie add_action( 'woocommerce_api_wpfnl_mollie_cc_webhook', array( $this, 'maybe_process_mollie_webhook' ) ); // hook for saving refund meta add_action( 'wpfunnels/child_order_created_' . $this->key, array( $this, 'store_mollie_meta_keys_for_refund' ), 10, 3 ); // hook for saving subscription meta add_action( 'wpfunnels/subscription_created', array( $this, 'add_subscription_payment_meta_for_mollie' ), 10, 3 ); } /** * Process AJAX for Mollie credit card payment. */ public function process_credit_card() { $response = $this->process_mollie_payment( 'cc', 'creditcard' ); wp_send_json( $response ); } }public/gateways/class-wpfnl-pro-gateway-mollie-helper.php000064400000065747147600245720017633 0ustar00get_meta( '_wpfnl_mollie_customer_id' ); if ( ! $mollie_customer_id ) { $mollie_customer_id = $order->get_meta( '_mollie_customer_id' ); } return $mollie_customer_id ?? false; } /** * Get the Mollie API key based on the selected mode (live or test). * * This function retrieves the Mollie API key based on the selected mode in the plugin settings. If the plugin * is in live mode, it retrieves the live API key from the '_live_api_key' option. Otherwise, it retrieves the * test API key from the '_test_api_key' option. * * @return string The Mollie API key. * @since 1.9.4 */ public function get_mollie_api_key() { $is_live_mode = 'yes' === get_option( 'mollie-payments-for-woocommerce_test_mode_enabled' ) ? false : true; if ( $is_live_mode ) { $api_key = get_option( 'mollie-payments-for-woocommerce_live_api_key' ); } else { $api_key = get_option( 'mollie-payments-for-woocommerce_test_api_key' ); } return $api_key; } /** * Get the return URL for Mollie payment processing. * * This function generates the return URL to be used for Mollie payment processing. It takes the step ID, order ID, * and order key as input parameters and constructs the URL using the step permalink. The function adds specific * query arguments, such as 'wpfnl-order', 'wpfnl-key', and 'wpfnl-mollie-return', to the URL to pass essential data * for processing the Mollie payment return. * * @param int $step_id The step ID. * @param int $order_id The order ID. * @param string $order_key The order key. * * @return string|bool The generated return URL for Mollie payment processing. otherwise false * @since 1.9.4 */ public function get_return_url( $step_id, $order_id, $order_key ) { if( !$step_id || !$order_id || !$order_key ){ return false; } $url = get_permalink( $step_id ); if( !$url ){ return false; } $args = array( 'wpfnl-order' => $order_id, 'wpfnl-key' => $order_key, 'key' => $order_key, 'wpfnl-mollie-return' => true, ); return add_query_arg( $args, $url ); } /** * Get the redirect location after successfully completing the process_payment. * * This function takes the payment object as input and returns the URL where the user should be redirected after * successfully completing the process_payment for the payment object. The function retrieves the payment URL from the * payment object's _links property, specifically the checkout.href attribute, to determine the redirection location. * * @param object $payment_object The payment object. * * @return string The redirect URL after successfully completing the process_payment. * @since 1.9.4 */ public function get_process_payment_redirect( $payment_object ) { /* * Redirect to the payment URL */ return $payment_object->_links->checkout->href; } /** * Get the API response body from a given API URL with provided arguments. * * This function performs a GET request to the specified API URL with the provided arguments and retrieves the response body. * It returns the decoded JSON response body or false if an error occurs during the API request. * * @param string $url The API URL to request. * @param array $args The arguments to include in the API request. * * @return mixed|false The decoded JSON response body or false if an error occurs during the API request. * @since 1.9.4 */ public function get_mollie_api_response_body( $url, $args ) { $result = wp_remote_get( $url, $args ); if ( is_wp_error( $result ) ) { return false; } $retrieved_body = wp_remote_retrieve_body( $result ); return json_decode( $retrieved_body ); } /** * Get the WooCommerce payment gateway for the specified key. * * This function retrieves the list of WooCommerce payment gateways and returns the payment gateway object * associated with the provided key. * * @return array The WooCommerce payment gateway object for the specified key. * @since 1.9.4 */ public function get_wc_gateway() { global $woocommerce; $gateways = $woocommerce->payment_gateways->payment_gateways(); return $gateways[ $this->key ]; } /** * Save the Mollie customer ID for the given order. * * This function sets the Mollie customer ID as metadata for the provided order object. * * @param object $order The order object. * @param string $customer_id The Mollie customer ID to be associated with the order. * * @return void * @since 1.9.4 */ public function set_mollie_customer_id( $order, $customer_id ) { if ( ! empty( $customer_id ) ) { try { // Save the Mollie customer ID as metadata for the order. $order->update_meta_data( '_mollie_customer_id', $customer_id ); $order->update_meta_data( '_wpfnl_mollie_customer_id', $customer_id ); $order->save(); } catch ( Exception $e ) { throw $e; } } } /** * Maybe get the Mollie customer ID from a previous order for a non-logged-in user. * * This function attempts to retrieve the Mollie customer ID associated with the provided billing email * from a previous order for a non-logged-in user. It queries the shop orders with the provided billing email * and checks for the existence of Mollie customer IDs in the metadata. If found, it returns the first Mollie * customer ID from the most recent order, or null if not found. * * @param string $billing_email The billing email of the user. * * @return null|string The Mollie customer ID associated with the billing email from a previous order, * or null if not found. * @since 1.9.4 */ public function maybe_get_mollie_customer_id_from_order( $billing_email ) { $mollie_customer_id = null; // Query shop orders with the provided billing email and existing Mollie customer IDs in metadata. $prev_orders_by_meta = new \WP_Query( array( 'post_type' => 'shop_order', 'post_status' => 'any', 'meta_query' => array( 'relation' => 'AND', array( 'key' => '_billing_email', 'value' => $billing_email, 'compare' => '=', ), array( 'key' => '_mollie_customer_id', 'compare' => 'EXISTS', ), ), 'fields' => 'ids', 'order' => 'ASC', ) ); if ( is_array( $prev_orders_by_meta->posts ) && count( $prev_orders_by_meta->posts ) > 0 ) { // Get the ID of the most recent order with a Mollie customer ID. $prev_order_id = $prev_orders_by_meta->posts[0]; // Get the order object. $prev_order = wc_get_order( $prev_order_id ); // Get the Mollie customer ID from the order's metadata. $mollie_customer_id = $prev_order->get_meta( '_mollie_customer_id' ); } return $mollie_customer_id; } /** * Format the currency value according to Mollie's requirements. * * This function takes an order value and its currency as input and returns the formatted * currency value as required by Mollie. For some specific currencies, such as JPY and ISK, * no decimal places are used, while for others, two decimal places are used. * * @param string $value The order value to be formatted. * @param string $currency The currency code of the order value. * * @return string The formatted currency value. * @since 1.9.4 */ public function format_currency_value( $value, $currency ) { $currencies_with_no_decimals = array( 'JPY', 'ISK' ); if ( in_array( $currency, $currencies_with_no_decimals, true ) ) { // For currencies like JPY and ISK, format the value without decimals. return number_format( $value, 0, '.', '' ); } // For other currencies, format the value with two decimal places. return number_format( $value, 2, '.', '' ); } /** * Set the HTTP response code for the current request. * * This function sets the HTTP response code for the current request. It first checks if the PHP SAPI is not 'cli' and * if the headers have not been sent. If the function `http_response_code` exists, it uses it to set the status code; * otherwise, it sets the status code using the `header` function. * * @param int $status_code The HTTP status code to set. * * @return void * @since 1.9.4 */ public function set_http_response_code( $status_code ) { if ( 'cli' !== PHP_SAPI && ! headers_sent() ) { if ( function_exists( 'http_response_code' ) ) { // Use http_response_code if available (PHP >= 5.4.0). http_response_code( $status_code ); } else { // Fallback for older PHP versions. header( ' ', true, $status_code ); } } } /** * Store required meta keys for refund in a separate order. * * This function stores the required meta keys for a refund in a separate order. It takes the parent order object, * child order object, and the Mollie transaction ID as parameters. If the transaction ID is not empty, it retrieves * the payment mode from the parent order's meta data. Then, it updates the child order's meta data with the Mollie * order ID, Mollie payment ID, and the payment mode obtained from the parent order. Finally, it saves the child order. * * @param object $parent_order The parent order object. * @param object $child_order The child order object for the refund. * @param string $transaction_id The Mollie transaction ID for the refund. * * @return void * @since 1.9.4 */ public function store_mollie_meta_keys_for_refund( $parent_order, $child_order, $transaction_id ) { if ( ! empty( $transaction_id ) ) { // Retrieve the payment mode from the parent order's meta data. $payment_mode = $parent_order->get_meta( '_mollie_payment_mode' ); // Update the child order's meta data with Mollie order ID, Mollie payment ID, and payment mode from the parent order. $child_order->update_meta_data( '_mollie_order_id', $transaction_id ); $child_order->update_meta_data( '_mollie_payment_id', $transaction_id ); $child_order->update_meta_data( '_mollie_payment_mode', $payment_mode ); // Save the child order. $child_order->save(); } } /** * Process offer refund for an order. * * This function processes the offer refund for an order. It takes the order object and offer data as parameters, * including the transaction ID, refund amount, and refund reason. The function checks if the refund amount is not null * and if the transaction ID is set. It then retrieves the API key using the `get_mollie_api_key` function and prepares * the API request to get the payment details from Mollie. If the payment is successfully retrieved and the remaining * amount to refund matches the order's currency and is greater than or equal to the refund amount, it proceeds to create * a refund using the Mollie API. The refund details are sent to the Mollie API, and the response ID is obtained. * * @param object $order The order object. * @param array $offer_data The offer data, including transaction_id, refund_amount, and refund_reason. * * @return string|bool The refund response ID on success, or false on failure. * @since 1.9.4 */ public function process_offer_refund( $order, $offer_data ) { $transaction_id = $offer_data['transaction_id']; $refund_amount = number_format( $offer_data['refund_amount'], 2 ); $refund_reason = $offer_data['refund_reason']; $order_currency = $order->get_currency( $order ); $response_id = false; if ( ! is_null( $refund_amount ) && isset( $transaction_id ) ) { // Retrieve the Mollie API key. $api_key = $this->get_mollie_api_key(); // Prepare the API request to get payment details from Mollie. $get_payment = $this->get_payment_api . $transaction_id; $arguments = array( 'method' => 'GET', 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ), ); // Get the payment details from Mollie. $payment = $this->get_mollie_api_response_body( $get_payment, $arguments ); if ( $payment && null !== $payment->amountRemaining && $payment->amountRemaining->currency === $order_currency && $payment->amountRemaining->value >= $refund_amount ) { // Proceed to create a refund using the Mollie API. $refund_api_url = 'https://api.mollie.com/v2/payments/' . $transaction_id . '/refunds'; $refund_args = array( 'amount' => array( 'currency' => $order_currency, 'value' => $refund_amount, ), ); $arguments = array( 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ), 'body' => wp_json_encode( $refund_args ), ); // Send the refund details to the Mollie API. $refund_response = $this->get_mollie_api_response_body( $refund_api_url, $arguments ); if ( $refund_response ) { $response_id = $refund_response->id; } } } return $response_id; } /** * Setup the Payment data for Mollie Automatic Subscription. * * This function is responsible for adding subscription payment meta data for Mollie when the payment method used for * the order is either 'mollie_wc_gateway_creditcard' or 'mollie_wc_gateway_ideal'. It takes the subscription object, * the order object, and an array of offer product as parameters. If the order's payment method is supported by Mollie, * the function updates the subscription's meta data with relevant payment information obtained from the order, including * the Mollie payment ID, payment mode, and customer ID. The updated subscription meta data is then saved. * * @param WC_Subscription $subscription An instance of a subscription object. * @param object $order Object of the order. * @param array $offer_product Array of offer product data. * * @return void * @since 1.9.4 */ public function add_subscription_payment_meta_for_mollie( $subscription, $offer_product, $order ) { // Check if the order's payment method is supported by Mollie. if ( in_array( $order->get_payment_method(), array( 'mollie_wc_gateway_creditcard', 'mollie_wc_gateway_ideal' ), true ) ) { // Update subscription meta data with relevant payment information. $subscription->update_meta_data( '_mollie_payment_id', $order->get_meta( '_mollie_payment_id' ) ); $subscription->update_meta_data( '_mollie_payment_mode', $order->get_meta( '_mollie_payment_mode' ) ); $subscription->update_meta_data( '_mollie_customer_id', $order->get_meta( '_mollie_customer_id' ) ); $subscription->save(); } } /** * Create a Mollie customer ID for non-logged-in users. * * This function is responsible for creating a Mollie customer ID for non-logged-in users during the checkout process. * It takes an array of payment arguments and the order data as parameters. If the provided payment arguments already * contain a customer ID or if the checkout ID is not available, the function simply returns the input data. Otherwise, * it attempts to create a new Mollie customer using the billing information from the order. If a customer with the same * billing email address is found, the function uses the existing customer ID. If no customer is found, a new Mollie * customer is created with the customer's name and email from the order. The Mollie customer ID is then stored in the * order's meta data using the set_mollie_customer_id function. The updated payment arguments with the customer ID are * then returned. * * @param array $data Payment arguments. * @param object $order Order data. * * @return array The updated payment arguments with the Mollie customer ID. * @since 1.9.4 */ public function maybe_create_mollie_customer_id( $data, $order ) { // Check if the payment arguments already contain a customer ID. if ( isset( $data['payment']['customerId'] ) && null !== $data['payment']['customerId'] ) { return $data; } // Get the checkout ID from the post data. $checkout_id = \WPFunnels\Wpfnl_functions::get_checkout_id_from_post_data(); if ( $checkout_id ) { try { // Get the billing information from the order. $billing_first_name = $order->get_billing_first_name(); $billing_last_name = $order->get_billing_last_name(); $billing_email = $order->get_billing_email(); // Check if the customer with the same billing email already exists. $customer_id = $this->maybe_get_mollie_customer_id_from_order( $billing_email ); if ( null === $customer_id ) { // Get the best name for use as Mollie Customer name. $user_full_name = $billing_first_name . ' ' . $billing_last_name; $api_key = $this->get_mollie_api_key(); // Create data for the new Mollie customer. $customer_data = array( 'name' => trim( $user_full_name ), 'email' => trim( $billing_email ), 'metadata' => array( 'order_id' => $order->get_id() ), ); $arguments = array( 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ), 'body' => wp_json_encode( $customer_data ), ); // Attempt to create a new Mollie customer. $response = $this->get_mollie_api_response_body( $this->create_customer_api, $arguments ); if ( $response && isset( $response->id ) ) { $customer_id = $response->id; } } // Store the Mollie customer ID in the order's meta data. $this->set_mollie_customer_id( $order, $customer_id ); } catch ( Exception $e ) { // Handle any exceptions or errors that may occur during the process. throw $e; } } return $data; } /** * Get webhook URL for Mollie credit card payments. * * @param int $step_id Step ID. * @param int $order_id Order ID. * @param string $order_key Order key. * * @return string The generated webhook URL. * @since 1.9.4 */ public function get_webhook_url( $step_id, $order_id, $order_key, $type ) { $url = WC()->api_request_url( 'wpfnl_mollie_'.$type.'_webhook' ); $args = array( 'step_id' => $step_id, 'order_id' => $order_id, 'order_key' => $order_key, ); return add_query_arg( $args, $url ); } /** * Process webhook from Mollie for credit card payments. */ public function maybe_process_mollie_webhook() { if ( empty( $_GET['step_id'] ) || empty( $_GET['order_id'] ) || empty( $_GET['order_key'] ) ) { return; } $step_id = sanitize_text_field( wp_unslash( $_GET['step_id'] ) ); $order_id = sanitize_text_field( wp_unslash( $_GET['order_id'] ) ); $order_key = sanitize_text_field( wp_unslash( $_GET['order_key'] ) ); $order = wc_get_order( $order_id ); if( false === is_a( $order, 'WC_Order' ) ){ $this->set_http_response_code( 404 ); return; } if ( ! $order->key_is_valid( $order_key ) ) { $this->set_http_response_code( 401 ); return; } // No Mollie payment ID provided. if ( empty( $_POST['id'] ) ) { $this->set_http_response_code( 400 ); return; } $payment_object_id = sanitize_text_field( wp_unslash( $_POST['id'] ) ); $get_payment = $this->get_payment_api . $payment_object_id; $api_key = $this->get_mollie_api_key(); $arguments = array( 'method' => 'GET', 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ), ); // Load the payment from Mollie, do not use cache. try { $payment = $this->get_mollie_api_response_body( $get_payment, $arguments ); } catch ( Exception $e ) { $this->set_http_response_code( 400 ); return; } // Payment not found. if ( ! $payment ) { $this->set_http_response_code( 404 ); return; } if ( $order_id != $payment->metadata->order_id ) { $this->set_http_response_code( 400 ); return; } // Update the order with the transaction ID from Mollie. $order->update_meta_data( '_' . $step_id . '_tr_id', $payment_object_id ); $order->save(); $order->add_order_note( __( 'Payment processed for Mollie.', 'wpfnl-pro' ) ); } /** * Process payment after a successful transaction. * * @param WC_Order $order The WooCommerce order object. * @param array $offer_product An array containing offer product details. * * @return array An array containing the processing result with 'is_success' and 'message' keys. * @since 1.9.4 */ public function process_payment( $order, $offer_product ) { $result = array( 'is_success' => false, 'message' => '' ); // Get the transaction ID from the order meta. $tr_id = $order->get_meta( '_mollie_payment_id' ); // Update the offer transaction response for the specific step. $order->update_meta_data( '_wpfunnels_offer_txn_resp_' . $offer_product['step_id'], $tr_id ); $order->save(); if ( '' !== $tr_id ) { // Payment processing was successful. $result['is_success'] = true; } return $result; } /** * process ajax for mollie ideal * * @throws \Mollie\Api\Exceptions\ApiException */ public function process_mollie_payment( $type, $method ) { $nonce = filter_input( INPUT_POST, 'security', FILTER_SANITIZE_STRING ); if ( ! wp_verify_nonce( $nonce, 'wpfnl_mollie_'.$type.'_process_nonce' ) ) { return array( 'result' => 'fail', 'message' => __( 'nonce is not verified', 'wpfnl-pro' ), ); } if( !isset($_POST['step_id']) || !isset( $_POST['order_id'] ) || !isset( $_POST['order_key'] ) ){ return array( 'result' => 'fail', 'message' => __( 'data not found', 'wpfnl-pro' ), ); } $step_id = intval( $_POST['step_id'] ); $order_id = sanitize_text_field( wp_unslash( $_POST['order_id'] ) ); $order_key = sanitize_text_field( wp_unslash( $_POST['order_key'] ) ); $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : ''; $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : ''; $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, $product_id, 0, $order_id ); if ( isset( $offer_product['price'] ) && ( floatval( 0 ) === floatval( $offer_product['price'] ) || '' === trim( $offer_product['price'] ) ) ) { return array( 'result' => 'fail', 'message' => __( '0 value product', 'wpfnl-pro' ), ); } $order = wc_get_order( $order_id ); $customer_id = $this->get_user_mollie_customer_id( $order ); if( !$customer_id || false === is_a( $order, 'WC_Order' )){ return array( 'result' => 'fail', 'message' => __( 'Order or Customer ID not found. Payment failed', 'wpfnl-pro' ), ); } $product_data = array( 'variation_id' => $product_id, 'input_qty' => $quantity, ); // Update the order metadata with the offer product data for the specific step. $order->update_meta_data( 'wpfnl_offer_product_data_' . $step_id, $product_data ); $order->save(); $api_key = $this->get_mollie_api_key(); if ( wc_tax_enabled() ) { if ( !wc_prices_include_tax() ) { $tax = 0; foreach ($order->get_items(array('tax')) as $item_id => $line_item) { $order_product_detail = $line_item->get_data(); if( isset($order_product_detail['tax_total']) && isset($order_product_detail['rate_percent']) ){ $tax = ($offer_product['total']*$order_product_detail['rate_percent'])/100; } } $offer_product['price'] = $offer_product['price'] + $tax; } } $data = array( 'amount' => array( 'currency' => $order->get_currency(), 'value' => $this->format_currency_value( $offer_product['price'], $order->get_currency() ), ), 'description' => "One-click payment {$order_id}_{$step_id}", 'redirectUrl' => $this->get_return_url( $step_id, $order_id, $order_key ), 'webhookUrl' => $this->get_webhook_url( $step_id, $order_id, $order_key, $type ), 'method' => $method, 'metadata' => array( 'order_id' => $order_id, ), 'customerId' => $customer_id, ); $arguments = array( 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ), 'body' => wp_json_encode( $data ), ); // Make the API request to create the payment object. $payment_object = $this->get_mollie_api_response_body( $this->create_payment_api, $arguments ); if ( !$payment_object ) { // Payment failed due to missing customer ID. return array( 'result' => 'fail', 'message' => __( 'Payment failed', 'wpfnl-pro' ), ); } // The payment object was successfully created. Return the response to the front-end. return array( 'result' => 'success', 'redirect' => $this->get_process_payment_redirect( $payment_object ), ); } }public/gateways/class-wpfnl-pro-gateway-mollie-ideal.php000064400000003102147600245720017403 0ustar00refund_support = true; add_filter( 'woocommerce_mollie_wc_gateway_ideal_args', array( $this, 'maybe_create_mollie_customer_id' ), 10, 2 ); add_action( 'wp_ajax_wpf_mollie_ideal_payment_process', array( $this, 'process_ideal_payment' ) ); add_action( 'wp_ajax_nopriv_wpf_mollie_ideal_payment_process', array( $this, 'process_ideal_payment' ) ); // run this action after successful payment to mollie add_action( 'woocommerce_api_wpfnl_mollie_ideal_webhook', array( $this, 'maybe_process_mollie_webhook' ) ); // hook for saving refund meta add_action( 'wpfunnels/child_order_created_' . $this->key, array( $this, 'store_mollie_meta_keys_for_refund' ), 10, 3 ); // hook for saving subscription meta add_action( 'wpfunnels/subscription_created', array( $this, 'save_offer_subscription_meta' ), 10, 3 ); } /** * process ajax for mollie ideal * * @throws \Mollie\Api\Exceptions\ApiException */ public function process_ideal_payment() { $response = $this->process_mollie_payment( 'ideal', 'ideal' ); wp_send_json( $response ); } } public/gateways/class-wpfnl-pro-gateway-officeguy.php000064400000007603147600245720017040 0ustar00update_meta_data('SingleUseToken', $token ); } /** * Process the offer payment * * @param mixed $order * @param mixed $offer_product * * @return bool * @since 1.0.0 */ public function process_payment($order, $offer_product) { $result = array( 'is_success' => false, 'message' => '' ); $Request = $this->PreapreOfferProductPaymentRequest($order, $offer_product); $Gateway = GetOfficeGuyGateway(); $IsOfficeGuySubscription = get_post_meta($offer_product['id'], 'OfficeGuySubscription', true) === 'yes'; if ($IsOfficeGuySubscription) $Response = OfficeGuyAPI::Post($Request, '/billing/recurring/charge/', $Gateway->settings['environment'], true); else $Response = OfficeGuyAPI::Post($Request, '/billing/payments/charge/', $Gateway->settings['environment'], true); if ( ! is_wp_error( $Response ) ) { if ( $Response['Status'] == 0 ) { $result['is_success'] = true; $result['message'] = "Payment Successful"; } else { $result['is_success'] = false; $result['message'] = $Response['UserErrorMessage']; } } return $result; } /** * Prepare an array for SUMIT payment charge API request * * @param mixed $order * @param mixed $product * * @return array * @since 1.0.0 */ private function PreapreOfferProductPaymentRequest($order, $product) { $Gateway = GetOfficeGuyGateway(); $Request = array(); $Request['Credentials'] = OfficeGuyPayment::GetCredentials($Gateway); $Request['SingleUseToken'] = $order->get_meta('SingleUseToken'); $Request['Items'] = array(); $Item = OfficeGuyPayment::GetPaymentOrderItem(null, $product['id'], round($product['price'], 2), 1, $order->get_currency(), null, null, $order); array_push($Request['Items'], $Item); $Request['VATIncluded'] = 'true'; $Request['VATRate'] = OfficeGuyPayment::GetOrderVatRate($order); $Request['Customer'] = array(); $Request['Customer']['ID'] = $order->get_meta("OfficeGuyCustomerID"); $Request['Customer']['Name'] = $this->getCustomerFullName($order); $Request['Customer']['EmailAddress'] = $order->data['billing']['email']; $Request['AuthoriseOnly'] = $Gateway->settings['testing'] != 'no' ? 'true' : 'false'; $Request['DraftDocument'] = $Gateway->settings['draftdocument'] != 'no' ? 'true' : 'false'; $Request['SendDocumentByEmail'] = $Gateway->settings['emaildocument'] == 'yes' ? 'true' : 'false'; $Request['UpdateCustomerByEmail'] = 'false'; $Request['DocumentDescription'] = __('Order number', 'officeguy') . ': ' . $order->get_id(); $Request['DocumentLanguage'] = OfficeGuyPayment::GetOrderLanguage($Gateway); $Request['MerchantNumber'] = $Gateway->settings['merchantnumber']; return $Request; } /** * Return customer full name * * @param mixed $order * * @return string * @since 1.0.0 */ private function getCustomerFullName($order) { return $order->data['billing']['first_name'] . ' ' . $order->data['billing']['last_name']; } }public/gateways/class-wpfnl-pro-gateway-payfast.php000064400000026611147600245720016527 0ustar00get_id(); $token = sanitize_text_field( $data['token'] ); $merchant_id = sanitize_text_field( $data['merchant_id'] ); $this->_set_order_token($token, $order); $this->_set_order_merchant_d($merchant_id, $order); } /** * Store the PayFast token * * @param string $token * @param WC_Order $order */ protected function _set_order_token( $token, $order ) { update_post_meta( $order->get_id(), '_payfast_subscription_token', $token ); $order->update_meta_data('_payfast_subscription_token', $token ); } /** * Store the PayFast Merchant ID * * @param string $merchant_id * @param WC_Order $order */ protected function _set_order_merchant_d( $merchant_id, $order ) { update_post_meta( $order->get_id(), '_payfast_merchant_id', $merchant_id ); $order->update_meta_data('_payfast_merchant_id', $merchant_id ); } /** * Retrieve the PayFast token for a given order id. * * @param WC_Order $order * @return mixed */ protected function _get_order_token( $order ) { return $order->get_meta('_payfast_subscription_token' ) ? $order->get_meta('_payfast_subscription_token' ) : get_post_meta( $order->get_id(), '_payfast_subscription_token', true ); } /** * Retrieve the PayFast merchant ID for a given order id. * * @param WC_Order $order * @return mixed */ protected function _get_order_merchant_id( $order ) { return $order->get_meta('_payfast_merchant_id' ) ? $order->get_meta('_payfast_merchant_id' ) : get_post_meta( $order->get_id(), '_payfast_merchant_id', true ); } /** * Getting woocommerce payfast settings information * @param mixed $key payment setting key * * @return mixed * @since 1.3.3 */ public function get_payfast_settings($key) { $payfast_settings = get_option('woocommerce_payfast_settings'); return isset($payfast_settings[$key]) ? $payfast_settings[$key] : ''; } /** * Processing of the offer payment * * @param $order * @return bool|WP_Error * * @since 1.3.3 */ public function process_payment( $order ) { $result = array( 'is_success' => false, 'message' => '' ); if ( ! $this->has_token( $order ) ) { return $result; } // Get Token from the database $token = $this->_get_order_token( $order ); $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : ''; $order_id = isset( $_POST['order_id'] ) ? intval($_POST['order_id']) : 0; // Get Offer Product Information $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, $product_id, 0, $order_id ); if ( isset($offer_product['price']) && (floatval(0) === floatval( $offer_product['price'] ) || '' === trim($offer_product['price'])) ) { wp_send_json(array( 'result' => 'fail', 'message' => __('Product price is less than 0', 'wpfnl-pro'), )); } else { // Submition of tokenization offer product payment $response = $this->submit_tokenization_payment($token, $offer_product, $order); // Success response check if($response){ return array( 'is_success' => true, 'message' => 'Success' ); } // Error response check if ( is_wp_error( $response ) ) { $result = array( 'is_success' => false, 'message' => sprintf( __( 'PayFast Pre-Order payment transaction failed (%1$s:%2$s)', 'woocommerce-gateway-payfast' ), $response->get_error_code() ,$response->get_error_message() ) ); return $result; } } } /** * Submission of offer product tokenization payment * * @param mixed $token * @param mixed $offer_product * @param mixed $order * * @return bool|WP_Error * @since 1.3.3 */ public function submit_tokenization_payment($token, $offer_product, $order) { $price = (int)$offer_product['price']; $product_id = (int)$offer_product['id']; $product = Wpfnl_functions::is_wc_active() ? wc_get_product( $product_id ) : null; $description = ''; if( $product ){ if ( $product->get_type() == 'variation' ) { $description = $product->get_description(); } else { $description = $product->get_short_description(); } } $merchant_id = $this->get_payfast_settings( 'merchant_id' ); $args = array( 'body' => array( 'amount' => $price * 100, // convert to cents 'item_name' => $offer_product['name'], 'item_description' => $description, ), ); return $this->api_request( 'adhoc', $token, $args, $merchant_id ); } /** * Check if token is present in the order * * @param $order * @since 1.3.3 */ private function has_token( $order ) { $token = $order->get_meta('_payfast_subscription_token'); if ( empty( $token ) ) { $token = $order->get_meta('_payfast_subscription_token'); } if ( ! empty( $token ) ) { return true; } return false; } /** * Send off API request. * * @param $command * @param $token * @param $api_args * @param string $method POST * * @return bool|WP_Error * @since 1.3.3 */ public function api_request( $command, $token, $api_args, $merchant_id, $method = 'POST' ) { if ( empty( $token ) ) { $this->log( "Error posting API request: No token supplied", true ); return new \WP_Error( '404', __( 'Can not submit PayFast request with an empty token', 'woocommerce-gateway-payfast' ), $results ); } $settings = get_option( 'woocommerce_payfast_settings', [] ); $api_endpoint = "https://api.payfast.co.za/subscriptions/$token/$command"; $api_endpoint .= ( isset($settings['testmode']) && 'yes' === $settings['testmode'] ) ? '?testing=true' : ''; $timestamp = current_time( rtrim( \DateTime::ATOM, 'P' ) ) . '+02:00'; $api_args['timeout'] = 45; $api_args['headers'] = array( 'merchant-id' => $merchant_id, 'timestamp' => $timestamp, 'version' => 'v1', ); // generate signature $all_api_variables = array_merge( $api_args['headers'], (array) $api_args['body'] ); $api_args['headers']['signature'] = md5( $this->_generate_parameter_string( $all_api_variables ) ); $api_args['method'] = strtoupper( $method ); $results = wp_remote_request( $api_endpoint, $api_args ); // Check PayFast server response if ( 200 !== $results['response']['code'] ) { $this->log( "Error posting API request:\n" . print_r( $results['response'], true ) ); return new \WP_Error( $results['response']['code'], json_decode( $results['body'] )->data->response, $results ); } // Check adhoc bank charge response $results_data = json_decode( $results['body'], true )['data']; if ( $command == 'adhoc' && (true !== $results_data['response'] && 'true' !== $results_data['response']) ) { $this->log( "Error posting API request:\n" . print_r( $results_data , true ) ); $code = is_array( $results_data['response'] ) ? $results_data['response']['code'] : $results_data['response']; $message = is_array( $results_data['response'] ) ? $results_data['response']['reason'] : $results_data['message']; // Use trim here to display it properly e.g. on an order note, since PayFast can include CRLF in a message. return new \WP_Error( $code, trim( $message ), $results ); } $maybe_json = json_decode( $results['body'], true ); if ( ! is_null( $maybe_json ) && isset( $maybe_json['status'] ) && 'failed' === $maybe_json['status'] ) { $this->log( "Error posting API request:\n" . print_r( $results['body'], true ) ); // Use trim here to display it properly e.g. on an order note, since PayFast can include CRLF in a message. return new \WP_Error( $maybe_json['code'], trim( $maybe_json['data']['message'] ), $results['body'] ); } return true; } /** * Generate signature for api request * * @param $api_data * @param bool $sort_data_before_merge? default true. * @param bool $skip_empty_values Should key value pairs be ignored when generating signature? Default true. * * @return string * @since 1.3.3 */ protected function _generate_parameter_string( $api_data, $sort_data_before_merge = true, $skip_empty_values = true ) { // if sorting is required the passphrase should be added in before sort. if ( ! empty( $this->get_payfast_settings('pass_phrase') ) && $sort_data_before_merge ) { $api_data['passphrase'] = $this->get_payfast_settings('pass_phrase'); } if ( $sort_data_before_merge ) { ksort( $api_data ); } // concatenate the array key value pairs. $parameter_string = ''; foreach ( $api_data as $key => $val ) { if ( $skip_empty_values && empty( $val ) ) { continue; } if ( 'signature' !== $key ) { $val = urlencode( $val ); $parameter_string .= "$key=$val&"; } } // when not sorting passphrase should be added to the end before md5 if ( $sort_data_before_merge ) { $parameter_string = rtrim( $parameter_string, '&' ); } elseif ( ! empty( $this->get_payfast_settings('pass_phrase') ) ) { $parameter_string .= 'passphrase=' . urlencode( $this->get_payfast_settings('pass_phrase') ); } else { $parameter_string = rtrim( $parameter_string, '&' ); } return $parameter_string; } /** * Log system processes. * @since 1.3.3 */ public function log( $message ) { if ( 'yes' === 'yes' || $this->enable_logging ) { if ( empty( $this->logger ) ) { $this->logger = new \WC_Logger(); } $this->logger->add( 'payfast', $message ); } } }public/gateways/class-wpfnl-pro-gateway-paypal-express.php000064400000211241147600245720020030 0ustar00is_enabled()) return $data; if ( true === $this->is_reference_transaction_enabled() ) { // // translators: blog name. // $description = sprintf( _x( 'Orders with %s', 'data sent to PayPal', 'wpfunnels-pro' ), get_bloginfo( 'name' ) ); // $description = html_entity_decode( $description, ENT_NOQUOTES, 'UTF-8' ); if ( $data && isset( $data['METHOD'] ) && 'SetExpressCheckout' == $data['METHOD'] && ! isset( $data['L_BILLINGTYPE0'] ) ) { $data['RETURNURL'] = add_query_arg( array( 'create-billing-agreement' => true ), $data['RETURNURL'] ); $data['L_BILLINGTYPE0'] = 'MerchantInitiatedBillingSingleAgreement'; $data['L_BILLINGAGREEMENTDESCRIPTION0'] = $this->get_billing_agreement_description(); $data['L_BILLINGAGREEMENTCUSTOM0'] = ''; } if ( $data && isset( $data['METHOD'] ) && 'DoReferenceTransaction' == $data['METHOD'] ) { $step_id = isset( $_POST['step_id'] ) ? (int) $_POST['step_id'] : 0; $order_id = isset( $_POST['order_id'] ) ? (int) $_POST['order_id'] : 0; // Return if the step id is not in the query string. if ( $step_id < 1 ) { return $data; } // Return if the order id is not in the query string. if ( $order_id < 1 ) { return $data; } $order = wc_get_order( $order_id ); $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : ''; $input_qty = isset( $_POST['input_qty'] ) ? intval( $_POST['input_qty'] ) : ''; $offer_package = Wpfnl_Pro_functions::get_offer_product_data($step_id); $data['AMT'] = $offer_package['total']; $data['ITEMAMT'] = $offer_package['total']; // shippingamt shoud be 0. if ( ( isset( $offer_package['shipping'] ) && isset( $offer_package['shipping']['diff'] ) ) && 0 < $offer_package['shipping']['diff'] ) { $data['SHIPPINGAMT'] = 0; $data['SHIPDISCAMT'] = ( isset( $offer_package['shipping'] ) && isset( $offer_package['shipping']['diff'] ) ) ? $offer_package['shipping']['diff']['cost'] : 0; } else { $data['SHIPPINGAMT'] = ( isset( $offer_package['shipping'] ) && isset( $offer_package['shipping']['diff'] ) ) ? $offer_package['shipping']['diff']['cost'] : 0; $data['SHIPDISCAMT'] = 0; } $data['TAXAMT'] = ( isset( $offer_package['taxes'] ) ) ? $offer_package['taxes'] : 0; $data['INVNUM'] = 'WC-' . $order_id . '_' . $step_id; $data['INSURANCEAMT'] = 0; $data['HANDLINGAMT'] = 0; $data = $this->remove_previous_line_items( $data ); $data['L_NAME0'] = $offer_package['name']; $data['L_DESC0'] = $offer_package['desc']; $data['L_AMT0'] = wc_format_decimal( $offer_package['unit_price_tax'], 2 ); $data['L_QTY0'] = $offer_package['qty']; $item_amt = $offer_package['total']; $data['ITEMAMT'] = $item_amt; } if ( isset( $data['METHOD'] ) && 'DoExpressCheckoutPayment' == $data['METHOD'] ) { if ( isset( $data['PAYMENTREQUEST_0_CUSTOM'] ) ) { $get_custom_attrs = json_decode( $data['PAYMENTREQUEST_0_CUSTOM'] ); if ( isset( $get_custom_attrs->order_id ) ) { $get_order = wc_get_order( $get_custom_attrs->order_id ); try { $checkout = wc_gateway_ppec()->checkout; $checkout_details = $checkout->get_checkout_details( $data['TOKEN'] ); $checkout->create_billing_agreement( $get_order, $checkout_details ); $token = $get_order->get_meta( '_ppec_billing_agreement_id' ); if ( ! empty( $token ) ) { // Saving meta by our own. $get_order->update_meta_data('_ppec_billing_agreement_id', $token ); } } catch ( \Exception $e ) { print_r($e->getMessage()); } } } } } return $data; } /** * Remove line items * * @since 1.0.0 * * @param array $array object. * * @return array */ public function remove_previous_line_items( $array ) { if ( is_array( $array ) && count( $array ) > 0 ) { foreach ( $array as $key => $val ) { if ( false !== strpos( strtoupper( $key ), 'L_' ) ) { unset( $array[ $key ] ); } } } return $array; } /** * Get billing agreement description to be passed to PayPal. * * @return string Billing agreement description * @since 1.0.0 * */ protected function get_billing_agreement_description() { /* translators: placeholder is blogname */ $description = sprintf( _x( 'Orders with %s', 'data sent to PayPal', 'wpfunnels' ), get_bloginfo( 'name' ) ); if ( strlen( $description ) > 127 ) { $description = substr( $description, 0, 124 ) . '...'; } return html_entity_decode( $description, ENT_NOQUOTES, 'UTF-8' ); } /** * check if paypal scripts should load or not * * @return bool * * @since 1.0.0 */ private function may_be_load_paypal_scripts() { if ( (Wpfnl_functions::check_if_this_is_step_type('upsell') || Wpfnl_functions::check_if_this_is_step_type('downsell')) && $this->has_paypal_gateway()) { return true; } return false; } /** * load paypal payment js */ public function load_payment_scripts() { if ( $this->may_be_load_paypal_scripts() ) { wp_enqueue_script( 'wpfnl-paypal-express-script', 'https://www.paypalobjects.com/api/checkout.js', array( 'jquery' ), WPFNL_PRO_VERSION, true ); $script = $this->paypal_script(); wp_add_inline_script( 'wpfnl-paypal-express-script', $script ); } } /** * paypal script * */ public function paypal_script(){ ob_start(); ?> (function($){ $( function($) { var $wpfnl_paypal_checkout = { init: function () { var getButtons = [ 'upsell_funnel_target', ]; window.paypalCheckoutReady = function () { paypal.checkout.setup( 'get_payer_id()); ?>', { environment: 'sandbox', buttons: getButtons, container: 'myContainer', locale: 'get_paypal_locale() ); ?>', click: function () { var variation_id = 0; var postData = { step_id: window.WPFunnelsOfferVars.step_id, funnel_id: window.WPFunnelsOfferVars.funnel_id, order_id: window.WPFunnelsOfferVars.order_id, order_key: window.WPFunnelsOfferVars.order_key, variation_id: 0, input_qty: 0, action: 'wpfunnels_create_paypal_express_checkout_token' }; paypal.checkout.initXO(); var action = $.post(window.WPFunnelsOfferVars.ajaxUrl, postData); action.done(function (data) { paypal.checkout.startFlow(data.token); }); action.fail(function () { paypal.checkout.closeFlow(); }); } }); } } }; $wpfnl_paypal_checkout.init(); }); })(jQuery); _supported_locales, true ) ) { $locale = 'en_US'; } return $locale; } /** * check id reference transaction is enabled * * @return bool */ public function is_reference_transaction_enabled() { return false; } /** * check if api creds are saved or not * * @return bool */ public function has_api_credentials_set() { $credentials_are_set = false; $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } if ('' !== $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username') && '' !== $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password') && '' !== $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature')) { $credentials_are_set = true; } return $credentials_are_set; } /** * Check if current order has paypal gatway * * @return bool */ public function has_paypal_gateway() { $order_id = isset( $_GET['wpfnl-order'] ) ? $_GET['wpfnl-order'] : ''; if ( empty( $order_id ) ) { return false; } $order = wc_get_order( $order_id ); $gateway = $order->get_payment_method(); if ( $this->get_key() === $gateway ) { return true; } return false; } public function process_payment( $offer_product, $order_id, $order_key ) { } /** * get payer id * * @return bool|mixed|void */ public function get_payer_id() { $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $option_key = 'woocommerce_ppec_payer_id_' . $environment . '_' . md5($this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username') . ':' . $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password')); $payer_id = get_option($option_key); if ($payer_id) { return $payer_id; } else { $result = $this->get_pal_details(); if (!empty($result['PAL'])) { update_option($option_key, wc_clean($result['PAL'])); return $payer_id; } } return false; } /** * get the paypal id, including the merchant account number * * @return object|API\SV_WC_API_Response */ public function get_pal_details() { $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $this->set_api_credentials( $this->get_key(), $environment, $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature') ); $this->add_parameter('METHOD', 'GetPalDetails'); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); return $this->perform_request($request); } /** * Create express checkout token ajax action. * It will return checkout token for express checkout */ public function create_express_checkout_token() { $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0; $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash( $_POST['order_key'] ) ) : ''; $session_key = isset( $_POST['session_key'] ) ? sanitize_text_field( wp_unslash( $_POST['session_key'] ) ) : ''; $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : ''; $input_qty = isset( $_POST['input_qty'] ) ? intval( $_POST['input_qty'] ) : ''; $is_valid_order = true; if ( $is_valid_order ) { $order = wc_get_order( $order_id ); $response = $this->init_express_checkout( array( 'currency' => $order ? $order->get_currency() : get_woocommerce_currency(), 'return_url' => $this->get_callback_url( array( 'action' => 'wpfunnels_paypal_express_return', 'step_id' => $step_id, 'order_id' => $order->get_id(), 'funnel_id' => $funnel_id, 'order_key' => $order_key, ) ), 'cancel_url' => $this->get_callback_url( array( 'action' => 'wpfunnels_paypal_express_cancel', 'step_id' => $step_id, 'funnel_id' => $funnel_id, 'order_id' => $order->get_id(), ) ), 'notify_url' => $this->get_callback_url( 'wpfunnels_notify_url' ), 'order' => $order, 'step_id' => $step_id, 'variation_id' => $variation_id, 'input_qty' => $input_qty, ), true ); if ( isset( $response['TOKEN'] ) && '' !== $response['TOKEN'] ) { wp_send_json( array( 'result' => 'success', 'token' => $response['TOKEN'], ) ); } } wp_send_json( array( 'result' => 'error', 'response' => $response, ) ); } /** * Initiate express checkout request * * @param $args * @param bool $is_upsell * @return object|API\SV_WC_API_Response */ public function init_express_checkout( $args, $is_upsell = false ) { $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $this->set_api_credentials( $this->key, $environment, $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature') ); $this->set_express_checkout_args( $args, $is_upsell ); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); WC()->session->set( 'paypal_request' , $this->get_parameters() ); return $this->perform_request($request); } /** * Sets up DoExpressCheckoutPayment API Call arguments * * @param string $token Unique token of the payment initiated * @param \WC_Order $order * @param array $args */ public function set_do_express_checkout_args($token, $order, $args) { $this->set_method('DoExpressCheckoutPayment'); // set base params $this->add_parameters(array( 'TOKEN' => $token, 'PAYERID' => $args['payer_id'], 'BUTTONSOURCE' => 'WooThemes_Cart', 'RETURNFMFDETAILS' => 1, )); $this->add_payment_details_parameters($order, $args['payment_action']); } /** * Do express checkout * * @param $token * @param $order * @param $args * @return object|string * * @since 1.0.0 */ private function do_express_checkout($token, $order, $args) { $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $this->set_api_credentials( $this->key, $environment, $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature') ); $this->set_do_express_checkout_args($token, $order, $args); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); return $this->perform_request($request); } /** * Sets up the API credentials for API request * * @param $gateway_id * @param $api_environment * @param $api_username * @param $api_password * @param $api_signature * * @since 1.0.0 */ private function set_api_credentials( $gateway_id, $api_environment, $api_username, $api_password, $api_signature ) { // tie API to gateway $this->gateway_id = $gateway_id; // request URI does not vary per-request $this->request_uri = ('production' === $api_environment) ? self::PRODUCTION_ENDPOINT : self::SANDBOX_ENDPOINT; // PayPal requires HTTP 1.1 $this->request_http_version = '1.1'; $this->api_username = $api_username; $this->api_password = $api_password; $this->api_signature = $api_signature; } /** * Sets up the express checkout transaction * * @link https://developer.paypal.com/docs/classic/express-checkout/integration-guide/ECGettingStarted/#id084RN060BPF * @link https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/SetExpressCheckout_API_Operation_NVP/ * * @param array $args { * @type string 'currency' (Optional) A 3-character currency code (default is store's currency). * @type string 'billing_type' (Optional) Type of billing agreement for reference transactions. You must have permission from PayPal to use this field. This field must be set to one of the following values: MerchantInitiatedBilling - PayPal creates a billing agreement for each transaction associated with buyer. You must specify version 54.0 or higher to use this option; MerchantInitiatedBillingSingleAgreement - PayPal creates a single billing agreement for all transactions associated with buyer. Use this value unless you need per-transaction billing agreements. You must specify version 58.0 or higher to use this option. * @type string 'billing_description' (Optional) Description of goods or services associated with the billing agreement. This field is required for each recurring payment billing agreement if using MerchantInitiatedBilling as the billing type, that means you can use a different agreement for each subscription/order. PayPal recommends that the description contain a brief summary of the billing agreement terms and conditions (but this only makes sense when the billing type is MerchantInitiatedBilling, otherwise the terms will be incorrectly displayed for all agreements). For example, buyer is billed at "9.99 per month for 2 years". * @type string 'maximum_amount' (Optional) The expected maximum total amount of the complete order and future payments, including shipping cost and tax charges. If you pass the expected average transaction amount (default 25.00). PayPal uses this value to validate the buyer's funding source. * @type string 'no_shipping' (Optional) Determines where or not PayPal displays shipping address fields on the PayPal pages. For digital goods, this field is required, and you must set it to 1. It is one of the following values: 0 – PayPal displays the shipping address on the PayPal pages; 1 – PayPal does not display shipping address fields whatsoever (default); 2 – If you do not pass the shipping address, PayPal obtains it from the buyer's account profile. * @type string 'page_style' (Optional) Name of the Custom Payment Page Style for payment pages associated with this button or link. It corresponds to the HTML variable page_style for customizing payment pages. It is the same name as the Page Style Name you chose to add or edit the page style in your PayPal Account profile. * @type string 'brand_name' (Optional) A label that overrides the business name in the PayPal account on the PayPal hosted checkout pages. Default: store name. * @type string 'landing_page' (Optional) Type of PayPal page to display. It is one of the following values: 'login' – PayPal account login (default); 'Billing' – Non-PayPal account. * @type string 'payment_action' (Optional) How you want to obtain payment. If the transaction does not include a one-time purchase, this field is ignored. Default 'Sale' – This is a final sale for which you are requesting payment (default). Alternative: 'Authorization' – This payment is a basic authorization subject to settlement with PayPal Authorization and Capture. You cannot set this field to Sale in SetExpressCheckout request and then change the value to Authorization or Order in the DoExpressCheckoutPayment request. If you set the field to Authorization or Order in SetExpressCheckout, you may set the field to Sale. * @type string 'return_url' (Required) URL to which the buyer's browser is returned after choosing to pay with PayPal. * @type string 'cancel_url' (Required) URL to which the buyer is returned if the buyer does not approve the use of PayPal to pay you. * @type string 'custom' (Optional) A free-form field for up to 256 single-byte alphanumeric characters * } * @since 1.0.0 * @source https://github.com/wp-premium/woocommerce-subscriptions/blob/master/includes/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php#L68 */ public function set_express_checkout_args( $args, $is_upsell = false ) { // translators: placeholder is blogname $default_description = sprintf( _x( 'Orders with %s', 'data sent to paypal', 'wpfnl-pro' ), get_bloginfo( 'name' ) ); $defaults = array( 'currency' => get_woocommerce_currency(), 'billing_type' => 'MerchantInitiatedBillingSingleAgreement', // translators: placeholder is for blog name 'billing_description' => html_entity_decode( apply_filters( 'woocommerce_subscriptions_paypal_billing_agreement_description', $default_description, $args ), ENT_NOQUOTES, 'UTF-8' ), 'maximum_amount' => null, 'no_shipping' => 1, 'page_style' => null, 'brand_name' => html_entity_decode( get_bloginfo( 'name' ), ENT_NOQUOTES, 'UTF-8' ), 'landing_page' => 'login', 'payment_action' => 'Sale', 'custom' => '', ); $args = wp_parse_args( $args, $defaults ); $this->set_method( 'SetExpressCheckout' ); $this->add_parameters( array( 'RETURNURL' => $args['return_url'], 'CANCELURL' => $args['cancel_url'], 'PAGESTYLE' => $args['page_style'], 'BRANDNAME' => $args['brand_name'], 'LANDINGPAGE' => ( 'login' == $args['landing_page'] ) ? 'Login' : 'Billing', 'NOSHIPPING' => $args['no_shipping'], 'MAXAMT' => $args['maximum_amount'], ) ); if ( false === $is_upsell ) { $this->add_parameter( 'L_BILLINGTYPE0', $args['billing_type'] ); $this->add_parameter( 'L_BILLINGAGREEMENTDESCRIPTION0', get_bloginfo( 'name' ) ); $this->add_parameter( 'L_BILLINGAGREEMENTCUSTOM0', '' ); } // if we have an order, the request is to create a subscription/process a payment (not just check if the PayPal account supports Reference Transactions) if ( isset( $args['order'] ) ) { if ( true === $is_upsell ) { $this->add_payment_details_parameters( $args['order'], $args['step_id'], $args['payment_action'], false, true, $args['variation_id'], $args['input_qty'] ); } else { $this->add_payment_details_parameters( $args['order'], $args['payment_action'], false, false ); } } $args['no_shipping'] = 0; if ( empty( $args['no_shipping'] ) ) { $this->maybe_add_shipping_address_params( $args['order'] ); } $set_express_checkout_params = apply_filters( 'wpfunnels/paypal_param_setexpresscheckout', $this->get_parameters(), $is_upsell ); if ( isset( $set_express_checkout_params['PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE'] ) && 2 === strlen( $set_express_checkout_params['PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE'] ) ) { $set_express_checkout_params['ADDRESSOVERRIDE'] = '1'; } $this->clean_params(); $this->add_parameters( $set_express_checkout_params ); } /** * Set the method for the request, currently using: * * + `SetExpressCheckout` - setup transaction * + `GetExpressCheckout` - gets buyers info from PayPal * + `DoExpressCheckoutPayment` - completes the transaction * + `DoCapture` - captures a previously authorized transaction * * @param string $method * @since 1.0.0 */ private function set_method( $method ) { $this->add_parameter( 'METHOD', $method ); } /** * Set up the payment details for a DoExpressCheckoutPayment or DoReferenceTransaction request * * @param \WC_Order $order * @param $step_id * @param $type * @param bool $use_deprecated_params whether to use deprecated PayPal NVP parameters (required for DoReferenceTransaction API calls) * @param false $is_offer_charge * @param string $variation_id * @param string $input_qty * * @since 1.0.0 */ protected function add_payment_details_parameters( \WC_Order $order, $step_id, $type, $use_deprecated_params = false, $is_offer_charge = false, $variation_id = '', $input_qty = '' ) { $calculated_total = 0; $order_subtotal = 0; $item_count = 0; $order_items = array(); $offer_data = Wpfnl_Pro_functions::get_offer_product_data( $step_id ); if ( true === $is_offer_charge ) { $order_items[] = array( 'NAME' => $offer_data['name'], 'DESC' => $offer_data['desc'], 'AMT' => $this->round( $offer_data['price'] ), 'QTY' => 1, 'ITEMURL' => $offer_data['url'], ); $order_subtotal += $offer_data['total_unit_price_amount']; } else { // add line items foreach ( $order->get_items() as $item ) { $product = new \WC_Product( $item['product_id'] ); $order_items[] = array( 'NAME' => $product->get_title(), 'DESC' => $this->get_item_description( $product ), 'AMT' => $this->round( $order->get_item_subtotal( $item ) ), 'QTY' => ( ! empty( $item['qty'] ) ) ? absint( $item['qty'] ) : 1, 'ITEMURL' => $product->get_permalink(), ); $order_subtotal += $item['line_total']; } // add fees foreach ( $order->get_fees() as $fee ) { $order_items[] = array( 'NAME' => ( $fee['name'] ), 'AMT' => $this->round( $fee['line_total'] ), 'QTY' => 1, ); $order_subtotal += $fee['line_total']; } if ( $order->get_total_discount() > 0 ) { $order_items[] = array( 'NAME' => __( 'Total Discount', 'wpfnl' ), 'QTY' => 1, 'AMT' => - $this->round( $order->get_total_discount() ), ); } } /**Do things for the main order **/ if ( false === $is_offer_charge ) { if ( $this->skip_line_items( $order ) ) { $total_amount = $this->round( $order->get_total() ); // calculate the total as PayPal would $calculated_total += $this->round( $order_subtotal + $order->get_cart_tax() ) + $this->round( $order->get_total_shipping() + $order->get_shipping_tax() ); // offset the discrepancy between the WooCommerce cart total and PayPal's calculated total by adjusting the order subtotal if ( $this->price_format( $total_amount ) !== $this->price_format( $calculated_total ) ) { $order_subtotal = $order_subtotal - ( $calculated_total - $total_amount ); } $item_names = array(); foreach ( $order_items as $item ) { $item_names[] = sprintf( '%1$s x %2$s', $item['NAME'], $item['QTY'] ); } // add a single item for the entire order $this->add_line_item_parameters( array( // translators: placeholder is blogname 'NAME' => sprintf( __( '%s - Order', 'wpfnl' ), get_option( 'blogname' ) ), 'DESC' => $this->get_item_description( implode( ', ', $item_names ) ), 'AMT' => $this->round( $order_subtotal + $order->get_cart_tax() ), 'QTY' => 1, ), 0, $use_deprecated_params ); // add order-level parameters // - Do not send the TAXAMT due to rounding errors if ( $use_deprecated_params ) { $this->add_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal + $order->get_cart_tax() ), 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() + $order->get_shipping_tax() ), 'INVNUM' => $this->get_wc_gateway()->get_option( 'invoice_prefix' ) . Wpfnl_Pro_functions::str_to_ascii( ltrim( $order->get_order_number(), _x( '#', 'hash before the order number. Used as a character to remove from the actual order number', 'wpfnl-pro' ) ) ), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $order->get_id(), ) ), ) ); } else { $this->add_payment_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal + $order->get_cart_tax() ), 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() + $order->get_shipping_tax() ), 'INVNUM' => $this->get_wc_gateway()->get_option( 'invoice_prefix' ) . Wpfnl_Pro_functions::str_to_ascii( ltrim( $order->get_order_number(), _x( '#', 'hash before the order number. Used as a character to remove from the actual order number', 'wpfnl' ) ) ), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $order->get_id(), ) ), ) ); } } else { // add individual order items foreach ( $order_items as $item ) { $this->add_line_item_parameters( $item, $item_count ++, $use_deprecated_params ); } $total_amount = $this->round( $order->get_total() ); // add order-level parameters if ( $use_deprecated_params ) { $this->add_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() ), 'TAXAMT' => $this->round( $order->get_total_tax() ), 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), ) ); } else { $this->add_payment_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() ), 'TAXAMT' => $this->round( $order->get_total_tax() ), 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $order->get_id(), ) ), ) ); } } } /** Handle paypal data setup for the offers */ else { /** * Code for reference transaction */ $total_amount = $offer_data['total']; $item_names = array(); foreach ( $order_items as $item ) { $item_names[] = sprintf( '%1$s x %2$s', $item['NAME'], $item['QTY'] ); } $item_count = 0; // add individual order items foreach ( $order_items as $item ) { $this->add_line_item_parameters( $item, $item_count ++, $use_deprecated_params ); } /** * Check if this is a referencetransaction call then send depreceated params */ if ( true === $use_deprecated_params && true === $is_offer_charge ) { /** * When shipping amount is a negative number, means user opted for free shipping offer * In this case we setup shippingamt as 0 and shipping discount amount is that negative amount that is coming. */ if ( ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) && 0 > $offer_data['shipping']['diff']['cost'] ) { $this->add_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => 0, 'SHIPDISCAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0, 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0, 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), ) ), ) ); } else { $this->add_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0, 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0, 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), ) ), ) ); } } else { if ( ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) && 0 > $offer_data['shipping']['diff']['cost'] ) { $this->add_payment_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => 0, 'SHIPDISCAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0, 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0, 'NOTIFYURL' => WC()->api_request_url( 'WC_Gateway_Paypal' ), 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), ) ), ) ); } else { $this->add_payment_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0, 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'NOTIFYURL' => WC()->api_request_url( 'WC_Gateway_Paypal' ), 'PAYMENTREQUESTID' => $order->get_id(), 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0, 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), ) ), ) ); } } } } /** * Adds a line item parameters to the request, auto-prefixes the parameter key * with `L_PAYMENTREQUEST_0_` for convenience and readability * * @param array $params * @param int $item_count current item count * * @since 2.0 */ private function add_line_item_parameters( array $params, $item_count, $use_deprecated_params = false ) { foreach ( $params as $key => $value ) { if ( $use_deprecated_params ) { $this->add_parameter( "L_{$key}{$item_count}", $value ); } else { $this->add_parameter( "L_PAYMENTREQUEST_0_{$key}{$item_count}", $value ); } } } /** * Add payment parameters, auto-prefixes the parameter key with `PAYMENTREQUEST_0_` * for convenience and readability * * @param array $params * * @since 1.0.0 */ private function add_payment_parameters( array $params ) { foreach ( $params as $key => $value ) { $this->add_parameter( "PAYMENTREQUEST_0_{$key}", $value ); } } /** * PayPal cannot properly calculate order totals when prices include tax (due * to rounding issues), so line items are skipped and the order is sent as * a single item * * @since 2.0.9 * @param \WC_Order $order Optional. The WC_Order object. Default null. * @return bool true if line items should be skipped, false otherwise */ private function skip_line_items( $order = null, $order_items = null ) { $skip_line_items = false; // Also check actual totals add up just in case totals have been manually modified to amounts that can not round correctly, see https://github.com/Prospress/woocommerce-subscriptions/issues/2213 if ( ! is_null( $order ) ) { $rounded_total = 0; $items = array(); foreach ( $order_items as $item ) { $amount = round( $item['line_subtotal'] / $item['qty'], 2 ); $rounded_total += round( $amount * $item['qty'], 2 ); $calculated_total += $this->round( $item['AMT'] * $item['QTY'] ); $amount = round( $item['line_subtotal'] / $item['qty'], 2 ); $item = array( 'name' => $item['name'], 'quantity' => $item['qty'], 'amount' => $amount, ); $items[] = $item; } $discounts = $order->get_total_discount(); $details = array( 'total_item_amount' => round( $order->get_subtotal(), 2 ) + $discounts, 'order_tax' => round( $order->get_total_tax(), 2 ), 'shipping' => round( $order->get_shipping_total(), 2 ), 'items' => $items, ); $details['order_total'] = round( $details['total_item_amount'] + $details['order_tax'] + $details['shipping'], 2 ); if ( (float) $details['order_total'] !== (float) $order->get_total() ) { $skip_line_items = true; } if ( $details['total_item_amount'] !== $rounded_total ) { $skip_line_items = true; } } /** * Filter whether line items should be skipped or not * * @since 3.3.0 * @param bool $skip_line_items True if line items should be skipped, false otherwise * @param \WC_Order/null $order The WC_Order object or null. */ return apply_filters( 'wpfunnels/paypal_skip_line_items', $skip_line_items, $order ); } /** * @param \WC_Order $order */ function maybe_add_shipping_address_params( \WC_Order $order, $prefix = 'PAYMENTREQUEST_0_SHIPTO' ) { if ( $order->has_shipping_address() ) { $shipping_first_name = $order->get_shipping_first_name(); $shipping_last_name = $order->get_shipping_last_name(); $shipping_address_1 = $order->get_shipping_address_1(); $shipping_address_2 = $order->get_shipping_address_2(); $shipping_city = $order->get_shipping_city(); $shipping_state = $order->get_shipping_state(); $shipping_postcode = $order->get_shipping_postcode(); $shipping_country = $order->get_shipping_country(); } else { $shipping_first_name = $order->get_billing_first_name(); $shipping_last_name = $order->get_billing_last_name(); $shipping_address_1 = $order->get_billing_address_1(); $shipping_address_2 = $order->get_billing_address_2(); $shipping_city = $order->get_billing_city(); $shipping_state = $order->get_billing_state(); $shipping_postcode = $order->get_billing_postcode(); $shipping_country = $order->get_billing_country(); } if ( empty( $shipping_country ) ) { $shipping_country = WC()->countries->get_base_country(); } $shipping_phone = $order->get_billing_phone(); $params = array( $prefix . 'NAME' => $shipping_first_name . ' ' . $shipping_last_name, $prefix . 'STREET' => $shipping_address_1, $prefix . 'STREET2' => $shipping_address_2, $prefix . 'CITY' => $shipping_city, $prefix . 'STATE' => $shipping_state, $prefix . 'ZIP' => $shipping_postcode, $prefix . 'COUNTRYCODE' => $this->get_country( $shipping_country ), $prefix . 'PHONENUM' => $shipping_phone, ); foreach ( $params as $key => $val ) { $this->add_parameter( $key, $val ); } } private function get_country( $country ) { if ( 2 === strlen( $country ) ) { return $country; } return substr( $country, 0, 2 ); } /** * Add api credentials parameters * * @param string $api_username API username. * @param string $api_password API password. * @param string $api_signature API signature. * @param string $api_version API version. * @return void */ public function set_credentials_params( $api_username, $api_password, $api_signature, $api_version ) { $this->add_parameters( array( 'USER' => $api_username, 'PWD' => $api_password, 'SIGNATURE' => $api_signature, 'VERSION' => $api_version, ) ); } /** * Add multiple parameters * * @param array $params * * @since 2.0 */ public function add_parameters(array $params) { foreach ($params as $key => $value) { $this->add_parameter($key, $value); } } /** * Add a parameter * * @param string $key * @param string|int $value * * @since 2.0 */ public function add_parameter($key, $value) { $this->parameters[$key] = $value; } /** * get call back url of paypal request * * @param $args * @return string * * @since 1.0.0 */ public function get_callback_url($args) { $api_request_url = WC()->api_request_url( 'wpfunnels_paypal_express' ); if ( is_array( $args ) ) { return add_query_arg( $args, $api_request_url ); } else { return add_query_arg( 'action', $args, $api_request_url ); } } /** * get paypal payment params * * @return array|mixed|void * @throws \Exception */ public function get_parameters() { $this->parameters = apply_filters('wcs_paypal_request_params', $this->parameters, $this); // validate parameters. foreach ( $this->parameters as $key => $value ) { // remove unused params. if ( '' === $value || is_null( $value ) ) { unset( $this->parameters[ $key ] ); } // format and check amounts. if ( false !== strpos( $key, 'AMT' ) ) { // amounts must be 10,000.00 or less for USD. if ( isset( $this->parameters['PAYMENTREQUEST_0_CURRENCYCODE'] ) && 'USD' == $this->parameters['PAYMENTREQUEST_0_CURRENCYCODE'] && $value > 10000 ) { throw new \Exception( sprintf( '%s amount of %s must be less than $10,000.00', $key, $value ) ); } $this->parameters[ $key ] = $this->price_format( $value ); } } return $this->parameters; } /** * Return the parsed response object for the request * * @since 1.0.0 * * @param string $raw_response_body response body. * * @return object */ protected function get_parsed_response( $raw_response_body ) { wp_parse_str( urldecode( $raw_response_body ), $this->response_params ); return $this->response_params; } /** * Get Express Checkout Details * * @param $token * @return object * * @since 1.0.0 */ public function get_express_checkout_details( $token ) { $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $this->set_api_credentials( $this->get_key(), $environment, $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature') ); $this->set_express_checkout_details_args($token); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); return $this->perform_request($request); } /** * Sets up GetExpressCheckoutDetails API call arguments * * @param string $token * * @since 1.0.0 */ public function set_express_checkout_details_args($token) { $this->set_method('GetExpressCheckoutDetails'); $this->add_parameter('TOKEN', $token); } /** * @hooked woocommerce_api_wpfunnels_paypal_express */ public function maybe_create_billing() { if (!isset($_GET['action'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $token = esc_attr( sanitize_text_field( wp_unslash( $_GET['token'] ) ) ); $order_id = isset( $_GET['order_id'] ) ? intval( $_GET['order_id'] ) : 0; $step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : 0; $funnel_id = isset( $_GET['funnel_id'] ) ? intval( $_GET['funnel_id'] ) : 0; switch ($_GET['action']) { // called when the customer is returned from PayPal after authorizing their payment, used for retrieving the customer's checkout details case 'wpfunnels_paypal_express_create_billing_agreement': // return if no token if (!isset($_GET['token'])) { return; } // get token to retrieve checkout details with $token = wc_clean( $_GET['token'] ); $order = null; try { $express_checkout_details_response = $this->get_express_checkout_details($token); $order = wc_get_order($order_id); // Make sure the billing agreement was accepted if (1 === $express_checkout_details_response['BILLINGAGREEMENTACCEPTEDSTATUS'] || '1' === $express_checkout_details_response['BILLINGAGREEMENTACCEPTEDSTATUS']) { if (is_null($order)) { throw new Wpfnl_Payment_Gateway_Exception(__('Unable to find order for PayPal billing agreement.', 'wpfnl-pro'), 101, $this->get_key()); } // we need to process an initial payment if (Wpfnl_Pro_functions::get_amount_for_comparisons($order->get_total()) > 0) { $billing_agreement_response = $this->do_express_checkout($token, $order, array( 'payment_action' => 'Sale', 'payer_id' => $this->get_value_from_response($express_checkout_details_response, 'PAYERID'), )); } else { throw new Wpfnl_Payment_Gateway_Exception(__('Order total is not greater than zero.', 'wpfnl'), 101, $this->get_key()); } if ($this->has_api_error($billing_agreement_response)) { throw new Wpfnl_Payment_Gateway_Exception($this->get_api_error($billing_agreement_response), 101, $this->get_key()); } $order->set_payment_method('paypal'); $order->update_meta_data('_paypal_subscription_id', $this->get_value_from_response($billing_agreement_response, 'BILLINGAGREEMENTID')); /** * mark primary payment as completed */ $order->payment_complete($billing_agreement_response['PAYMENTINFO_0_TRANSACTIONID']); $next_step = Wpfnl_functions::get_next_step( $funnel_id, $step_id ); $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step['step_id'] ); if( $custom_url ){ $redirect_url = $custom_url; }else{ $redirect_url = add_query_arg('utm_nooverride', '1', get_permalink($next_step['step_id'])); } //redirect customer to order received page wp_safe_redirect(esc_url_raw($redirect_url)); exit; } else { $this->handle_api_failures($order); } } catch (\Exception $e) { $this->handle_api_failures($order, $e); } } } /** * check if api response has any error * * @param $response * @return bool */ public function has_api_error($response) { // assume something went wrong if ACK is missing if (!isset($response['ACK'])) { return true; } // any non-success ACK is considered an error, see // https://developer.paypal.com/docs/classic/api/NVPAPIOverview/#id09C2F0K30L7 return ('Success' !== $this->get_value_from_response($response, 'ACK') && 'SuccessWithWarning' !== $this->get_value_from_response($response, 'ACK')); } /** * get order from response * * @param $response * @return bool|\WC_Order|\WC_Order_Refund */ public function get_order_from_response($response) { if ($response && isset($response['CUSTOM'])) { $getjson = json_decode($response['CUSTOM'], true); return wc_get_order($getjson['_wpfunnels_o_id']); } } /** * Handles Payment Gateway API error * * @param $order * @param $e */ protected function handle_api_failures($order, $e = '') { if ($order instanceof \WC_Order) { if ($e instanceof Wpfnl_Payment_Gateway_Exception) { $order->add_order_note($e->getMessage()); } $redirect = $order->get_checkout_order_received_url(); wp_redirect($redirect); exit; } wp_die('Unable to process further. Please contact to the store admin & enquire about the status of your order.'); exit; } public function handle_api_calls() { if (!isset($_GET['action'])) { return; } $step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : 0; $funnel_id = isset( $_GET['funnel_id'] ) ? intval( $_GET['funnel_id'] ) : 0; $order_id = isset( $_GET['order_id'] ) ? intval( $_GET['order_id'] ) : 0; $order_key = isset( $_GET['order_key'] ) ? sanitize_text_field( wp_unslash( $_GET['order_key'] ) ) : ''; $variation_id = isset( $_GET['variation_id'] ) ? intval( $_GET['variation_id'] ) : ''; $input_qty = isset( $_GET['input_qty'] ) ? intval( $_GET['input_qty'] ) : ''; $order = null; try { switch ($_GET['action']) { case 'wpfunnels_paypal_express_return': $offer_product = Wpfnl_Pro_functions::get_offer_product_data($step_id); $order = wc_get_order( $order_id ); if ( isset( $_GET['token'] ) && ! empty( $_GET['token'] ) ) { $api_response_result = false; $express_checkout_details_response = $this->get_express_checkout_details( wp_unslash( $_GET['token'] ) ); //phpcs:ignore $get_paypal_data = WC()->session->get('paypal_request'); /** * Check if product total is greater than 0. */ if ( $offer_product['total'] > 0 ) { /** * Prepare DoExpessCheckout Call to finally charge the user. */ $do_express_checkout_data = array( 'TOKEN' => $express_checkout_details_response['TOKEN'], 'PAYERID' => $express_checkout_details_response['PAYERID'], 'METHOD' => 'DoExpressCheckoutPayment', ); $do_express_checkout_data = wp_parse_args( $do_express_checkout_data, $get_paypal_data ); $environment = ( 'sandbox' === $this->get_wc_gateway()->environment ) ? 'sandbox' : 'live'; $api_prefix = ''; if ( 'sandbox' === $environment ) { $api_prefix = 'sandbox_'; } /** * Setup & perform DoExpressCheckout API Call. */ $this->set_api_credentials( $this->key, $environment, $this->get_wc_gateway()->get_option( $api_prefix . 'api_username' ), $this->get_wc_gateway()->get_option( $api_prefix . 'api_password' ), $this->get_wc_gateway()->get_option( $api_prefix . 'api_signature' ) ); $this->add_parameters( $do_express_checkout_data ); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); $response_checkout = $this->perform_request( $request ); if ( false === $this->has_api_error( $response_checkout ) ) { $api_response_result = true; $data = $this->handle_offer_charge($order, $api_response_result, $offer_product); } } else { $api_response_result = true; $data = $this->handle_offer_charge($order, $api_response_result, $offer_product); } $result = Wpfnl_Pro_functions::after_offer_charged( $step_id, $order_id, $order_key, $api_response_result, $variation_id, $input_qty, $offer_product ); $next_step = Wpfnl_functions::get_next_step($funnel_id, $step_id); $next_step_url = get_permalink($next_step['step_id']); $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step['step_id'] ); if( $custom_url ){ $redirect_url = $custom_url; }else{ $redirect_url = add_query_arg( array( 'wpfnl-order' => $order_id, 'wpfnl-key' => $order_key, 'key' => $order_key, ), $next_step_url ); } wp_safe_redirect( $redirect_url ); exit; } else { $result = Wpfnl_Pro_functions::after_offer_charged( $step_id, $order_id, $order_key, $api_response_result, $variation_id, $input_qty, $offer_product ); $next_step = Wpfnl_functions::get_next_step($funnel_id, $step_id); $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step['step_id'] ); if( $custom_url ){ $redirect_url = $custom_url; }else{ $next_step_url = get_permalink($next_step['step_id']); $redirect_url = add_query_arg( array( 'wpfnl-order' => $order_id, 'wpfnl-key' => $order_key, 'key' => $order_key, ), $next_step_url ); } wp_safe_redirect( $redirect_url ); exit; } break; case 'cancel_url': /** * Getting the current URL from the session and loading the same offer again. * User needs to chose "no thanks" if he want to move to upsell/order received. */ $url = get_permalink( $step_id ); $args = array( 'wpfnl-order' => $order_id, 'wpfnl-key' => $order_key, 'key' => $order_key, ); $url = add_query_arg( $args, $url ); wp_redirect($url); exit; } } catch (Exception $e) { $this->handle_api_failures($order, $e); } } /** * Store Offer Trxn Charge. * * @param \WC_Order $order The order that is being paid for. * @param Object $response The response that is send from the payment gateway. * @param array $product Product data. */ public function handle_offer_charge( \WC_Order $order, $response, $product ) { $order_id = $order->get_id(); $txn_id = ''; if ( ! isset( $response['PAYMENTINFO_0_TRANSACTIONID'] ) ) { if(is_array($response)){ $txn_id = $response['TRANSACTIONID']; } } else { if(is_array($response)){ $txn_id = $response['PAYMENTINFO_0_TRANSACTIONID']; } } $order->update_meta_data( '_wpfunnels_offer_txn_resp_' . $product['step_id'], $txn_id ); $order->save(); } /** * Modify offer refund data. * * @param array $request request. * @param object $order the order object. * @param float $amount refund amount. * * @return object */ public function wpfnl_offer_refund_request( $request, $order, $amount ) { $payment_method = $order->get_payment_method(); if ( $this->key !== $payment_method ) { return $request; } if ( isset( $order->data['transaction_id'] ) && ! empty( $order->data['transaction_id'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing $request['TRANSACTIONID'] = wc_clean( $order->data['transaction_id'] ); //phpcs:ignore WordPress.Security.NonceVerification.Missing $environment = ('sandbox' === $this->get_wc_gateway()->environment) ? 'sandbox' : 'live'; if ( 'live' === $environment ) { $request['USER'] = $this->get_wc_gateway()->get_option( 'api_username' ); $request['PWD'] = $this->get_wc_gateway()->get_option( 'api_password' ); $request['SIGNATURE'] = $this->get_wc_gateway()->get_option( 'api_signature' ); } else { $request['USER'] = $this->get_wc_gateway()->get_option( 'sandbox_api_username' ); $request['PWD'] = $this->get_wc_gateway()->get_option( 'sandbox_api_password' ); $request['SIGNATURE'] = $this->get_wc_gateway()->get_option( 'sandbox_api_signature' ); } } return $request; } } public/gateways/class-wpfnl-pro-gateway-paypal-standard.php000064400000206536147600245720020152 0ustar00refund_support = true; if( Wpfnl_functions::is_wc_active() ){ add_filter('woocommerce_paypal_args', array($this, 'modify_paypal_args'), 9999, 2); add_action( 'wp_enqueue_scripts', array( $this, 'load_payment_scripts' ), 9999 ); add_filter( 'woocommerce_paypal_refund_request', array( $this, 'offer_refund_request_data' ), 9999, 4 ); add_action( 'wpfunnels/subscription_created', array( $this, 'add_offer_subscription_meta' ), 9999, 3 ); } } /** * check if paypal scripts should load or not * * @return bool * * @since 1.0.0 */ private function may_be_load_paypal_scripts() { if ( (Wpfnl_functions::check_if_this_is_step_type('upsell') || Wpfnl_functions::check_if_this_is_step_type('downsell')) && $this->has_paypal_gateway()) { return true; } return false; } /** * load paypal payment js */ public function load_payment_scripts() { if ( $this->may_be_load_paypal_scripts() ) { wp_enqueue_script( 'wpfnl-paypal-script', 'https://www.paypalobjects.com/api/checkout.js', array( 'jquery' ), WPFNL_PRO_VERSION, true ); $script = $this->paypal_script(); wp_add_inline_script( 'wpfnl-paypal-script', $script ); } } /** * paypal script * */ public function paypal_script(){ $environment = ( true === $this->get_wc_gateway()->testmode ) ? 'sandbox' : 'live'; ob_start(); ?> (function($){ $( function($) { var $wpfnl_paypal_checkout = { init: function () { var getButtons = [ 'wpfunnels_upsell_accept', 'wpfunnels_downsell_accept', ]; window.paypalCheckoutReady = function () { paypal.checkout.setup( 'get_payer_id()); ?>', { environment: '', buttons: getButtons, container: 'myContainer', locale: 'get_paypal_locale() ); ?>', click: function () { var variation_id = 0; var postData = { step_id: window.WPFunnelsOfferVars.step_id, funnel_id: window.WPFunnelsOfferVars.funnel_id, order_id: window.WPFunnelsOfferVars.order_id, order_key: window.WPFunnelsOfferVars.order_key, variation_id: 0, input_qty: 0, action: 'wpfunnels_create_express_checkout_token' }; paypal.checkout.initXO(); var action = $.post(window.WPFunnelsOfferVars.ajaxUrl, postData); action.done(function (data) { paypal.checkout.startFlow(data.token); }); action.fail(function () { paypal.checkout.closeFlow(); }); } }); } } }; $wpfnl_paypal_checkout.init(); }); })(jQuery); should_tokenize($funnel_id)) { return $args; } if (false === $this->has_api_credentials_set()) { return $args; } /** * Check if gateway is enabled and we have reference transactions turned off. */ if (true === $this->is_enabled() && false === $this->is_reference_transaction_enabled()) { $is_upsell = false; $next_step_obj = Wpfnl_functions::get_next_step($funnel_id, $checkout_id); if ($next_step_obj && ($next_step_obj['step_type'] === 'upsell' || $next_step_obj['step_type'] === 'downsell')) { if ($this->has_api_credentials_set()) { $is_upsell = true; } } if ($is_upsell) { $args['return'] = $this->get_wc_gateway()->get_return_url( $order ); } } else { try { // Initiate express checkout request. $response = $this->init_express_checkout( array( 'currency' => $args['currency_code'], 'return_url' => $this->get_callback_url( array( 'action' => 'wpfunnels_paypal_create_billing_agreement', 'step_id' => $checkout_id, 'funnel_id' => $funnel_id, 'order_id' => $order->get_id(), ) ), 'cancel_url' => $this->get_callback_url( array( 'action' => 'wpfunnel_paypal_cancel', 'step_id' => $checkout_id, 'funnel_id' => $funnel_id, 'order_id' => $order->get_id(), ) ), 'notify_url' => $args['notify_url'], 'custom' => $args['custom'], 'order' => $order, 'step_id' => $checkout_id, ) ); if (!isset($response['TOKEN']) || '' === $response['TOKEN']) { return $args; } return array( 'cmd' => '_express-checkout', 'token' => $response['TOKEN'], ); } catch (\Exception $e) { return $args; } } return $args; } /** * Get locale data for PayPal. * * @return string * * @since 1.0.0 */ public function get_paypal_locale() { $locale = get_locale(); if ( ! in_array( $locale, $this->_supported_locales, true ) ) { $locale = 'en_US'; } return $locale; } /** * check id reference transaction is enabled * * @return bool */ public function is_reference_transaction_enabled() { return false; } /** * check if api creds are saved or not * * @return bool */ public function has_api_credentials_set() { $credentials_are_set = false; $environment = (true === $this->get_wc_gateway()->testmode) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } if ('' !== $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username') && '' !== $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password') && '' !== $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature')) { $credentials_are_set = true; } return $credentials_are_set; } /** * Check if current order has paypal gatway * * @return bool */ public function has_paypal_gateway() { $order_id = isset( $_GET['wpfnl-order'] ) ? $_GET['wpfnl-order'] : ''; if ( empty( $order_id ) || 'lms_order' === $order_id) { return false; } $funnel_id = Wpfnl_functions::get_funnel_id_from_order($order_id); if( 'lms' === get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ) ){ return false; } $order = wc_get_order( $order_id ); $gateway = $order->get_payment_method(); if ( $this->get_key() === $gateway ) { return true; } return false; } public function process_payment( $offer_product, $order_id, $order_key ) { } /** * get payer id * * @return bool|mixed|void */ public function get_payer_id() { $environment = (true === $this->get_wc_gateway()->testmode) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $option_key = 'woocommerce_ppec_payer_id_' . $environment . '_' . md5($this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username') . ':' . $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password')); $payer_id = get_option($option_key); if ($payer_id) { return $payer_id; } else { $result = $this->get_pal_details(); if (!empty($result['PAL'])) { update_option($option_key, wc_clean($result['PAL'])); return $payer_id; } } return false; } /** * get the paypal id, including the merchant account number * * @return object|API\SV_WC_API_Response */ public function get_pal_details() { $environment = (true === $this->get_wc_gateway()->testmode) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $this->set_api_credentials( $this->get_key(), $environment, $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature') ); $this->add_parameter('METHOD', 'GetPalDetails'); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); return $this->perform_request($request); } /** * Create express checkout token ajax action. * It will return checkout token for express checkout */ public function create_express_checkout_token() { $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0; $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash( $_POST['order_key'] ) ) : ''; $session_key = isset( $_POST['session_key'] ) ? sanitize_text_field( wp_unslash( $_POST['session_key'] ) ) : ''; $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : ''; $input_qty = isset( $_POST['input_qty'] ) ? intval( $_POST['input_qty'] ) : ''; $is_valid_order = true; if ( $is_valid_order ) { $order = wc_get_order( $order_id ); $response = $this->init_express_checkout( array( 'currency' => $order ? $order->get_currency() : get_woocommerce_currency(), 'return_url' => $this->get_callback_url( array( 'action' => 'wpfunnels_paypal_return', 'step_id' => $step_id, 'order_id' => $order->get_id(), 'funnel_id' => $funnel_id, 'order_key' => $order_key, 'variation_id' => $variation_id, 'input_qty' => $input_qty, ) ), 'cancel_url' => $this->get_callback_url( array( 'action' => 'wpfunnel_paypal_cancel', 'step_id' => $step_id, 'funnel_id' => $funnel_id, 'order_id' => $order->get_id(), 'variation_id' => $variation_id, 'input_qty' => $input_qty, ) ), 'notify_url' => $this->get_callback_url( 'wpfunnels_notify_url' ), 'order' => $order, 'step_id' => $step_id, 'variation_id' => $variation_id, 'input_qty' => $input_qty, ), true ); if ( isset( $response['TOKEN'] ) && '' !== $response['TOKEN'] ) { wp_send_json( array( 'result' => 'success', 'token' => $response['TOKEN'], ) ); } } wp_send_json( array( 'result' => 'error', 'response' => $response, ) ); } /** * Initiate express checkout request * * @param $args * @param bool $is_upsell * @return object|API\SV_WC_API_Response */ public function init_express_checkout( $args, $is_upsell = false ) { $environment = (true === $this->get_wc_gateway()->testmode) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $this->set_api_credentials( $this->key, $environment, $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature') ); $this->set_express_checkout_args( $args, $is_upsell ); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); WC()->session->set( 'paypal_request' , $this->get_parameters() ); return $this->perform_request($request); } /** * Sets up DoExpressCheckoutPayment API Call arguments * * @param string $token Unique token of the payment initiated * @param \WC_Order $order * @param array $args */ public function set_do_express_checkout_args($token, $order, $args) { $this->set_method('DoExpressCheckoutPayment'); // set base params $this->add_parameters(array( 'TOKEN' => $token, 'PAYERID' => $args['payer_id'], 'BUTTONSOURCE' => 'WPFunnels_Cart', 'RETURNFMFDETAILS' => 1, )); $this->add_payment_details_parameters($order, $args['payment_action']); } /** * Do express checkout * * @param $token * @param $order * @param $args * @return object|string * * @since 1.0.0 */ private function do_express_checkout($token, $order, $args) { $environment = (true === $this->get_wc_gateway()->testmode) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $this->set_api_credentials( $this->key, $environment, $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature') ); $this->set_do_express_checkout_args($token, $order, $args); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); return $this->perform_request($request); } /** * Sets up the API credentials for API request * * @param $gateway_id * @param $api_environment * @param $api_username * @param $api_password * @param $api_signature * * @since 1.0.0 */ private function set_api_credentials( $gateway_id, $api_environment, $api_username, $api_password, $api_signature ) { // tie API to gateway $this->gateway_id = $gateway_id; // request URI does not vary per-request $this->request_uri = ('production' === $api_environment) ? self::PRODUCTION_ENDPOINT : self::SANDBOX_ENDPOINT; // PayPal requires HTTP 1.1 $this->request_http_version = '1.1'; $this->api_username = $api_username; $this->api_password = $api_password; $this->api_signature = $api_signature; } /** * Sets up the express checkout transaction * * @link https://developer.paypal.com/docs/classic/express-checkout/integration-guide/ECGettingStarted/#id084RN060BPF * @link https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/SetExpressCheckout_API_Operation_NVP/ * * @param array $args { * @type string 'currency' (Optional) A 3-character currency code (default is store's currency). * @type string 'billing_type' (Optional) Type of billing agreement for reference transactions. You must have permission from PayPal to use this field. This field must be set to one of the following values: MerchantInitiatedBilling - PayPal creates a billing agreement for each transaction associated with buyer. You must specify version 54.0 or higher to use this option; MerchantInitiatedBillingSingleAgreement - PayPal creates a single billing agreement for all transactions associated with buyer. Use this value unless you need per-transaction billing agreements. You must specify version 58.0 or higher to use this option. * @type string 'billing_description' (Optional) Description of goods or services associated with the billing agreement. This field is required for each recurring payment billing agreement if using MerchantInitiatedBilling as the billing type, that means you can use a different agreement for each subscription/order. PayPal recommends that the description contain a brief summary of the billing agreement terms and conditions (but this only makes sense when the billing type is MerchantInitiatedBilling, otherwise the terms will be incorrectly displayed for all agreements). For example, buyer is billed at "9.99 per month for 2 years". * @type string 'maximum_amount' (Optional) The expected maximum total amount of the complete order and future payments, including shipping cost and tax charges. If you pass the expected average transaction amount (default 25.00). PayPal uses this value to validate the buyer's funding source. * @type string 'no_shipping' (Optional) Determines where or not PayPal displays shipping address fields on the PayPal pages. For digital goods, this field is required, and you must set it to 1. It is one of the following values: 0 – PayPal displays the shipping address on the PayPal pages; 1 – PayPal does not display shipping address fields whatsoever (default); 2 – If you do not pass the shipping address, PayPal obtains it from the buyer's account profile. * @type string 'page_style' (Optional) Name of the Custom Payment Page Style for payment pages associated with this button or link. It corresponds to the HTML variable page_style for customizing payment pages. It is the same name as the Page Style Name you chose to add or edit the page style in your PayPal Account profile. * @type string 'brand_name' (Optional) A label that overrides the business name in the PayPal account on the PayPal hosted checkout pages. Default: store name. * @type string 'landing_page' (Optional) Type of PayPal page to display. It is one of the following values: 'login' – PayPal account login (default); 'Billing' – Non-PayPal account. * @type string 'payment_action' (Optional) How you want to obtain payment. If the transaction does not include a one-time purchase, this field is ignored. Default 'Sale' – This is a final sale for which you are requesting payment (default). Alternative: 'Authorization' – This payment is a basic authorization subject to settlement with PayPal Authorization and Capture. You cannot set this field to Sale in SetExpressCheckout request and then change the value to Authorization or Order in the DoExpressCheckoutPayment request. If you set the field to Authorization or Order in SetExpressCheckout, you may set the field to Sale. * @type string 'return_url' (Required) URL to which the buyer's browser is returned after choosing to pay with PayPal. * @type string 'cancel_url' (Required) URL to which the buyer is returned if the buyer does not approve the use of PayPal to pay you. * @type string 'custom' (Optional) A free-form field for up to 256 single-byte alphanumeric characters * } * @since 1.0.0 * @source https://github.com/wp-premium/woocommerce-subscriptions/blob/master/includes/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php#L68 */ public function set_express_checkout_args( $args, $is_upsell = false ) { // translators: placeholder is blogname $default_description = sprintf( _x( 'Orders with %s', 'data sent to paypal', 'wpfnl-pro' ), get_bloginfo( 'name' ) ); $defaults = array( 'currency' => get_woocommerce_currency(), 'billing_type' => 'MerchantInitiatedBillingSingleAgreement', // translators: placeholder is for blog name 'billing_description' => html_entity_decode( apply_filters( 'woocommerce_subscriptions_paypal_billing_agreement_description', $default_description, $args ), ENT_NOQUOTES, 'UTF-8' ), 'maximum_amount' => null, 'no_shipping' => 1, 'page_style' => null, 'brand_name' => html_entity_decode( get_bloginfo( 'name' ), ENT_NOQUOTES, 'UTF-8' ), 'landing_page' => 'login', 'payment_action' => 'Sale', 'custom' => '', ); $args = wp_parse_args( $args, $defaults ); $this->set_method( 'SetExpressCheckout' ); $this->add_parameters( array( 'RETURNURL' => $args['return_url'], 'CANCELURL' => $args['cancel_url'], 'PAGESTYLE' => $args['page_style'], 'BRANDNAME' => $args['brand_name'], 'LANDINGPAGE' => ( 'login' == $args['landing_page'] ) ? 'Login' : 'Billing', 'NOSHIPPING' => $args['no_shipping'], 'MAXAMT' => $args['maximum_amount'], ) ); if ( false === $is_upsell ) { $this->add_parameter( 'L_BILLINGTYPE0', $args['billing_type'] ); $this->add_parameter( 'L_BILLINGAGREEMENTDESCRIPTION0', get_bloginfo( 'name' ) ); $this->add_parameter( 'L_BILLINGAGREEMENTCUSTOM0', '' ); } // if we have an order, the request is to create a subscription/process a payment (not just check if the PayPal account supports Reference Transactions) if ( isset( $args['order'] ) ) { if ( true === $is_upsell ) { $this->add_payment_details_parameters( $args['order'], $args['step_id'], $args['payment_action'], false, true, $args['variation_id'], $args['input_qty'] ); } else { $this->add_payment_details_parameters( $args['order'], $args['payment_action'], false, false ); } } $args['no_shipping'] = 0; if ( empty( $args['no_shipping'] ) ) { $this->maybe_add_shipping_address_params( $args['order'] ); } $set_express_checkout_params = apply_filters( 'wpfunnels/paypal_param_setexpresscheckout', $this->get_parameters(), $is_upsell ); if ( isset( $set_express_checkout_params['PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE'] ) && 2 === strlen( $set_express_checkout_params['PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE'] ) ) { $set_express_checkout_params['ADDRESSOVERRIDE'] = '1'; } $this->clean_params(); $this->add_parameters( $set_express_checkout_params ); } /** * Set the method for the request, currently using: * * + `SetExpressCheckout` - setup transaction * + `GetExpressCheckout` - gets buyers info from PayPal * + `DoExpressCheckoutPayment` - completes the transaction * + `DoCapture` - captures a previously authorized transaction * * @param string $method * @since 1.0.0 */ private function set_method( $method ) { $this->add_parameter( 'METHOD', $method ); } /** * Set up the payment details for a DoExpressCheckoutPayment or DoReferenceTransaction request * * @param \WC_Order $order * @param $step_id * @param $type * @param bool $use_deprecated_params whether to use deprecated PayPal NVP parameters (required for DoReferenceTransaction API calls) * @param false $is_offer_charge * @param string $variation_id * @param string $input_qty * * @since 1.0.0 */ protected function add_payment_details_parameters( \WC_Order $order, $step_id, $type, $use_deprecated_params = false, $is_offer_charge = false, $variation_id = '', $input_qty = '' ) { $calculated_total = 0; $order_subtotal = 0; $item_count = 0; $order_items = array(); $offer_data = Wpfnl_Pro_functions::get_offer_product_data( $step_id ); if ( true === $is_offer_charge ) { if($offer_data) { $order_items[] = array( 'NAME' => $offer_data['name'], 'DESC' => $offer_data['desc'], 'AMT' => $this->round( $offer_data['unit_price_tax'] ), 'QTY' => ( ! empty( $offer_data['qty'] ) ) ? absint( $offer_data['qty'] ) : 1, 'ITEMURL' => $offer_data['url'], ); $order_subtotal += $offer_data['total_unit_price_amount']; } } else { // add line items foreach ( $order->get_items() as $item ) { $product = new \WC_Product( $item['product_id'] ); $order_items[] = array( 'NAME' => $product->get_title(), 'DESC' => $this->get_item_description( $product ), 'AMT' => $this->round( $order->get_item_subtotal( $item ) ), 'QTY' => ( ! empty( $item['qty'] ) ) ? absint( $item['qty'] ) : 1, 'ITEMURL' => $product->get_permalink(), ); $order_subtotal += $item['line_total']; } // add fees foreach ( $order->get_fees() as $fee ) { $order_items[] = array( 'NAME' => ( $fee['name'] ), 'AMT' => $this->round( $fee['line_total'] ), 'QTY' => 1, ); $order_subtotal += $fee['line_total']; } if ( $order->get_total_discount() > 0 ) { $order_items[] = array( 'NAME' => __( 'Total Discount', 'wpfnl' ), 'QTY' => 1, 'AMT' => - $this->round( $order->get_total_discount() ), ); } } /**Do things for the main order **/ if ( false === $is_offer_charge ) { if ( $this->skip_line_items( $order ) ) { $total_amount = $this->round( $order->get_total() ); // calculate the total as PayPal would $calculated_total += $this->round( $order_subtotal + $order->get_cart_tax() ) + $this->round( $order->get_total_shipping() + $order->get_shipping_tax() ); // offset the discrepancy between the WooCommerce cart total and PayPal's calculated total by adjusting the order subtotal if ( $this->price_format( $total_amount ) !== $this->price_format( $calculated_total ) ) { $order_subtotal = $order_subtotal - ( $calculated_total - $total_amount ); } $item_names = array(); foreach ( $order_items as $item ) { $item_names[] = sprintf( '%1$s x %2$s', $item['NAME'], $item['QTY'] ); } // add a single item for the entire order $this->add_line_item_parameters( array( 'NAME' => sprintf( __( '%s - Order', 'wpfnl' ), get_option( 'blogname' ) ), 'DESC' => $this->get_item_description( implode( ', ', $item_names ) ), 'AMT' => $this->round( $order_subtotal + $order->get_cart_tax() ), 'QTY' => 1, ), 0, $use_deprecated_params ); // add order-level parameters // - Do not send the TAXAMT due to rounding errors if ( $use_deprecated_params ) { $this->add_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal + $order->get_cart_tax() ), 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() + $order->get_shipping_tax() ), 'INVNUM' => $this->get_wc_gateway()->get_option( 'invoice_prefix' ) . Wpfnl_Pro_functions::str_to_ascii( ltrim( $order->get_order_number(), _x( '#', 'hash before the order number. Used as a character to remove from the actual order number', 'wpfnl-pro' ) ) ), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $order->get_id(), ) ), ) ); } else { $this->add_payment_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal + $order->get_cart_tax() ), 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() + $order->get_shipping_tax() ), 'INVNUM' => $this->get_wc_gateway()->get_option( 'invoice_prefix' ) . Wpfnl_Pro_functions::str_to_ascii( ltrim( $order->get_order_number(), _x( '#', 'hash before the order number. Used as a character to remove from the actual order number', 'wpfnl' ) ) ), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $order->get_id(), ) ), ) ); } } else { // add individual order items foreach ( $order_items as $item ) { $this->add_line_item_parameters( $item, $item_count ++, $use_deprecated_params ); } $total_amount = $this->round( $order->get_total() ); // add order-level parameters if ( $use_deprecated_params ) { $this->add_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() ), 'TAXAMT' => $this->round( $order->get_total_tax() ), 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), ) ); } else { $this->add_payment_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => $this->round( $order->get_total_shipping() ), 'TAXAMT' => $this->round( $order->get_total_tax() ), 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $order->get_id(), ) ), ) ); } } } /** Handle paypal data setup for the offers */ else { /** * Code for reference transaction */ $total_amount = $offer_data['total']; $item_names = array(); foreach ( $order_items as $item ) { $item_names[] = sprintf( '%1$s x %2$s', $item['NAME'], $item['QTY'] ); } $item_count = 0; // add individual order items foreach ( $order_items as $item ) { $this->add_line_item_parameters( $item, $item_count++, $use_deprecated_params ); } /** * Check if this is a referencetransaction call then send depreceated params */ if ( true === $use_deprecated_params && true === $is_offer_charge ) { /** * When shipping amount is a negative number, means user opted for free shipping offer * In this case we setup shippingamt as 0 and shipping discount amount is that negative amount that is coming. */ if ( ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) && 0 > $offer_data['shipping']['diff']['cost'] ) { $this->add_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => 0, 'SHIPDISCAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0, 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0, 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), ) ), ) ); } else { $this->add_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0, 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0, 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), ) ), ) ); } } else { if ( ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) && 0 > $offer_data['shipping']['diff']['cost'] ) { $this->add_payment_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => 0, 'SHIPDISCAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0, 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->get_id(), 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0, 'NOTIFYURL' => WC()->api_request_url( 'get_wc_gateway_Paypal' ), 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), ) ), ) ); } else { $this->add_payment_parameters( array( 'AMT' => $total_amount, 'CURRENCYCODE' => $order ? $order->get_currency() : get_woocommerce_currency(), 'ITEMAMT' => $this->round( $order_subtotal ), 'SHIPPINGAMT' => ( isset( $offer_data['shipping'] ) && isset( $offer_data['shipping']['diff'] ) ) ? $offer_data['shipping']['diff']['cost'] : 0, 'INVNUM' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), 'PAYMENTACTION' => $type, 'NOTIFYURL' => WC()->api_request_url( 'get_wc_gateway_Paypal' ), 'PAYMENTREQUESTID' => $order->get_id(), 'TAXAMT' => ( isset( $offer_data['taxes'] ) ) ? $offer_data['taxes'] : 0, 'CUSTOM' => wp_json_encode( array( '_wpfunnels_o_id' => $this->get_wc_gateway()->get_option('invoice_prefix') . $this->get_order_number($order, $offer_data['step_id']), ) ), ) ); } } } } /** * Adds a line item parameters to the request, auto-prefixes the parameter key * with `L_PAYMENTREQUEST_0_` for convenience and readability * * @param array $params * @param int $item_count current item count * * @since 2.0 */ private function add_line_item_parameters( array $params, $item_count, $use_deprecated_params = false ) { foreach ( $params as $key => $value ) { if ( $use_deprecated_params ) { $this->add_parameter( "L_{$key}{$item_count}", $value ); } else { $this->add_parameter( "L_PAYMENTREQUEST_0_{$key}{$item_count}", $value ); } } } /** * Add payment parameters, auto-prefixes the parameter key with `PAYMENTREQUEST_0_` * for convenience and readability * * @param array $params * * @since 1.0.0 */ private function add_payment_parameters( array $params ) { foreach ( $params as $key => $value ) { $this->add_parameter( "PAYMENTREQUEST_0_{$key}", $value ); } } /** * PayPal cannot properly calculate order totals when prices include tax (due * to rounding issues), so line items are skipped and the order is sent as * a single item * * @since 2.0.9 * @param \WC_Order $order Optional. The WC_Order object. Default null. * @return bool true if line items should be skipped, false otherwise */ private function skip_line_items( $order = null, $order_items = null ) { $skip_line_items = false; // Also check actual totals add up just in case totals have been manually modified to amounts that can not round correctly, see https://github.com/Prospress/woocommerce-subscriptions/issues/2213 if ( ! is_null( $order ) ) { $rounded_total = 0; $items = array(); foreach ( $order_items as $item ) { $amount = round( $item['line_subtotal'] / $item['qty'], 2 ); $rounded_total += round( $amount * $item['qty'], 2 ); $calculated_total += $this->round( $item['AMT'] * $item['QTY'] ); $amount = round( $item['line_subtotal'] / $item['qty'], 2 ); $item = array( 'name' => $item['name'], 'quantity' => $item['qty'], 'amount' => $amount, ); $items[] = $item; } $discounts = $order->get_total_discount(); $details = array( 'total_item_amount' => round( $order->get_subtotal(), 2 ) + $discounts, 'order_tax' => round( $order->get_total_tax(), 2 ), 'shipping' => round( $order->get_shipping_total(), 2 ), 'items' => $items, ); $details['order_total'] = round( $details['total_item_amount'] + $details['order_tax'] + $details['shipping'], 2 ); if ( (float) $details['order_total'] !== (float) $order->get_total() ) { $skip_line_items = true; } if ( $details['total_item_amount'] !== $rounded_total ) { $skip_line_items = true; } } /** * Filter whether line items should be skipped or not * * @since 3.3.0 * @param bool $skip_line_items True if line items should be skipped, false otherwise * @param \WC_Order/null $order The WC_Order object or null. */ return apply_filters( 'wpfunnels/paypal_skip_line_items', $skip_line_items, $order ); } /** * @param \WC_Order $order */ function maybe_add_shipping_address_params( \WC_Order $order, $prefix = 'PAYMENTREQUEST_0_SHIPTO' ) { if ( $order->has_shipping_address() ) { $shipping_first_name = $order->get_shipping_first_name(); $shipping_last_name = $order->get_shipping_last_name(); $shipping_address_1 = $order->get_shipping_address_1(); $shipping_address_2 = $order->get_shipping_address_2(); $shipping_city = $order->get_shipping_city(); $shipping_state = $order->get_shipping_state(); $shipping_postcode = $order->get_shipping_postcode(); $shipping_country = $order->get_shipping_country(); } else { $shipping_first_name = $order->get_billing_first_name(); $shipping_last_name = $order->get_billing_last_name(); $shipping_address_1 = $order->get_billing_address_1(); $shipping_address_2 = $order->get_billing_address_2(); $shipping_city = $order->get_billing_city(); $shipping_state = $order->get_billing_state(); $shipping_postcode = $order->get_billing_postcode(); $shipping_country = $order->get_billing_country(); } if ( empty( $shipping_country ) ) { $shipping_country = WC()->countries->get_base_country(); } $shipping_phone = $order->get_billing_phone(); $params = array( $prefix . 'NAME' => $shipping_first_name . ' ' . $shipping_last_name, $prefix . 'STREET' => $shipping_address_1, $prefix . 'STREET2' => $shipping_address_2, $prefix . 'CITY' => $shipping_city, $prefix . 'STATE' => $shipping_state, $prefix . 'ZIP' => $shipping_postcode, $prefix . 'COUNTRYCODE' => $this->get_country( $shipping_country ), $prefix . 'PHONENUM' => $shipping_phone, ); foreach ( $params as $key => $val ) { $this->add_parameter( $key, $val ); } } private function get_country( $country ) { if ( 2 === strlen( $country ) ) { return $country; } return substr( $country, 0, 2 ); } /** * Add api credentials parameters * * @param string $api_username API username. * @param string $api_password API password. * @param string $api_signature API signature. * @param string $api_version API version. * @return void */ public function set_credentials_params( $api_username, $api_password, $api_signature, $api_version ) { $this->add_parameters( array( 'USER' => $api_username, 'PWD' => $api_password, 'SIGNATURE' => $api_signature, 'VERSION' => $api_version, ) ); } /** * Add multiple parameters * * @param array $params * * @since 2.0 */ public function add_parameters(array $params) { foreach ($params as $key => $value) { $this->add_parameter($key, $value); } } /** * Add a parameter * * @param string $key * @param string|int $value * * @since 2.0 */ public function add_parameter($key, $value) { $this->parameters[$key] = $value; } /** * get call back url of paypal request * * @param $args * @return string * * @since 1.0.0 */ public function get_callback_url($args) { $api_request_url = WC()->api_request_url( 'wpfunnels_paypal' ); if ( is_array( $args ) ) { return add_query_arg( $args, $api_request_url ); } else { return add_query_arg( 'action', $args, $api_request_url ); } } /** * get paypal payment params * * @return array|mixed|void * @throws \Exception */ public function get_parameters() { $this->parameters = apply_filters('wcs_paypal_request_params', $this->parameters, $this); // validate parameters. foreach ( $this->parameters as $key => $value ) { // remove unused params. if ( '' === $value || is_null( $value ) ) { unset( $this->parameters[ $key ] ); } // format and check amounts. if ( false !== strpos( $key, 'AMT' ) ) { // amounts must be 10,000.00 or less for USD. if ( isset( $this->parameters['PAYMENTREQUEST_0_CURRENCYCODE'] ) && 'USD' == $this->parameters['PAYMENTREQUEST_0_CURRENCYCODE'] && $value > 10000 ) { throw new \Exception( sprintf( '%s amount of %s must be less than $10,000.00', $key, $value ) ); } $this->parameters[ $key ] = $this->price_format( $value ); } } return $this->parameters; } /** * Return the parsed response object for the request * * @since 1.0.0 * * @param string $raw_response_body response body. * * @return object */ protected function get_parsed_response( $raw_response_body ) { wp_parse_str( urldecode( $raw_response_body ), $this->response_params ); return $this->response_params; } /** * Get Express Checkout Details * * @param $token * @return object * * @since 1.0.0 */ public function get_express_checkout_details( $token ) { $environment = (true === $this->get_wc_gateway()->testmode) ? 'sandbox' : 'production'; $api_creds_prefix = ''; if ('sandbox' === $environment) { $api_creds_prefix = 'sandbox_'; } $this->set_api_credentials( $this->get_key(), $environment, $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_username'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_password'), $this->get_wc_gateway()->get_option($api_creds_prefix . 'api_signature') ); $this->set_express_checkout_details_args($token); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); return $this->perform_request($request); } /** * Sets up GetExpressCheckoutDetails API call arguments * * @param string $token * * @since 1.0.0 */ public function set_express_checkout_details_args($token) { $this->set_method('GetExpressCheckoutDetails'); $this->add_parameter('TOKEN', $token); } /** * @hooked woocommerce_api_wpfunnels_paypal */ public function maybe_create_billing() { if (!isset($_GET['action'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $token = esc_attr( sanitize_text_field( wp_unslash( $_GET['token'] ) ) ); $order_id = isset( $_GET['order_id'] ) ? intval( $_GET['order_id'] ) : 0; $step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : 0; $funnel_id = isset( $_GET['funnel_id'] ) ? intval( $_GET['funnel_id'] ) : 0; switch ($_GET['action']) { // called when the customer is returned from PayPal after authorizing their payment, used for retrieving the customer's checkout details case 'wpfunnels_paypal_create_billing_agreement': // return if no token if (!isset($_GET['token'])) { return; } // get token to retrieve checkout details with $token = wc_clean( $_GET['token'] ); $order = null; try { $express_checkout_details_response = $this->get_express_checkout_details($token); $order = wc_get_order($order_id); // Make sure the billing agreement was accepted if (1 === $express_checkout_details_response['BILLINGAGREEMENTACCEPTEDSTATUS'] || '1' === $express_checkout_details_response['BILLINGAGREEMENTACCEPTEDSTATUS']) { if (is_null($order)) { throw new Wpfnl_Payment_Gateway_Exception(__('Unable to find order for PayPal billing agreement.', 'wpfnl-pro'), 101, $this->get_key()); } // we need to process an initial payment if (Wpfnl_Pro_functions::get_amount_for_comparisons($order->get_total()) > 0) { $billing_agreement_response = $this->do_express_checkout($token, $order, array( 'payment_action' => 'Sale', 'payer_id' => $this->get_value_from_response($express_checkout_details_response, 'PAYERID'), )); } else { throw new Wpfnl_Payment_Gateway_Exception(__('Order total is not greater than zero.', 'wpfnl'), 101, $this->get_key()); } if ($this->has_api_error($billing_agreement_response)) { throw new Wpfnl_Payment_Gateway_Exception($this->get_api_error($billing_agreement_response), 101, $this->get_key()); } $order->set_payment_method('paypal'); $order->update_meta_data('_paypal_subscription_id', $this->get_value_from_response($billing_agreement_response, 'BILLINGAGREEMENTID')); /** * mark primary payment as completed */ $order->payment_complete($billing_agreement_response['PAYMENTINFO_0_TRANSACTIONID']); $next_step = Wpfnl_functions::get_next_step( $funnel_id, $step_id ); $redirect_url = add_query_arg('utm_nooverride', '1', get_permalink($next_step['step_id'])); //redirect customer to order received page wp_safe_redirect(esc_url_raw($redirect_url)); exit; } else { $this->handle_api_failures($order); } } catch (\Exception $e) { $this->handle_api_failures($order, $e); } } } /** * check if api response has any error * * @param $response * @return bool */ public function has_api_error($response) { // assume something went wrong if ACK is missing if (!isset($response['ACK'])) { return true; } // any non-success ACK is considered an error, see // https://developer.paypal.com/docs/classic/api/NVPAPIOverview/#id09C2F0K30L7 return ('Success' !== $this->get_value_from_response($response, 'ACK') && 'SuccessWithWarning' !== $this->get_value_from_response($response, 'ACK')); } /** * get order from response * * @param $response * @return bool|\WC_Order|\WC_Order_Refund */ public function get_order_from_response($response) { if ($response && isset($response['CUSTOM'])) { $getjson = json_decode($response['CUSTOM'], true); return wc_get_order($getjson['_wpfunnels_o_id']); } } /** * Handles Payment Gateway API error * * @param $order * @param $e */ protected function handle_api_failures($order, $e = '') { if ($order instanceof \WC_Order) { if ($e instanceof Wpfnl_Payment_Gateway_Exception) { $order->add_order_note($e->getMessage()); } $redirect = $order->get_checkout_order_received_url(); wp_redirect($redirect); exit; } wp_die('Unable to process further. Please contact to the store admin & enquire about the status of your order.'); exit; } public function handle_api_calls() { if (!isset($_GET['action'])) { return; } $step_id = isset( $_GET['step_id'] ) ? intval( $_GET['step_id'] ) : 0; $funnel_id = isset( $_GET['funnel_id'] ) ? intval( $_GET['funnel_id'] ) : 0; $order_id = isset( $_GET['order_id'] ) ? intval( $_GET['order_id'] ) : 0; $order_key = isset( $_GET['order_key'] ) ? sanitize_text_field( wp_unslash( $_GET['order_key'] ) ) : ''; $variation_id = isset( $_GET['variation_id'] ) ? intval( $_GET['variation_id'] ) : ''; $input_qty = isset( $_GET['input_qty'] ) ? intval( $_GET['input_qty'] ) : ''; $order = null; try { switch ($_GET['action']) { case 'wpfunnels_paypal_return': $offer_product = Wpfnl_Pro_functions::get_offer_product_data($step_id, '', '', $order_id); $order = wc_get_order( $order_id ); if ( isset( $_GET['token'] ) && ! empty( $_GET['token'] ) ) { $api_response_result = false; $express_checkout_details_response = $this->get_express_checkout_details( wp_unslash( $_GET['token'] ) ); //phpcs:ignore $get_paypal_data = WC()->session->get('paypal_request'); /** * Check if product total is greater than 0. */ if ( $offer_product['total'] > 0 ) { /** * Prepare DoExpessCheckout Call to finally charge the user. */ $do_express_checkout_data = array( 'TOKEN' => $express_checkout_details_response['TOKEN'], 'PAYERID' => $express_checkout_details_response['PAYERID'], 'METHOD' => 'DoExpressCheckoutPayment', ); $do_express_checkout_data = wp_parse_args( $do_express_checkout_data, $get_paypal_data ); $environment = ( true === $this->get_wc_gateway()->testmode ) ? 'sandbox' : 'live'; $api_prefix = ''; if ( 'sandbox' === $environment ) { $api_prefix = 'sandbox_'; } /** * Setup & perform DoExpressCheckout API Call. */ $this->set_api_credentials( $this->key, $environment, $this->get_wc_gateway()->get_option( $api_prefix . 'api_username' ), $this->get_wc_gateway()->get_option( $api_prefix . 'api_password' ), $this->get_wc_gateway()->get_option( $api_prefix . 'api_signature' ) ); $this->add_parameters( $do_express_checkout_data ); $this->set_credentials_params( $this->api_username, $this->api_password, $this->api_signature, 124 ); $request = new \stdClass(); $request->path = ''; $request->method = 'POST'; $request->body = $this->to_string(); $response_checkout = $this->perform_request( $request ); if ( false === $this->has_api_error( $response_checkout ) ) { $api_response_result = true; $this->handle_offer_charge($order, $response_checkout, $offer_product); } } else { $api_response_result = true; } $result = Wpfnl_Pro_functions::after_offer_charged( $funnel_id, $step_id, $order_id, $order_key, $offer_product, $api_response_result, $variation_id, $input_qty ); } $next_node = Wpfnl_functions::get_next_conditional_step( $funnel_id, $step_id, $order, $checker = 'accept' ); $query_args = array( 'wpfnl-order' => $order_id, 'wpfnl-key' => $order_key, 'key' => $order_key, ); $redirect_url = add_query_arg($query_args, get_permalink($next_node['step_id'])); if ($next_node && $redirect_url) { wp_safe_redirect( $redirect_url ); exit; } break; case 'cancel_url': /** * Getting the current URL from the session and loading the same offer again. * User needs to chose "no thanks" if he want to move to upsell/order received. */ $url = get_permalink( $step_id ); $args = array( 'wpfnl-order' => $order_id, 'wpfnl-key' => $order_key, 'key' => $order_key, ); $url = add_query_arg( $args, $url ); wp_redirect($url); exit; } } catch (\Exception $e) { $this->handle_api_failures($order, $e); } } /** * Store Offer Trxn Charge. * * @param \WC_Order $order The order that is being paid for. * @param Object $response The response that is send from the payment gateway. * @param array $product Product data. */ public function handle_offer_charge( \WC_Order $order, $response, $product ) { $order_id = $order->get_id(); $txn_id = ''; if ( ! isset( $response['PAYMENTINFO_0_TRANSACTIONID'] ) ) { $txn_id = $response['TRANSACTIONID']; } else { $txn_id = $response['PAYMENTINFO_0_TRANSACTIONID']; } $order->update_meta_data( '_wpfunnels_offer_txn_resp_' . $product['step_id'], $txn_id ); $order->save(); } /** * replace the transaction id of the offer instead of the parent order * transaction id * * @param $request * @param $order * @param $amount * @param $reason * @return mixed */ public function offer_refund_request_data( $request, $order, $amount, $reason ) { $payment_method = $order->get_payment_method(); if ($this->key !== $payment_method) { return $request; } if (isset($_POST['payload']['transaction_id']) && !empty($_POST['payload']['transaction_id'])) { $request['TRANSACTIONID'] = wc_clean($_POST['payload']['transaction_id']); } return $request; } /** * process refund * * @param $order * @param $data */ public function process_refund_offer( $order, $data ) { $amount = $data['amount']; $refund_reason = $data['reason']; $response = false; if (!is_null($amount) && class_exists('WC_Gateway_Paypal')) { $paypal = $this->get_wc_gateway(); if ( $this->refund_support ) { if (!class_exists('WC_Gateway_Paypal_API_Handler')) { include_once wc()->plugin_path() . '/includes/gateways/paypal/includes/class-wc-gateway-paypal-api-handler.php'; } \WC_Gateway_Paypal_API_Handler::$api_username = $paypal->testmode ? $paypal->get_option('sandbox_api_username') : $paypal->get_option('api_username'); \WC_Gateway_Paypal_API_Handler::$api_password = $paypal->testmode ? $paypal->get_option('sandbox_api_password') : $paypal->get_option('api_password'); \WC_Gateway_Paypal_API_Handler::$api_signature = $paypal->testmode ? $paypal->get_option('sandbox_api_signature') : $paypal->get_option('api_signature'); \WC_Gateway_Paypal_API_Handler::$sandbox = $paypal->testmode; $result = \WC_Gateway_Paypal_API_Handler::refund_transaction($order, $amount, $refund_reason); if (!is_wp_error($result)) { switch (strtolower($result->ACK)) { case 'success': case 'successwithwarning': $response = $result->REFUNDTRANSACTIONID; } } } } return $response ? $response : false; } /** * add subscription offer meta to order * * @param $subscription * @param $offer_product * @param $order */ public function add_offer_subscription_meta( $subscription, $offer_product, $order ) { if ( 'paypal' === $order->get_payment_method() ) { $subscription_id = $subscription->get_id(); update_post_meta( $subscription_id, '_stripe_source_id', $order->get_meta( '_stripe_source_id', true ) ); update_post_meta( $subscription_id, '_stripe_customer_id', $order->get_meta( '_stripe_customer_id', true ) ); } } } public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php000064400000076526147600245720020675 0ustar00refund_support = true; add_action( 'wp_ajax_wpfnl_create_paypal_order', array( $this, 'create_wc_paypal_order' ),9999 ); add_action( 'wp_ajax_nopriv_wpfnl_create_paypal_order', array( $this, 'create_wc_paypal_order' ),9999 ); add_action( 'wp_ajax_wpfnl_capture_paypal_order', array( $this, 'capture_paypal_order' ),9999 ); add_action( 'wp_ajax_nopriv_wpfnl_capture_paypal_order', array( $this, 'capture_paypal_order' ),9999 ); add_filter( 'woocommerce_paypal_refund_request', array( $this, 'offer_refund_request_data' ), 9999, 4 ); add_action( 'wpfunnels/child_order_created_' . $this->key, array( $this, 'add_capture_meta_to_child_order' ), 9999, 3 ); add_action( 'wpfunnels/subscription_created', array( $this, 'add_offer_subscription_meta' ), 9999, 3 ); } /** * process upsell payment * * @param $order * @param $offer_product * @return bool */ public function process_payment( $order, $offer_product ) { $result = array( 'is_success' => false, 'message' => '' ); $txn_id = $order->get_meta( '_wpfunnels_capture_paypal_txn_id_' . $offer_product['step_id']. '_' .$order->get_id() ); if ( empty( $txn_id ) ) { $result['is_success'] = false; } else { $is_charge_success = true; $response = array( 'id' => $txn_id, ); $order->update_meta_data( '_wpfunnels_offer_txn_resp_' . $offer_product['step_id'], $response['id'] ); $order->save(); $result['is_success'] = true; } return $result; } /** * return array */ public function create_wc_paypal_order() { $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpfnl_create_paypal_order_nonce' ) ) { return; } $settings = get_option( 'woocommerce-ppcp-settings', true ); $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0; $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $order_id = isset( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : 0; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash( $_POST['order_key'] ) ) : ''; $step_type = get_post_meta( $step_id, '_step_type', true ); $order = wc_get_order( $order_id ); $variation_id = ''; $quantity = ''; $args = array( 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'order_id' => $order_id, 'order_key' => $order_key, 'ppcp_data' => $this->get_ppcp_meta( $order ), ); /** get the authorization token */ $token = $this->get_token( $order ); if ( isset( $_POST['variation_id'] ) && ! empty( $_POST['variation_id'] ) ) { $variation_id = intval( $_POST['variation_id'] ); } if ( isset( $_POST['input_qty'] ) && ! empty( $_POST['input_qty'] ) ) { $quantity = intval( $_POST['input_qty'] ); } $offer_product = get_post_meta( $step_id, '_wpfnl_'.$step_type.'_products', true ); if ( isset( $offer_product[0]['id'] ) ) { $vr_product = wc_get_product($offer_product[0]['id']); if( $vr_product && 'variable' == $vr_product->get_type() ){ $payload = [ 'product_id' => $offer_product[0]['id'], 'data' => $_POST['attr'], ]; $variation_id = Wpfnl_Pro_functions::modify_offer_product($payload); } } $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, $variation_id, $quantity, $order_id ); $return_arg = [ 'wpfnl-order' => $order_id, 'wpfnl-key' => $order_key, 'key' => $order_key, 'wpfnl-paypal-return' => true, ]; if( $variation_id ){ $return_arg['wpfnl-variation-id'] = $variation_id; } /** data for generating order */ $data = array( 'intent' => $args['ppcp_data']['intent'], 'purchase_units' => $this->generate_purchase_unit( $order, $offer_product, $args ), 'application_context' => array( 'brand_name' => isset($settings['brand_name']) ? $settings['brand_name'] : '', 'landing_page' => isset($settings['landing_page']) ? $settings['landing_page'] : 'LOGIN', 'user_action' => 'CONTINUE', "shipping_preference" => "GET_FROM_FILE", 'return_url' => add_query_arg( $return_arg, get_the_permalink($step_id)), 'cancel_url' => add_query_arg(array( 'wpfnl-order' => $order_id, 'wpfnl-key' => $order_key, 'key' => $order_key, 'wpfnl-paypal-cancel' => true, ), get_the_permalink($step_id)), ), 'payment_method' => array( 'payee_preferred' => 'UNRESTRICTED', 'payer_selected' => 'PAYPAL', ), 'payment_instruction' => array( 'disbursement_mode' => 'INSTANT', 'platform_fees' => array( array( 'amount' => array( 'currency_code' => $this->get_currency( $order ), 'value' => $offer_product['unit_price_tax'], ), ), ), ), ); $arguments = array( 'method' => 'POST', 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $token, 'PayPal-Partner-Attribution-Id' => 'WPF_WC_PAYPAL', ), 'body' => wp_json_encode( $data ), ); $is_sandbox = isset($settings['sandbox_on']) ? $settings['sandbox_on'] : false; $payment_env = $is_sandbox ? 'sandbox' : 'production'; $host = 'https://api-m.'.$payment_env.'.'; if($payment_env === 'production') { $host = 'https://api-m.'; } $url = $host. 'paypal.com/v2/checkout/orders/'; $response = wp_remote_get( $url, $arguments ); if ( is_wp_error( $response ) ) { $token = $this->get_token( $order, true ); $arguments = array( 'method' => 'POST', 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $token, 'PayPal-Partner-Attribution-Id' => 'WPF_WC_PAYPAL', ), 'body' => wp_json_encode( $data ), ); $is_sandbox = isset($settings['sandbox_on']) ? $settings['sandbox_on'] : false; $payment_env = $is_sandbox ? 'sandbox' : 'production'; $host = 'https://api-m.'.$payment_env.'.'; if($payment_env === 'production') { $host = 'https://api-m.'; } $url = $host. 'paypal.com/v2/checkout/orders/'; $response = wp_remote_get( $url, $arguments ); if ( is_wp_error( $response ) ) { $json_response = array( 'status' => false, 'message' => $response->get_error_message(), ); return wp_send_json( $json_response ); } } $json = json_decode( $response['body'] ); $json_response = array( 'result' => false, 'message' => __( 'PayPal order is not created', 'wpfnl-pro' ), 'response' => $response, ); if ( $json->status ?? false && 'CREATED' === $json->status ) { $order->update_meta_data( '_wpfunnels_paypal_'.$step_type.'_'.$step_id.'_order_id_'.$order_id, $json->id ); $order->save(); $json_response = array( 'status' => 'success', 'message' => __( 'Order created successfully', 'wpfnl-pro' ), 'paypal_order_id' => $json->id, 'redirect' => $json->links[1]->href, 'response' => $json, ); }else{ $token = $this->get_token( $order, true ); $arguments = array( 'method' => 'POST', 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $token, 'PayPal-Partner-Attribution-Id' => 'WPF_WC_PAYPAL', ), 'body' => wp_json_encode( $data ), ); $is_sandbox = isset($settings['sandbox_on']) ? $settings['sandbox_on'] : false; $payment_env = $is_sandbox ? 'sandbox' : 'production'; $host = 'https://api-m.'.$payment_env.'.'; if($payment_env === 'production') { $host = 'https://api-m.'; } $url = $host. 'paypal.com/v2/checkout/orders/'; $response = wp_remote_get( $url, $arguments ); if ( is_wp_error( $response ) ) { $json_response = array( 'status' => false, 'message' => $response->get_error_message(), ); return wp_send_json( $json_response ); }else{ $json = json_decode( $response['body'] ); $json_response = array( 'result' => false, 'message' => __( 'PayPal order is not created', 'wpfnl-pro' ), 'response' => $response, ); $order->update_meta_data( '_wpfunnels_paypal_'.$step_type.'_'.$step_id.'_order_id_'.$order_id, $json->id ); $order->save(); $json_response = array( 'status' => 'success', 'message' => __( 'Order created successfully', 'wpfnl-pro' ), 'paypal_order_id' => $json->id, 'redirect' => $json->links[1]->href, 'response' => $json, ); } } return wp_send_json( $json_response ); } /** * @throws \WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException */ public function capture_paypal_order() { $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpfnl_capture_paypal_order_nonce' ) ) { return; } $order_id = isset( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : 0; $step_id = isset( $_POST['step_id'] ) ? sanitize_text_field( wp_unslash( $_POST['step_id'] ) ) : 0; $order = wc_get_order( $order_id ); $token = $this->get_token( $order ); $step_type = isset( $_POST['step_type'] ) ? sanitize_text_field( wp_unslash( $_POST['step_type'] ) ) : ''; $paypal_order_id = $order->get_meta('_wpfunnels_paypal_'.$step_type.'_'.$step_id.'_order_id_' . $order->get_id()); $settings = get_option( 'woocommerce-ppcp-settings', true ); $is_sandbox = isset($settings['sandbox_on']) ? $settings['sandbox_on'] : false; $payment_env = $is_sandbox ? 'sandbox' : 'production'; $host = 'https://api-m.'.$payment_env.'.'; if($payment_env === 'production') { $host = 'https://api-m.'; } $url = $host. 'paypal.com/v2/checkout/orders/'.$paypal_order_id.'/capture'; if ( 'AUTHORIZE' === $order->get_meta( '_ppcp_paypal_intent' ) ) { $url = $host. 'paypal.com/v2/checkout/orders/'.$paypal_order_id.'/authorize'; } $args = array( 'method' => 'POST', 'headers' => array( 'Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json', 'Prefer' => 'return=representation', 'PayPal-Partner-Attribution-Id' => 'WPF_WC_PAYPAL', ), ); $response = wp_remote_get( $url, $args ); if ( is_wp_error( $response ) ) { $token = $this->get_token( $order, true ); $args = array( 'method' => 'POST', 'headers' => array( 'Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json', 'Prefer' => 'return=representation', 'PayPal-Partner-Attribution-Id' => 'WPF_WC_PAYPAL', ), ); $response = wp_remote_get( $url, $args ); if ( is_wp_error( $response ) ) { $json_response = array( 'status' => false, 'message' => $response->get_error_message(), 'response' => $response, ); return wp_send_json( $json_response ); } } $json = json_decode( $response['body'] ); $json_response = array( 'status' => false, 'message' => __( 'PayPal order is not created', 'wpfnl-pro' ), 'paypal_order_id' => '', 'redirect_url' => '', 'response' => $json, ); if ( isset($json->status) && $json->status ?? false && ('CREATED' === $json->status || 'COMPLETED' === $json->status ) ) { if ( 'AUTHORIZE' === $order->get_meta( '_ppcp_paypal_intent' ) ) { $txn_id = isset($json->purchase_units[0]->payments->authorizations[0]->id) ? $json->purchase_units[0]->payments->authorizations[0]->id : ''; } else { $txn_id = isset($json->purchase_units[0]->payments->captures[0]->id) ? $json->purchase_units[0]->payments->captures[0]->id : ''; } $order->update_meta_data( '_wpfunnels_capture_paypal_txn_id_' . $step_id. '_' .$order->get_id(), $txn_id ); $order->save(); if( !$this->maybe_child_order() ){ $prev_fees = $order->get_meta('_ppcp_paypal_fees'); if( $prev_fees && isset($prev_fees['paypal_fee']['value'],$prev_fees['net_amount']['value'])){ if( isset($json->purchase_units[0]->payments->captures[0]->seller_receivable_breakdown->paypal_fee->value, $json->purchase_units[0]->payments->captures[0]->seller_receivable_breakdown->net_amount->value)){ $prev_fees['paypal_fee']['value'] = $prev_fees['paypal_fee']['value'] + ($json->purchase_units[0]->payments->captures[0]->seller_receivable_breakdown->paypal_fee->value); $prev_fees['net_amount']['value'] = $prev_fees['net_amount']['value'] + ($json->purchase_units[0]->payments->captures[0]->seller_receivable_breakdown->net_amount->value); $order->update_meta_data('_ppcp_paypal_fees', $prev_fees); } } } $json_response = array( 'status' => 'success', 'message' => __('Order Captured successfully', 'wpfnl-pro' ), 'paypal_order_id' => $json->id, 'redirect_url' => '', 'response' => $json, ); }else{ $token = $this->get_token( $order, true ); $args = array( 'method' => 'POST', 'headers' => array( 'Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json', 'Prefer' => 'return=representation', 'PayPal-Partner-Attribution-Id' => 'WPF_WC_PAYPAL', ), ); $response = wp_remote_get( $url, $args ); if ( is_wp_error( $response ) ) { $json_response = array( 'status' => false, 'message' => $response->get_error_message(), 'response' => $response, ); return wp_send_json( $json_response ); }else{ $json = json_decode( $response['body'] ); $json_response = array( 'status' => false, 'message' => __( 'PayPal order is not created', 'wpfnl-pro' ), 'paypal_order_id' => '', 'redirect_url' => '', 'response' => $json, ); if ( 'AUTHORIZE' === $order->get_meta( '_ppcp_paypal_intent' ) ) { $txn_id = isset($json->purchase_units[0]->payments->authorizations[0]->id) ? $json->purchase_units[0]->payments->authorizations[0]->id : ''; } else { $txn_id = isset($json->purchase_units[0]->payments->captures[0]->id) ? $json->purchase_units[0]->payments->captures[0]->id : ''; } $order->update_meta_data( '_wpfunnels_capture_paypal_txn_id_' . $step_id. '_' .$order->get_id(), $txn_id ); $order->save(); if( !$this->maybe_child_order() ){ $prev_fees = $order->get_meta('_ppcp_paypal_fees'); if( $prev_fees && isset($prev_fees['paypal_fee']['value'],$prev_fees['net_amount']['value'])){ if( isset($json->purchase_units[0]->payments->captures[0]->seller_receivable_breakdown->paypal_fee->value, $json->purchase_units[0]->payments->captures[0]->seller_receivable_breakdown->net_amount->value)){ $prev_fees['paypal_fee']['value'] = $prev_fees['paypal_fee']['value'] + ($json->purchase_units[0]->payments->captures[0]->seller_receivable_breakdown->paypal_fee->value); $prev_fees['net_amount']['value'] = $prev_fees['net_amount']['value'] + ($json->purchase_units[0]->payments->captures[0]->seller_receivable_breakdown->net_amount->value); $order->update_meta_data('_ppcp_paypal_fees', $prev_fees); } } } $json_response = array( 'status' => 'success', 'message' => __('Order Captured successfully', 'wpfnl-pro' ), 'paypal_order_id' => $json->id, 'redirect_url' => '', 'response' => $json, ); } } return wp_send_json( $json_response ); } /** * @param $order * @return array */ public function get_ppcp_meta( $order ) { $settings = get_option( 'woocommerce-ppcp-settings', true ); return array( 'environment' => $order->get_meta( '_ppcp_paypal_payment_mode' ), 'intent' => $order->get_meta( '_ppcp_paypal_intent' ), 'merchant_email' => isset($settings['merchant_email']) ? $settings['merchant_email'] : '', 'merchant_id' => isset($settings['merchant_id']) ? $settings['merchant_id'] : '', 'invoice_prefix' => isset($settings['prefix']) ? $settings['prefix'] : '', ); } /** * get access token * * @param $order * @return string * @throws \WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException */ public function get_token( $order,$is_new = false) { $token = ''; if( !$is_new ){ $bearer = get_option( '_transient_ppcp-paypal-bearerppcp-bearer' ); if ( !empty( $bearer ) ) { $bearer = json_decode( $bearer ); $token = $bearer->access_token; } }else{ $token = ''; } if ( empty( $token ) ) { $settings = get_option( 'woocommerce-ppcp-settings', true ); $is_sandbox = isset($settings['sandbox_on']) ? $settings['sandbox_on'] : false; $payment_env = $is_sandbox ? 'sandbox' : 'production'; $key = isset($settings['client_id']) ? $settings['client_id'] : ''; $secret = isset($settings['client_secret']) ? $settings['client_secret'] : ''; $host = 'https://api-m.'.$payment_env.'.'; $host = 'https://api-m.'.$payment_env.'.'; if($payment_env === 'production') { $host = 'https://api-m.'; } $url = $host. 'paypal.com/v1/oauth2/token?grant_type=client_credentials'; $args = array( 'method' => 'POST', 'headers' => array( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode 'Authorization' => 'Basic ' . base64_encode( $key . ':' . $secret ), ), ); $response = wp_remote_get( $url, $args ); if ( ! is_wp_error( $response ) ) { $res_body = json_decode( $response['body'] ); $token = $res_body->access_token; } } return $token; } /** * generate purchase unit data * * @param $order * @param $offer_product * @param $args * @return array */ public function generate_purchase_unit( $order, $offer_product, $args ) { $invoice_id = $args['ppcp_data']['invoice_prefix'] . '-wpfnl-' . $args['order_id'] . '_' . $args['funnel_id'] . '_' . $args['step_id']; $unit_value = round($offer_product['unit_price_tax']/$offer_product['qty'],2); $value = round($unit_value * $offer_product['qty'],2); return array( array( 'reference_id' => 'default', 'amount' => array( 'currency_code' => $this->get_currency($order), 'value' => strval($value), 'breakdown' => $this->get_item_breakdown( $order, $offer_product ) ), 'description' => $offer_product['desc'], 'items' => array( $this->get_items($order, $offer_product) ), 'payee' => array( 'email_address' => $args['ppcp_data']['merchant_email'], 'merchant_id' => $args['ppcp_data']['merchant_id'], ), 'invoice_id' => $invoice_id, 'custom_id' => $invoice_id, 'shipping' => array( 'name' => array( 'full_name' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(), ), ), ) ); } /** * generate item breakdown data * * @param $order * @param $offer_product * @return mixed */ public function get_item_breakdown( $order, $offer_product ) { $unit_value = round($offer_product['unit_price_tax']/$offer_product['qty'],2); $value = round($unit_value * $offer_product['qty'],2); $breakdown['item_total'] = array( 'currency_code' => $this->get_currency($order), 'value' => strval($value), ); // if ( ! empty( $offer_product['shipping_fee'] ) ) { // $breakdown['shipping'] = array( // 'currency_code' => $this->get_currency($order), // 'value' => $offer_product['shipping_fee_incl_tax'], // ); // } return $breakdown; } /** * prepare order items * * @param $order * @param $offer_product * @return array */ public function get_items($order, $offer_product) { $value = round($offer_product['unit_price_tax']/$offer_product['qty'],2); return array( 'name' => $offer_product['name'], 'unit_amount' => array( 'currency_code' => $this->get_currency($order), 'value' => strval($value), ), 'quantity' => $offer_product['qty'], 'id' => $offer_product['id'], 'description' => wp_strip_all_tags( $offer_product['desc'] ), ); } /** * get the currency * * @param \WC_Order $order * @return string */ public function get_currency( \WC_Order $order) { return $order->get_currency() ? $order->get_currency() : get_woocommerce_currency(); } /** * replace the transaction id of the offer instead of the parent order * transaction id * * @param $request * @param $order * @param $amount * @param $reason * @return mixed */ public function offer_refund_request_data( $request, $order, $amount, $reason ) { $payment_method = $order->get_payment_method(); if ($this->key !== $payment_method) { return $request; } if (isset($_POST['txn_id']) && !empty($_POST['txn_id'])) { $request['TRANSACTIONID'] = wc_clean($_POST['txn_id']); } return $request; } /** * filter paypal order id for offer refund * * @param $value * @param $order * @return mixed */ public function paypal_order_id_for_offer( $value, $order ) { if($this->is_offer_exits_in_order( $order )) { $step_id = $this->get_refund_processing_step_from_session(); if( $step_id ) { $step_type = get_post_meta( $step_id, '_step_type', true ); if( $step_type && $order->get_meta('_wpfunnels_paypal_'.$step_type.'_'.$step_id.'_order_id_'.$order->get_id() )) { $value = $order->get_meta('_wpfunnels_paypal_'.$step_type.'_'.$step_id.'_order_id_'.$order->get_id()); } } } return $value; } /** * get refund procession step id from * session * * @return string|null */ private function get_refund_processing_step_from_session() { return WC()->session->get('wpfunnels_refund_processing_step_id'); } /** * check if offer exits in order * * @param \WC_Order $order * @return bool */ private function is_offer_exits_in_order( \WC_Order $order ) { $is_offer = false; $line_items = $order->get_items(); foreach ($line_items as $item_id => $item) { $is_upsell_offer = wc_get_order_item_meta( $item_id, '_wpfunnels_upsell', true ); $is_downsell_offer = wc_get_order_item_meta( $item_id, '_wpfunnels_downsell', true ); $txn_id = wc_get_order_item_meta( $item_id, '_wpfunnels_offer_txn_id', true ); if ( 'yes' == $is_upsell_offer || 'yes' == $is_downsell_offer && ! empty( $txn_id ) ) { $is_offer = true; break; } } return $is_offer; } /** * process refund offer * * @param $order * @param $data * @return false */ public function process_refund_offer( $order, $data ) { $transaction_id = $data['transaction_id']; $amount = $data['amount']; $refund_reason = $data['reason']; $offer_id = $data['offer_id']; $step_id = $data['step_id']; $response = false; $gateway = $this->get_wc_gateway(); if ( $this->refund_support ) { WC()->session->set( 'wpfunnels_refund_processing_step_id', null ); WC()->session->set( 'wpfunnels_refund_processing_step_id', $step_id ); add_filter( 'woocommerce_order_get__ppcp_paypal_order_id', array( $this, 'paypal_order_id_for_offer' ), 10, 4 ); // WC()->session->set( 'wpfunnels_refund_processing_step_id', null ); $result = $gateway->process_refund( $order->get_id(), $amount, $refund_reason ); if ( is_wp_error( $result ) ) { } elseif ( $result ) { $response = $result; } } return $response; } /** * add required meta for refund * * @param $parent_order * @param $child_order * @param $transaction_id */ public function add_capture_meta_to_child_order( $parent_order, $child_order, $transaction_id ) { if ( ! empty( $transaction_id ) ) { $offer_type = $child_order->get_meta('_wpfunnels_offer_type'); $step_id = $child_order->get_meta('_wpfunnels_offer_step_id'); $paypal_order_id = $parent_order->get_meta( '_wpfunnels_paypal_'.$offer_type.'_'.$step_id.'_order_id_'.$parent_order->get_id() ); $child_order->update_meta_data( '_ppcp_paypal_order_id', $paypal_order_id ); $child_order->update_meta_data( '_ppcp_paypal_intent', 'CAPTURE' ); $child_order->save(); } } /** * add subscription offer meta to order * * @param $subscription * @param $offer_product * @param $order */ public function add_offer_subscription_meta( $subscription, $offer_product, $order ) { if ( 'ppcp-gateway' === $order->get_payment_method() ) { $subscription_id = $subscription->get_id(); update_post_meta( $subscription_id, '_ppcp_paypal_order_id', $order->get_meta( '_ppcp_paypal_order_id', true ) ); update_post_meta( $subscription_id, 'payment_token_id', $order->get_meta( 'payment_token_id', true ) ); } } /** * Check cild order or not * @since 1.6.25 */ private function maybe_child_order(){ $offer_settings = get_option('_wpfunnels_offer_settings'); if( isset($offer_settings['offer_orders']) && $offer_settings['offer_orders'] ){ return 'child-order' == $offer_settings['offer_orders'] ? true : false; } return false; } }public/gateways/class-wpfnl-pro-gateway-square.php000064400000035507147600245720016364 0ustar00refund_support = true; add_filter( 'wc_' . $this->key . '_payment_form_tokenization_forced', array( $this, 'is_square_tokenization_allowed' ), 10, 2 ); add_filter( 'wc_payment_gateway_' . $this->key . '_get_order', [ $this, 'get_order_by_hook' ], 10 ); add_filter( 'wc_payment_gateway_' . $this->key . '_process_payment', [ $this, 'is_credit_card_process_payment' ], 10, 3 ); add_action( 'wpfunnels/child_order_created_' . $this->key, array( $this, 'add_capture_meta_to_child_order' ), 9999, 3 ); add_action( 'wpfunnels/subscription_created', array( $this, 'add_offer_subscription_meta' ), 9999, 3 ); } /** * Get order * * @param $order * @param WC_Payment_Gateway $gateway * * @since 1.9.1 * @return mixed */ public function get_order_by_hook( $order ) { $this->set_square_client_environment(); if ( ! isset( $order->payment->token ) ) { $order->payment->token = $order->get_meta('_wc_square_credit_card_payment_token'); } if ( $this->maybe_guest_token_call && isset( $order->payment->verification_token ) && ! empty( $order->payment->verification_token ) ) { $order->payment->verification_token = null; if ( isset( $_POST[ 'wc-' . $this->get_wc_gateway()->get_id_dasherized() . '-buyer-verification-token' ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing unset( $_POST[ 'wc-' . $this->get_wc_gateway()->get_id_dasherized() . '-buyer-verification-token' ] ); //phpcs:ignore WordPress.Security.NonceVerification.Missing } } return $order; } /** * Credit card payment processing * * @param bool $process_payment The current process payment status. * @param int $order_id The ID of the order. * @param object $gateway The WooCommerce payment gateway object. * * @return bool Returns the updated process payment status. * * @since 1.6.4 */ public function is_credit_card_process_payment($process_payment, $order_id, $gateway) { $order = wc_get_order( $order_id ); if ( $order ) { $this->create_token_for_offer_payment( $order ); } return $process_payment; } /** * Create payment token for offer product charge * * Creates a payment token for processing the payment of an offer product charge. * * @param object $order full order object. * * @return void * * @since 1.9.1 */ private function create_token_for_offer_payment( $order ) { $this->set_square_client_environment(); $order = $this->get_wc_gateway()->get_order( $order ); $is_checkout_nonce_present_in_request = isset( $_REQUEST['wc_square_credit_card_checkout_validate_nonce'] ) ? wc_clean( $_REQUEST['wc_square_credit_card_checkout_validate_nonce'] ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( empty( $order->payment->token ) && empty( $is_checkout_nonce_present_in_request ) && 1 > $order->get_customer_id() ) { $create_token = true; if ( class_exists( '\WooCommerce\Square\Gateway\Customer_Helper' ) ) { $indexed_customers = Customer_Helper::get_customers_by_email( $order->get_billing_email() ); if ( is_array( $indexed_customers ) && count( $indexed_customers ) > 1 ) { $create_token = false; } } if ( $create_token ) { $order = $this->get_wc_gateway()->get_payment_tokens_handler()->create_token( $order ); } $this->maybe_guest_token_call = true; if ( isset( $_POST[ 'wc-' . $this->get_wc_gateway()->get_id_dasherized() . '-buyer-verification-token' ] ) ) { unset( $_POST[ 'wc-' . $this->get_wc_gateway()->get_id_dasherized() . '-buyer-verification-token' ] ); } } } /** * Remove underscore and add dash inside a string * * @return string|string[] * @since 1.6.4 */ public function get_id_dasherized() { return str_replace( '_', '-', $this->key ); } /** * Return order information for creating customer response * * @param $order * * @return bool|\WC_Order|\WC_Order_Refund * @since 1.6.4 */ public function get_order( $order ) { if ( is_numeric( $order ) ) { $order = wc_get_order( $order ); } $order->payment = new \stdClass(); if ( empty( $order->payment->token ) ) { $order->payment->nonce = Square_Helper::get_post( 'wc-' . $this->get_id_dasherized() . '-payment-nonce' ); $order->payment->card_type = Payment_Gateway_Helper::normalize_card_type( Square_Helper::get_post( 'wc-' . $this->get_id_dasherized() . '-card-type' ) ); $order->payment->account_number = $order->payment->last_four = substr( Square_Helper::get_post( 'wc-' . $this->get_id_dasherized() . '-last-four' ), -4 ); $order->payment->exp_month = Square_Helper::get_post( 'wc-' . $this->get_id_dasherized() . '-exp-month' ); $order->payment->exp_year = Square_Helper::get_post( 'wc-' . $this->get_id_dasherized() . '-exp-year' ); $order->payment->postcode = Square_Helper::get_post( 'wc-' . $this->get_id_dasherized() . '-payment-postcode' ); } return $order; } /** * Payment Gateway Payment Form Tokenization Forced. * * Filters whether tokenization is forced for the payment form. * * @param bool $tokenization_forced The current tokenization forced status. * * @return bool Returns the updated tokenization forced status. * * @since 1.6.4 */ public function is_square_tokenization_allowed($tokenization_forced) { return true; if(get_the_ID() === 1){ $post = Wpfnl_Pro_functions::get_sanitized_get_post(); $checkout_id = Wpfnl_functions::get_checkout_id_from_post($post); }else{ $checkout_id = get_the_ID(); } $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $checkout_id ); if ( $checkout_id && $funnel_id ) { $next_step = Wpfnl_functions::get_next_step($funnel_id, $checkout_id); if ( $next_step && Wpfnl_Pro_functions::is_offer_page($next_step['step_id']) ) { $tokenization_forced = true; } } return $tokenization_forced; } /** * Processing of the Upsell or Downsell offers * * @param mixed $order * @param mixed $offer_product * * @return array * @since 1.6.4 */ public function process_payment($order, $offer_product) { $currency = $order->get_currency(); // Setup square payment configuration $client = $this->set_square_client_environment(); if ( isset($offer_product['price']) && (floatval(0) === floatval( $offer_product['price'] ) || '' === trim($offer_product['price'])) ) { wp_send_json(array( 'result' => 'fail', 'message' => __('Product price is less than 0', 'wpfnl-pro'), )); } else { $money_utility_object = new \WooCommerce\Square\Utilities\Money_Utility(); $amount_money = $money_utility_object::amount_to_money($offer_product['total'], $currency); $credit_card_payment_token = $order->get_meta('_wc_square_credit_card_payment_token'); $credit_card_customer_id = $order->get_meta('_wc_square_credit_card_customer_id'); // Prepare data structure for request $body = new \Square\Models\CreatePaymentRequest( $credit_card_payment_token, uniqid() ); $body->setAutocomplete(true); $body->setCustomerId($credit_card_customer_id); $body->setAmountMoney($amount_money); // Handle payment request API responses $api_response = $client->getPaymentsApi()->createPayment($body); if ($api_response->isSuccess()) { $transaction_id = $api_response->getResult()->getPayment()->getId(); // Store transaction ID for offer products $this->store_offer_transaction( $order, $transaction_id, $offer_product ); // Reduce offer products from stock $this->reduce_the_offer_product_from_stock($order, $offer_product); return array( 'is_success' => true, 'message' => 'Success' ); } else { $errors = $api_response->getErrors(); return array( 'is_success' => false, 'message' => sprintf( __( 'Square payment transaction failed (%1$s:%2$s)', 'wpfnl-pro' ), $errors ) ); } } } /** * Store transaction ID for offer products * * @param $order * @param $response * @param $product * * @since 1.6.4 */ public function store_offer_transaction( $order, $transaction_id, $product ) { $order->update_meta_data('_wpfunnels_offer_txn_resp_' . $product['step_id'], $transaction_id); $order->save(); } /** * Set up the required configuration for payment. * * @return object * * @since 1.6.4 */ public function set_square_client_environment() { // Set the access tokan. $this->access_token = $this->get_wc_gateway()->get_plugin()->get_settings_handler()->get_access_token(); $this->access_token = empty( $this->access_token ) ? $this->get_wc_gateway()->get_plugin()->get_settings_handler()->get_option( 'sandbox_token' ) : $this->access_token; // Set the location id. $this->location_id = $this->get_wc_gateway()->get_plugin()->get_settings_handler()->get_location_id(); $client = new SquareClient([ 'accessToken' => $this->access_token, 'environment' => $this->get_environment(), ]); return $client; } /** * Get WooCommerce payment geteways. * * @return array * * @since 1.6.4 */ public function get_wc_gateway() { global $woocommerce; $gateways = $woocommerce->payment_gateways->payment_gateways(); return $gateways[ $this->key ]; } /** * Gets the configured environment. * * @since 1.6.4 * * @return string */ public function get_environment() { $sanboxed = ( defined( 'WC_SQUARE_SANDBOX' ) && WC_SQUARE_SANDBOX ) || $this->is_sandbox_setting_enabled(); return $sanboxed ? 'sandbox' : 'production'; } /** * Tells is if the setting for enabling sandbox is checked. * * @since 1.6.4 * * @return boolean */ public function is_sandbox_setting_enabled() { return 'yes' === $this->get_enable_sandbox(); } /** * Gets setting enabled sandbox. * * @since 1.6.4 * * @return string */ public function get_enable_sandbox() { $setting = get_option('wc_square_settings'); return $setting['enable_sandbox']; } /** * Determines if configured in the sandbox environment. * * @since 1.6.4 * * @return bool */ public function is_sandbox() { return 'sandbox' === $this->get_environment(); } /** * Update post meta for refund process on upsell or downsell * * @param $parent_order * @param $child_order * @param $transaction_id * * @since 1.6.4 */ public function add_capture_meta_to_child_order( $parent_order, $child_order, $transaction_id ) { $child_order->update_meta_data('_wc_square_credit_card_trans_id', $parent_order->data['transaction_id'] ); $child_order->update_meta_data('_wc_square_credit_card_square_location_id', $parent_order->get_meta( '_wc_square_credit_card_square_location_id', true ) ); $child_order->update_meta_data('_wc_square_credit_card_authorization_code', $parent_order->data['transaction_id'] ); $child_order->update_meta_data('_wc_square_credit_card_customer_id', $parent_order->get_meta( '_wc_square_credit_card_customer_id', true ) ); $child_order->update_meta_data('_wc_square_credit_card_square_order_id', $parent_order->data['transaction_id'] ); $child_order->update_meta_data('_wc_square_credit_card_charge_captured', 'yes' ); } /** * Process offer refund * * @param object $order Order Object. * @param array $offer_data offer data. * * @return string/bool. * * @since 1.6.4 */ public function process_refund_offer( $order, $offer_data ) { $payment_id = $offer_data['transaction_id']; // set up the square payment configuration. $client = $this->set_square_client_environment(); $money_utility_object = new \WooCommerce\Square\Utilities\Money_Utility(); $amount_money = $money_utility_object::amount_to_money($offer_data['amount'],'USD'); $body = new \Square\Models\RefundPaymentRequest( uniqid(), $amount_money, $payment_id ); $api_response = $client->getRefundsApi()->refundPayment($body); if ($api_response->isSuccess()) { $response_id = $api_response->getResult()->getRefund()->getId(); return $response_id; } else { $errors = $api_response->getErrors(); return false; } } /** * add subscription offer meta to order * * @param $subscription * @param $offer_product * @param $order * * @since 1.6.4 */ public function add_offer_subscription_meta( $subscription, $offer_product, $order ) { if ( $this->key === $order->get_payment_method() ) { $subscription_id = $subscription->get_id(); update_post_meta( $subscription_id, '_wc_square_credit_card_payment_token', $order->get_meta( '_wc_square_credit_card_payment_token', true ) ); update_post_meta( $subscription_id, '_wc_square_credit_card_customer_id', $order->get_meta( '_wc_square_credit_card_customer_id', true ) ); } } /** * Reduce the offer product from the stock after upsell or downsell offer has been accepted * * @param object $order Object of order. * @param array $offer_product array of offer product. * * @since 1.6.4 */ public function reduce_the_offer_product_from_stock( $order, $offer_product ) { $product = wc_get_product( $offer_product['id'] ); if( $product ){ $new_stock = wc_update_product_stock( $offer_product['id'], $offer_product['qty'], 'decrease' ); $changes[] = array( 'product' => $product, 'from' => $new_stock + intval( $offer_product['qty'] ), 'to' => $new_stock, ); wc_trigger_stock_change_notifications( $order, $changes ); } } }public/gateways/class-wpfnl-pro-gateway-stripe.php000064400000077362147600245720016377 0ustar00refund_support = true; add_filter( 'wc_stripe_force_save_source', array( $this, 'should_tokenize_stripe' ), 9999); add_filter( 'wc_stripe_3ds_source', array( $this, 'may_be_modify_3ds_param' ), 9999, 2); add_action( 'wc_gateway_stripe_process_response', array( $this, 'handle_redirection' ), 9999, 2 ); add_action( 'wp_ajax_wpfunnels_stripe_sca_check', array( $this, 'check_stripe_sca' ),9999); add_action( 'wp_ajax_nopriv_wpfunnels_stripe_sca_check', array( $this, 'check_stripe_sca' ),9999); add_action( 'wpfunnels/child_order_created_' . $this->key, array( $this, 'add_capture_meta_to_child_order' ), 9999, 3 ); add_action( 'wpfunnels/subscription_created', array( $this, 'add_offer_subscription_meta' ), 9999, 3 ); add_action( 'woocommerce_checkout_after_order_review', array( $this, 'add_stripe_hidden_field' ), 99 ); } /** * wpfnl_stripe_tokenization * If required then tokenize to save source of payment * * @param bool $save_source force save source. * */ public function should_tokenize_stripe( $save_source ) { $checkout_id = Wpfnl_functions::get_checkout_id_from_post_data(); // Get checkout id if not found in post data. $checkout_id = !$checkout_id ? get_the_ID() : $checkout_id; $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $checkout_id ); if ( $checkout_id && $funnel_id ) { if ( Wpfnl_Pro_functions::is_offer_exists_in_funnel($funnel_id) ) { $save_source = true; } } return $save_source; } /** * @param $funnel_id * @param $node_found * @return bool */ public function go_to_output_1($funnel_id, $node_found) { $funnel_json = get_post_meta($funnel_id, '_funnel_data', true); if ($funnel_json) { $node_data = $funnel_json['drawflow']['Home']['data']; foreach ($node_data as $node_key => $node_value) { if ($node_value['id'] == $node_found) { $next_node = $node_value['outputs']['output_1']['connections'][0]['node']; return $next_node; } } return false; } } /** * @param $funnel_id * @param $node_found * @return bool */ public function go_to_output_2($funnel_id, $node_found) { $funnel_json = get_post_meta($funnel_id, '_funnel_data', true); if ($funnel_json) { $node_data = $funnel_json['drawflow']['Home']['data']; foreach ($node_data as $node_key => $node_value) { if ($node_value['id'] == $node_found) { $next_node = $node_value['outputs']['output_2']['connections'][0]['node']; return $next_node; } } return false; } } /** * save 3ds source data for offers * * @param $post_data * @param $order * @return mixed */ public function may_be_modify_3ds_param( $post_data, $order ) { if ( $order && Wpfnl_Pro_functions::check_if_offer_exists($order) ) { $order->update_meta_data( '_wpfunnels_stripe_source_id', $post_data['three_d_secure']['card'] ); $order->save(); } return $post_data; } /** * Redirection to order received url * * @param $response * @param $order */ public function handle_redirection( $response, $order ) { if ( 1 === did_action( 'wpfunnels/offer_funnel_started' ) && 1 === did_action( 'wc_gateway_stripe_process_redirect_payment' ) ) { $order_received_url = $order->get_checkout_order_received_url(); wp_safe_redirect( $order_received_url ); exit(); } } /** * wpfnl_stripe_maybe_hide_save_payment */ public function wpfnl_stripe_maybe_hide_save_payment( $is_show ) { return $is_show; } /** * wpfnl_stripe_verify_sca * Verify if payment type is SCA or not * * @throws \WC_Stripe_Exception */ public function check_stripe_sca() { $security = filter_input( INPUT_POST, 'security', FILTER_SANITIZE_STRING ); if ( ! wp_verify_nonce( $security, 'wpfnl_stripe_sca_check_nonce' ) ) { return; } global $woocommerce; $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $order_id = isset( $_POST['order_id'] ) ? intval($_POST['order_id']) : 0; $offer_type = isset( $_POST['offer_type'] ) ? sanitize_text_field( wp_unslash( $_POST['offer_type'] ) ) : ''; $offer_action = isset( $_POST['offer_action'] ) ? sanitize_text_field( wp_unslash( $_POST['offer_action'] ) ) : ''; $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : ''; if( !empty($_POST['attr']) && Wpfnl_functions::is_wc_active() ){ $variation_id = (new \WC_Product_Data_Store_CPT())->find_matching_product_variation( new \WC_Product($product_id), $_POST['attr'] ); if( $variation_id ){ $product_id = $variation_id; } } $order = wc_get_order( $order_id ); $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, $product_id, 0, $order_id ); if ( isset($offer_product['price']) && (floatval(0) === floatval( $offer_product['price'] ) || '' === trim($offer_product['price'])) ) { wp_send_json(array( 'result' => 'fail', 'message' => __('Product price is less than 0', 'wpfnl-pro'), )); } else { $gateways = $woocommerce->payment_gateways->payment_gateways(); $gateway = $gateways['stripe']; if ( $gateway ) { $order_source = $gateway->prepare_order_source($order); $is_3ds = isset($order_source->source_object->card->three_d_secure) ? $order_source->source_object->card->three_d_secure : false; $_3ds_array = [ 'optional', 'not_supported', ]; // check if 3ds is active or not if ( isset($is_3ds) && !in_array( $is_3ds,$_3ds_array ) ) { $intent = $this->create_intent($order, $order_source, $offer_product); $main_settings = get_option('woocommerce_stripe_settings'); $testmode = (!empty($main_settings['testmode']) && 'yes' === $main_settings['testmode']) ? true : false; if ($testmode) { $publishable_key = !empty($main_settings['test_publishable_key']) ? $main_settings['test_publishable_key'] : ''; } else { $publishable_key = !empty($main_settings['publishable_key']) ? $main_settings['publishable_key'] : ''; } $offer_settings = Wpfnl_functions::get_offer_settings(); // Confirm the intent after locking the order to make sure webhooks will not interfere. if ( empty( $intent->error ) ) { $intent = $this->confirm_stripe_intent( $intent, $order, $order_source ); } $response = isset($intent->charges->data) ? end( $intent->charges->data ) : false; if( !empty( $response->balance_transaction ) ){ $order->update_meta_data( '_stripe_balance_transaction_' . $step_id, $response->balance_transaction ); if ( isset($offer_settings['offer_orders']) && 'main-order' === $offer_settings['offer_orders'] ) { self::update_stripe_fees( $order, $response->balance_transaction ); $this->store_offer_transaction( $order, $response, $offer_product ); } } if ( $order ) { $order->update_meta_data( '_stripe_intent_id_' . $step_id, $intent->id ); $order->save(); } wp_send_json(array( 'result' => 'success', 'redirect' => $gateway->get_return_url( $order ), 'intent_secret' => $intent->client_secret, 'stripe_pk' => $publishable_key, )); } } wp_send_json(array( 'result' => 'fail', 'message' => 'No 3ds payment', )); } } /** * Confirms an intent if it is the `requires_confirmation` state. * * @param object $intent The intent to confirm. * @param WC_Order $order The order that the intent is associated with. * @param object $prepared_source The source that is being charged. * @return object Either an error or the updated intent. * * @since 2.2.3 */ public function confirm_stripe_intent( $intent, $order, $prepared_source ) { if ( 'requires_confirmation' !== $intent->status ) { return $intent; } // Try to confirm the intent & capture the charge (if 3DS is not required). $confirm_request = WC_Stripe_Helper::add_payment_method_to_request_array( $prepared_source->source, [] ); $level3_data = $this->get_level3_data_from_order( $order ); $confirmed_intent = WC_Stripe_API::request_with_level3_data( $confirm_request, "payment_intents/$intent->id/confirm", $level3_data, $order ); if ( ! empty( $confirmed_intent->error ) ) { return $confirmed_intent; } return $confirmed_intent; } /** * Create the level 3 data array to send to Stripe when making a purchase. * * @param WC_Order $order The order that is being paid for. * @return array The level 3 data to send to Stripe. * * @since 2.2.3 */ public function get_level3_data_from_order( $order ) { // Get the order items. Don't need their keys, only their values. // Order item IDs are used as keys in the original order items array. $order_items = array_values( $order->get_items( [ 'line_item', 'fee' ] ) ); $currency = $order->get_currency(); $stripe_line_items = array_map( function( $item ) use ( $currency ) { if ( is_a( $item, 'WC_Order_Item_Product' ) ) { $product_id = $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(); $subtotal = $item->get_subtotal(); } else { $product_id = substr( sanitize_title( $item->get_name() ), 0, 12 ); $subtotal = $item->get_total(); } $product_description = substr( $item->get_name(), 0, 26 ); $quantity = $item->get_quantity(); $unit_cost = WC_Stripe_Helper::get_stripe_amount( ( $subtotal / $quantity ), $currency ); $tax_amount = WC_Stripe_Helper::get_stripe_amount( $item->get_total_tax(), $currency ); $discount_amount = WC_Stripe_Helper::get_stripe_amount( $subtotal - $item->get_total(), $currency ); return (object) [ 'product_code' => (string) $product_id, // Up to 12 characters that uniquely identify the product. 'product_description' => $product_description, // Up to 26 characters long describing the product. 'unit_cost' => $unit_cost, // Cost of the product, in cents, as a non-negative integer. 'quantity' => $quantity, // The number of items of this type sold, as a non-negative integer. 'tax_amount' => $tax_amount, // The amount of tax this item had added to it, in cents, as a non-negative integer. 'discount_amount' => $discount_amount, // The amount an item was discounted—if there was a sale,for example, as a non-negative integer. ]; }, $order_items ); $level3_data = [ 'merchant_reference' => $order->get_id(), // An alphanumeric string of up to characters in length. This unique value is assigned by the merchant to identify the order. Also known as an “Order ID”. 'shipping_amount' => WC_Stripe_Helper::get_stripe_amount( (float) $order->get_shipping_total() + (float) $order->get_shipping_tax(), $currency ), // The shipping cost, in cents, as a non-negative integer. 'line_items' => $stripe_line_items, ]; // The customer’s U.S. shipping ZIP code. $shipping_address_zip = $order->get_shipping_postcode(); if ( $this->is_valid_us_zip_code( $shipping_address_zip ) ) { $level3_data['shipping_address_zip'] = $shipping_address_zip; } // The merchant’s U.S. shipping ZIP code. $store_postcode = get_option( 'woocommerce_store_postcode' ); if ( $this->is_valid_us_zip_code( $store_postcode ) ) { $level3_data['shipping_from_zip'] = $store_postcode; } return $level3_data; } /** * Verifies whether a certain ZIP code is valid for the US, incl. 4-digit extensions. * * @param string $zip The ZIP code to verify. * @return boolean * * @since 2.2.3 */ public function is_valid_us_zip_code( $zip ) { return ! empty( $zip ) && preg_match( '/^\d{5,5}(-\d{4,4})?$/', $zip ); } /** * @param $order * @param $order_source * @param $product * @return array|\stdClass * @throws \WC_Stripe_Exception */ public function create_intent($order, $order_source, $product) { // The request for a charge contains metadata for the intent. $full_request = $this->generate_payment_request($order, $order_source, $product); $request = [ 'amount' => \WC_Stripe_Helper::get_stripe_amount($product['price']), 'currency' => strtolower($order->get_currency()), 'description' => $full_request['description'], 'metadata' => $full_request['metadata'], 'statement_descriptor' => \WC_Stripe_Helper::clean_statement_descriptor($full_request['statement_descriptor']), 'capture_method' => (isset($full_request['capture']) && 'true' === $full_request['capture']) ? 'automatic' : 'manual', 'payment_method_types' => [ 'card', ], 'customer' => $order_source->customer, ]; $request = \WC_Stripe_Helper::add_payment_method_to_request_array( $order_source->source, $request ); if ($order_source->customer) { $request['customer'] = $order_source->customer; } if (!empty($full_request['statement_descriptor_suffix'])) { $request['statement_descriptor_suffix'] = $full_request['statement_descriptor_suffix']; } // Create an intent that awaits an action. $intent = \WC_Stripe_API::request( $request, 'payment_intents' ); if (!empty($intent->error)) { $intent_id = $order->get_meta('_stripe_intent_id'); if ( $intent_id ) { $intent = $this->get_intent( 'payment_intents', $intent_id ); if( !empty($intent->error) ){ return $intent; } } } $order_id = $order->get_id(); $step_id = filter_input(INPUT_POST, 'step_id', FILTER_VALIDATE_INT); // Save the intent ID to the order. update_post_meta($order_id, '_stripe_intent_id_' . $step_id, $intent->id); return $intent; } /** * Retrieves intent from Stripe API by intent id. * * @param string $intent_type Either 'payment_intents' or 'setup_intents'. * @param string $intent_id Intent id. * @return object|bool Either the intent object or `false`. * @throws Exception Throws exception for unknown $intent_type. */ public function get_intent( $intent_type, $intent_id ) { if ( ! in_array( $intent_type, [ 'payment_intents', 'setup_intents' ], true ) ) { throw new \Exception( "Failed to get intent of type $intent_type. Type is not allowed" ); } $response = \WC_Stripe_API::request( [], "$intent_type/$intent_id?expand[]=payment_method", 'GET' ); if ( $response && isset( $response->{ 'error' } ) ) { return false; } return $response; } /** * check if token is present in the order * * @param $order */ private function has_token( $order ) { if( false === is_a( $order, 'WC_Order' ) ){ return false; } $token = $order->get_meta('_wpfunnels_stripe_source_id'); if ( empty( $token ) ) { $token = $order->get_meta('_stripe_source_id'); } if ( ! empty( $token ) ) { return true; } return false; } /** * process the offer payment * * @param $order * @param $offer_product * @return bool * @throws \WC_Stripe_Exception */ public function process_payment( $order, $offer_product ) { $result = array( 'is_success' => false, 'message' => '' ); if ( ! $this->has_token( $order ) ) { return $result; } $gateway = $this->get_wc_gateway(); $order_source = $gateway->prepare_order_source( $order ); $response = \WC_Stripe_API::request( $this->generate_payment_request( $order, $order_source, $offer_product ) ); if ( ! is_wp_error( $response ) ) { if ( ! empty( $response->error ) ) { $result['message'] = $response->error->message; } else { $result['is_success'] = true; $this->update_stripe_payout_details( $order, $response ); $this->store_offer_transaction( $order, $response, $offer_product ); } } return $result; } /** * generate payment request post data * * @param $order * @param $order_source * @param $product * @return mixed|void */ public function generate_payment_request($order, $order_source, $product) { $settings = get_option( 'woocommerce_stripe_settings', [] ); $is_short_statement_descriptor_enabled = ! empty( $settings['is_short_statement_descriptor_enabled'] ) && 'yes' === $settings['is_short_statement_descriptor_enabled']; $capture = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false; $post_data = []; if ( \WC_Stripe_Helper::payment_method_allows_manual_capture( $order->get_payment_method() ) ) { $post_data['capture'] = $capture ? 'true' : 'false'; if ( $is_short_statement_descriptor_enabled ) { $post_data['statement_descriptor_suffix'] = WC_Stripe_Helper::get_dynamic_statement_descriptor_suffix( $order ); } } $post_data['currency'] = strtolower($order ? $order->get_currency() : get_woocommerce_currency()); $post_data['amount'] = \WC_Stripe_Helper::get_stripe_amount($product['price'], $post_data['currency']); /* translators: %1s site name */ $post_data['description'] = sprintf(__('%1$s - Order %2$s - One Time offer', 'wpfnl'), wp_specialchars_decode(get_bloginfo('name'), ENT_QUOTES), $order->get_order_number()); /* translators: %1s order number */ $post_data['statement_descriptor'] = apply_filters( 'wpfunnels/stripe_descriptor_text_modifiaction', sprintf(__('Order %1$s-OTO', 'wpfnl'), $order->get_order_number()), $order ); $billing_first_name = $order->get_billing_first_name(); $billing_last_name = $order->get_billing_last_name(); $billing_email = $order->get_billing_email(); if (!empty($billing_email) && apply_filters('wc_stripe_send_stripe_receipt', false)) { $post_data['receipt_email'] = $billing_email; } $metadata = [ __('customer_name', 'wpfnl') => sanitize_text_field($billing_first_name) . ' ' . sanitize_text_field($billing_last_name), __('customer_email', 'wpfnl') => sanitize_email($billing_email), 'order_id' => apply_filters( 'wpfunnels/stripe_descriptor_text_modifiaction', sprintf(__('Order %1$s-OTO', 'wpfnl'), $order->get_order_number()), $order ) . '_' . $product['id'], ]; $post_data['expand[]'] = 'balance_transaction'; $post_data['metadata'] = apply_filters('wc_stripe_payment_metadata', $metadata, $order, $order_source); if ($order_source->customer) { $post_data['customer'] = $order_source->customer; } if ($order_source->source) { $source_3ds = $order->get_meta('_wpfunnels_stripe_source_id', true); $post_data['source'] = ('' !== $source_3ds) ? $source_3ds : $order_source->source; } $post_data['source'] = $order_source->source; return $post_data; } /** * @param $order * @param $response */ public function update_stripe_payout_details($order, $response) { $fee = !empty($response->balance_transaction->fee) ? \WC_Stripe_Helper::format_balance_fee($response->balance_transaction, 'fee') : 0; $net = !empty($response->balance_transaction->net) ? \WC_Stripe_Helper::format_balance_fee($response->balance_transaction, 'net') : 0; $fee = $fee + \WC_Stripe_Helper::get_stripe_fee($order); $net = $net + \WC_Stripe_Helper::get_stripe_net($order); \WC_Stripe_Helper::update_stripe_fee($order, $fee); \WC_Stripe_Helper::update_stripe_net($order, $net); } /** * Updates Stripe fees/net. * e.g usage would be after a refund. * * @since 4.0.0 * @version 4.0.6 * @param object $order The order object * @param int $balance_transaction_id */ public static function update_stripe_fees( $order, $balance_transaction_id ) { $balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id ); if ( empty( $balance_transaction->error ) ) { if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) { // Fees and Net needs to both come from Stripe to be accurate as the returned // values are in the local currency of the Stripe account, not from WC. $fee_refund = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0; $net_refund = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0; // Current data fee & net. $fee_current = WC_Stripe_Helper::get_stripe_fee( $order ); $net_current = WC_Stripe_Helper::get_stripe_net( $order ); // Calculation. $fee = (float) $fee_current + (float) $fee_refund; $net = (float) $net_current + (float) $net_refund; WC_Stripe_Helper::update_stripe_fee( $order, $fee ); WC_Stripe_Helper::update_stripe_net( $order, $net ); $currency = ! empty( $balance_transaction->currency ) ? strtoupper( $balance_transaction->currency ) : null; WC_Stripe_Helper::update_stripe_currency( $order, $currency ); if ( is_callable( [ $order, 'save' ] ) ) { $order->save(); } } } } /** * @param $order * @param $response * @param $product */ public function store_offer_transaction( $order, $response, $product ) { $order->update_meta_data('_wpfunnels_offer_txn_resp_' . $product['step_id'], $response->id); $order->save(); } /** * create child order reference with the parent order * * @param $parent_order * @param $product_data * @param string $type * @return bool|\WC_Order|\WP_Error * @throws \WC_Data_Exception */ public function create_child_order( $parent_order, $product_data, $type = 'upsell' ) { $order = false; if (!empty($parent_order)) { $parent_order_id = $parent_order->get_id(); $parent_order_billing = $parent_order->get_address('billing'); $funnel_id = $parent_order->get_meta('_wpfunnels_funnel_id'); if (!empty($parent_order_billing['email'])) { $customer_id = $parent_order->get_customer_id(); $order = wc_create_order( [ 'customer_id' => $customer_id, 'status' => 'wc-pending', 'parent' => $parent_order_id, ] ); /* Set Order type */ $order->update_meta_data('_wpfunnels_offer', 'yes' ); $order->update_meta_data('_wpfunnels_offer_type', $type ); $order->update_meta_data('_wpfunnels_parent_funnel_id', $funnel_id); $order->update_meta_data('_wpfunnels_offer_step_id', $product_data['step_id'] ); $order->update_meta_data('_wpfunnels_offer_parent_id', $parent_order_id ); $item_id = $order->add_product(wc_get_product($product_data['id']), $product_data['qty'], $product_data['args']); if( $item_id ){ wc_add_order_item_meta( $item_id, "_wpfunnels_{$type}", 'yes' ); wc_add_order_item_meta( $item_id, '_wpfunnels_step_id', $product_data['step_id']); } $order->set_address($parent_order->get_address('billing'), 'billing'); $order->set_address($parent_order->get_address('shipping'), 'shipping'); // Set shipping data. $order->set_payment_method($parent_order->get_payment_method()); $order->set_payment_method_title($parent_order->get_payment_method_title()); if (!wc_tax_enabled()) { // Reports won't track orders fix. $order->set_shipping_tax(0); $order->set_cart_tax(0); } $order->calculate_totals(); $offer_orders_meta = $parent_order->get_meta('_wpfunnels_offer_child_orders'); if (!is_array($offer_orders_meta)) { $offer_orders_meta = []; } $offer_orders_meta[$order->get_id()] = ['type' => $type]; $parent_order->update_meta_data('_wpfunnels_offer_child_orders', $offer_orders_meta); // Save the order. $parent_order->save(); } } if ($order) { $transaction_id = $parent_order->get_transaction_id(); $this->payment_complete($order, $transaction_id); $order->set_transaction_id($transaction_id); $order->save(); $transaction_id_note = ''; if (!empty($transaction_id)) { $transaction_id_note = sprintf(' (Transaction ID: %s)', $transaction_id); } $order->add_order_note('Offer Accepted | ' . $type . ' | Step ID - ' . $product_data['step_id'] . ' | ' . $transaction_id_note); return $order; } return false; } /** * payment_complete * Complete the payment * * @param WC_Order $order Parent order detail * @param String $transaction_id Transaction id. * */ public function payment_complete($order, $transaction_id = '') { $payment_method = $order->get_payment_method(); if ('cod' === $payment_method) { $order->set_status('processing'); wc_reduce_stock_levels($order); } elseif ('bacs' === $payment_method) { $order->set_status('on-hold'); wc_reduce_stock_levels($order); } else { $order->payment_complete($transaction_id); } } /** * Get WooCommerce payment geteways. * * @return array */ public function get_wc_gateway() { global $woocommerce; $gateways = $woocommerce->payment_gateways->payment_gateways(); return $gateways[ $this->key ]; } /** * process refund offer * * @param $order * @param $data * @return bool * @throws \WC_Stripe_Exception */ public function process_refund_offer( $order, $data ) { $transaction_id = $data['transaction_id']; $amount = $data['amount']; $currency = $order->get_currency( $order ); $request = array(); $response_id = false; if ( ! is_null( $amount ) && class_exists( 'WC_Stripe_Helper' ) ) { $request['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $currency ); } if ( ! is_null( $amount ) && class_exists( 'WC_Stripe_API' ) ) { $request['charge'] = $transaction_id; $response = WC_Stripe_API::request( $request, 'refunds' ); if ( ! empty( $response->error ) || ! $response ) { $response_id = false; } else { $this->get_wc_gateway()->update_fees( $order, $response->balance_transaction ); $response_id = isset( $response->id ) ? $response->id : true; } } return $response_id; } /** * add required meta for refund * * @param $parent_order * @param $child_order * @param $transaction_id */ public function add_capture_meta_to_child_order( $parent_order, $child_order, $transaction_id ) { $child_order->update_meta_data('_stripe_charge_captured', 'yes' ); } /** * add subscription offer meta to order * * @param $subscription * @param $offer_product * @param $order */ public function add_offer_subscription_meta( $subscription, $offer_product, $order ) { if ( 'stripe' === $order->get_payment_method() ) { $subscription_id = $subscription->get_id(); update_post_meta( $subscription_id, '_stripe_source_id', $order->get_meta( '_stripe_source_id', true ) ); update_post_meta( $subscription_id, '_stripe_customer_id', $order->get_meta( '_stripe_customer_id', true ) ); } } /** * Add hidden field for stripe * * @return void * @since 2.2.3 */ public function add_stripe_hidden_field(){ // Get funnel checkout ID from post data. $checkout_id = Wpfnl_functions::get_checkout_id_from_post($_POST); // Get checkout id if not found in post data. $checkout_id = !$checkout_id ? get_the_ID() : $checkout_id; // Get funnel ID from checkout ID. $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $checkout_id ); // Check if checkout ID and funnel ID exists. if ( $checkout_id && $funnel_id ) { // Check if offer exists in funnel. if ( Wpfnl_Pro_functions::is_offer_exists_in_funnel($funnel_id) ) { // Add hidden field for stripe. echo ''; } } } } public/gateways/class-wpfnl-pro-gateway-woocommerce-payments.php000064400000061131147600245720021231 0ustar00key, array( $this, 'add_required_meta_to_child_order' ), 10, 3 ); add_action( 'wpfunnels/subscription_created', array( $this, 'add_subscription_payment_meta' ), 10, 3 ); $this->prepare_objects(); } /** * Prepare the payment gateway object to use it. * * @since 1.9.0 * * @return void */ public function prepare_objects() { $api_client = ''; $api_account_service = ''; $database_cache = ''; $customer_services = ''; $token_service = ''; if ( class_exists( 'WC_Payments' ) && class_exists( 'WC_Payments_Customer_Service' ) && class_exists( 'WC_Payments_Token_Service' ) ) { $api_client = \WC_Payments::get_payments_api_client(); $api_account_service = \WC_Payments::get_account_service(); $database_cache = class_exists( 'WCPay\Database_Cache' ) ? \WC_Payments::get_database_cache() : null; /** * @modified_date 07-11-2023 */ if ( version_compare( '6.7.0', WCPAY_VERSION_NUMBER, '<=' ) && version_compare( '7.6.0', WCPAY_VERSION_NUMBER, '>=' ) ) { $order_service = new \WC_Payments_Order_Service( $api_client ); $session_service = new \WC_Payments_Session_Service( $api_client ); $customer_services = new \WC_Payments_Customer_Service( $api_client, $api_account_service, $database_cache, $session_service ); } elseif ( version_compare( '7.7.0', WCPAY_VERSION_NUMBER, '<=' ) ) { $order_service = new \WC_Payments_Order_Service( $api_client ); $session_service = new \WC_Payments_Session_Service( $api_client ); $customer_services = new \WC_Payments_Customer_Service( $api_client, $api_account_service, $database_cache, $session_service, $order_service ); } else { $customer_services = new \WC_Payments_Customer_Service( $api_client, $api_account_service, $database_cache ); } $token_service = new \WC_Payments_Token_Service( $api_client, $customer_services ); } $this->gateway_obj = array( 'api_client' => $api_client ? $api_client : '', 'api_account_service' => $api_account_service ? $api_account_service : '', 'database_cache' => $database_cache ? $database_cache : '', 'customer_services' => $customer_services ? $customer_services : '', 'token_service' => $token_service ? $token_service : '', ); } /** * Create intent for the offer order. * Processes a payment intent by verifying the security nonce and handling the payment for an order. * * @since 1.9.0 * @return void */ public function create_payment_intent() { $security = filter_input( INPUT_POST, 'security', FILTER_SANITIZE_STRING ); if ( ! wp_verify_nonce( $security, 'wpfunnels_woop_create_payment_intent' ) ) { return; } $variation_id = ''; $input_qty = ''; if ( isset( $_POST['variation_id'] ) ) { $variation_id = intval( $_POST['variation_id'] ); } if ( isset( $_POST['input_qty'] ) && ! empty( $_POST['input_qty'] ) ) { $input_qty = intval( $_POST['input_qty'] ); } $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $order_id = isset( $_POST['order_id'] ) ? intval($_POST['order_id']) : 0; $offer_type = isset( $_POST['offer_type'] ) ? sanitize_text_field( wp_unslash( $_POST['offer_type'] ) ) : ''; $offer_action = isset( $_POST['offer_action'] ) ? sanitize_text_field( wp_unslash( $_POST['offer_action'] ) ) : ''; $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : ''; $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : ''; $order = wc_get_order( $order_id ); $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, $product_id, 0, $order_id ); global $woocommerce; if ( isset( $offer_product['price'] ) && ( floatval( 0 ) === floatval( $offer_product['price'] ) || '' === trim( $offer_product['price'] ) ) ) { wp_send_json( array( 'result' => 'fail', 'message' => 'Zero value product', ) ); } else { $gateways = $woocommerce->payment_gateways->payment_gateways(); $gateway = $gateways['woocommerce_payments']; if ( $gateway ) { try { $token = $this->get_token_from_order( $order ); $payment_information = new Payment_Information( '', $order, Payment_Type::SINGLE(), $token, Payment_Initiated_By::CUSTOMER() ); $response = $this->process_payment_against_intent( $order, $offer_product, $payment_information ); } catch ( Exception $e ) { wp_send_json( array( 'result' => 'fail', 'message' => 'Payment Failed', ) ); } // Process the response for the order. if ( ! empty( $response ) ) { $intent_id = $response->get_id() ? $response->get_id() : 0; $status = $response->get_status(); $charge = $response->get_charge(); $charge_id = ! empty( $charge ) ? $charge->get_id() : null; $client_secret = $response->get_client_secret(); $payload = array( 'step_id' => $step_id, 'order_id' => $order_id, 'intent_id' => $intent_id, 'charge_id' => $charge_id, ); $this->save_order_meta( $order, $payment_information, $payload ); wp_send_json( array( 'result' => 'success', 'status' => $status, 'client_secret' => $client_secret, 'client_intend' => $intent_id, ) ); } } wp_send_json( array( 'result' => 'fail', 'message' => __( 'No payment. No gateway found', 'wpfnl-pro' ), ) ); } } /** * Save order meta * * @param object $order Order object. * @param object $payment_information Object of payment information with all data. * @param array $payload Any extra data that may require. * * @since 1.9.0 * @return void */ public function save_order_meta( $order, $payment_information, $payload ) { $payment_method = $payment_information->get_payment_method(); $order_id = $order->get_id(); $step_id = $payload['step_id']; $order->update_meta_data( '_wpfnl_woop_offer_stripe_intent_id_' . $step_id, $payload['intent_id'] ); $order->update_meta_data( '_wpfnl_woop_offer_charge_id_' . $step_id . '_' . $order_id, $payload['charge_id'] ); $order->update_meta_data( '_wpfnl_woop_offer_payment_method_id', $payment_method ); $order->update_meta_data( '_wpfnl_woop_offer_wcpay_mode', \WC_Payments::get_gateway()->is_in_test_mode() ? 'test' : 'prod' ); $order->save(); } /** * Get the token from the main order. * * @param object $order Object of current order. * @param bool $has_token Separator used to log the logs. * * @since 1.9.0 * @return string $token order token. */ public function get_token_from_order( $order, $has_token = false ) { $order_tokens = $order->get_payment_tokens(); $token_id = end( $order_tokens ); $token = $token_id ? \WC_Payment_Tokens::get( $token_id ) : null; return $token; } /** * If required then tokenize to save source of payment * * @param bool $save_source force save source. * * @since 1.9.0 * @return bool */ public function should_tokenize_stripe( $save_source ){ global $post; if( !$post ){ return $save_source; } if ( wp_doing_ajax() || isset( $_GET['wc-ajax'] ) ) { $checkout_id = Wpfnl_functions::get_checkout_id_from_post($_POST); } else { $checkout_id = $post->ID; } if( !$checkout_id ){ return $save_source; } $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $checkout_id ); if( !$funnel_id ){ return $save_source; } $steps = get_post_meta( $funnel_id, '_steps_order', true ); $offer_steps = ['upsell', 'downsell']; if( is_array($steps) ){ $step_types = wp_list_pluck($steps, 'step_type'); $intersect = array_intersect($offer_steps, $step_types); if( !empty($intersect) ) { $save_source = false; } } return $save_source; } /** * Save token for 3ds. * * @param int $order_id order ID. * * @since 1.9.0 * @return void */ public function save_token_for_3ds( $order ) { if ( $order && $this->key === $order->get_payment_method() ) { try { $payment_method_id = $order->get_meta( '_payment_method_id' ); $token = $this->gateway_obj['token_service']->add_payment_method_to_user( $payment_method_id, wp_get_current_user() ); $this->get_wc_gateway()->add_token_to_order( $order, $token ); $order->update_meta_data( '_wpfnl_' . $this->key . '_source_id', $payment_method_id ); $order->update_meta_data( '_wpfnl_' . $this->key . '_token', $token ); $order->read_meta_data( true ); } catch ( Exception $e ) { error_log(print_r($e->getMessage(),1)); } } } /** * Check if token is present. * * @param object $order order data. */ public function maybe_token( $order ) { $order_id = $order->get_id(); $token = $this->get_token_from_order( $order, true ); if ( empty( $token ) ) { $source_id = $order->get_meta( '_wpfnl_' . $this->key . '_source_id' ); $token = $order->get_meta( '_wpfnl_' . $this->key . '_token' ); } if ( ! empty( $token ) ) { return true; } return false; } /** * Get Woocommerce payment geteways. * * @return object */ public function get_wc_gateway() { global $woocommerce; $gateways = $woocommerce->payment_gateways->payment_gateways(); return $gateways[ $this->key ]; } /** * Process payment. * * @param object $order order data. * @param array $product product data. * * * @since 1.9.0 * * @return array */ public function process_payment( $order, $product ) { $result = array( 'is_success' => false, 'message' => '' ); if ( ! $this->maybe_token( $order ) ) { return $result; } if ( isset( $_POST['woop_intent_id'] ) ) { $stored_intent_id = $order->get_meta( '_wpfnl_woop_offer_stripe_intent_id_' . $product['step_id'] ); $payment_method = $order->get_meta( '_wpfnl_' . $this->key . '_source_id' ); $intent_id = sanitize_text_field( wp_unslash( $_POST['woop_intent_id'] ) ); $confirm_intent = ( $intent_id === $stored_intent_id ) ? true : false; if ( $confirm_intent ) { $result['is_success'] = true; $data = array( 'id' => $intent_id, 'payment_method' => $payment_method, 'customer' => $order->get_meta( '_stripe_customer_id', true ), ); $this->save_offer_transaction_meta( $order, $data, $product ); } } return $result; } /** * Save offer transaction meta to order * * @param WC_Order $order The order that is being paid for. * @param Object $response The response that is send from the payment gateway. * @param array $product The product data. * * @since 1.9.0 * @return void */ public function save_offer_transaction_meta( $order, $response, $product ) { $order->update_meta_data( '_wpfunnels_offer_txn_resp_' . $product['step_id'], $response['id'] ); $order->update_meta_data( '_wpfnl_offer_txn_stripe_source_id_' . $product['step_id'], $response['payment_method'] ); $order->update_meta_data( '_wpfnl_offer_txn_stripe_customer_id_' . $product['step_id'], $response['customer'] ); $order->save(); } /** * Create a new PaymentIntent against the new order. * * @param object $order The order that is being paid for. * @param array $offer_product Offer product. * @param object $payment_information The source that is used for the payment. * * @since 1.9.0 * @return mix object|bool An intent or false. */ public function process_payment_against_intent( $order, $offer_product, $payment_information ) { if( !$order && !isset($offer_product['step_id'], $offer_product['id'], $offer_product['price'] ) ){ return false; } $f_name = $order->get_billing_first_name(); $l_name = $order->get_billing_last_name(); $email = $order->get_billing_email(); $customer_id = $order->get_meta( '_stripe_customer_id' ); $offer_amount = \WC_Payments_Utils::prepare_amount( floatval($offer_product['price']), $order->get_currency() ); $step_id = $offer_product['step_id']; $product = wc_get_product( $offer_product['id'] ); $payment_source = \WC_Payments::get_gateway()->get_payment_method_ids_enabled_at_checkout( null, true ); $metadata = array( 'customer_name' => sanitize_text_field( $f_name ) . ' ' . sanitize_text_field( $l_name ), 'customer_email' => sanitize_email( $email ), 'site_url' => esc_url( get_site_url() ), 'payment_type' => $payment_information->get_payment_type(), 'order_number' => $order->get_order_number() . '_' . $offer_product['id'] . '_' . $step_id, 'statement_descriptor' => apply_filters( 'wpfunnels/' . $this->key . '_offer_statement_descriptor', substr( trim( sprintf( __( 'Order %1$s-OTO', 'wpfnl-pro' ), $order->get_order_number() ) ), 0, 22 ) ), ); /** * Use this in case to add the backward compatibility of the payment gateway. * We don't need to add the backward compatibility as we are adding the support from scratch newly. */ if ( version_compare( '5.8.0', WCPAY_VERSION_NUMBER, '<=' ) ) { $amount_array = [ 'shipping_price' => \WC_Payments_Utils::prepare_amount( (float) $offer_product['shipping_fee_tax'], $order->get_currency() ), 'unit_price' => \WC_Payments_Utils::prepare_amount( (float) $offer_product['unit_price'], $order->get_currency() ), 'discount_price' => \WC_Payments_Utils::prepare_amount( $offer_product['original_price'] - $offer_product['price'], $order->get_currency() ), ]; $request = \WCPay\Core\Server\Request\Create_And_Confirm_Intention::create(); $request->set_amount( $offer_amount ); $request->set_currency_code( strtolower( $order->get_currency() ) ); $request->set_payment_method( $payment_information->get_payment_method() ); $request->set_customer( $customer_id ); $request->set_capture_method( $payment_information->is_using_manual_capture() ); $request->set_metadata( $metadata ); $request->set_level3( $this->get_level3_data_for_offer_product( $order, $offer_product, $amount_array ) ); $request->set_off_session( $payment_information->is_merchant_initiated() ); $request->set_payment_methods( $payment_source ); $request->set_cvc_confirmation( $payment_information->get_cvc_confirmation() ); $request->set_fingerprint( $payment_information->get_fingerprint() ); if( $product ){ $is_subscription_product = $product->is_type( 'subscription' ) || $product->is_type( 'variable-subscription' ) || $product->is_type( 'subscription_variation' ); if ( $is_subscription_product ) { $mandate = $order->get_meta( '_stripe_mandate_id', true ); if ( $mandate ) { $request->set_mandate( $mandate ); } } } /** * @modified_date 07-11-2023 */ if ( version_compare( '6.6.0', WCPAY_VERSION_NUMBER, '<=' ) ) { $request->assign_hook( 'wcpay_create_and_confirm_intent_request_api' ); $intent = $request->send(); }else{ $intent = $request->send( 'wcpay_create_and_confirm_intent_request', $payment_information ); } }else if ( version_compare( '3.9.0', WCPAY_VERSION_NUMBER, '<=' ) && version_compare( '5.8.0', WCPAY_VERSION_NUMBER, '>' ) ) { $save_payment_method = $payment_information->should_save_payment_method_to_store(); $intent = $this->gateway_obj['api_client']->create_and_confirm_intention( $offer_amount, strtolower( $order->get_currency() ), $payment_source, $customer_id, $payment_information->is_using_manual_capture(), $payment_information->should_save_payment_method_to_store(), $payment_information->should_save_payment_method_to_platform(), $metadata ); } else { // $save_payment_method = $payment_information->should_save_payment_method(); $intent = $this->gateway_obj['api_client']->create_and_confirm_intention( $offer_amount, strtolower( $order->get_currency() ), $payment_source, $customer_id, $payment_information->is_using_manual_capture(), $payment_information->should_save_payment_method(), $metadata, array(), $payment_information->is_merchant_initiated() ); } return $intent; } /** * Prepare offer data to sent in the payment request. * * @param object $order Current Order Object. * @param array $offer_product Selected offer Product Data. * * @since 1.9.0 * @return array $level3_data modified data for the payment request. */ public function get_level3_data_for_offer_product( $order, $offer_product, $amount_array ) { $level3_data = []; if( $order && isset($offer_product['shipping_fee_tax'], $offer_product['name'], $offer_product['desc'] , $offer_product['unit_price'], $offer_product['unit_price_tax'], $offer_product['qty'], $offer_product['original_price'], $offer_product['price'] ) ){ $level3_data = array( 'merchant_reference' => (string) $order->get_id(), 'customer_reference' => (string) $order->get_id(), 'shipping_amount' => isset($amount_array['shipping_amount']) ? $amount_array['shipping_amount'] : '', 'line_items' => (object) array( 'product_code' => (string) substr( $offer_product['name'], 0, 12 ), 'product_description' => substr( $offer_product['desc'], 0, 26 ), 'unit_cost' => isset($amount_array['unit_price']) ? $amount_array['unit_price'] : '', 'quantity' => $offer_product['qty'], 'tax_amount' => ! empty( $offer_product['unit_price_tax'] ) ? ( $offer_product['unit_price_tax'] - $offer_product['unit_price'] ) : 0, 'discount_amount' => isset($amount_array['discount_price']) ? $amount_array['discount_price'] : '', ), ); } return $level3_data; } /** * Process offer refund * * @param object $order Order Object. * @param array $offer_data offer data. * * @since 1.9.0 * @return string|bool. */ public function process_offer_refund( $order, $offer_data ) { $transaction_id = $offer_data['transaction_id']; $refund_amount = $offer_data['refund_amount']; $order_currency = $order->get_currency( $order ); $response_id = false; if ( ! is_null( $refund_amount ) && ! empty( $transaction_id ) ) { $intent = $this->gateway_obj['api_client']->get_intent( $transaction_id ); $charge_id = $intent->get_charge()->get_id(); $response = $this->gateway_obj['api_client']->refund_charge( $charge_id, \WC_Payments_Utils::prepare_amount( $refund_amount, $order_currency ) ); if ( ! empty( $response->error ) || ! $response ) { $response_id = false; } else { $response_id = isset( $response->id ) ? $response->id : true; } } return $response_id; } /** * Allow gateways to declare whether they support offer refund * * @since 1.9.0 * @return bool */ public function is_api_refund() { return apply_filters( 'wpfunnels/enable_wc_payment_refund', $this->is_api_refund ) ; } /** * Setup the Payment data for Stripe's Automatic Subscription. * * @param WC_Subscription $subscription An instance of a subscription object. * @param object $order Object of order. * @param array $offer_product array of offer product. * * @since 1.9.0 * @return void */ public function add_subscription_payment_meta( $subscription, $offer_product, $order ) { if ( $subscription && $order && $this->key === $order->get_payment_method() ) { $txn_id = $order->get_meta( '_wpfunnels_offer_txn_resp_' . $offer_product['step_id'], true ); $intent_id = $order->get_meta( '_wpfnl_woop_offer_stripe_intent_id_' . $offer_product['step_id'], true ); $subscription->update_meta_data( '_payment_method_id', $txn_id ); $subscription->update_meta_data( '_payment_tokens', $order->get_payment_tokens() ); $subscription->update_meta_data( '_stripe_customer_id', $order->get_meta( '_stripe_customer_id', true ) ); $subscription->update_meta_data( '_charge_id', $order->get_meta( '_wpfnl_woop_offer_charge_id_' . $offer_product['step_id'] . '_' . $order->get_id(), true ) ); $subscription->update_meta_data( '_intent_id', $intent_id ); $subscription->save_meta_data(); $subscription->save(); } } /** * Save the parent payment meta to child order. * * @param object $parent_order Object of order. * @param object $child_order Object of order. * @param int $transaction_id transaction id. * * @since 1.9.0 * @return void */ public function add_required_meta_to_child_order( $parent_order, $child_order, $transaction_id ) { $offer_intent_id = array( 'id' => $transaction_id, ); // Get order/intent/payment data from the parent order object. $step_id = $child_order->get_meta( '_wpfunnels_offer_step_id', true ); $intent_id = $parent_order->get_meta( '_wpfnl_woop_offer_stripe_intent_id_' . $step_id, true ); $charge_id = $parent_order->get_meta( '_wpfnl_woop_offer_charge_id_' . $step_id . '_' . $parent_order->get_id(), true ); $payment_method_id = $parent_order->get_meta( '_wpfnl_woop_offer_payment_method_id', true ); $payment_mode = $parent_order->get_meta( '_wpfnl_woop_offer_wcpay_mode', true ); $customer_id = $parent_order->get_meta( '_wpfnl_offer_txn_stripe_customer_id_' . $step_id ); $intent = $this->gateway_obj['api_client']->get_intent( $intent_id ); $intent_status = $intent->get_status(); $child_order->update_meta_data( '_wpfnl_woop_offer_stripe_intent_id_' . $step_id, $offer_intent_id ); $child_order->set_transaction_id( $intent_id ); $child_order->update_meta_data( '_intent_id', $intent_id ); $child_order->update_meta_data( '_charge_id', $charge_id ); $child_order->update_meta_data( '_intention_status', $intent_status ); $child_order->update_meta_data( '_payment_method_id', $payment_method_id ); $child_order->update_meta_data( '_stripe_customer_id', $customer_id ); $child_order->save(); } } if( Wpfnl_Pro_functions::is_wc_payment_active() ){ Wpfnl_Pro_Woocommerce_Payments::get_instance(); } public/gateways/class-wpfnl-pro-gateway.php000064400000006425147600245720015063 0ustar00key; } /** * This function checks for the need to do the tokenization. * We have to fetch the funnel to decide whether to tokenize the user or not. * @return int|false funnel ID on success false otherwise * */ public function should_tokenize($funnel_id) { return Wpfnl_functions::is_funnel_exists($funnel_id); } /** * Get WooCommerce payment geteways. * * @return array */ public function get_wc_gateway() { global $woocommerce; $gateways = $woocommerce->payment_gateways->payment_gateways(); return $gateways[ $this->key ]; } /** * Round a float number * * @param float $number * @param int $precision Optional. The number of decimal digits to round to. * * @since 1.0.0 * */ public function round( $number, $precision = 2 ) { return round( (float) $number, $precision ); } /** * Helper method to return the item description, which is composed of item * meta flattened into a comma-separated string, if available. Otherwise the * product SKU is included. * * The description is automatically truncated to the 127 char limit. * * @param array $item cart or order item * @param \WC_Product $product product data * * @return string * @since 2.0 */ public function get_item_description( $product ) { return Wpfnl_Pro_functions::get_item_description($product); } /** * Format prices. * * @param float|int $price * @param int $decimals Optional. The number of decimal points. * * @return string * @since 2.2.12 * */ public function price_format( $price, $decimals = 2 ) { return number_format( $price, $decimals, '.', '' ); } /** * get order number * * @param \WC_Order $order * @return int|mixed|void * * @since 1.0.0 */ public function get_order_number( \WC_Order $order, $step_id ) { if ( ! empty( $step_id ) ) { return apply_filters( 'wpfunnels/payments_get_order_number', $order->get_id() . '_' . $step_id, $this ); } else { return $order->get_id(); } } /** * @param $response * @param $key * @return mixed */ public function get_value_from_response($response, $key) { if ($response && isset($response[$key])) { return $response[$key]; } } /** * check if payment gateway is enabled * * @param \WC_Order $order * * @return bool * @since 1.0.0 */ public function is_enabled( $order = false ) { global $woocommerce; $gateways = $woocommerce->payment_gateways->payment_gateways(); if ( is_array( $gateways ) && in_array( $this->key, $gateways, true ) ) { return true; } return false; } public function to_string() { return http_build_query($this->get_parameters(), '', '&'); } }public/modules/analytics/class-wpfnl-pro-analytics.bak.php.bak000064400000031770147600245720020515 0ustar00ID; $this->save_visit_info( $funnel_id, $step_id); if ( ! $funnel_id ) { return; } } } /** * Save Visitor information data, after getting specific specific data from template direct. * * @param $funnel_id * @param $step_id * @since 1.0.0 */ private function save_visit_info( $funnel_id, $step_id ) { $step_type = $this->get_post_meta_value($step_id, '_step_type'); $user_type = $this->get_user_type($step_type); $user_info = $this->get_user_info(); $user_status = $this->get_user_status($step_type); $check_user = $this->check_user_by_step($step_id, $user_info); $analytics_data = $this->process_analytics_data($funnel_id, $step_id, $user_type, $user_status, $user_info); $meta_data = $this->process_analytics_meta_data($step_type); if($check_user == false) { if($step_type == 'landing') { $set_user_ip = $this->set_user_cookie ('user_ip', $user_info['user_ip'], 43200); $set_referrer_url = $this->set_user_cookie ('referer_url', $user_info['referer_url'], 43200); } else { $user_info_data = $this->get_user_info_data(); $result = $this->insert_analytics_data( $funnel_id, $step_id, $user_type, $user_status, $user_info_data, $analytics_data); $meta_result = $this->insert_analytics_meta_data( $funnel_id, $step_id, $meta_data); } } else { $result = $this->update_analytics_data($step_id, $user_type, $user_status, $user_info, $analytics_data); } } /** * Save user activity event data in meta table * * @param $step_id * @param $offer_product array * @since 1.0.0 */ public function save_offer_data( $step_id, $offer_product ) { $meta_data = array(); foreach ($offer_product as $key => $value) { $meta_data[$key] = $value; } $this->insert_analytics_meta_data($offer_product['funnel_id'], $step_id, $meta_data); } /** * Save activity data. * * @param $analytics_id * @param $funnel_id * @param $step_id * @param $meta_data array * @since 1.0.0 */ public function insert_analytics_meta_data($funnel_id, $step_id, $meta_data) { global $wpdb; $table_name = $wpdb->prefix . "wpfnl_analytics_meta"; $analytics_id = $this->get_analytics_id(); if(!empty($meta_data)) { foreach ($meta_data as $key => $value) { $wpdb->insert($table_name, array( 'analytics_id' => $analytics_id, 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'meta_key' => $key, 'meta_value' => $value )); } } return true; } /** * Save activity data. * * @param $funnel_id * @param $step_id * @param $user_type * @param $user_status * @param $user_info array * @param $analytics_data array * @return ID|int|mixed * @since 1.0.0 */ public function insert_analytics_data($funnel_id, $step_id, $user_type, $user_status, $user_info, $analytics_data) { global $wpdb; $table_name = $wpdb->prefix . "wpfnl_analytics"; $wpdb->insert($table_name, array( 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'user_id' => $user_info['user_id'], 'user_type' => $user_type, 'visitor_type' => $user_info['visitor_type'], 'user_ip' => $user_info['user_ip'], 'referrer_url' => $user_info['referrer_url'], 'analytics_data' => json_encode($analytics_data), 'date_created' => current_time( 'timestamp' ), 'date_created_gmt' => current_time( 'timestamp', 1 ), 'date_modified' => current_time( 'timestamp' ), 'date_modified_gmt' => current_time( 'timestamp', 1 ), 'status' => $user_status )); $id = $wpdb->insert_id; $this->set_user_cookie ('analytics_id', $id, 1800); return $id; } /** * Update activity data. * * @param $step_id * @param $user_type * @param $user_status * @param $user_info array * @param $analytics_data array * @since 1.0.0 */ public function update_analytics_data($step_id, $user_type, $user_status, $user_info, $analytics_data) { global $wpdb; $table_name = $wpdb->prefix . "wpfnl_analytics"; $wpdb->update($table_name, array( 'user_id' => $user_info['user_id'], 'user_type' => $user_type, 'visitor_type' => $user_info['visitor_type'], 'user_ip' => $user_info['user_ip'], 'referrer_url' => $user_info['referrer_url'], 'analytics_data' => json_encode($analytics_data), 'date_modified' => current_time( 'timestamp' ), 'date_modified_gmt' => current_time( 'timestamp', 1 ), 'status' => $user_status ), array( 'step_id' => $step_id ) ); } /** * Process activity data for api. * * @param $funnel_id * @param $step_id * @param $user_type * @param $user_status * @param $user_info array * @return array * @since 1.0.0 */ public function process_analytics_data($funnel_id, $step_id, $user_type, $user_status, $user_info) { $analytics_data = [ "v" => 1, "visitor" => [ "id" => $user_info['user_id'], "type" => $user_info['visitor_type'], "user_type" => $user_type, "ip" => $user_info['user_ip'], "referrer" => $user_info['referer_url'], "status" => $user_status, ], "funnel" => [ "id" => $funnel_id, "meta" => [], ], "steps" => [], ]; return $analytics_data; } /** * Process activity event data for api. * * @param $step_type * @return array * @since 1.0.0 */ public function process_analytics_meta_data($step_type) { $meta_data = array(); if($step_type != 'landing') { $meta_data['conversion'] = 'yes'; } $meta_data['step_type'] = $step_type; return $meta_data; } /** * Check user exist in current step * * @param $step_id * @param $user_info * @return boolean * @since 1.0.0 */ public function check_user_by_step($step_id, $user_info) { $step_id_exist = $this->get_user_cookie ('step_id', 1800); $user_ip_exist = $this->get_user_cookie ('user_ip', 432000); if($step_id_exist == false || $user_ip_exist == false ) { return false; } elseif( $step_id_exist == $step_id && $user_ip_exist == $user_info['user_ip']) { return true; } else { return false; } } /** * Set user cookie for user * * @param $key * @param $value * @param $time * @return boolean * @since 1.0.0 */ public function set_user_cookie ($key, $value, $time) { setcookie( $key, $value , time() + $time, COOKIEPATH, COOKIE_DOMAIN ); $value = isset( $_COOKIE[$key] ) ? $_COOKIE[$key] : ''; if($value == '') { return false; } return true; } /** * Get user cookie * * @param $key * @param $time * @return $value|boolean|mixed * @since 1.0.0 */ public function get_user_cookie ($key, $time) { $value = isset( $_COOKIE[$key] ) ? $_COOKIE[$key] : ''; if($value != '') { setcookie( $key, '', time() - $time, COOKIEPATH, COOKIE_DOMAIN ); setcookie( $key, $value , time() + $time, COOKIEPATH, COOKIE_DOMAIN ); return $value; } return false; } /** * Get Analytics ID from user cookie * * @param null * @return ID|int|mixed * @since 1.0.0 */ public function get_analytics_id() { $analytics_id = isset( $_COOKIE['analytics_id'] ) ? $_COOKIE['analytics_id'] : ''; if($analytics_id != '') { setcookie( 'analytics_id', '', time() - 1800, COOKIEPATH, COOKIE_DOMAIN ); setcookie( 'analytics_id', $analytics_id , time() + 1800, COOKIEPATH, COOKIE_DOMAIN ); $id = $analytics_id; } else { $id = 0; } return $id; } /** * Get User information from cookie and system * * @param null * @return array * @since 1.0.0 */ public function get_user_info_data() { $user_id = get_current_user_id(); $user_ip = $this->get_user_cookie ('user_ip', 43200); $referer_url = $this->get_user_cookie ('referer_url', 43200); $visitor_type = $this->get_visitor_type($user_ip); $user_info = [ "user_id" => $user_id, "referer_url" => $referer_url, "user_ip" => $user_ip, "visitor_type" => $visitor_type, ]; return $user_info; } /** * Get User information from browser * * @param null * @return array * @since 1.0.0 */ public function get_user_info() { $user_id = get_current_user_id(); $referer_url = ''; $user_ip = $this->get_user_ip(); $referer = wp_get_referer(); $site_url = site_url(); if ( strpos($referer , $site_url) === false ) { $referer_url = $referer; } $visitor_type = $this->get_visitor_type($user_ip); $user_info = [ "user_id" => $user_id, "referer_url" => $referer_url, "user_ip" => $user_ip, "visitor_type" => $visitor_type, ]; return $user_info; } /** * Get User IP Address * * @param null * @return string * @since 1.0.0 */ public function get_user_ip() { if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"]; $_SERVER['HTTP_CLIENT_IP'] = $_SERVER["HTTP_CF_CONNECTING_IP"]; } $client = @$_SERVER['HTTP_CLIENT_IP']; $forward = @$_SERVER['HTTP_X_FORWARDED_FOR']; $remote = $_SERVER['REMOTE_ADDR']; if(filter_var($client, FILTER_VALIDATE_IP)) { $ip = $client; } elseif(filter_var($forward, FILTER_VALIDATE_IP)) { $ip = $forward; } else { $ip = $remote; } return $ip; } /** * Get post meta value for postmeta table * * @param null * @return string * @since 1.0.0 */ public function get_post_meta_value($id, $key) { $value = get_post_meta($id, $key, true); return $value; } /** * Get User Type * * @param $step_type * @param $step_order * @return string * @since 1.0.0 */ public function get_user_type($step_type, $step_order = false) { $user_type_fixed = 'visitor'; $user_id = get_current_user_id(); if($user_id != 0) { if($step_type != 'landing') { $user_type = 'optin'; } elseif($step_type == 'thankyou') { $user_type = 'purchase'; } else { $user_type = $user_type_fixed; } } else { if($step_type == 'thankyou') { $user_type = 'guest'; } else { $user_type = $user_type_fixed; } } return $user_type; } /** * Get User Status * * @param $step_type * @param $step_order * @return string * @since 1.0.0 */ public function get_user_status($step_type, $step_order = false) { $user_status = 'exit'; if($step_type != 'landing') { $user_status = 'clickthrough'; } return $user_status; } /** * Get Visitor Type * * @param $user_ip * @return string * @since 1.0.0 */ public function get_visitor_type($user_ip) { $visitor_type = 'new'; $user_ip_cookie = isset( $_COOKIE['user_ip'] ) ? $_COOKIE['user_ip'] : ''; if($user_ip_cookie != '') { setcookie( 'user_ip', '', time() - 432000, COOKIEPATH, COOKIE_DOMAIN ); $visitor_type = 'returning'; } setcookie( 'user_ip', $user_ip , time() + 432000, COOKIEPATH, COOKIE_DOMAIN ); return $visitor_type; } } public/modules/analytics/class-wpfnl-pro-analytics.php000064400000042372147600245720017225 0ustar00roles as $role){ if( !isset($general_settings['disable_analytics'][$role] )){ add_action( 'template_redirect', array( $this, 'save_analytics_data' ) ); add_action( 'wpfunnels/funnel_order_placed', array($this, 'funnel_order_placed'), 10, 3 ); add_action( 'wpfunnels/offer_accepted', array( $this, 'save_offer_conversion' ), 20, 2 ); add_action( 'wpfunnels/after_optin_submit', array( $this, 'save_optin_conversion' ), 10, 4 ); } } } /** * save analytics data * * @since 1.0.0 */ public function save_analytics_data() { if ( Wpfnl_functions::is_funnel_step_page() && ( method_exists( 'WPFunnels\Wpfnl_functions','is_builder_edit_page' ) && !Wpfnl_functions::is_builder_edit_page() ) ) { global $post; $funnel_id = Wpfnl_functions::get_funnel_id(); $step_id = $post->ID; if ( ! $funnel_id ) { return; } $parent_step_id = get_post_meta($step_id, '_parent_step_id', true); if( $parent_step_id ){ $parent_step_id_cookie_name = 'wpfunnels_ab_testings_variant_step_id_' . $funnel_id; $parent_step_id_cookie = isset($_COOKIE[$parent_step_id_cookie_name]) ? json_decode(wp_unslash($_COOKIE[$parent_step_id_cookie_name]), true) : ''; if ($step_id != $parent_step_id_cookie) { @setcookie($parent_step_id_cookie_name, $step_id, time() + 3600 * 6, '/', COOKIE_DOMAIN); } }else{ $parent_step_id_cookie_name = 'wpfunnels_ab_testings_variant_step_id_' . $funnel_id; $parent_step_id_cookie = isset($_COOKIE[$parent_step_id_cookie_name]) ? json_decode(wp_unslash($_COOKIE[$parent_step_id_cookie_name]), true) : ''; if ($parent_step_id_cookie) { @setcookie($parent_step_id_cookie_name, '', time() - 3600, '/', COOKIE_DOMAIN); } } $cookie_name = 'wpfunnels_visited_step_ids_' . $funnel_id; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $is_returning = in_array( $step_id, $cookie ); if( !$is_returning ) { $cookie[] = $step_id; } @setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); $this->save_conversion_info( $funnel_id, $step_id ); $this->save_visit_info( $funnel_id, $step_id, $is_returning ); } } /** * save conversion information * * @param $funnel_id * @param $step_id * * @since 1.0.0 */ private function save_conversion_info( $funnel_id, $step_id ) { $parent_step_id_cookie_name = 'wpfunnels_ab_testings_variant_step_id_' . $funnel_id; $variant_step_id = isset($_COOKIE[$parent_step_id_cookie_name]) ? intval($_COOKIE[$parent_step_id_cookie_name]) : ''; if($variant_step_id){ $step_id = $variant_step_id; }else{ $step_id = $this->get_previous_step_id($funnel_id, $step_id); } $this->save_conversion( $funnel_id, $step_id ); } /** * save offer conversion * * @param $order * @param $offer_product * * @since 1.0.0 */ public function save_offer_conversion( $order, $offer_product ) { $step_id = $offer_product['step_id']; $funnel_id = get_post_meta($step_id, '_funnel_id', true); $this->save_conversion( $funnel_id, $step_id, '', 'yes', $order->get_id(), false ); } /** * Main Order Tracking hook * * @param $order_id * @param $funnel_id */ public function funnel_order_placed( $order_id, $funnel_id, $step_id ) { if($funnel_id){ global $post; $step_id = $_POST['_wpfunnels_checkout_id'] ? $_POST['_wpfunnels_checkout_id'] : $post->ID; if( $step_id ){ $previous_step_id = $this->get_previous_step_id( $funnel_id, $step_id ); $this->save_conversion( $funnel_id, $step_id, '', 'yes', $order_id ); } } } /** * save conversion * * @param $funnel_id * @param $step_id * @param bool $exclude_offer_data * * @since 1.0.0 */ private function save_conversion( $funnel_id, $step_id, $isOptin = '', $isConversion = 'yes', $order_id = '', $exclude_offer_data = true ) { $cookie_name = 'wpfunnels_visited_steps_data_' . $funnel_id; $cookie_data = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); if( $cookie_data && isset( $cookie_data[$step_id] ) ) { $previous_step_data = $cookie_data[$step_id]; $previous_step_type = $previous_step_data['step_type']; $offer_step_type = array('upsell', 'downsell'); $save_conversion = true; if( $exclude_offer_data && in_array( $previous_step_type, $offer_step_type ) ) { $save_conversion = false; $funnel_type = get_post_meta($funnel_id,'_wpfnl_funnel_type', true); if( 'lms' == $funnel_type && isset($_COOKIE['wpfunnels_automation_data']) ){ $lms_data = isset( $_COOKIE['wpfunnels_automation_data'] ) ? json_decode( wp_unslash( $_COOKIE['wpfunnels_automation_data'] ), true ) : array(); if( isset($lms_data['offer']) ){ if( isset($lms_data['offer'][$step_id] )){ if( $lms_data['offer'][$step_id]['status'] == 'accepted' ){ $save_conversion = true; } } } } } if( $save_conversion && 'no' === $previous_step_data['conversion'] ) { global $wpdb; $table_fix = $wpdb->prefix; $analytics_meta_db = $table_fix . 'wpfnl_analytics_meta'; /** update conversion data in conversion meta table */ $analytics_id = $previous_step_data['analytics_id']; if( $isConversion ){ $wpdb->update( $analytics_meta_db, array( 'analytics_id' => $analytics_id, 'meta_key' => 'conversion', 'meta_value' => 'yes', ), array( 'analytics_id' => $analytics_id, 'meta_key' => 'conversion', ) ); } if ( is_user_logged_in() ) { $user_id = get_current_user_id(); $user = get_userdata( $user_id ); $user_roles = $user->roles; $user_role = $user_roles[0]; }else{ $user_role = ''; } $wpdb->insert( $analytics_meta_db, array( 'analytics_id' => $analytics_id, 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'meta_key' => 'user_role', 'meta_value' => $user_role, ) ); if( $order_id ){ $wpdb->insert( $analytics_meta_db, array( 'analytics_id' => $analytics_id, 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'meta_key' => 'wpfunnel_order_id', 'meta_value' => $order_id, ) ); } if( $isOptin ){ $wpdb->insert( $analytics_meta_db, array( 'analytics_id' => $analytics_id, 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'meta_key' => 'wpfunnel_optin_submit', 'meta_value' => 'yes', ) ); } if( ! $exclude_offer_data ) { $offer_type = $previous_step_type; $wpdb->insert( $analytics_meta_db, array( 'analytics_id' => $analytics_id, 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'meta_key' => 'offer-type', 'meta_value' => $offer_type, ) ); } /** update cookie */ $cookie_data[$step_id]['conversion'] = 'yes'; @setcookie( $cookie_name, wp_json_encode( $cookie_data ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); } } } /** * save visit info * * @param $funnel_id * @param $step_id * @param $is_returning * * @since 1.0.0 */ private function save_visit_info( $funnel_id, $step_id, $is_returning ) { global $wpdb; $table_fix = $wpdb->prefix; $analytics_db = $table_fix . 'wpfnl_analytics'; $analytics_meta_db = $table_fix . 'wpfnl_analytics_meta'; $http_referer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; $user_info = $this->get_user_info_data( $is_returning ); $analytics_data= $this->get_analytics_data( $funnel_id, $step_id, $user_info ); /** save visit data on analytics db */ $wpdb->insert( $analytics_db, array( 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'visitor_type' => $user_info['visitor_type'], 'user_id' => $user_info['user_id'], 'user_ip' => $user_info['user_ip'], 'analytics_data' => json_encode($analytics_data), 'date_created' => current_time( 'Y-m-d H:i:s' ), 'date_created_gmt' => current_time( 'Y-m-d H:i:s', 1 ), 'date_modified' => current_time( 'Y-m-d H:i:s' ), 'date_modified_gmt' => current_time( 'Y-m-d H:i:s', 1 ), ) ); $analytics_id = $wpdb->insert_id; /** save meta info on meta table */ $meta_keys = array( 'conversion' => 'no', 'http_referer' => $http_referer, 'bounced' => 'yes' ); foreach ( $meta_keys as $key => $value ) { $wpdb->insert( $analytics_meta_db, array( 'analytics_id' => $analytics_id, 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'meta_key' => $key, 'meta_value' => $value, ) ); } /** set cookie data */ $cookie_name = 'wpfunnels_visited_steps_data_' . $funnel_id; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $cookie[$step_id] = array( 'funnel_id' => $funnel_id, 'step_id' => $step_id, 'step_type' => get_post_meta( $step_id, '_step_type', true ), 'analytics_id' => $analytics_id, 'conversion' => 'no' ); @setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); do_action( 'wpfunnels/update_ab_testing_winner', $funnel_id, $step_id ); } /** * get previous step id * * @param $funnel_id * @param $step_id * @return bool|int * * @since 1.0.0 */ private function get_previous_step_id( $funnel_id, $step_id ) { $prev_step_id = false; $prev_step_type = ''; $funnel_data = Wpfnl_functions::get_funnel_data($funnel_id); if ( $funnel_data ) { $node_id = Wpfnl_functions::get_node_id( $funnel_id, $step_id ); $node_data = $funnel_data['drawflow']['Home']['data']; foreach ( $node_data as $node_key => $node_value ) { if ( $node_value['id'] == $node_id ) { if( !empty($node_value['inputs']) && isset($node_value['inputs']['input_1']['connections'][0])){ $prev_node_id = $node_value['inputs']['input_1']['connections'][0]['node']; $prev_step_id = Wpfnl_functions::get_step_by_node( $funnel_id, $prev_node_id ); $prev_step_type = Wpfnl_functions::get_node_type( $node_data, $prev_node_id ); } break; } } } if( 'conditional' === $prev_step_type ) { $prev_step_id = $this->get_previous_step_id( $funnel_id, $prev_step_id ); } $cookie_name = 'wpfnl_ab_testings_' . $prev_step_id; $cookie_data = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : ''; if( $cookie_data ) { $prev_step_id = $cookie_data; } return $prev_step_id; } /** * get user ip * * @return mixed|void * * @since 1.0.0 */ function get_the_user_ip() { if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) { //check ip from share internet $ip = $_SERVER['HTTP_CLIENT_IP']; } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { //to check ip is pass from proxy $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } else { $ip = $_SERVER['REMOTE_ADDR']; } return apply_filters( 'wpfunnels/get_the_user_ip', $ip ); } /** * Process activity data for api. * * @param $funnel_id * @param $step_id * @param $user_type * @param $user_status * @param $user_info array * @return array * @since 1.0.0 */ public function get_analytics_data( $funnel_id, $step_id, $user_info ) { $http_referer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; return array( "v" => 1, "visitor" => array( "id" => $user_info['user_id'], "type" => $user_info['visitor_type'], "ip" => $user_info['user_ip'], "referrer" => $http_referer, ), "funnel" => array( "id" => $funnel_id, "meta" => array(), ), "step" => array( "id" => $step_id, "meta" => array(), ), ); } /** * get user information * * @param $is_returning * @return array * * @since 1.0.0 */ public function get_user_info_data( $is_returning ) { $user_id = get_current_user_id(); return array( "user_id" => $user_id, "user_ip" => $this->get_the_user_ip(), "visitor_type" => $is_returning ? 'returning' : 'new', ); } /** * @param $step_id * @param $post_action * @param $record */ public function save_optin_conversion( $step_id, $post_action, $action_type, $record ) { $funnel_id = get_post_meta($step_id, '_funnel_id', true); if('notification' === $post_action || ( 'redirect_to' == $post_action && 'redirect_to_url' == $action_type ) ) { $this->save_conversion( $funnel_id, $step_id, 'yes' ,'' ); }elseif( ('redirect_to' == $post_action && 'next_step' == $action_type) || ('next_step' == $post_action && 'next_step' == $action_type) ){ $this->save_conversion( $funnel_id, $step_id, 'yes' ,'yes' ); } } } public/modules/checkout/classes/class-wpfnl-pro-edit-field.php000064400000113717147600245720020521 0ustar00countries->get_allowed_countries(), WC()->countries->get_shipping_countries() ); $countries = array_keys($countries); if(is_array($locale) && is_array($countries)){ foreach($countries as $country){ if(isset($locale[$country])){ $locale[$country] = $this->wpfnl_prepare_country_locale($locale[$country]); } } } return $locale; } /** * Prepare country local value for custom edit * @param array country local fields */ public function wpfnl_prepare_country_locale($fields) { if(is_array($fields)){ foreach($fields as $key => $props){ if( isset($props['label'])){ unset($fields[$key]['label']); } if( isset($props['placeholder'])){ unset($fields[$key]['placeholder']); } if( isset($props['class'])){ unset($fields[$key]['class']); } if( isset($props['priority'])){ unset($fields[$key]['priority']); } } } return $fields; } /** * add custom checkout field for additional section * @param array checkout for field */ public function wpfnl_add_custom_checkout_field_for_additional_section($checkout) { $get_custom_data = get_post_meta(get_the_ID(), 'wpfnl_checkout_additional_fields', true); $custom_field_count = 0; if( is_array($get_custom_data) && !empty($get_custom_data) ){ foreach($get_custom_data as $key=>$field_data){ if( 'order_comments' !== $key && $field_data['enable'] == 1){ $custom_field_count ++; } } if(!$custom_field_count) return false; echo '
'; foreach($get_custom_data as $key=>$field_data){ $tcf_option = array(); if( isset($field_data['type']) && ($field_data['type'] == 'radio' || $field_data['type'] == 'select' || $field_data['type'] == 'checkbox')){ $cf_option = []; if (isset($field_data['type']) && ($field_data['type'] == 'select' || $field_data['type'] == 'radio')) { for ($i=0; $i < count($field_data['options']); $i++) { $cf_option[$field_data['options'][$i]['optionValue']] = $field_data['options'][$i]['optionTitle']; } } } if( !('order_comments' == $key )){ if($field_data['enable'] == 1){ if( $field_data['type'] == 'radio' || $field_data['type'] == 'select' || $field_data['type'] == 'checkbox' ){ if( $field_data['type'] == 'radio' || $field_data['type'] == 'checkbox'){ woocommerce_form_field( ($field_data['name']), $this->set_field_for_additional_section($field_data,$cf_option), $checkout->get_value( ($field_data['name'] ))); }else{ $default_select = array("" => "Please Select..."); $new_arr = $default_select+$cf_option; woocommerce_form_field( ($field_data['name']),$this->set_field_for_additional_section($field_data,$new_arr), $checkout->get_value( ($field_data['name'] ))); } }else{ woocommerce_form_field( $field_data['name'],$this->set_field_for_additional_section($field_data) , $checkout->get_value( $field_data['name'] )); } } } } echo '
'; } } /** * Update the order meta with field value * @param int order_id for update order meta by order id */ public function wpfnl_custom_checkout_field_update_order_meta($order_id) { $step_id = $this->get_step_id(); if( $step_id ){ $get_custom_data = get_post_meta($step_id, 'wpfnl_checkout_additional_fields', true); $get_billing_data = get_post_meta($step_id, 'wpfnl_checkout_billing_fields', true); $shipping_updated_field = get_post_meta($step_id, 'wpfnl_checkout_shipping_fields', true); if ($get_custom_data) { foreach ($get_custom_data as $custom_data) { if(isset($custom_data['name'], $_POST[$custom_data['name']] )){ update_post_meta($order_id, $custom_data['name'], sanitize_text_field($_POST[$custom_data['name']])); update_post_meta($step_id, $custom_data['name'], sanitize_text_field($_POST[$custom_data['name']])); } } } if ($get_billing_data) { foreach ($get_billing_data as $key => $value) { if(isset($value['name'], $_POST[$value['name']])){ update_post_meta($order_id, $key, sanitize_text_field($_POST[$value['name']])); update_post_meta($step_id, $key, sanitize_text_field($_POST[$value['name']])); } } } if ($shipping_updated_field) { foreach ($shipping_updated_field as $key => $value) { if(isset($value['name'], $_POST[$value['name']])){ update_post_meta($order_id, $key, sanitize_text_field($_POST[$value['name']])); update_post_meta($step_id, $key, sanitize_text_field($_POST[$value['name']])); } } } } } /** * wpfnl_display_additional_field_custom * Display additional field value on the order edit page * * @param array order for get order_id */ public function wpfnl_display_additional_field_custom($order) { $step_id = Wpfnl_functions::get_checkout_id_from_order($order->get_id()); if( $step_id ){ $get_custom_data = get_post_meta($step_id, 'wpfnl_checkout_additional_fields', true); if ($get_custom_data) { foreach ($get_custom_data as $gccd=>$value) { if ($value['show'] == 1) { echo '

'.($value['label']).': ' . get_post_meta($order->get_id(), $gccd, true) . '

'; } } } } } /** * wpfnl_display_shipping_field_custom * Display shipping field value on the order edit page * * @param array order for get order_id */ public function wpfnl_display_shipping_field_custom($order) { $step_id = Wpfnl_functions::get_checkout_id_from_order($order->get_id()); if( $step_id ){ $get_shipping_data = get_post_meta($step_id, 'wpfnl_checkout_shipping_fields', true); $countries = new WC_Countries(); $get_shipping_default_data = $countries->get_address_fields($countries->get_base_country(), 'shipping_'); $key_array = []; foreach ($get_shipping_default_data as $key=> $avlue) { array_push($key_array, $key); } if ($get_shipping_data) { foreach ($get_shipping_data as $gccd => $value) { if (!(in_array($gccd, $key_array))) { if ($value['show'] == 1) { echo '

'.__($value['label']).': ' . get_post_meta($order->get_id(), $gccd, true) . '

'; } } } } } } /** * wpfnl_display_billing_field_custom * Display billing field value on the order edit page * * @param array order for get order_id */ public function wpfnl_display_billing_field_custom($order) { $step_id = Wpfnl_functions::get_checkout_id_from_order($order->get_id()); if( $step_id ){ $get_billing_data = get_post_meta($step_id, 'wpfnl_checkout_billing_fields', true); $get_billing_default_data = WC()->countries->get_address_fields(); $key_array = []; foreach ($get_billing_default_data as $key=> $value) { array_push($key_array, $key); } if ($get_billing_data) { foreach ($get_billing_data as $gccd => $value) { if (!(in_array($gccd, $key_array))) { if ($value['show'] == 1) { echo '

'.__($value['label']).': ' . get_post_meta($order->get_id(), $gccd, true) . '

'; } } } } }else{ return false; } } /** * wpfnl_custom_checkout_field_process * Process the checkout notice for additional field */ public function wpfnl_custom_checkout_field_process() { if (isset($_POST['_wpfunnels_checkout_id'])){ $get_custom_data = get_post_meta($_POST['_wpfunnels_checkout_id'], 'wpfnl_checkout_additional_fields', true); if ( $get_custom_data != '' ){ $this->wpfnl_custom_fields_validation_notice($get_custom_data); } } } /** * Get checkout field error notice * @param $get_field_data */ public function wpfnl_custom_fields_validation_notice($get_field_data){ if ($get_field_data) { foreach ($get_field_data as $gccd) { if ($gccd['required'] == 1 && $gccd['enable'] == true) { if (! $_POST[$gccd['name']] || $_POST[$gccd['name']] == "") { wc_add_notice(__('Please enter something into this '.$gccd['label'].' field.'), 'error'); } } } } } /** * Step id is get_the_ID() if Checkout is not enable ajax * @return int */ public function get_step_id() { if (isset($_POST['_wpfunnels_checkout_id'])){ $step_id = $_POST['_wpfunnels_checkout_id']; }else{ $step_id = get_the_ID(); } return $step_id; } /** * wpfnl_add_fields * add billing,shipping field in checkout page * @param array fields of all fields */ public function wpfnl_add_custom_checkout_field($fields){ if( Wpfnl_functions::is_funnel_step_page() ) { $step_id = $this->get_step_id(); } else { if ( Wpfnl_Pro_functions::is_doing_ajax() ) { $step_id = Wpfnl_functions::get_checkout_id_from_post_data(); } else { return $fields; } } $get_additional_data = get_post_meta( $step_id, 'wpfnl_checkout_additional_fields', true ); if($get_additional_data) { if( !isset($get_additional_data['order_comments']) ){ unset($fields['order']['order_comments']); add_filter( 'woocommerce_enable_order_notes_field', '__return_false', 9999 ); } else{ if($get_additional_data['order_comments']['enable'] == 0){ unset($fields['order']['order_comments']); add_filter( 'woocommerce_enable_order_notes_field', '__return_false', 9999 ); }else{ $i = 10; foreach($get_additional_data as $key => $updated_field){ if( $key == 'order_comments' ){ $fields['order'][$key] = $this->set_checkout_field_for_order_comment($updated_field, '', $i); } } } } } /** * for billing */ $fields = $this->billing_checkout_fields($step_id,$fields); /** * for shipping */ $fields = $this->shipping_checkout_fields($step_id,$fields); if( isset($fields['billing']['billing_state']) ){ $country_code = isset($_POST['billing_country']) ? sanitize_text_field($_POST['billing_country']) : ''; if( $country_code ){ $is_state_less = $this->is_country_stateless($country_code); $fields['billing']['billing_state']['required'] = !$is_state_less; } } return $fields; } /** * Check if a country is stateless. * This function checks whether the selected country is stateless, * meaning it does not have any associated states/provinces. * * @param string $country_code The country code. * * @return bool Returns true if the country is stateless, false otherwise. * * @since 1.9.3 */ public function is_country_stateless( $country_code ){ $wc_countries = WC()->countries; $countries = $wc_countries->get_countries(); $states = $wc_countries->get_states(); $countries_without_states = array(); if( is_array($countries) ){ foreach ($countries as $code => $name) { if (!isset($states[$code]) || empty($states[$code])) { $countries_without_states[$code] = $name; } } } return array_key_exists($country_code, $countries_without_states) ? true : false; } /** * @param $updated_field * @param $options * @return array */ public function set_checkout_field($updated_field, $options = '', $priority = '', $key = '' ) { $name = preg_replace('/[0-9\/\,\.\;\" "]+/', ' ', strtolower($updated_field['label'])); $name = str_replace(" ","-",($name)); if( isset($updated_field['validate']) && !is_array($updated_field['validate']) ){ $updated_field['validate'] = explode(" ",$updated_field['validate']); } if( !empty($updated_field['validate']) ){ $updated_field['validate'] = is_array($updated_field['validate']) ? $updated_field['validate'] : explode(" ",$updated_field['validate']); }else{ $updated_field['validate'] = ''; } $type = ''; if( 'billing_country' == $key || 'shipping_country' == $key ){ $type = 'country'; }elseif( 'billing_state' == $key || 'shipping_state' == $key ){ $type = 'state'; }elseif( isset($updated_field['type']) && '' !== $updated_field['type'] ){ $type = $updated_field['type']; }else{ $type = 'text'; } $class = []; if( defined('WCPAY_VERSION_NUMBER') && 'billing_email' == $key ){ array_push($class, 'woopay-billing-email'); } if( $key == 'billing_country' || $key == 'billing_state' || $key == 'billing_postcode' || $key == 'billing_state' || $key == 'billing_address_1' || $key == 'billing_address_2' ){ array_push($class,'form-row-wide', 'address-field', 'wpfnl-form-row', $name, 'field-'.$updated_field['type'].''); }else{ array_push($class,'form-row-wide','wpfnl-form-row', $name, 'field-'.$updated_field['type'].''); } // $class = $key == 'billing_country' || $key == 'billing_state' || $key == 'billing_postcode' || $key == 'billing_state' || $key == 'billing_address_1' || $key == 'billing_address_2' ? array('form-row-wide', 'address-field', 'wpfnl-form-row', $name, 'field-'.$updated_field['type'].'') : array('form-row-wide','wpfnl-form-row', $name, 'field-'.$updated_field['type'].''); $fields = array( 'type' => $type, 'label' => isset($updated_field['label']) ? $updated_field['label'] : '', 'placeholder' => isset($updated_field['placeholder']) ? $updated_field['placeholder'] : '', 'required' => $updated_field['required'], 'class' => $class, 'priority' => isset($updated_field['priority']) ? $updated_field['priority'] : 1000, 'clear' => true, 'default' => isset($updated_field['default']) ? $updated_field['default'] : '', 'validate' => isset($updated_field['validate']) ? $updated_field['validate'] : '', 'options' => $options, ); $cookie_name = 'wpfunnels_send_data_checkout'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); if(!empty($cookie)){ if(isset($cookie['after_optin_submit_send_for_checkout'])){ $first_name = isset($cookie['after_optin_submit_send_for_checkout']['first_name']) ? $cookie['after_optin_submit_send_for_checkout']['first_name'] : ''; $last_name = isset($cookie['after_optin_submit_send_for_checkout']['last_name']) ? $cookie['after_optin_submit_send_for_checkout']['last_name'] : ''; $phone = isset($cookie['after_optin_submit_send_for_checkout']['phone']) ? $cookie['after_optin_submit_send_for_checkout']['phone'] : ''; $email = isset($cookie['after_optin_submit_send_for_checkout']['email']) ? $cookie['after_optin_submit_send_for_checkout']['email'] : ''; if( 'billing_first_name' === $key ){ $fields['default'] = $first_name; }elseif( 'billing_last_name' === $key ){ $fields['default'] = $last_name; }elseif( 'billing_phone' === $key ){ $fields['default'] = $phone; }elseif( 'billing_email' === $key ){ $fields['default'] = $email; } } } return $fields; } /** * @param $updated_field * @param $options * @return array */ public function set_checkout_field_for_order_comment( $updated_field, $options = '', $priority = 10 ) { $name = preg_replace('/[0-9\/\,\.\;\" "]+/', ' ', strtolower($updated_field['label'])); $name = str_replace(" ","-",($name)); if( isset($updated_field['validate']) && !is_array($updated_field['validate']) ){ $updated_field['validate'] = explode(" ",$updated_field['validate']); } $updated_field['validate'] = is_array($updated_field['validate']) ? $updated_field['validate'] : explode(" ",$updated_field['validate']); $fields = array( 'type' => isset($updated_field['type']) && $updated_field['type'] !== '' ? $updated_field['type'] : 'textarea', 'label' => isset($updated_field['label']) ? $updated_field['label'] : '', 'placeholder' => isset($updated_field['placeholder']) ? $updated_field['placeholder'] : '', 'required' => $updated_field['required'], 'class' => array('form-row-wide', 'wpfnl-form-row', $name, 'field-'.$updated_field['type'].''), 'priority' => isset($updated_field['priority']) ? $updated_field['priority'] : 1000, 'clear' => true, 'default' => isset($updated_field['default']) ? $updated_field['default'] : '', 'validate' => isset($updated_field['validate']) ? $updated_field['validate'] : '', 'options' => $options, ); return $fields; } /** * @param $field_data * @param string $options * @return array */ public function set_field_for_additional_section ($field_data, $options = '') { $name = preg_replace('/[0-9\/\,\.\;\" "]+/', ' ', strtolower($field_data['label'])); $name = str_replace(" ","-",($name)); $fields = array( 'type' => isset($field_data['type']) && $field_data['type'] != '' ? $field_data['type'] : 'text', 'class' => array('form-row-wide', 'wpfnl-form-row', $name, 'field-'.$field_data['type'].''), 'clear' => true, 'label' => __($field_data['label']), 'placeholder' => __($field_data['placeholder']), 'required' => $field_data['required'], 'priority' => isset($field_data['priority']) ? $field_data['priority'] : 1000, 'default' => __($field_data['default']), 'options' => $options, 'validate' => explode(" ",$field_data['validate']) ); return $fields; } /** * @param $step_id * @param $fields * @return array */ public function billing_checkout_fields($step_id,$fields) { $billing_default_field = isset( $fields['billing'] ) ? $fields['billing'] : []; $billing_updated_field = get_post_meta($step_id, 'wpfnl_checkout_billing_fields', true); $billing_updated_field_check = $billing_updated_field; if (is_array($billing_updated_field)) { $formatted_fields = array(); foreach($billing_updated_field as $key => $updated_field){ if($updated_field['enable'] == 1){ $i = 10; if(isset($billing_default_field[$key]) != isset($billing_updated_field_check[$key])){ $default_select = array("" => "Please Select..."); $cf_option = []; if (isset($updated_field['type']) && ( $updated_field['type'] == 'select' || $updated_field['type'] == 'multiselect' || $updated_field['type'] == 'radio') ) { for ($i=0; $i < count($updated_field['options']); $i++) { $cf_option[$updated_field['options'][$i]['optionValue']] = $updated_field['options'][$i]['optionTitle']; } } if( isset($updated_field['type']) && $updated_field['type'] == 'select' || $updated_field['type'] == 'multiselect'){ $formatted_fields[$key] = $this->set_checkout_field($updated_field,$default_select+$cf_option, $i , $key); } elseif( isset($updated_field['type']) && ($updated_field['type'] == 'radio' || $updated_field['type'] == 'checkbox')){ $formatted_fields[$key] = $this->set_checkout_field($updated_field,$cf_option, $i, $key); } else{ $formatted_fields[$key] = $this->set_checkout_field($updated_field,'', $i, $key); } } else{ $default_select = array("" => "Please Select..."); $cf_option = []; if (isset($updated_field['type']) && ( $updated_field['type'] == 'select' || $updated_field['type'] == 'multiselect' || $updated_field['type'] == 'radio') ) { for ($i=0; $i < count($updated_field['options']); $i++) { $cf_option = $updated_field['options']; } } if( isset($updated_field['type']) && $updated_field['type'] == 'select' || $updated_field['type'] == 'multiselect'){ $formatted_fields[$key] = $this->set_checkout_field($updated_field,$cf_option, $i , $key); }else{ $formatted_fields[$key] = $this->set_checkout_field($updated_field,'',$i, $key); } } $i += 10; } else{ unset($formatted_fields[$key]); } } $fields['billing'] = $formatted_fields; } return $fields; } /** * @param $step_id * @param $fields * @return array */ public function shipping_checkout_fields($step_id,$fields) { $countries = new WC_Countries(); $shipping_default_field = $countries->get_address_fields($countries->get_base_country(), 'shipping_'); $shipping_updated_field = get_post_meta($step_id, 'wpfnl_checkout_shipping_fields', true); $shipping_updated_field_check = $shipping_updated_field; if (is_array($shipping_updated_field)) { $formatted_fields = array(); foreach($shipping_updated_field as $key => $updated_field){ if( $updated_field['enable'] == 1){ if(isset($shipping_default_field[$key]) != isset($shipping_updated_field_check[$key])){ $default_select = array("" => "Please Select..."); $cf_option = []; if (isset($updated_field['type']) && ($updated_field['type'] == 'select' || $updated_field['type'] == 'radio') ) { for ($i=0; $i < count($updated_field['options']); $i++) { $cf_option[$updated_field['options'][$i]['optionValue']] = $updated_field['options'][$i]['optionTitle']; } } if($updated_field['type'] == 'select'){ $formatted_fields[$key] = $this->set_checkout_field($updated_field,$default_select + $cf_option , '' ,$key); }elseif($updated_field['type'] == 'radio' || $updated_field['type'] == 'checkbox'){ $formatted_fields[$key] = $this->set_checkout_field($updated_field,$cf_option, '', $key); }else{ $formatted_fields[$key] = $this->set_checkout_field($updated_field,'','', $key); } }else{ $formatted_fields[$key] = $this->set_checkout_field($updated_field, '', '', $key); } } else{ unset($formatted_fields[$key]); } } $fields['shipping'] = $formatted_fields; } return $fields; } /** * Unset default priority of checkout fields * * @param $fields * @return $fields */ public function unset_default_priority($fields) { if(is_array($fields)){ foreach($fields as $key => $props){ if( isset($fields[$key]['priority']) ){ unset($fields[$key]['priority']); } } } return $fields; } /** * Add custom address field value for billing section in thankyou page * * @param $address, $WC_order * */ public function wpfnl_display_extra_field_value_in_billing( $address, $WC_Order ) { $address = $this->add_billing_custom_field_value_in_thankyou( 'address', '' , '', '' , $address, $WC_Order ); return $address; } /** * Add custom address field value for shipping section in thankyou page * * @param $address, $WC_order * */ public function wpfnl_display_extra_field_value_in_shipping( $address, $WC_Order ) { $address = $this->add_shipping_custom_field_value_in_thankyou( 'address', '' , '', '' , $address, $WC_Order ); return $address; } /** * Adress replacement for thank you page * * @param $replacements, $args * */ public function wpfnl_address_replacements( $replacements, $args ){ $replacements = $this->add_custom_field_value_in_thankyou( 'replacement', '' , $replacements, $args,'', '' ); return $replacements; } /** * Change address format for custom checkout fields in thankyou page * * @param $address-formats */ public function wpfnl_localisation_address_formats( $address_formats ){ $address_formats = $this->add_custom_field_value_in_thankyou( 'address_format', $address_formats, '', '','','' ); return $address_formats; } /** * Add custom field in thankyou page * * @param $change_type, $address_formats = '', $replacements = '', $args = '', $address='', $WC_Order='' */ private function add_custom_field_value_in_thankyou( $change_type, $address_formats = '', $replacements = '', $args = '', $address='', $WC_Order='' ){ $is_thankyou = Wpfnl_functions::check_if_this_is_step_type('thankyou'); if( !$is_thankyou ){ if( $change_type == 'address_format' ){ return $address_formats; } if( $change_type == 'replacement' ){ return $replacements; } if( $change_type == 'address' ){ return $address; } } $funnel_id = Wpfnl_functions::get_funnel_id_from_step( get_the_ID() ); $steps = Wpfnl_functions::get_steps( $funnel_id ); $key = array_search('checkout', array_column($steps, 'step_type')); $step_id = $steps[$key]['id']; if( $step_id ){ //for billing $get_billing_data = get_post_meta($step_id, 'wpfnl_checkout_billing_fields', true); $get_billing_default_data = WC()->countries->get_address_fields(); $key_array = []; foreach ($get_billing_default_data as $key=> $value) { array_push($key_array, $key); } if ($get_billing_data) { foreach ($get_billing_data as $gccd => $value) { if (!(in_array($gccd, $key_array))) { if ($value['show'] == 1) { if( $change_type == 'address_format'){ $address_formats['default'] .= "\n{".$gccd."}"; } if( $change_type == 'replacement' ){ $replacements['{'.$gccd.'}'] = isset($args[$gccd]) ? $args[$gccd] : ''; } if( $change_type == 'address' ){ $address[$gccd] = get_post_meta($step_id, $gccd, true); } } } } } //for shipping $get_shipping_data = get_post_meta($step_id, 'wpfnl_checkout_shipping_fields', true); $countries = new WC_Countries(); $get_shipping_default_data = $countries->get_address_fields($countries->get_base_country(), 'shipping_'); $key_array = []; foreach ($get_shipping_default_data as $key=> $avlue) { array_push($key_array, $key); } if ($get_shipping_data) { foreach ($get_shipping_data as $gccd => $value) { if (!(in_array($gccd, $key_array))) { if( $change_type == 'address_format'){ $address_formats['default'] .= "\n{".$gccd."}"; } if( $change_type == 'replacement' ){ $replacements['{'.$gccd.'}'] = isset($args[$gccd]) ? $args[$gccd] : ''; } if( $change_type == 'address' ){ $address[$gccd] = get_post_meta($step_id, $gccd, true); } } } } if( $change_type == 'address_format'){ return $address_formats; } if( $change_type == 'replacement' ){ return $replacements; } if( $change_type == 'address' ){ return $address; } } } /** * Add address field for shipping * * @param $change_type, $address_formats = '', $replacements = '', $args = '', $address='', $WC_Order='' */ private function add_shipping_custom_field_value_in_thankyou( $change_type, $address_formats = '', $replacements = '', $args = '', $address='', $WC_Order='' ){ $is_thankyou = Wpfnl_functions::check_if_this_is_step_type('thankyou'); if( !$is_thankyou ){ if( $change_type == 'address' ){ return $address; } } $funnel_id = Wpfnl_functions::get_funnel_id_from_step( get_the_ID() ); $steps = Wpfnl_functions::get_steps( $funnel_id ); $key = array_search('checkout', array_column($steps, 'step_type')); $step_id = $steps[$key]['id']; if( $step_id ){ //for shipping $get_shipping_data = get_post_meta($step_id, 'wpfnl_checkout_shipping_fields', true); $countries = new WC_Countries(); $get_shipping_default_data = $countries->get_address_fields($countries->get_base_country(), 'shipping_'); $key_array = []; foreach ($get_shipping_default_data as $key=> $avlue) { array_push($key_array, $key); } if ($get_shipping_data) { foreach ($get_shipping_data as $gccd => $value) { if (!(in_array($gccd, $key_array))) { if( $change_type == 'address' ){ $address[$gccd] = get_post_meta($step_id, $gccd, true); } } } } if( $change_type == 'address' ){ return $address; } } } /** * Add address field for billing * * @param $change_type, $address_formats = '', $replacements = '', $args = '', $address='', $WC_Order='' */ private function add_billing_custom_field_value_in_thankyou( $change_type, $address_formats = '', $replacements = '', $args = '', $address='', $WC_Order='' ){ $is_thankyou = Wpfnl_functions::check_if_this_is_step_type('thankyou'); if( !$is_thankyou ){ if( $change_type == 'address' ){ return $address; } } $funnel_id = Wpfnl_functions::get_funnel_id_from_step( get_the_ID() ); $steps = Wpfnl_functions::get_steps( $funnel_id ); $key = array_search('checkout', array_column($steps, 'step_type')); $step_id = $steps[$key]['id']; if( $step_id ){ $get_billing_data = get_post_meta($step_id, 'wpfnl_checkout_billing_fields', true); $get_billing_default_data = WC()->countries->get_address_fields(); $key_array = []; foreach ($get_billing_default_data as $key=> $value) { array_push($key_array, $key); } if ($get_billing_data) { foreach ($get_billing_data as $gccd => $value) { if (!(in_array($gccd, $key_array))) { if ($value['show'] == 1) { if( $change_type == 'address' ){ $address[$gccd] = get_post_meta($step_id, $gccd, true); } } } } } if( $change_type == 'address' ){ return $address; } } } } public/modules/checkout/classes/class-wpfnl-single-product.php000064400000033641147600245720020651 0ustar00is_funnel_checkout_page = Wpfnl_functions::is_funnel_checkout_page(); // add_action( 'wp_ajax_wpfnl_single_product_quantity_ajax', [$this, 'wpfnl_single_product_quantity_ajax']); // add_action( 'wp_ajax_nopriv_wpfnl_single_product_quantity_ajax', [$this, 'wpfnl_single_product_quantity_ajax']); if( Wpfnl_functions::is_funnel_step_page() && Wpfnl_functions::is_wc_active() ) { add_filter('woocommerce_locate_template', array('WPFunnelsPro\Modules\Frontend\Checkout\Single\Wpfnl_Single_Product', 'wpfunnels_woocommerce_locate_template'), 20, 3); } if( Wpfnl_functions::is_wc_active() ){ add_action( 'wp_ajax_wpfnl_update_quantity_ajax', [$this, 'wpfnl_update_quantity_ajax']); add_action( 'wp_ajax_nopriv_wpfnl_update_quantity_ajax', [$this, 'wpfnl_update_quantity_ajax']); } // add_action('woocommerce_after_order_notes', array($this, 'wpfnl_checkout_field_for_simple_product')); $values = array(); if( isset($_POST['post_data']) ){ parse_str($_POST['post_data'], $values); $this->step_id = isset($values['_wpfunnels_checkout_id']) ? $values['_wpfunnels_checkout_id'] : ''; } if( ( isset($values['_wpfunnels_checkout_id']) && $values['_wpfunnels_checkout_id'] ) || get_the_ID() != 1 || $this->is_funnel_checkout_page['status'] ){ if( Wpfnl_functions::is_wc_active() ){ add_filter( 'woocommerce_checkout_cart_item_quantity', array($this, 'wpfnl_checkout_cart_item_quantity'), 10, 3 ); add_filter( 'woocommerce_cart_item_name', array($this, 'wpfnl_checkout_cart_item_name'), 10, 3 ); } } } /** * Add custom checkout fields for simple products * * @param $checkout */ public function wpfnl_checkout_field_for_simple_product($checkout){ global $woocommerce; $items = $woocommerce->cart->get_cart(); $products_info = get_post_meta(get_the_ID(), '_wpfnl_checkout_products', true); $products = array(); $i=0; if(!empty($products_info)){ foreach($products_info as $product_info){ $product = wc_get_product($product_info['id']); if( $product ){ $product_id = $product_info['id']; if($product->get_type() == 'simple'){ $products[$i]['product'] = $product; $products[$i]['quantity'] = $product_info['quantity']; $i++; } } } if(!empty($products)){ $isQuantity = get_post_meta(get_the_ID(), '_wpfnl_quantity_support', true); if($isQuantity){ if($isQuantity == 'yes'){ require WPFNL_PRO_DIR . 'public/modules/checkout/templates/single-product/header.php'; require WPFNL_PRO_DIR . 'public/modules/checkout/templates/single-product/table-header.php'; foreach($products as $product){ $quantity = $product['quantity']; $product = $product['product']; $this->render_checkout_fields_body($checkout,$product->get_id(),$product,$quantity,$isQuantity); } require WPFNL_PRO_DIR . 'public/modules/checkout/templates/single-product/table-footer.php'; require WPFNL_PRO_DIR . 'public/modules/checkout/templates/single-product/footer.php'; } } } } } /** * * Render Checkout fields body for simple product * * @param $checkout,$product_id,$product,$quantity,$isQuantity */ private function render_checkout_fields_body($checkout,$product_id,$product,$quantity,$isQuantity){ require WPFNL_PRO_DIR . 'public/modules/checkout/templates/single-product/body.php'; } /** * wpfnl_single_product_quantity_ajax * Select quantity option for simple product * * @since 1.1. */ public function wpfnl_single_product_quantity_ajax() { $step_id = filter_input(INPUT_POST, 'step_id', FILTER_VALIDATE_INT); $product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT); $quantity = filter_input(INPUT_POST, 'quantity', FILTER_SANITIZE_STRING); $isQuantity = false; if (isset($_POST['isQuantity'])){ $isQuantity = filter_input(INPUT_POST, 'isQuantity', FILTER_SANITIZE_STRING); } $response = $this->wpfnl_add_simple_product_quantity($step_id,$product_id,$quantity,$isQuantity); if($response){ wp_send_json_success($response); }else{ $response = [ 'status'=>'fail' ]; wp_send_json_success($response); } } /** * wpfnl_single_product_quantity_ajax * Select quantity option for simple product * * @since 1.1. */ public function wpfnl_update_quantity_ajax() { $step_id = filter_input(INPUT_POST, 'step_id', FILTER_VALIDATE_INT); $product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT); $quantity = filter_input(INPUT_POST, 'quantity', FILTER_SANITIZE_STRING); $variation_id = filter_input(INPUT_POST, 'variation_id', FILTER_SANITIZE_STRING); $variations = array(); if(isset($_POST['variation'])){ $variations = $_POST['variation']; } $response = $this->wpfnl_add_product_quantity($step_id,$product_id,$quantity,$variation_id,$variations); if($response){ wp_send_json_success($response); }else{ $response = [ 'status'=>'fail' ]; wp_send_json_success($response); } } /** * wpfnl_add_simple_products * Add simple product quantity * * @param $step_id,$product_id,$quantity,$isQuantity */ private function wpfnl_add_product_quantity($step_id,$product_id,$quantity,$variation_id,$variations){ $order_bump_settings = get_post_meta($step_id, 'order-bump-settings', true); $_product = wc_get_product($product_id); $response = array(); if( $_product ){ $product_price = $_product->get_price(); $ob_cart_item_data = [ 'custom_price' => $variation_id ? get_post_meta($variation_id, '_regular_price', true) : $product_price ]; $backorders_allowed = false; $woo_quantity = ''; if($_product->get_type() == 'variable'){ $prdct = wc_get_product($variation_id); $woo_quantity = $prdct ? $prdct->get_stock_quantity() : ''; $backorders_allowed = $prdct->backorders_allowed(); }else{ $woo_quantity = $_product->get_stock_quantity(); $backorders_allowed = $_product->backorders_allowed(); } if($woo_quantity == '' || ( !$backorders_allowed && $woo_quantity >= $quantity ) || $backorders_allowed ){ foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) { if( !isset($cart_item['wpfnl_order_bump']) || !$cart_item['wpfnl_order_bump'] ){ if( !empty($cart_item['variation_id']) ){ if( $cart_item['variation_id'] == $variation_id){ WC()->cart->set_quantity($cart_item_key, $quantity); } }else{ if ( isset($cart_item['product_id']) && $cart_item['product_id'] == $product_id) { WC()->cart->set_quantity($cart_item_key, $quantity); } } } } $response = [ 'status' => 'success', 'message' => __('Successfully added', 'wpfnl'), ]; }else{ $message = sprintf( __('Sorry, we only have %d in stock. You can only order a maximum of %d.', 'wpfnl'), $woo_quantity, $woo_quantity ); $response = [ 'status' => 'fail', 'message' => $message, 'quantity' => $woo_quantity ]; } } return $response; } /** * wpfnl_add_simple_products * Add simple product quantity * * @param $step_id,$product_id,$quantity,$isQuantity */ private function wpfnl_add_simple_product_quantity($step_id,$product_id,$quantity,$isQuantity){ $order_bump_settings = get_post_meta($step_id, 'order-bump-settings', true); $_product = wc_get_product($product_id); $product_price = $_product->get_price(); $ob_cart_item_data = [ 'custom_price' => $product_price, ]; $response = array(); if ($isQuantity){ foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) { if ( $cart_item['product_id'] == $product_id) { WC()->cart->remove_cart_item($cart_item_key); } } } WC()->cart->add_to_cart( $product_id, $quantity, 0, [], $ob_cart_item_data ); $response = [ 'status' => 'success', 'message' => __('Successfully added', 'wpfnl'), ]; return $response; } /** * Update quantity from checkout page * * @param $quantity, $cart_item, $cart_item_key */ public function wpfnl_checkout_cart_item_quantity( $quantity, $cart_item, $cart_item_key ) { $step_id = 0; $isQuantity = 'no'; if( wp_doing_ajax() ) { $checkout_step = Wpfnl_functions::is_funnel_checkout_page(); $step_id = isset($checkout_step['id']) ? $checkout_step['id'] : ''; } else { $step_id = get_the_ID(); } $isQuantity = get_post_meta($step_id, '_wpfnl_quantity_support',true); $order_bump_products = get_post_meta($step_id,'order-bump-settings',true); if($isQuantity === 'yes'){ if( !isset($cart_item['wpfnl_order_bump']) || !$cart_item['wpfnl_order_bump'] ){ $variations = json_encode($cart_item['variation']); $product_id = isset($cart_item["product_id"]) ? $cart_item["product_id"] : ''; $quantity = isset($cart_item["quantity"]) ? $cart_item["quantity"] : 1; $variation_id = isset($cart_item["variation_id"]) ? $cart_item["variation_id"] : ''; $quantityLimit = get_post_meta($step_id, '_wpfnl_quantity_limit', true); $isQuantityLimit = false; $set_quantity = 0; if( isset($quantityLimit['isEnabled']) && $quantityLimit['isEnabled'] === 'yes' ){ $set_quantity = $quantityLimit['quantity']; $isQuantityLimit = true; } if( $isQuantityLimit ){ $quantity = "× "; }else{ $quantity = "× "; } $cookie_name = 'wpfunnels_global_funnel_product'; $data = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $key = array_search($product_id, array_column($data, 'id')); if( false !== $key ){ $data[$key]['quantity'] = isset($cart_item["quantity"]) ? $cart_item["quantity"] : 1; @setcookie( $cookie_name, wp_json_encode( $data ), time() + 3600, '/', COOKIE_DOMAIN ); } } } return $quantity; } /** * */ public function wpfnl_checkout_cart_item_name( $product_get_name, $cart_item, $cart_item_key ){ $step_id = $this->get_step_id(); if( $step_id ){ $funnel_id = get_post_meta($step_id, '_funnel_id', true); $type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true); if('lms' == $type || !Wpfnl_functions::is_wc_active() ){ return false; } $products = get_post_meta($step_id, '_wpfnl_checkout_products', true); if( is_array( $products )){ foreach( $products as $product ){ $__product = wc_get_product( $product['id'] ); if( $__product ){ if( $__product->get_type() == 'variation' ){ $is_perfect_variation = Wpfnl_functions::is_perfect_variations( $product['id'] ); if( !$is_perfect_variation['status'] ){ if( $cart_item['variation_id'] == $product['id'] ){ $select = ''; foreach( $is_perfect_variation['data'] as $key=>$attr ){ $select .= ' , '; $select .= ''; } $product_get_name = $product_get_name.$select; } } } } } } } return $product_get_name; } /** * Step id is get_the_ID() if Checkout is not enable ajax * @return int */ public function get_step_id() { if( get_the_ID() == 1 ){ $values = array(); if( isset($_POST['post_data']) ){ parse_str($_POST['post_data'], $values); } $step_id = isset($values['_wpfunnels_checkout_id']) ? $values['_wpfunnels_checkout_id'] : ''; }else{ $step_id = get_the_ID(); } return $step_id; } /** * Get Custom Woocommerce template * @param $template * @param $template_name * @param $template_path * @return mixed|string */ public static function wpfunnels_woocommerce_locate_template($template, $template_name, $template_path) { if( apply_filters( 'wpfunnels/maybe_locate_template', true ) ) { global $woocommerce; $_template = $template; $plugin_path = WPFNL_DIR . '/woocommerce/templates/'; if (file_exists($plugin_path . $template_name)) { $template = $plugin_path . $template_name; } if (!$template) { $template = $_template; } } return $template; } }public/modules/checkout/classes/class-wpfnl-variable-product.php000064400000034165147600245720021157 0ustar00wpfnl_add_variable_product_to_cart($step_id,$product_id,$quantity,$attributes); if($response){ wp_send_json_success($response); }else{ $response = [ 'status'=>'fail' ]; wp_send_json_success($response); } } /** * wpfnl_add_variable_product_to_cart * Add variation to cart from variable product * * @param $step_id,$product_id,$quantity,$checker,$type */ private function wpfnl_add_variable_product_to_cart($step_id,$product_id,$quantity,$attributes){ $order_bump_settings = get_post_meta($step_id, 'order-bump-settings', true); $response = [ 'status' => 'fail', ]; $_product = wc_get_product($product_id); if( $_product ){ $product = wc_get_product($product_id); foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) { if( !isset($cart_item['product_type']) || (isset($cart_item['product_type']) && ('variable' === $cart_item['product_type'] || 'variation' === $cart_item['product_type'] || 'variable-subscription' === $cart_item['product_type'] ))){ if ( $cart_item['product_id'] == $product_id ) { WC()->cart->remove_cart_item($cart_item_key); } } } $formatted_attr = []; foreach($attributes as $attribute){ foreach($attribute as $key=>$value){ $formatted_attr["attribute_".$key] = $value; } } $variation_id = (new \WC_Product_Data_Store_CPT())->find_matching_product_variation( new \WC_Product($product_id), $formatted_attr ); if($variation_id > 0){ $ob_cart_item_data = [ 'custom_price' => get_post_meta($variation_id, '_price', true) ? get_post_meta($variation_id, '_price', true) : get_post_meta($variation_id, '_regular_price', true) ]; $checkout_products = get_post_meta( $step_id, '_wpfnl_checkout_products', true ); if(is_array( $checkout_products )){ foreach( $checkout_products as $key=>$checkout_product ){ if( $product_id == $checkout_product['id'] ){ if( !empty($checkout_product['discount']['discountOptions']) && !empty($checkout_product['discount']['mutedDiscountValue']) ){ if( 'discount-price' === $checkout_product['discount']['discountOptions'] ){ $ob_cart_item_data['custom_price'] = $ob_cart_item_data['custom_price'] - $checkout_product['discount']['mutedDiscountValue']; }else{ $ob_cart_item_data['custom_price'] = $ob_cart_item_data['custom_price'] - (($ob_cart_item_data['custom_price'] * $checkout_product['discount']['mutedDiscountValue'] ) / 100); } } } } } WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $formatted_attr, $ob_cart_item_data); } $response = [ 'status' => 'success', 'message' => __('Successfully added', 'wpfnl'), ]; } return $response; } /** * wpfnl_variable_ajax * Select multiple/one products from variations * * @since 1.1. */ public function wpfnl_variable_product_ajax() { if( !Wpfnl_functions::is_wc_active() ){ return false; } $step_id = filter_input(INPUT_POST, 'step_id', FILTER_VALIDATE_INT); $product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT); $checker = filter_input(INPUT_POST, 'checker', FILTER_SANITIZE_STRING); $quantity = filter_input(INPUT_POST, 'quantity', FILTER_SANITIZE_STRING); $type = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_STRING); $isQuantity = false; if (isset($_POST['isQuantity'])){ $isQuantity = filter_input(INPUT_POST, 'isQuantity', FILTER_SANITIZE_STRING); } $response = ($type == 'checkbox') ? $this->wpfnl_add_variable_products($step_id,$product_id,$quantity,$checker,'checkbox',$isQuantity) : $this->wpfnl_add_variable_products($step_id,$product_id,$quantity,$checker,'radio',$isQuantity); if($response){ wp_send_json_success($response); }else{ $response = [ 'status'=>'fail' ]; wp_send_json_success($response); } } /** * wpfnl_add_variable_products * Add variation to cart from variable product * * @param $step_id,$product_id,$quantity,$checker,$type */ private function wpfnl_add_variable_products($step_id,$product_id,$quantity,$checker,$type,$isQuantity){ $order_bump_settings = get_post_meta($step_id, 'order-bump-settings', true); $_product = wc_get_product($product_id); $response = array(); if( $_product ){ $discount_type = $order_bump_settings['discountOption']; $discount_apply_to = $_product->is_on_sale() ? 'sale' : 'regular' ; $product_price = $_product->get_price(); $ob_cart_item_data = [ 'custom_price' => $product_price, ]; if ($checker == "true") { $product = wc_get_product($product_id); if( $product ){ $parent_id = $product->get_parent_id(); if($type == 'radio'){ foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) { if ( $cart_item['product_id'] == $parent_id) { WC()->cart->remove_cart_item($cart_item_key); } } } if ($isQuantity){ foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) { if ( $cart_item['variation_id'] == $product_id) { WC()->cart->remove_cart_item($cart_item_key); } } } WC()->cart->add_to_cart( $product_id, $quantity, 0, [], $ob_cart_item_data ); $response = [ 'status' => 'success', 'message' => __('Successfully added', 'wpfnl'), ]; } } elseif ($checker == "false") { foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) { if ($cart_item['variation_id'] == $product_id) { WC()->cart->remove_cart_item($cart_item_key); } } $response = [ 'status' => 'success', 'message' => __('Successfully removed', 'wpfnl'), ]; } } return $response; } /** * Remove optional text from checkout fields * * @param $field, $key, $args, $value */ public function wpfnl_remove_checkout_optional_text( $field, $key, $args, $value ) { if( Wpfnl_functions::is_wc_active() && is_checkout() && ! is_wc_endpoint_url() ) { $optional = ' (' . esc_html__( 'optional', 'wpfnl' ) . ')'; $field = str_replace( $optional, '', $field ); } return $field; } /** * Add custom checkout fields for variable products * * @param $checkout */ public function wpfnl_checkout_field_for_variable($checkout){ $funnel_id = get_post_meta(get_the_ID(), '_funnel_id', true); $type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true); if('lms' == $type || !Wpfnl_functions::is_wc_active() ){ return false; } global $woocommerce; $items = $woocommerce->cart->get_cart(); $products_info = get_post_meta(get_the_ID(), '_wpfnl_checkout_products', true); $products = array(); $i=0; if(!empty($products_info)){ foreach($products_info as $product_info){ $product = wc_get_product($product_info['id']); if( $product ){ $product_id = $product_info['id']; if( 'variable-subscription' === $product->get_type() || 'variable' == $product->get_type() ){ $products[$i]['product'] = $product; $products[$i]['quantity'] = $product_info['quantity']; $i++; } } } $isMultipleProduct = get_post_meta(get_the_ID(), '_wpfnl_multiple_product', true); $isQuantity = get_post_meta(get_the_ID(), '_wpfnl_quantity_support', true); if($isMultipleProduct){ if($isMultipleProduct == 'yes'){ $this->wpfnl_add_fields($checkout,$products,'checkbox',$isQuantity); }else{ $this->wpfnl_add_fields($checkout,$products,'radio',$isQuantity); } } } } /** * Add fields for variable products * * @param $checkout, $products, $type */ private function wpfnl_add_fields($checkout,$products,$type,$isQuantity){ if(!empty($products)){ require WPFNL_PRO_DIR . 'public/modules/checkout/templates/variable-product/header.php'; $attr = []; wp_enqueue_script( 'wc-add-to-cart-variation' ); $supported_types = [ 'variable', 'variable-subscription' ]; foreach($products as $product){ $quantity = $product['quantity']; $product = $product['product']; $variations = $product->get_available_variations(); $product_id = $product->get_id(); if( !in_array( $product->get_type() ,$supported_types) ) { return false; } ?>

get_title() ?>

get_children() ) <= apply_filters( 'woocommerce_ajax_variation_threshold', 30, $product ); $formatted_attr = $this->get_formatted_attributes( $product->get_variation_attributes(), $product->get_attributes() ); // Load the template. wc_get_template( 'single-product/add-to-cart/variable.php', array( 'available_variations' => $get_variations ? $product->get_available_variations() : false, 'attributes' => $formatted_attr, 'selected_attributes' => $product->get_default_attributes(), 'class' => '', 'product' => $product, 'quantity' => $quantity, ) ); } require WPFNL_PRO_DIR . 'public/modules/checkout/templates/variable-product/footer.php'; } } /** * Get formatted attributes. * * This function filters the attributes and returns the formatted variation attributes * based on the provided variation attributes and all attributes. * * @param array $variation_attr The variation attributes. * @param array $all_attr The all attributes. * * @return array The formatted variation attributes. * @since 1.9.3 */ public function get_formatted_attributes( $variation_attr, $all_attr ){ if ( ! is_array( $variation_attr ) || ! is_array( $all_attr ) ) { return []; } $filtered_attributes = array_filter($all_attr, function ($key) { return strpos($key, 'pa_') !== 0; }, ARRAY_FILTER_USE_KEY); foreach ($variation_attr as $index => $attr) { $lowercase_index = strtolower($index); if (array_key_exists($lowercase_index, $filtered_attributes)) { $variation_attr[$index] = $filtered_attributes[$lowercase_index]->get_options(); } } return $variation_attr; } /** * * Render Checkout fields body for variable prosuct * * @param $key,$value,$product,$default_attr */ private function render_checkout_attr_fields($key,$value,$product,$default_attr,$product_id){ require WPFNL_PRO_DIR . 'public/modules/checkout/templates/variable-product/body.php'; } /** * Add fields for variable products * * @param $checkout, $products, $type */ private function wpfnl_add_fields_previous($checkout,$products,$type,$isQuantity){ if(!empty($products)){ require WPFNL_PRO_DIR . 'public/modules/checkout/templates/variable-product/header.php'; foreach($products as $product){ $quantity = $product['quantity']; $product = $product['product']; $variations = $product->get_available_variations(); $i =0; $parent_id = $product->get_id(); if(count($variations)>0){ require WPFNL_PRO_DIR . 'public/modules/checkout/templates/variable-product/table-header.php'; foreach ($variations as $variation) { if($variation['is_in_stock'] && array_search('', $variation['attributes']) == ''){ $isDefVariation = false; $default['default'] = 0; if($type == 'radio'){ if($product->get_default_attributes()){ foreach($product->get_default_attributes() as $key=>$val){ if($variation['attributes']['attribute_'.$key]==$val){ $isDefVariation=true; $default['default'] = $variation['variation_id']; } } }else{ if($i == 0){ $default['default'] = $variation['variation_id']; } } }else{ if($product->get_default_attributes()){ foreach($product->get_default_attributes() as $key=>$val){ if($variation['attributes']['attribute_'.$key]==$val){ $isDefVariation=true; $default['default'] = 1; } } }else{ if($i == 0){ $default['default'] = 1; } } } $this->render_checkout_fields_body_prev($checkout,$variation,$parent_id,$product,$type,$default,$quantity,$isQuantity); $i++; } } require WPFNL_PRO_DIR . 'public/modules/checkout/templates/variable-product/table-footer.php'; } } require WPFNL_PRO_DIR . 'public/modules/checkout/templates/variable-product/footer.php'; } } /** * * Render Checkout fields body for variable prosuct * * @param $checkout,$variation,$parent_id,$product,$type,$default */ private function render_checkout_fields_body_prev($checkout,$variation,$parent_id,$product,$type,$default,$quantity,$isQuantity){ require WPFNL_PRO_DIR . 'public/modules/checkout/templates/variable-product/body.php'; } } public/modules/checkout/templates/single-product/body.php000064400000001063147600245720017711 0ustar00get_title(); ?> get_price() ?> public/modules/checkout/templates/single-product/footer.php000064400000000006147600245720020246 0ustar00
public/modules/checkout/templates/single-product/header.php000064400000000243147600245720020203 0ustar00

public/modules/checkout/templates/single-product/table-footer.php000064400000000010147600245720021326 0ustar00public/modules/checkout/templates/single-product/table-header.php000064400000000365147600245720021275 0ustar00 public/modules/checkout/templates/variable-product/body.php000064400000001600147600245720020212 0ustar00public/modules/checkout/templates/variable-product/footer.php000064400000000006147600245720020552 0ustar00public/modules/checkout/templates/variable-product/header.php000064400000000365147600245720020514 0ustar00

public/modules/checkout/templates/variable-product/product-qty.php000064400000000721147600245720021553 0ustar00
public/modules/checkout/templates/variable-product/product-title.php000064400000000334147600245720022057 0ustar00public/modules/checkout/templates/variable-product/table-footer.php000064400000000010147600245720021632 0ustar00
$value, 'attribute' => $key, 'product' => $product, 'class' => 'wpfnl-variable-attribute', 'selected' => $default_attr ? $default_attr : $value[0], ) ); ?>

get_title() ?>

public/modules/checkout/templates/variable-product/table-header.php000064400000000072147600245720021574 0ustar00 public/modules/checkout/templates/variable-product-prev/body.php000064400000004101147600245720021163 0ustar00get_title(); $formatted_name = $title.' - '.ucfirst(implode(" , ", $variation_product->get_variation_attributes())); ?> public/modules/checkout/templates/variable-product-prev/footer.php000064400000000006147600245720021524 0ustar00public/modules/checkout/templates/variable-product-prev/header.php000064400000000243147600245720021461 0ustar00

public/modules/checkout/templates/variable-product-prev/table-footer.php000064400000000010147600245720022604 0ustar00
$type, 'default' => $default['default'], 'custom_attributes' => array( 'data-id' => $variation['variation_id'], 'data-qty' => $quantity, ), 'options' => array($variation['variation_id']=>$formatted_name), 'class' => array( 'form-row-wide wpfnl-product-variation' ), ) ,$checkout->get_value('wpfnl-product-variation_'.$parent_id)); }else{ woocommerce_form_field('wpfnl-product-variation_'.$parent_id, array( 'type' => $type, 'label' => ''.$formatted_name .'', 'default' => $default['default'], 'custom_attributes' => array( 'data-id' => $variation['variation_id'], 'data-qty' => $quantity, ), 'class' => array( 'form-row-wide wpfnl-product-variation' ), ) ,$checkout->get_value('wpfnl-product-variation_'.$parent_id)); } ?>
public/modules/checkout/templates/variable-product-prev/table-header.php000064400000000463147600245720022552 0ustar00 public/modules/checkout/templates-style/order-bump-template-popup.php000064400000010013147600245720022174 0ustar00get_regular_price(); $sale_price = $product->get_sale_price(); $price = $product->get_price_html(); if (isset($order_bump_settings['discountOption'])) { if ($order_bump_settings['discountOption'] == "discount-price" || $order_bump_settings['discountOption'] == "discount-percentage") { if ($order_bump_settings['discountapply'] == 'regular') { $price = wc_format_sale_price($regular_price, $order_bump_settings['discountPrice']); } else { $price = wc_format_sale_price($sale_price, $order_bump_settings['discountPrice']); } } } ?>

Price:

Order bump popup image
public/modules/checkout/templates-style/order-bump-template-style1.php000064400000005067147600245720022267 0ustar00get_regular_price(); $sale_price = $product->get_sale_price(); $price = $product->get_price_html(); if (isset($settings['discountOption'])) { if ($settings['discountOption'] == "discount-price" || $settings['discountOption'] == "discount-percentage") { if ($settings['discountapply'] == 'regular') { $price = wc_format_sale_price( $regular_price, $settings['discountPrice'] ); } else { $price = wc_format_sale_price( $sale_price, $settings['discountPrice'] ); } } } ?>

Price: ' . $price . ''; ?>
public/modules/checkout/templates-style/order-bump-template-style2.php000064400000006136147600245720022266 0ustar00get_regular_price(); $sale_price = $product->get_sale_price(); $price = $product->get_price_html(); if (isset($settings['discountOption'])) { if ($settings['discountOption'] == "discount-price" || $settings['discountOption'] == "discount-percentage") { if ($settings['discountapply'] == 'regular') { $price = wc_format_sale_price( $regular_price, $settings['discountPrice'] ); } else { $price = wc_format_sale_price( $sale_price, $settings['discountPrice'] ); } } } ?>

public/modules/checkout/class-wpfnl-pro-checkout.php000064400000004620147600245720016653 0ustar00set_checkout_cookie(); $additional_settings = get_post_meta( $step_id, '_wpfunnels_edit_field_additional_settings', true ); if( isset($additional_settings['custom_order_text']) ){ $button = ''; } } } return $button; } private function set_checkout_cookie(){ $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[$cookie_name] ) ? json_decode( wp_unslash( $_COOKIE[$cookie_name] ), true ) : array(); $cookie['orderbump_accepted'] = false; if( !isset($cookie['ob_accepetd_products']) ){ $cookie['ob_accepetd_products'] = []; } setcookie( $cookie_name, wp_json_encode( $cookie ), time() + 3600 * 6, '/', COOKIE_DOMAIN ); } } public/modules/dynamic-offer-templates/bricks/offer-button.php000064400000014167147600245720020631 0ustar00
'.$settings['variation_tbl_title'].''; } echo '
'; echo '
'; if( isset( $settings['show_product_price'] ) && $settings['show_product_price'] ){ echo ''; } $post_id = get_the_ID(); echo do_shortcode( '[wpf_variable_offer post_id="'.$post_id.'"]' ); echo '
'; } ?>
get_regular_price() * $response['quantity'] : $offer_product->get_regular_price(); if( isset($discount['discountApplyTo'], $discount['discountType']) && 'original' !== $discount['discountType'] ){ if( 'sale' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_sale_price() ? $offer_product->get_sale_price() : $offer_product->get_regular_price(); }elseif( 'regular' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_regular_price() ? $offer_product->get_regular_price() : $offer_product->get_price(); }else{ $sale_price = $offer_product->get_price(); } $product_price = \WPFunnelsPro\Wpfnl_Pro_functions::calculate_discount_price_for_widget( $discount['discountType'] , $discount['discountValue'], $sale_price ); if( $product_price != $total_price ){ echo wc_price(number_format( (float) $product_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $product_price, 2, '.', '' )); } }else{ if( $offer_product->get_sale_price() ){ $sale_price = $offer_product->get_sale_price(); $sale_price = isset($response['quantity']) ? $sale_price * $response['quantity'] : $sale_price; echo wc_price(number_format( (float) $sale_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $total_price, 2, '.', '' )); } } } } ?> render_attributes( 'offer-button' ); ?> >
'; //end ".has-variation-product" } ?>
'.$props['variation_tbl_title'].''; } echo '
'; echo '
'; if( 'yes' === $props['show_product_price'] ){ echo ''; } if( $step_id ){ echo do_shortcode( '[wpf_variable_offer post_id="'.$step_id.'"]' ); }else{ echo do_shortcode( '[wpf_variable_offer]' ); } echo '
'; } ?>
get_regular_price() * $response['quantity'] : $offer_product->get_regular_price(); if( isset($discount['discountApplyTo'], $discount['discountType']) && 'original' !== $discount['discountType'] ){ if( 'sale' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_sale_price() ? $offer_product->get_sale_price() : $offer_product->get_regular_price(); }elseif( 'regular' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_regular_price() ? $offer_product->get_regular_price() : $offer_product->get_price(); }else{ $sale_price = $offer_product->get_price(); } $product_price = \WPFunnelsPro\Wpfnl_Pro_functions::calculate_discount_price_for_widget( $discount['discountType'] , $discount['discountValue'], $sale_price ); if( $product_price != $total_price ){ echo wc_price(number_format( (float) $product_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $product_price, 2, '.', '' )); } }else{ if( $offer_product->get_sale_price() ){ $sale_price = $offer_product->get_sale_price(); $sale_price = isset($response['quantity']) ? $sale_price * $response['quantity'] : $sale_price; echo wc_price(number_format( (float) $sale_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $total_price, 2, '.', '' )); } } } } ?>
'; //end ".has-variation-product" } ?>
public/modules/dynamic-offer-templates/elementor/offer-button.php000064400000012774147600245720021350 0ustar00
get_render_attribute_string('wrapper'); ?> >
'.$settings['variation_tbl_title'].''; } echo '
'; echo '
'; if( isset( $settings['show_product_price'] ) && 'yes' === $settings['show_product_price'] ){ echo ''; } $post_id = get_the_ID(); echo do_shortcode( '[wpf_variable_offer post_id="'.$post_id.'"]' ); echo '
'; } ?>
get_regular_price() * $response['quantity'] : $offer_product->get_regular_price(); if( isset($discount['discountApplyTo'], $discount['discountType']) && 'original' !== $discount['discountType'] ){ if( 'sale' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_sale_price() ? $offer_product->get_sale_price() : $offer_product->get_regular_price(); }elseif( 'regular' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_regular_price() ? $offer_product->get_regular_price() : $offer_product->get_price(); }else{ $sale_price = $offer_product->get_price(); } $product_price = \WPFunnelsPro\Wpfnl_Pro_functions::calculate_discount_price_for_widget( $discount['discountType'] , $discount['discountValue'], $sale_price ); if( $product_price != $total_price ){ echo wc_price(number_format( (float) $product_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $product_price, 2, '.', '' )); } }else{ if( $offer_product->get_sale_price() ){ $sale_price = $offer_product->get_sale_price(); $sale_price = isset($response['quantity']) ? $sale_price * $response['quantity'] : $sale_price; echo wc_price(number_format( (float) $sale_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $total_price, 2, '.', '' )); } } } } ?> get_render_attribute_string('button'); ?> id="wpfunnels__" data-offertype="" > render_text(); ?>
'; //end ".has-variation-product" } ?>
'.$attributes['variationTblTitle'].''; } ?>
'; } echo do_shortcode('[wpf_variable_offer]'); ?>
get_regular_price() * $response['quantity'] : $offer_product->get_regular_price(); if( isset($discount['discountApplyTo'], $discount['discountType']) && 'original' !== $discount['discountType'] ){ if( 'sale' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_sale_price() ? $offer_product->get_sale_price() : $offer_product->get_regular_price(); }elseif( 'regular' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_regular_price() ? $offer_product->get_regular_price() : $offer_product->get_price(); }else{ $sale_price = $offer_product->get_price(); } $product_price = \WPFunnelsPro\Wpfnl_Pro_functions::calculate_discount_price_for_widget( $discount['discountType'] , $discount['discountValue'], $sale_price ); if( $product_price != $total_price ){ echo wc_price(number_format( (float) $product_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $product_price, 2, '.', '' )); } }else{ if( $offer_product->get_sale_price() ){ $sale_price = $offer_product->get_sale_price(); $sale_price = isset($response['quantity']) ? $sale_price * $response['quantity'] : $sale_price; echo wc_price(number_format( (float) $sale_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $total_price, 2, '.', '' )); } } } } } ?>
'; //end ".has-variation-product" } ?>
public/modules/dynamic-offer-templates/gutenberg/offer-style1.php000064400000001460147600245720021234 0ustar00
product-img

public/modules/dynamic-offer-templates/gutenberg/offer-style2.php000064400000001510147600245720021231 0ustar00
product-img

public/modules/dynamic-offer-templates/gutenberg/offer-style3.php000064400000001510147600245720021232 0ustar00
product-img

public/modules/dynamic-offer-templates/oxygen/offer-button.php000064400000012011147600245720020647 0ustar00
'.$variation_tbl_title.''; } echo '
'; echo '
'; if( 'yes' === $show_product_price ){ echo ''; } echo do_shortcode( '[wpf_variable_offer post_id="'.$step_id.'"]' ); echo '
'; } ?>
get_regular_price() * $response['quantity'] : $offer_product->get_regular_price(); if( isset($discount['discountApplyTo'], $discount['discountType']) && 'original' !== $discount['discountType'] ){ if( 'sale' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_sale_price() ? $offer_product->get_sale_price() : $offer_product->get_regular_price(); }elseif( 'regular' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_regular_price() ? $offer_product->get_regular_price() : $offer_product->get_price(); }else{ $sale_price = $offer_product->get_price(); } $product_price = \WPFunnelsPro\Wpfnl_Pro_functions::calculate_discount_price_for_widget( $discount['discountType'] , $discount['discountValue'], $sale_price ); if( $product_price != $total_price ){ echo wc_price(number_format( (float) $product_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $product_price, 2, '.', '' )); } }else{ if( $offer_product->get_sale_price() ){ $sale_price = $offer_product->get_sale_price(); $sale_price = isset($response['quantity']) ? $sale_price * $response['quantity'] : $sale_price; echo wc_price(number_format( (float) $sale_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $total_price, 2, '.', '' )); } } } } ?>
'; //end ".has-variation-product" } ?>
public/modules/dynamic-offer-templates/shortcode/offer-button.php000064400000012476147600245720021347 0ustar00
attributes['action'] ) { if( isset($this->attributes['variation_tbl_title']) && !empty($this->attributes['variation_tbl_title']) ){ echo '
'.$this->attributes['variation_tbl_title'].'
'; } echo '
'; echo '
'; if( 'yes' === $this->attributes['show_product_price'] ){ echo ''; } if( $step_id ){ echo do_shortcode( '[wpf_variable_offer post_id="'.$step_id.'"]' ); }else{ echo do_shortcode( '[wpf_variable_offer]' ); } echo '
'; } ?>
attributes['show_product_price'] ){ ?> attributes['action'] ){ if( $offer_product ){ $step_type = get_post_meta( get_the_ID(), '_step_type', true ); $discount = get_post_meta( get_the_ID(), '_wpfnl_'.$step_type.'_discount', true ); $total_price = isset($response['quantity']) ? $offer_product->get_regular_price() * $response['quantity'] : $offer_product->get_regular_price(); if( isset($discount['discountApplyTo'], $discount['discountType']) && 'original' !== $discount['discountType'] ){ if( 'sale' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_sale_price() ? $offer_product->get_sale_price() : $offer_product->get_regular_price(); }elseif( 'regular' === $discount['discountApplyTo'] ){ $sale_price = $offer_product->get_regular_price() ? $offer_product->get_regular_price() : $offer_product->get_price(); }else{ $sale_price = $offer_product->get_price(); } $product_price = \WPFunnelsPro\Wpfnl_Pro_functions::calculate_discount_price_for_widget( $discount['discountType'] , $discount['discountValue'], $sale_price ); if( $product_price != $total_price ){ echo wc_price(number_format( (float) $product_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $product_price, 2, '.', '' )); } }else{ if( $offer_product->get_sale_price() ){ $sale_price = $offer_product->get_sale_price(); $sale_price = isset($response['quantity']) ? $sale_price * $response['quantity'] : $sale_price; echo wc_price(number_format( (float) $sale_price, 2, '.', '' )).''.wc_price(number_format( (float) $total_price, 2, '.', '' )).''; }else{ echo wc_price(number_format( (float) $total_price, 2, '.', '' )); } } } } ?> attributes["class"].'"'; $html .= 'id="wpfunnels_'.$this->attributes["offer_type"].'_'.$this->attributes["action"].'"'; $html .= 'data-offertype="'.$this->attributes["offer_type"].'">'; $html .= $this->render_text( $this->attributes['btn_text'] ? $this->attributes['btn_text'] : 'Accept' ); $html .= '
'; echo $html; ?>
attributes['action'] ) { echo '
'; //end ".has-variation-product" } ?>
attributes['action'] && \WPFunnelsPro\Wpfnl_Pro_functions::maybe_unsupported_payment_gateway() ){ /** * Fires after the offer button is displayed. * * This action hook allows developers to add custom functionality after the offer button is rendered on the page. * * @since 2.0.5 * * @param void */ do_action( 'wpfunnels/after_offer_button' ); } ?>public/modules/dynamic-offer-templates/styles/offer-style1.php000064400000001644147600245720020601 0ustar00
product-img

get_title() : '' ?>

get_data()['short_description']) ? $offer_product->get_data()['short_description'] : '' ?>
public/modules/dynamic-offer-templates/styles/offer-style2.php000064400000001711147600245720020575 0ustar00
product-img

get_title() : '' ?>

get_data()['short_description']) ? $offer_product->get_data()['short_description'] : '' ?>
public/modules/dynamic-offer-templates/styles/offer-style3.php000064400000001700147600245720020574 0ustar00
product-img

get_title() : '' ?>

get_data()['short_description']) ? $offer_product->get_data()['short_description'] : '' ?>
public/modules/thank-you/templates/child-orders.php000064400000010214147600245720016504 0ustar00get_items( apply_filters( 'woocommerce_purchase_order_item_types', 'line_item' ) ); $show_purchase_note = $order->has_status( apply_filters( 'woocommerce_purchase_note_order_statuses', array( 'completed', 'processing' ) ) ); $show_customer_details = is_user_logged_in() && $order->get_user_id() === get_current_user_id(); $downloads = $order->get_downloadable_items(); $show_downloads = $order->has_downloadable_item() && $order->is_download_permitted(); if ( $show_downloads ) { wc_get_template( 'order/order-downloads.php', array( 'downloads' => $downloads, 'show_title' => true, ) ); } ?>

$item ) { $product = $item->get_product(); wc_get_template( 'order/order-details-item.php', array( 'order' => $order, 'item_id' => $item_id, 'item' => $item, 'show_purchase_note' => $show_purchase_note, 'purchase_note' => $product ? $product->get_purchase_note() : '', 'product' => $product, ) ); } do_action( 'woocommerce_order_details_after_order_table_items', $order ); ?> get_order_item_totals() as $key => $total ) { ?> get_customer_note() ) : ?>
get_customer_note() ) ) ); ?>
get_id(), '_wpfunnels_offer_child_orders', true); if( !$child_orders ){ $child_orders = $order->get_meta('_wpfunnels_offer_child_orders'); } if($child_orders) { foreach ($child_orders as $order_id => $child_order) { include plugin_dir_path(__FILE__).'templates/child-orders.php'; } } } /** * @desc Update the main order total price in thank you page * @param $formatted_total * @param \WC_Order $order * @return mixed|string */ public function update_main_order_total( $formatted_total, \WC_Order $order ) { if( $order && !is_wp_error( $order ) && !$order->get_parent_id() ) { $child_orders = get_post_meta( $order->get_id(), '_wpfunnels_offer_child_orders', true ); if( !$child_orders ){ $child_orders = $order->get_meta('_wpfunnels_offer_child_orders'); } $order_total = $order->get_total(); if( $child_orders ) { foreach( $child_orders as $order_id => $offer_type ) { $child_order = wc_get_order( $order_id ); if( $child_order ) { $refund_order = $child_order->get_total_refunded(); $order_total += $child_order->get_total(); $order_total = $order_total - $refund_order; } } } $refund_main_order = $order->get_total_refunded(); $order_total = $order_total - $refund_main_order; $formatted_total = wc_price( $order_total, array( 'currency' => $order->get_currency() ) ); } return $formatted_total; } }public/modules/upsell/templates/upsell.php000064400000000253147600245720015020 0ustar00
$order ]); ?>
public/modules/upsell/class-wpfnl-upsell.php000064400000174564147600245720015272 0ustar00handle('wpfnl-get-variation-id') ->with_callback([ $this, 'wpfnl_get_variation_id' ]); } /** * Removes the checkout validation for the upsell module. * This is required to prevent the checkout validation from running on the offer page. * * @param array $data The data submitted during checkout. * @param array $errors The validation errors. * @return array The modified data and errors. * @since 2.0.5 */ public function remove_checkout_validation($data, $errors) { if ( isset( $_POST['wpfnl_offer_page'] ) ) { $errors->errors = []; $errors->error_messages = []; $errors->error_data = []; } } /** * Adds the offer product to the cart. * This method is used to add the offer product to the cart when the user clicks on the offer button. * * @return void * @since 2.0.5 */ public function add_offer_product_to_cart(){ $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0; $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0; $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : 0; $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; if( isset( $_POST['is_variable'], $_POST['attr'] ) && $_POST['is_variable'] == 'true' ){ $payload = [ 'product_id' => $product_id, 'data' => $_POST['attr'], ]; $variation_id = $this->wpfnl_get_variation_id($payload); } $total = $this->get_discounted_price( $step_id, $product_id, $quantity ); $total = floatval($total)/intval($quantity); $custom_data = [ 'custom_price' => $total, ]; \WC()->cart->empty_cart(); \WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, [], $custom_data); $result = [ 'success' => true, ]; wp_send_json($result); } /** * Calculates the discounted price for a given step, product, and quantity. * * @param int $step_id The ID of the step. * @param int $product_id The ID of the product. * @param int $quantity The quantity of the product. * @return float The discounted price. * @since 2.0.5 */ public function get_discounted_price( $step_id, $product_id, $quantity ){ $discount_instance = new WpfnlDiscount(); $type = get_post_meta($step_id, '_step_type', true); $discount_data = get_post_meta($step_id, '_wpfnl_'.$type.'_discount', true); $product = wc_get_product( $product_id ); $total = isset($discount_data['discountApplyTo']) && 'regular' === $discount_data['discountApplyTo'] ? $product->get_regular_price() : $product->get_sale_price(); $total = !$total ? $product->get_price() : $total; $total = floatval($total) * intval($quantity); if( $discount_instance->maybe_time_bound_discount( $step_id ) && !$discount_instance->maybe_validate_discount_time( $step_id ) ) { return $total; } if( isset($discount_data['discountType']) && $discount_data['discountType'] && 'original' !== $discount_data['discountType'] ){ $discount_amount = $discount_instance->get_discount_amount( $discount_data['discountType'], $discount_data['discountValue'], $total ); $discount_amount = filter_var($discount_amount, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); $discount_amount = floatval($discount_amount); $total = floatval($total) - floatval($discount_amount); } return $total; } /** * Saves the checkout fields for an order. * This method is used to save the offer details on the order. * * @param int $order_id The ID of the order. * @param array $posted The posted data from the checkout form. * @return void * @since 2.0.5 */ public function save_checkout_fields( $order_id, $posted ){ if ( isset( $_POST['wpfnl_offer_page'] ) ) { $order = wc_get_order($order_id); $step_id = isset($_POST['wpfnl_step_id']) ? $_POST['wpfnl_step_id'] : 0; $funnel_id = isset($_POST['wpfnl_funnel_id']) ? $_POST['wpfnl_funnel_id'] : 0; $type = isset($_POST['wpfnl_offer_type']) ? $_POST['wpfnl_offer_type'] : ''; $parent_order_id = isset($_POST['wpfnl_parent_order_id']) ? $_POST['wpfnl_parent_order_id'] : 0; $parent_order_key = isset($_POST['wpfnl_parent_order_key']) ? $_POST['wpfnl_parent_order_key'] : ''; $order->update_meta_data('_wpfunnels_offer', 'yes'); $order->update_meta_data('_wpfunnels_order', 'yes'); $order->update_meta_data('_wpfunnels_offer_type', sanitize_text_field($type)); $order->update_meta_data('_wpfunnels_parent_funnel_id', intval($funnel_id)); $order->update_meta_data('_wpfunnels_funnel_id', intval($funnel_id)); $order->update_meta_data('_wpfunnels_offer_step_id', intval($step_id)); $order->update_meta_data('_wpfunnels_offer_parent_id', intval($parent_order_id)); $parent_order = wc_get_order($parent_order_id); $order->update_meta_data('_wpfunnels_offer_parent_key', $parent_order_key); $offer_orders_meta = $parent_order->get_meta('_wpfunnels_offer_child_orders'); if ( !is_array($offer_orders_meta) ) { $offer_orders_meta = []; } $offer_orders_meta[$order->get_id()] = ['type' => $type]; $parent_order->update_meta_data('_wpfunnels_offer_child_orders', $offer_orders_meta); $parent_order->save(); $order->calculate_totals(); $order->save(); $actual_product_id = ''; $quantity = ''; foreach ($order->get_items() as $item_id => $item) { $product_id = $item->get_product_id(); $variation_id = $item->get_variation_id(); $actual_product_id = $variation_id ? $variation_id : $product_id; $quantity = $item->get_quantity(); break; // Exit the loop after the first iteration } $offer_product = Wpfnl_Pro_functions::update_variation_product_details( $step_id, $actual_product_id, $quantity, $order_id ); /** * $order /WC_Order object * $step_type String * $offer_product Array * $offer_product['step_id'] String represent the associate step id * $offer_product['id'] String represent the id of the product * $offer_product['name'] String represent the name of the product * $offer_product['desc'] String represent the description of the product * $offer_product['qty'] String represent the quantity of the product * $offer_product['original_price'] String represent the price of the product * $offer_product['unit_price'] String represent the unit_price of the product * $offer_product['unit_price_tax'] String represent the unit_price with tax of the product * $offer_product['args'] Array represent the extra arguments of the product * $offer_product['args']['subtotal'] String represent the subtotal of the product * $offer_product['args']['total'] String represent the total of the product * $offer_product['price'] String represent the price of the product * $offer_product['url'] String represent the url of the product * $offer_product['total_unit_price_amount'] String represent the $unit_price_tax * $product_qty of the product * $offer_product['total'] String represent the $custom_price of the product if any * $offer_product['cancel_main_order'] Bool checker if cancel main order is enabled/disabled from funnel settings */ ob_start(); do_action( 'wpfunnels/offer_accepted', $order, $offer_product ); ob_get_clean(); } } /** * Executes before the offer checkout process. * This method is used to set the billing and shipping address on the new order. * * @param int $order_id The ID of the order. * @param array $posted_data The data posted during checkout. * @return void * @since 2.0.5 */ public function before_offer_checkout_process($order_id, $posted_data) { if ( isset( $_POST['wpfnl_offer_page']) && isset( WC()->session ) ) { $parent_order_id = $_POST['wpfnl_parent_order_id']; if ($order_id) { $this->set_address_on_new_order($order_id, $parent_order_id); } } } /** * Sets the address on a new order based on the previous order. * * @param int $order_id The ID of the new order. * @param int $previous_order_id The ID of the previous order. * @return void * @since 2.0.5 */ private function set_address_on_new_order($order_id, $previous_order_id) { $order = wc_get_order($order_id); if ( false === is_a( $order, 'WC_Order' ) ) { return; // Order not found } $address = $this->get_address_from_previous_order($previous_order_id); if ($address) { // Set billing and shipping addresses $order->set_address($address['billing'], 'billing'); $order->set_address($address['shipping'], 'shipping'); // Save the order $order->save(); } } /** * Retrieves the address from a previous order. * * @param int $previous_order_id The ID of the previous order. * @return array|null The address details if found, null otherwise. * @since 2.0.5 */ private function get_address_from_previous_order($previous_order_id) { $previous_order = wc_get_order($previous_order_id); if ( false === is_a( $previous_order, 'WC_Order' ) ) { return false; // Previous order not found } // Get address from previous order return array( 'billing' => $previous_order->get_address('billing'), 'shipping' => $previous_order->get_address('shipping') ); } /** * Makes the offer page act as a checkout page. * This is required for some payment gateways to work properly. * * @param bool $isCheckout Whether the offer page should act as a checkout page. * @return void * @since 2.0.5 */ public function make_offer_page_as_checkout($isCheckout){ if( Wpfnl_Pro_functions::is_upsell_downsell_step() && Wpfnl_Pro_functions::maybe_unsupported_payment_gateway() ){ return true; } return $isCheckout; } /** * Sets the current step ID for the upsell module. * This is required for some payment gateways to work properly. * * @param int $step_id The ID of the current step. * @return int The ID of the current step. * @since 2.0.5 */ public function current_step_id( $step_id, $order ){ $current_step_id = $order->get_meta('_wpfunnels_offer_step_id'); if( $current_step_id ){ return $current_step_id; } return $step_id; } /** * Updates the order key. * This is required for some payment gateways to work properly. * * @param array $query_args The query arguments. * @param object $order The order object. * @return array The updated query arguments. * @since 2.0.5 */ public function update_order_key( $query_args, $order ){ $parent_order_id = $order->get_meta('_wpfunnels_offer_parent_id'); $parent_order_key = $order->get_meta('_wpfunnels_offer_parent_key'); if( $parent_order_id && $parent_order_key ){ $query_args = array( 'wpfnl-order' => $parent_order_id, 'wpfnl-key' => $parent_order_key, 'key' => $parent_order_key, ); } return $query_args; } /** * This method is called after the offer button is displayed. * It can be used to perform additional actions or modifications. * * @return void * @since 2.0.5 */ public function after_offer_button(){ global $post; $step_id = $post->ID; $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); $offer_type = get_post_meta( $step_id, '_step_type', true ); $order_id = ( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0; $order_key = ( isset( $_GET['wpfnl-key'] ) ) ? sanitize_text_field( wp_unslash( $_GET['wpfnl-key'] ) ) : ''; $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, '', '', $order_id ); $quantity = 0; $product_id = ''; if ( is_array($offer_product) && !empty($offer_product)) { $product_id = $offer_product['id']; $quantity = $offer_product['qty']; } $is_variable = false; if( !$product_id ){ return; } \WC()->cart->empty_cart(); \WC()->cart->add_to_cart( $product_id, $quantity, 0, []); ?> start_upsell_downsell_process( $order ); } /** * start the upsell/downsell process * * @param $order * * @since 1.0.0 */ private function start_upsell_downsell_process($order) { if ( ! is_object( $order ) ) { return; } $order_gateway = $order->get_payment_method(); $payment_gateway_obj = Wpfnl_Pro::instance()->payment_gateways->build_gateway($order_gateway); } /** * enqueue upsell/downsell offer * scripts * * @since 1.0.0 */ public function load_offer_scripts() { if( Wpfnl_functions::is_wc_active() && Wpfnl_Pro_functions::is_upsell_downsell_step() ) { global $post; $step_id = $post->ID; $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); $step_type = get_post_meta( $step_id, '_step_type', true ); $order_id = ( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0; $order_key = ( isset( $_GET['wpfnl-key'] ) ) ? sanitize_text_field( wp_unslash( $_GET['wpfnl-key'] ) ) : ''; $order = wc_get_order( $order_id ); $payment_method = ''; $is_reference_transaction = false; $skip_offer = 'no'; $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, '', '', $order_id ); $maybe_unsupported_payment = Wpfnl_Pro_functions::maybe_unsupported_payment_gateway(); $quantity = 0; $product_id = ''; if ( is_array($offer_product) && !empty($offer_product)) { $product_id = $offer_product['id']; $quantity = $offer_product['qty']; } $is_variable = false; if( $product_id ){ $product = wc_get_product( $product_id ); if( $product && $product->get_type() === 'variable' ){ $is_variable = true; } } if($order) { $gateways = array( 'paypal','ppec_paypal' ); $payment_method = $order->get_payment_method(); // for now we ignore the paypal reference transactions. In future there will be a settings // for paypal reference transaction. User can define if they want to use paypal reference transaction // or not. if ( ( in_array( $payment_method, $gateways ) ) && !$is_reference_transaction ) { $skip_offer = 'yes'; } } $currency_symbol = get_woocommerce_currency_symbol(); $localize_data = array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'step_id' => $step_id, 'funnel_id' => $funnel_id, 'is_lms' => get_post_meta($funnel_id, '_wpfnl_funnel_type', true) === 'lms' ? 'yes' : 'no', 'product_id' => $product_id, 'is_variable' => $is_variable, 'quantity' => $quantity, 'order_id' => $order_id, 'order_key' => $order_key, 'offer_type' => get_post_meta($step_id, '_step_type', true), 'currency_symbol' => $currency_symbol, 'skip_offer' => $skip_offer, 'wpfnl_offer_nonce' => wp_create_nonce( 'wpfnl_offer_nonce' ), 'wpfnl_upsell_accepted_nonce' => wp_create_nonce( 'wpfnl_upsell_accepted_nonce' ), 'wpfnl_upsell_rejected_nonce' => wp_create_nonce( 'wpfnl_upsell_rejected_nonce' ), 'wpfnl_downsell_accepted_nonce' => wp_create_nonce( 'wpfnl_downsell_accepted_nonce' ), 'wpfnl_downsell_rejected_nonce' => wp_create_nonce( 'wpfnl_downsell_rejected_nonce' ), 'wpfnl_create_paypal_order_nonce'=> wp_create_nonce( 'wpfnl_create_paypal_order_nonce' ), 'wpfnl_capture_paypal_order_nonce'=> wp_create_nonce( 'wpfnl_capture_paypal_order_nonce' ), 'payment_gateway' => $payment_method, 'maybe_unsupported_payment' => $maybe_unsupported_payment ? 'yes' : 'no', ); if ( 'stripe' === $payment_method ) { $localize_data['wpfnl_stripe_sca_check_nonce'] = wp_create_nonce( 'wpfnl_stripe_sca_check_nonce' ); wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true ); wp_enqueue_script( 'stripe' ); } if ( 'woocommerce_payments' === $payment_method ) { $localize_data['wpfunnels_woop_create_payment_intent'] = wp_create_nonce( 'wpfunnels_woop_create_payment_intent' ); wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true ); wp_enqueue_script( 'stripe' ); } if ( 'mollie_wc_gateway_creditcard' === $payment_method ) { $localize_data['wpfnl_mollie_cc_process_nonce'] = wp_create_nonce( 'wpfnl_mollie_cc_process_nonce' ); } if ( 'mollie_wc_gateway_creditcard' === $payment_method ) { $localize_data['wpfnl_mollie_ideal_process_nonce'] = wp_create_nonce( 'wpfnl_mollie_ideal_process_nonce' ); } $localize_script = ''; $localize_script .= ''; echo $localize_script; }else{ global $post; $step_id = ''; if( $post ){ $step_id = $post->ID; } $funnel_id = get_post_meta( $step_id, '_funnel_id', true ); if( $funnel_id ){ $localize_data = array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'step_id' => $step_id, 'funnel_id' => $funnel_id, 'is_lms' => get_post_meta($funnel_id, '_wpfnl_funnel_type', true) === 'lms' ? 'yes' : 'no', 'wpfnl_offer_nonce' => wp_create_nonce( 'wpfnl_offer_nonce' ), 'wpfnl_upsell_accepted_nonce' => wp_create_nonce( 'wpfnl_upsell_accepted_nonce' ), 'wpfnl_upsell_rejected_nonce' => wp_create_nonce( 'wpfnl_upsell_rejected_nonce' ), 'wpfnl_downsell_accepted_nonce' => wp_create_nonce( 'wpfnl_downsell_accepted_nonce' ), 'wpfnl_downsell_rejected_nonce' => wp_create_nonce( 'wpfnl_downsell_rejected_nonce' ), 'wpfnl_create_paypal_order_nonce'=> wp_create_nonce( 'wpfnl_create_paypal_order_nonce' ), 'wpfnl_capture_paypal_order_nonce'=> wp_create_nonce( 'wpfnl_capture_paypal_order_nonce' ), ); $localize_script = ''; $localize_script .= ''; echo $localize_script; } } } /** * Process upsell accept ajax action * * @since 1.0.0 */ public function process_upsell_accepted_action() { $nonce = filter_input( INPUT_POST, 'security', FILTER_SANITIZE_STRING ); if ( $nonce && ! wp_verify_nonce( $nonce, 'wpfnl_upsell_accepted_nonce' ) ) { return array( 'status' => 'success', 'message' => __( 'Unauthorized request', 'wpfnl-pro' ) ); } $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0; $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash($_POST['order_key']) ) : ''; $offer_type = isset( $_POST['offer_type'] ) ? sanitize_text_field( $_POST['offer_type'] ) : ''; $offer_action = isset( $_POST['offer_action'] ) ? sanitize_text_field( $_POST['offer_action'] ) : ''; $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0; $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0; $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : 0; $step_type = get_post_meta($step_id, '_step_type', true); $attr = []; if( isset( $_POST['is_variable'], $_POST['attr'] ) && $_POST['is_variable'] == 'true' ){ $payload = [ 'product_id' => $product_id, 'data' => $_POST['attr'], ]; $attr = $_POST['attr']; $product_id = $this->wpfnl_get_variation_id($payload); } if( $variation_id ){ $product_id = $variation_id; } $order = wc_get_order($order_id); $wpfnl_functions_instance = new Wpfnl_functions(); if( method_exists($wpfnl_functions_instance,'is_valid_order_owner')){ if (!Wpfnl_functions::is_valid_order_owner($order)) { wp_send_json([ 'status' => 'error', 'message' => __( 'Unauthorized request', 'wpfnl-pro' ), ]); } } $all_products = []; $product_data = array(); if ( $order_id && $product_id ) { $data = array( 'order_id' => $order_id, 'product_id' => $product_id, 'quantity' => $quantity, 'order_key' => $order_key, 'step_type' => $step_type, 'step_id' => $step_id, 'funnel_id' => $funnel_id, ); $result = $this->offer_accepted( $step_id, $data , $attr ); wp_send_json($result); } else { wp_send_json([ 'status' => 'error', 'message' => 'No product data found', ]); } } /** * Process downsell accept ajax action * * @since 1.0.0 */ public function process_downsell_accepted_action() { $nonce = filter_input( INPUT_POST, 'security', FILTER_SANITIZE_STRING ); if ( $nonce && ! wp_verify_nonce( $nonce, 'wpfnl_downsell_accepted_nonce' ) ) { return array( 'status' => 'success', 'message' => __( 'Unauthorized request', 'wpfnl-pro' ) ); } $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0; $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash($_POST['order_key']) ) : ''; $offer_type = isset( $_POST['offer_type'] ) ? sanitize_text_field( $_POST['offer_type'] ) : ''; $offer_action = isset( $_POST['offer_action'] ) ? sanitize_text_field( $_POST['offer_action'] ) : ''; $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0; $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0; $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : 0; $step_type = get_post_meta($step_id, '_step_type', true); $attr = []; if( isset( $_POST['is_variable'], $_POST['attr'] ) && $_POST['is_variable'] == 'true' ){ $payload = [ 'product_id' => $product_id, 'data' => $_POST['attr'], ]; $attr = $_POST['attr']; $product_id = $this->wpfnl_get_variation_id($payload); } if( $variation_id ){ $product_id = $variation_id; } $order = wc_get_order($order_id); $wpfnl_functions_instance = new Wpfnl_functions(); if( method_exists($wpfnl_functions_instance,'is_valid_order_owner')){ if (!Wpfnl_functions::is_valid_order_owner($order)) { wp_send_json([ 'status' => 'error', 'message' => __( 'Unauthorized request', 'wpfnl-pro' ), ]); } } $all_products = []; $product_data = array(); if ( $order_id && $product_id ) { $data = array( 'order_id' => $order_id, 'product_id' => $product_id, 'quantity' => $quantity, 'order_key' => $order_key, 'step_type' => $step_type, 'step_id' => $step_id, 'funnel_id' => $funnel_id, ); $result = $this->offer_accepted( $step_id, $data , $attr ); wp_send_json($result); } else { wp_send_json([ 'status' => 'error', 'message' => 'No product data found', ]); } } /** * Process upsell reject ajax action * * @since 1.0.0 */ public function process_upsell_rejected_action() { $nonce = filter_input( INPUT_POST, 'security', FILTER_SANITIZE_STRING ); if ( ! wp_verify_nonce( $nonce, 'wpfnl_upsell_rejected_nonce' ) ) { return array( 'status' => 'success', 'message' => __( 'Unauthorized request', 'wpfnl-pro' ) ); } $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0; $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash($_POST['order_key']) ) : ''; $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0; $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0; $step_type = get_post_meta($step_id, '_step_type', true); $result = array( 'status' => 'failed', 'redirect' => '#', ); if ( $step_id ) { $type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ); if( $type === 'lms' ){ /** Change lms order meta value */ Wpfnl_lms_learndash_functions::update_lms_order_status( $funnel_id, $step_id, get_current_user_id() ); $result['message'] = 'Redirecting...'; $result['status'] = 'success'; $result['redirect_url'] = $this->offer_rejected( $step_id, [] ); }else{ $result = array( 'status' => 'failed', 'redirect_url' => '#', 'message' => __( 'Order does not exist', 'wpfnl-pro' ), ); if ( $order_id ) { $data = array( 'order_id' => $order_id, 'product_id' => $product_id, 'quantity' => $quantity, 'order_key' => $order_key, 'step_type' => $step_type, 'step_id' => $step_id, 'funnel_id' => $funnel_id, ); $result['message'] = 'Redirecting...'; $result['status'] = 'success'; $result['redirect_url'] = $this->offer_rejected( $step_id, $data ); } } } // send json. wp_send_json( $result ); } /** * Process downsell reject ajax action * * @since 1.0.0 */ public function process_downsell_rejected_action() { $nonce = filter_input( INPUT_POST, 'security', FILTER_SANITIZE_STRING ); if ( ! wp_verify_nonce( $nonce, 'wpfnl_downsell_rejected_nonce' ) ) { return array( 'status' => 'success', 'message' => __( 'Unauthorized request', 'wpfnl-pro' ) ); } $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0; $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0; $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash($_POST['order_key']) ) : ''; $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0; $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0; $step_type = get_post_meta($step_id, '_step_type', true); $result = array( 'status' => 'failed', 'redirect' => '#', ); if ( $step_id ) { $type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ); if( $type === 'lms' ){ /** Change lms order meta value */ Wpfnl_lms_learndash_functions::update_lms_order_status( $funnel_id, $step_id, get_current_user_id() ); $result['message'] = 'Redirecting...'; $result['status'] = 'success'; $result['redirect_url'] = $this->offer_rejected( $step_id, [] ); }else{ $result = array( 'status' => 'failed', 'redirect_url' => '#', 'message' => __( 'Order does not exist', 'wpfnl-pro' ), ); if ( $order_id ) { $data = array( 'order_id' => $order_id, 'product_id' => $product_id, 'quantity' => $quantity, 'order_key' => $order_key, 'step_type' => $step_type, 'step_id' => $step_id, 'funnel_id' => $funnel_id, ); $result['message'] = 'Redirecting...'; $result['status'] = 'success'; $result['redirect_url'] = $this->offer_rejected( $step_id, $data ); } } } // send json. wp_send_json( $result ); } /** * Process upsell accept ajax action with auth * * @since 1.0.0 */ public function wpfnl_after_process_with_auth_ajax() { $step_id = $_POST['step_id']; $order_id = $_POST['order_id']; $order_key = $_POST['order_key']; $step_type = get_post_meta($step_id, '_step_type', true); $intent_id = ''; if (isset($_POST['intent_id'])) { $intent_id = $_POST['intent_id']; } $extra_data = []; $extra_data['step_id'] = $step_id; $extra_data['step_type'] = $step_type; $extra_data['order_id'] = $order_id; $extra_data['order_key'] = $order_key; $extra_data['intent_id'] = $intent_id; $step_meta = get_post_meta($step_id, '_wpfnl_'.$step_type.'_products', true); if ($step_meta[0]) { $step_meta = $step_meta[0]; } else { wp_send_json([ 'status' => 'error', 'message' => 'No attach product found', ]); } $product_id = $step_meta['id']; $quantity = $step_meta['quantity']; $extra_data['product_id'] = $product_id; $extra_data['input_qty'] = $quantity; $response = $this->offer_accepted($step_id, $extra_data); wp_send_json($response); } /** * offer accepted action * * @param $step_id * @param $data * @return array * @throws \Exception */ public function offer_accepted( $step_id, $data , $attr = null) { $order_id = $data['order_id']; $funnel_id = $data['funnel_id']; $order_key = $data['order_key']; $input_qty = $data['quantity']; $order = wc_get_order($order_id); $skip_payment = filter_input(INPUT_POST, 'stripe_sca_payment', FILTER_VALIDATE_BOOLEAN); $order_gateway = $order->get_payment_method(); if( 'stripe_cc' === $order_gateway ){ $order_gateway = 'stripe'; } $payment_gateway_obj = Wpfnl_Pro::instance()->payment_gateways->build_gateway($order_gateway); $is_charge_success = false; if ($skip_payment) { $_stripe_intent_id = get_post_meta($order->get_id(), '_stripe_intent_id_' . $step_id, true); $intent_id = filter_input(INPUT_POST, 'stripe_intent_id', FILTER_SANITIZE_STRING); $skip_payment = ($intent_id === $_stripe_intent_id) ? true : false; } $offer_product = Wpfnl_Pro_functions::update_variation_product_details( $step_id, $data['product_id'], $input_qty, $order_id ); $product = wc_get_product($data['product_id']); if( $product && !$product->is_in_stock() || $product->managing_stock() && ( $product->get_stock_quantity() < intval($offer_product['qty']) || $product->get_stock_quantity() == 0 || intval($offer_product['qty']) == 0 ) ) { if( 'variation' === $product->get_type() ) { $message = __( 'The selected variation is out of stock. Please, try choosing different variation.', 'wpfnl-pro' ); } else { $message = __( 'Product is out of stock', 'wpfnl-pro' ); } return array( 'status' => 'failed', 'message' => $message, ); } $no_payment_gateway = false; $shipping = []; if($offer_product['qty'] != 0){ if ( (isset($offer_product['price']) && '' === trim($offer_product['price'])) || $skip_payment ) { $is_charge_success = array( 'is_success' => true, 'message' => '', ); if ( $skip_payment ) { $response = \WC_Stripe_API::retrieve('charges?payment_intent=' . $intent_id); $charge_data = reset($response->data); $order->update_meta_data('_wpfunnels_offer_txn_resp_' . $step_id, $charge_data->id); $order->save(); } } else { if ($payment_gateway_obj) { $tax_enabled = get_option( 'woocommerce_calc_taxes' ); if ( 'yes' === $tax_enabled ) { if ( !wc_prices_include_tax() ) { $product = wc_get_product($offer_product['id']); $offer_product['total'] = $product ? wc_get_price_including_tax( $product, array( 'price' => $offer_product['total'] ) ) : ''; } } $offer_product_id = isset( $offer_product[ 'id' ] ) ? $offer_product[ 'id' ] : 0; $offer_total_price = isset( $offer_product[ 'total' ] ) ? $offer_product[ 'total' ] : 0; $shipping = $this->calculate_offer_product_shipping_price( $order, $offer_product_id, $offer_total_price ); $shipping_price = isset( $shipping[ 'total' ] ) ? $shipping[ 'total' ] : 0; $shipping_tax = isset( $shipping[ 'total_tax' ] ) ? $shipping[ 'total_tax' ] : 0; $shipping_price = $shipping_price + $shipping_tax; $offer_product[ 'price' ] = $offer_total_price + $shipping_price; $offer_product[ 'total' ] = $offer_total_price + $shipping_price; $offer_product[ 'price' ] = $offer_total_price; $offer_product[ 'total' ] = $offer_total_price; $chained_product_class_instance = new \WPFunnelsPro\Compatibility\ChainedProduct(); $total_chained_product_price = 0; if( Wpfnl_functions::is_wc_active() ){ $chained_products = $chained_product_class_instance->get_chain_product_details( $offer_product['id'] ); if ( !empty($chained_products) ){ foreach ($chained_products as $chained_product_id => $chianed_product){ $new_product_price = 0; $product = wc_get_product( $chained_product_id ); $chained_product = wc_get_product( $chained_product_id ); if( 'yes' === $chianed_product['priced_individually'] ){ $chained_product_price = $chained_product->get_price(); if ( ! empty( $chained_product_price ) ) { $total_chained_product_price += $chained_product_price * $chianed_product['unit']; } } } } } $offer_product[ 'price' ] = $offer_product[ 'price' ] + $total_chained_product_price; $offer_product[ 'total' ] = $offer_product[ 'total' ] + $total_chained_product_price; $is_charge_success = $payment_gateway_obj->process_payment( $order, $offer_product ); }else{ $no_payment_gateway = true; } } } if( isset($is_charge_success['is_success'] ) && $is_charge_success['is_success'] ) { $response = Wpfnl_Pro_functions::after_offer_charged( $funnel_id, $step_id, $order_id, $order_key, $offer_product, true , $attr, '', '', $shipping ); if( 'success' === $response['status'] ) { delete_option( 'wpfunnels_dynamic_offer_data' ); $result = array( 'status' => 'success', 'message' => __( 'Offer product is added successfully', 'wpfnl-pro' ), ); } else { $result = array( 'status' => 'success', 'message' => __( 'Offer product is not added', 'wpfnl-pro' ), ); } } else{ if( $no_payment_gateway ){ $result = array( 'status' => 'failed', 'message' => __( 'No payment gateway found', 'wpfnl-pro' ), ); }else{ $result = array( 'status' => 'failed', 'message' => $is_charge_success['message'], ); } } $this->offerStatus = 'accept'; $result['redirect_url'] = $this->get_next_step_url( $order, $step_id, $funnel_id ); $next_step = Wpfnl_functions::get_next_step( $funnel_id, $step_id ); $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step ); $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step[ 'step_id' ] ); if( $custom_url ){ $result['redirect_url'] = $custom_url; } if( isset($result['redirect_url']) && !$result['redirect_url'] ){ $result['redirect_url'] = Wpfnl_functions::redirect_to_deafult_thankyou(); } return $result; } /** * @param $step_id * @param $data * @return string */ public function offer_rejected( $step_id, $data ) { if( $data ){ $order = wc_get_order($data['order_id']); $product_id = $data['product_id']; $input_qty = $data['quantity']; $this->offerStatus = 'reject'; $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, $product_id, $input_qty, $data['order_id'] ); delete_option( 'wpfunnels_dynamic_offer_data' ); ob_start(); do_action( 'wpfunnels/offer_rejected', $order, $offer_product ); ob_get_clean(); $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); $next_step = Wpfnl_functions::get_next_step( $funnel_id, $step_id ); $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step ); $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step[ 'step_id' ] ); if( $order ){ $order->update_meta_data('_wpfunnels_order', 'yes'); $order->update_meta_data('_wpfunnels_funnel_id', $funnel_id); } if( $custom_url ){ return $custom_url; } if( $this->get_next_step_url( $order, $step_id, $funnel_id ) ){ return $this->get_next_step_url( $order, $step_id, $funnel_id ); } return Wpfnl_functions::redirect_to_deafult_thankyou(); }else{ $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); $step_type = get_post_meta( $step_id, '_step_type', true ); $offer_product = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_products', true ); if( isset($offer_product[0]) ){ $offer_product[0]['step_id'] = $step_id; ob_start(); do_action( 'wpfunnels/offer_rejected', '', $offer_product[0] ); ob_get_clean(); } $next_step = Wpfnl_functions::get_next_step( $funnel_id, $step_id ); $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step ); $custom_url = Wpfnl_functions::custom_url_for_thankyou_page( $next_step[ 'step_id' ] ); if( $custom_url ){ return $custom_url; } return $this->get_next_step_url_for_lms( (object)[], $step_id, $funnel_id ); } } /** * get next step url * * @param \WC_Order $order * @param $step_id * @param $funnel_id * @return string */ public function get_next_step_url_for_lms( $order , $step_id, $funnel_id ) { $current_page_id = $step_id; $next_step = Wpfnl_functions::get_next_step( $funnel_id, $current_page_id ); $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step ); if ( 'conditional' === $next_step['step_type'] ) { $condition = \WPFunnels\Conditions\Wpfnl_Condition_Checker::getInstance(); $condition_identifier = strval($next_step['step_id']); $condition_matched = $condition->check_condition($funnel_id, $order, $condition_identifier, $current_page_id, $this->offerStatus); $node_found = Wpfnl_functions::get_node_id($funnel_id, $next_step['step_id']); if ($condition_matched) { $next_node = $this->go_to_output_1($funnel_id, $node_found); $next_step = Wpfnl_functions::get_step_by_node($funnel_id, $next_node); } else { $next_node = $this->go_to_output_2($funnel_id, $node_found); $next_step = Wpfnl_functions::get_step_by_node($funnel_id, $next_node); } } else { $next_step = $next_step['step_id']; } $redirect_url = get_post_permalink($next_step); $query_args = array( 'wpfnl-order' => 'lms_order', 'wpfnl-key' => 'lms_order', ); return add_query_arg($query_args, $redirect_url); } /** * get next step url * * @param \WC_Order $order * @param $step_id * @param $funnel_id * @return string */ public function get_next_step_url( \WC_Order $order , $step_id, $funnel_id ) { $current_page_id = $step_id; $next_step = Wpfnl_functions::get_next_conditional_step( $funnel_id, $step_id, $order, $this->offerStatus ); $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step ); $next_step_id = isset($next_step['step_id']) ? $next_step['step_id'] : ''; if( $order ){ $next_step = apply_filters( 'wpfunnels/modify_next_step_based_on_order', $next_step, $order ); $next_step_id = isset($next_step['step_id']) ? $next_step['step_id'] : ''; } $redirect_url = $next_step_id ? get_post_permalink($next_step_id) : ''; if( $order ){ $query_args = array( 'wpfnl-order' => $order->get_id(), 'wpfnl-key' => $order->get_order_key(), 'key' => $order->get_order_key(), ); }else{ $query_args = array( 'wpfnl-order' => 'lms_order', 'wpfnl-key' => 'lms_order', ); } if( $redirect_url ){ return add_query_arg($query_args, $redirect_url); } return false; } public function detect_upsell() { $type = ''; if (get_post_type(get_the_ID()) == 'wpfunnel_steps') { $type = get_post_meta(get_the_ID(), '_step_type', true); if ($type == 'upsell' || $type == 'downsell') { if (isset($_GET['wpfnl-order']) && isset($_GET['wpfnl-key'])) { return; } else { $user = wp_get_current_user(); if (!in_array('administrator', (array)$user->roles)) { echo '

You are not allowed to access this page directly

'; wp_die(); } } } } } public function wpfnl_modify_product_price($product_price) { if (class_exists('WOOMC\MultiCurrency\API')) { $api_obj = new WOOMC\MultiCurrency\API(); $product_price = $api_obj->convert($product_price, get_woocommerce_currency(), get_option('woocommerce_currency')); } return $product_price; } public function funnel_upsell_shortcode_button() { global $post; return '
'; //wpfnl-success, wpfnl-error, box } public function funnel_upsell_reject_shortcode_button() { global $post; return '
'; //wpfnl-success, wpfnl-error, box } public function go_to_output_1($funnel_id, $node_found) { $funnel_json = get_post_meta($funnel_id, '_funnel_data', true); if ($funnel_json) { // $funnel_data = json_decode($funnel_json, true); $node_data = $funnel_json['drawflow']['Home']['data']; foreach ($node_data as $node_key => $node_value) { if ($node_value['id'] == $node_found) { $next_node = isset($node_value['outputs']['output_1']['connections'][0]['node']) ? $node_value['outputs']['output_1']['connections'][0]['node'] : ''; return $next_node; } } return false; } } public function go_to_output_2($funnel_id, $node_found) { $funnel_json = get_post_meta($funnel_id, '_funnel_data', true); if ($funnel_json) { // $funnel_data = json_decode($funnel_json, true); $node_data = $funnel_json['drawflow']['Home']['data']; foreach ($node_data as $node_key => $node_value) { if ($node_value['id'] == $node_found) { $next_node = isset($node_value['outputs']['output_2']['connections'][0]['node']) ? $node_value['outputs']['output_2']['connections'][0]['node'] : ''; return $next_node; } } return false; } } public function get_conditional_redirect($step_id, $response, $type = 'accept') { $funnel_id = ''; $condition_identifier = ''; $group_conditions = get_post_meta($funnel_id, $condition_identifier, true); if ($group_conditions) { // Loop through group condition. foreach ($group_conditions as $group) { if (empty($group)) { continue; } $match_group = true; // Loop over rules and determine if all rules match. foreach ($group as $rule) { if (!$this->match_rule($rule, $order, $current_page_id)) { $match_group = false; break; } } // If this group matches, show the field group. if ($match_group) { return true; } } } // Return default. return false; } public function get_discount_data($step_id) { $data = []; $type = get_post_meta($step_id, '_step_type', true); if ($type == 'upsell') { $data['type'] = get_post_meta($step_id, '_wpfnl_upsell_discount_type', true); $data['value'] = get_post_meta($step_id, '_wpfnl_upsell_discount_value', true); } else { $data['type'] = get_post_meta($step_id, '_wpfnl_downsell_discount_type', true); $data['value'] = get_post_meta($step_id, '_wpfnl_downsell_discount_value', true); } return $data; } /** * */ private function wpfnl_get_variation_id( $payload ){ if( isset($payload['product_id'], $payload['data']) && is_array($payload['data']) ){ $variation_id = (new \WC_Product_Data_Store_CPT())->find_matching_product_variation( new \WC_Product($payload['product_id']), $payload['data'] ); return $variation_id; } return false; } /** * @desc calculate shipping cost for offer steps (upsell/downsell) * * @param \WC_Order $order * @param $product_id * @param $total_price * @return int|mixed|void */ private function calculate_offer_product_shipping_price( \WC_Order $order, $product_id, $total_price ) { if( !$product_id ) { return; } if( !WC()->shipping()->get_shipping_classes() ) { return; } $product = wc_get_product( $product_id ); if( $product && $product->is_virtual() ) { return; } $shipping = []; $shipping_methods = $order->get_shipping_methods(); $offer_settings = Wpfnl_functions::get_offer_settings(); $offer_orders_option = isset( $offer_settings[ 'offer_orders' ] ) ? $offer_settings[ 'offer_orders' ] : ''; if( !empty( $shipping_methods ) ) { foreach( $shipping_methods as $key => $method ) { if( $method ){ $shipping = [ 'name' => $method->get_name(), 'method_title' => $method->get_method_title(), 'method_id' => $method->get_method_id(), 'instance_id' => $method->get_instance_id(), 'total' => $method->get_total(), 'total_tax' => $method->get_total_tax(), 'taxes' => $method->get_taxes(), 'tax_percentage' => ( $method->get_total() != 0 ) || ( $method->get_total() != 0.00 ) ? ( $method->get_total_tax() * 100 ) / $method->get_total() : 0, ]; } } } if( 'main-order' === $offer_orders_option ) { $wc_shipping_zones = \WC_Shipping_Zones::get_zones(); if( !empty( $wc_shipping_zones ) && isset( $shipping[ 'method_id' ] ) && 'flat_rate' === $shipping[ 'method_id' ] ) { foreach( $wc_shipping_zones as $zone ) { if( isset( $zone[ 'shipping_methods' ] ) && !empty( $zone[ 'shipping_methods' ] ) ) { foreach( $zone[ 'shipping_methods' ] as $method ) { if( 'WC_Shipping_Flat_Rate' === get_class( $method ) ) { if( isset( $method->instance_id ) && isset( $shipping[ 'instance_id' ] ) ) { if( $method->instance_id == $shipping[ 'instance_id' ] ) { $product_shipping_class_id = $product->get_shipping_class_id(); $instance_settings = isset( $method->instance_settings ) ? $method->instance_settings : []; $shipping_cost = isset( $instance_settings[ 'class_cost_' . $product_shipping_class_id ] ) ? $instance_settings[ 'class_cost_' . $product_shipping_class_id ] : $instance_settings[ 'no_class_cost' ]; $shipping_cost = $shipping_cost ? $shipping_cost : 0; $tax_percentage = isset( $shipping[ 'tax_percentage' ] ) ? $shipping[ 'tax_percentage' ] : 0; $total_tax = ( $shipping_cost * $tax_percentage ) / 100; $shipping[ 'total' ] = $shipping_cost; $shipping[ 'total_tax' ] = $total_tax; $shipping[ 'taxes' ] = [ 'total' => [ $total_tax ] ]; } } } } } } } } unset( $shipping[ 'tax_percentage' ] ); return $shipping; } } public/modules/webhook/classes/class-wpfunnels-pro-webhook-mapping.php000064400000021515147600245720022320 0ustar00offer_event_control( $order, $offer_product, 'accepted' ); } } /** * This will trigger after user rejected the offer * * @param $order * @param $offer_product */ public function offer_rejected( $order, $offer_product ) { $is_activated = Wpfnl_functions::is_webhook_license_activated(); if( $is_activated ){ $this->offer_event_control( $order, $offer_product, 'rejected' ); } } /** * Send optin data * * @param $step_id, $record */ public function send_optin_data( $step_id, $post_action, $action_type, $record ){ $is_activated = Wpfnl_functions::is_webhook_license_activated(); if( $is_activated ){ $funnel_id = get_post_meta($step_id, '_funnel_id', true); $settings = Wpfnl_Pro_Webhook_Functions::get_webhook_settings( $funnel_id ); if( !empty( $settings )){ foreach( $settings as $key=>$setting ){ $status = Wpfnl_Pro_Webhook_Functions::check_webhook_status( $setting['status'] ); if( !$status ){ continue; } if( false !== strpos($setting['conditions'], 'optin_submitted') ){ $condition = 'optin_submitted'; } $is_matched = Wpfnl_Pro_Webhook_Functions::match_conditions( 'optin_submitted', $condition ); if( $is_matched ){ $class = Wpfnl_Pro_Webhook_Functions::get_class_instance( 'optin', $setting, $record ); if( $class ){ $class->send_data(); break; } } } } } } /** * Main Order Tracking hook * * @param $order_id * @param $funnel_id */ public function funnel_order_placed( $order_id, $funnel_id ) { $checkout_id = Wpfnl_functions::get_checkout_id_from_order( $order_id ); $is_ob_settings = get_post_meta( $checkout_id , 'order-bump-settings' , true ); $order = wc_get_order($order_id); $settings = Wpfnl_Pro_Webhook_Functions::get_webhook_settings( $funnel_id ); foreach( $order->get_items() as $item ){ if( $item ){ $is_ob = $item->get_meta('_wpfunnels_order_bump'); if( 'yes' === $is_ob ){ if( !empty( $settings )){ foreach( $settings as $setting ){ $status = Wpfnl_Pro_Webhook_Functions::check_webhook_status( $setting['status'] ); if( !$status ){ continue; } $index = str_replace('_order_bump_accepted','',$setting['conditions']); if ( 'any' === $index ) { $is_ob_key_matched = true; $_item_id = $item[ 'variation_id' ] ? $item[ 'variation_id' ] : $item[ 'product_id' ]; } elseif( !empty( $is_ob_settings[ $index ][ 'product' ] ) ) { $proudct_id = $is_ob_settings[ $index ][ 'product' ]; $_item_id = $item[ 'variation_id' ] ? $item[ 'variation_id' ] : $item[ 'product_id' ]; $is_ob_key_matched = Wpfnl_Pro_Webhook_Functions::ob_key_matching( $proudct_id, $_item_id ); } if( $is_ob_key_matched ){ $is_matched = Wpfnl_Pro_Webhook_Functions::match_conditions( $index.'_order_bump_accepted', $setting['conditions'] ); if( $is_matched ){ $class = Wpfnl_Pro_Webhook_Functions::get_class_instance( 'orderbump', $setting, [], $order_id, '', 'accepted', $_item_id ); if( $class ){ $class->send_data(); } } } } } } } } if( !empty( $settings )){ foreach( $settings as $setting ){ $status = Wpfnl_Pro_Webhook_Functions::check_webhook_status( $setting['status'] ); if( !$status ){ continue; } if ( !empty( $setting['conditions'] ) ) { if ( 'wpfnl_wc_checkout_order_placed' === $setting['conditions'] ) { $class = Wpfnl_Pro_Webhook_Functions::get_class_instance( 'checkout', $setting, [], $order_id, '', 'not Accepted' ); if ( $class ) { $class->send_data(); } } if ( !empty( $is_ob_settings[ $index ][ 'product' ] ) ) { $index = str_replace( '_order_bump_rejected', '', $setting[ 'conditions' ] ); $product_id = $is_ob_settings[ $index ][ 'product' ]; $cookie_name = 'wpfunnels_automation_data'; $cookie = isset( $_COOKIE[ $cookie_name ] ) ? json_decode( wp_unslash( $_COOKIE[ $cookie_name ] ), true ) : []; if ( isset( $cookie[ 'ob_accepetd_products' ] ) && is_array( $cookie[ 'ob_accepetd_products' ] ) && !in_array( $product_id, $cookie[ 'ob_accepetd_products' ] ) ) { $is_matched = Wpfnl_Pro_Webhook_Functions::match_conditions( $index . '_order_bump_rejected', $setting[ 'conditions' ] ); if ( $is_matched ) { $class = Wpfnl_Pro_Webhook_Functions::get_class_instance( 'orderbump', $setting, [], $order_id, '', 'not Accepted', $product_id ); if ( $class ) { $class->send_data(); } } } } } } } } /** * offer event control * @param $order * @param $offer_product * @param $offer_status * */ private function offer_event_control( $order, $offer_product , $offer_status ){ $step_id = isset($offer_product['step_id']) ? $offer_product['step_id'] : ''; $funnel_id = get_post_meta($step_id, '_funnel_id', true); $is_lms = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true ); $step_type = get_post_meta($step_id, '_step_type', true); $settings = Wpfnl_Pro_Webhook_Functions::get_webhook_settings( $funnel_id ); if( !empty( $settings )){ foreach( $settings as $key=>$setting ){ $status = Wpfnl_Pro_Webhook_Functions::check_webhook_status( $setting['status'] ); if( !$status ){ continue; } $is_matched = Wpfnl_Pro_Webhook_Functions::match_conditions( $step_type.'_'.$offer_status, $setting['conditions'] ); if( $is_matched || "{$step_id}_{$step_type}_$offer_status" === $setting['conditions'] ){ if ( 'lms' === $is_lms ) { $order_id = ''; } else { $order_id = $order->get_id(); } $class = Wpfnl_Pro_Webhook_Functions::get_class_instance( 'offer', $setting, [], $order_id, $offer_product , $offer_status ); if( $class ){ $class->send_data(); } } } } } /** * Create offer class instance */ private function create_offer_class_instance( $is_ob_key_matched, $step_type, $setting, $cookie_data, $status, $step_id){ if( $is_ob_key_matched ){ $offer_product = Wpfnl_lms_learndash_functions::get_course_details($step_id); $product_id = ''; if( is_array($offer_product) ){ if( isset($offer_product['id']) ){ $product_id = $offer_product['id']; } } if( $product_id ){ $class = Wpfnl_Pro_Webhook_Functions::get_class_instance( 'offer', $setting, [], '' , '', $status, $product_id ,'lms' , $cookie_data , $step_id); if( $class ){ $class->send_data(); } } } } }public/modules/webhook/class-wpfnl-pro-webhook.php000064400000000602147600245720016331 0ustar00 public/class-wpfnl-pro-public.php000064400000060646147600245720013061 0ustar00 */ class Wpfnl_Pro_Public { private static $instance; /** * The ID of this plugin. * * @since 1.0.0 * @access private * @var string $plugin_name The ID of this plugin. */ private $plugin_name; /** * The version of this plugin. * * @since 1.0.0 * @access private * @var string $version The current version of this plugin. */ private $version; /** * Get class instance. * * @return object Instance. */ public static function getInstance() { if (!self::$instance) { // new self() will refer to the class that uses the trait self::$instance = new self(); } return self::$instance; } /** * Initialize the class and set its properties. * * @since 1.0.0 */ public function __construct( ) { add_action( 'wp_enqueue_scripts', array($this, 'enqueue_styles'),9999); add_action( 'wp_enqueue_scripts', array($this, 'enqueue_scripts'),9999 ); add_action( 'wp_footer', array($this, 'load_offer_loader') ); add_action( 'init', array( $this, 'init_funnel' ), 9999 ); add_action( 'init', array( $this, 'init_skip_offer_class' ), 9999 ); /** this is the hook where we checked if the funnel has any offer step for payments. */ add_action( 'wpfunnels/funnel_order_placed', array( $this, 'maybe_setup_offer_funnel' ), 9999, 1 ); add_action( 'wp', [$this, 'wpfnl_get_ab_testing_variation'], 10 ); add_action( 'wpfunnels/offer_accepted', array( $this, 'add_accepted_offer_details_to_logger' ), 10, 2 ); add_action( 'wpfunnels/offer_accepted', array( $this, 'enroll_to_tutor_course' ), 10, 2 ); add_action( 'wpfunnels/offer_rejected', array( $this, 'add_rejected_offer_details_to_logger' ), 10, 2 ); wp_ajax_helper()->handle('wpfnl-get-variation-price') ->with_callback([ $this, 'wpfnl_get_variation_price' ]); add_action( 'wp_ajax_nopriv_wpfnl_set_bounce_rate', [ $this, 'set_bounce_rate' ] ); add_action( 'wp_ajax_wpfnl_set_bounce_rate', [ $this, 'set_bounce_rate' ] ); add_action('wp_footer', [ $this, 'add_fraudnet_checkout_script'] ); add_action('mint_automation_condition_result', [ $this, 'mint_automation_condition'],10, 3 ); } /** * set plugin name * * @param $plugin_name */ public function set_name( $plugin_name ) { $this->plugin_name = $plugin_name; } /** * set plugin version * @param $version */ public function set_version( $version ) { $this->version = $version; } /** * Register the stylesheets for the public-facing side of the site. * * @since 1.0.0 */ public function enqueue_styles() { /** * This function is provided for demonstration purposes only. * * An instance of this class should be passed to the run() function * defined in Wpfnl_Pro_Loader as all of the hooks are defined * in that particular class. * * The Wpfnl_Pro_Loader will then create the relationship * between the defined hooks and the functions defined in this * class. */ if ( Wpfnl_functions::is_funnel_step_page() ) { wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'assets/css/wpfnl-pro-public.css', array(), $this->version, 'all' ); if( Wpfnl_Pro_functions::maybe_unsupported_payment_gateway() ){ global $post; $step_id = $post->ID; $order_id = ( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0; $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, '', '', $order_id ); $product_id = ''; if ( is_array($offer_product) && !empty($offer_product)) { $product_id = $offer_product['id']; } if( $product_id ){ wp_enqueue_style('woocommerce-layout'); wp_enqueue_style('woocommerce-smallscreen'); wp_enqueue_style('woocommerce-general'); } } } } /** * Register the JavaScript for the public-facing side of the site. * * @since 1.0.0 */ public function enqueue_scripts() { /** * This function is provided for demonstration purposes only. * * An instance of this class should be passed to the run() function * defined in Wpfnl_Pro_Loader as all the hooks are defined * in that particular class. * * The Wpfnl_Pro_Loader will then create the relationship * between the defined hooks and the functions defined in this * class. */ if ( Wpfnl_functions::is_funnel_step_page() ) { if( Wpfnl_Pro_functions::maybe_unsupported_payment_gateway() ){ global $post; $step_id = $post->ID; $order_id = ( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0; $offer_product = Wpfnl_Pro_functions::get_offer_product_data( $step_id, '', '', $order_id ); $product_id = ''; if ( is_array($offer_product) && !empty($offer_product)) { $product_id = $offer_product['id']; } if( $product_id ){ wp_enqueue_script('woocommerce'); wp_enqueue_script('wc-checkout'); wp_enqueue_script('wc-cart'); wp_enqueue_script('wc-cart-fragments'); wp_enqueue_script('wc-add-to-cart'); wp_enqueue_script('wc-single-product'); wp_enqueue_script('wc-add-to-cart-variation'); wp_enqueue_script('wc-credit-card-form'); wp_enqueue_script('wc-add-payment-method'); } } wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'assets/js/wpfnl-pro-public.js', [ 'jquery' ], $this->version, true ); wp_localize_script( $this->plugin_name, 'wpfnl_pro_obj', [ 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'funnel_id' => get_post_meta( get_the_ID(), '_funnel_id', true ), 'step_id' => get_the_ID(), 'ajaxNonce' => wp_create_nonce( 'wpfnl_public_pro' ), 'is_user_logged_in' => is_user_logged_in(), 'user_id' => get_current_user_id(), ] ); } } /** * add_fraudnet_checkout_script: enabled to add fraudnet script to send fraud data to paypal * * @since 2.1.1 * @return void */ public function add_fraudnet_checkout_script() { if ( Wpfnl_functions::is_funnel_step_page() ) { if(Wpfnl_functions::check_if_this_is_step_type( 'checkout') || Wpfnl_functions::check_if_this_is_step_type( 'upsell') || Wpfnl_functions::check_if_this_is_step_type( 'downsell')) { $paypal_enabled = false; $guid = Wpfnl_Pro_functions::generate_guid(); $paypal_merchant_id = ''; $gateways = Wpfnl_Pro_functions::get_available_payment_methods(); $paypal_settings = get_option('woocommerce-ppcp-settings'); if ($paypal_settings && isset($paypal_settings['merchant_id'])) { $paypal_merchant_id = $paypal_settings['merchant_id']; } if(!empty($gateways)){ foreach ($gateways as $gateway_key => $gateway) { if($gateway == 'PayPal'){ $paypal_enabled = true; } } } if($paypal_enabled) { ?>

Loading . . .

initialize_funnel_session(); } /** * Init skip offer class * * @since 1.8.4 */ public function init_skip_offer_class(){ $skip_offer_instance = SkipOffer::getInstance(); $skip_offer_instance->init(); } /** * start session for a funnel * * @since 1.0.0 */ private function initialize_funnel_session() { if(Wpfnl_functions::is_funnel_step_page()) { $funnel_id = Wpfnl_functions::get_funnel_id(); if ( ! $funnel_id ) { return; } if ( Wpfnl_functions::check_if_this_is_step_type('landing') || Wpfnl_functions::check_if_this_is_step_type('checkout') ) { $data = array( 'funnel_id' => $funnel_id, 'steps' => get_post_meta( $funnel_id, '_steps_order', true ), ); Wpfnl_Pro::instance()->session->set_session( $funnel_id, $data ); } elseif ( Wpfnl_functions::check_if_this_is_step_type('thankyou') ) { Wpfnl_Pro::instance()->session->destroy_session( $funnel_id ); } elseif ( Wpfnl_functions::check_if_this_is_step_type('upsell') || Wpfnl_functions::check_if_this_is_step_type('downsell') ) { if ( ! ( is_user_logged_in() && current_user_can( 'wpf_manage_funnels' ) ) ) { if ( ! Wpfnl_Pro::instance()->session->is_active_session( $funnel_id ) ) { wp_die( esc_html__( 'Your session is expired', 'wpfnl-pro' ) ); } } } } } /** * may be setup offer funnel * * @param string $order_id * * @since 2.2.6 */ public function maybe_setup_offer_funnel( $order_id = '' ) { if ( empty( $order_id ) ) { return; } $order = wc_get_order( $order_id ); $this->start_offer_funnel($order); } /** * setup offer funnels * * @param string $order */ public function start_offer_funnel( $order ) { $is_offer_funnel = Wpfnl_Pro_functions::is_offer_exists( $order ); if( $is_offer_funnel ) { $order_gateway = $order->get_payment_method(); $payment_gateway_obj = Wpfnl_Pro::instance()->payment_gateways->build_gateway($order_gateway); if ( $payment_gateway_obj ) { ob_start(); do_action( 'wpfunnels/offer_funnel_started', $order ); ob_get_clean(); } } } /** * */ public function wpfnl_get_variation_price($payload){ $response = [ 'success' => false, 'data' => 'data not found' ]; if( isset($payload['product_id']) && $payload['product_id'] ){ if( isset($payload['attr']) && $payload['attr'] ){ $variation_id = (new \WC_Product_Data_Store_CPT())->find_matching_product_variation( new \WC_Product($payload['product_id']), $payload['attr'] ); if( $variation_id ){ $product = wc_get_product($variation_id); }else{ $product = wc_get_product($payload['product_id']); } $response = [ 'success' => true, 'data' => $product ? $product->get_price_html() : '', ]; } } return $response; } /** * Get AB testing variation for view in frontend * * @since 1.7.3 * @return @void */ public function wpfnl_get_ab_testing_variation(){ $get = Wpfnl_Pro_functions::get_sanitized_get_post(); if( isset($get['get']['wpfnl-step-id']) ){ if (class_exists('\WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing')) { $instance = new \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing; $function_exist = is_callable(array($instance, 'get_ab_testing_variation_id')); if( $function_exist ){ $step_id = $get['get']['wpfnl-step-id']; $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); $displayable_variation_id = \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing::get_ab_testing_variation_id( $funnel_id, $step_id ); if( $displayable_variation_id ){ $url = get_the_permalink($displayable_variation_id); $utm_settings = Wpfnl_functions::get_funnel_utm_settings( $funnel_id ); if($utm_settings['utm_enable'] == 'on') { unset($utm_settings['utm_enable']); $url = add_query_arg($utm_settings,$url); $url = strtolower($url); } wp_safe_redirect( $url ); exit; } } } } } /** * Determine and Redirect to the Appropriate A/B Testing Variation URL. * * This function manages the process of selecting the appropriate A/B testing variation * based on the provided 'wpfnl-step-id' GET parameter and redirects users to the URL * of the chosen variation. It integrates with the 'Wpfnl_Ab_Testing' class for variation * selection and redirection. * * @since 1.7.3 * * @return void */ public function redirect_to_ab_variation_url() { // Get sanitized GET and POST data. $get = Wpfnl_Pro_functions::get_sanitized_get_post(); // Check if 'wpfnl-step-id' is set in the request. if ( isset( $get['get']['wpfnl-step-id'] ) ) { $step_id = $get['get']['wpfnl-step-id']; $funnel_id = Wpfnl_functions::get_funnel_id_from_step( $step_id ); // Get the displayable variation ID using the 'get_ab_testing_variation_id' function. $displayable_variation_id = \WPFunnelsPro\AbTesting\Wpfnl_Ab_Testing::get_ab_testing_variation_id( $funnel_id, $step_id ); if ( $displayable_variation_id ) { $url = Wpfnl_functions::get_step_url( $funnel_id, $displayable_variation_id ); if( $url ){ // Redirect to the URL of the displayable variation. wp_safe_redirect( $url ); exit; } } } } /** * @desc Skip the next offer step(s) for gbf * if the assigned product is out of stock. * @since 1.6.21 * @param $next_step * @return mixed */ private function skip_next_step_for_gbf( $funnel_id, $step_id ){ $step_type = get_post_meta($step_id, '_step_type', true); $offer_product = []; $order_id = ( isset( $_POST['order_id'] ) ) ? intval( $_POST['order_id'] ) : (( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0); $gbf_product = $this->get_gbf_product_from_cookie(); $gb_funnel_settings = wpfnl()->meta->get_funnel_meta( $funnel_id, 'global_funnel_start_condition' ); if( $gb_funnel_settings && is_array( $gb_funnel_settings ) ) { $offer_mappings = wpfnl()->meta->get_funnel_meta( $step_id, "global_funnel_{$step_type}_rules" ); if( isset($offer_mappings['type']) ){ if( 'specificProduct' == $offer_mappings['type'] ){ $product = isset( $offer_mappings['show'] ) && $offer_mappings['show'] ? wc_get_product( $offer_mappings['show'] ) : ''; if( $product ){ if( $product->get_type() == 'variable' ){ return $offer_product; } } } $param_type = Wpfnl_Pro_GBF_Offer_Conditions_Factory::build($offer_mappings['type']); $offer_product = $param_type->get_offer_product( $offer_mappings, $order_id, $step_id, $gbf_product ); } } return $offer_product; } /** * Get GBF product from cookie data * * @return Array */ private function get_gbf_product_from_cookie() { if( Wpfnl_functions::is_wc_active() ){ return WC()->session->get('wpfunnels_global_funnel_specific_product') ? WC()->session->get('wpfunnels_global_funnel_specific_product') : []; } return []; } /** * Add offer details to logger after accepted * @param Obj $order * @param Array $offer_product * * @return void * @since 1.6.28 */ public function add_accepted_offer_details_to_logger( $order, $offer_product ){ $this->prepare_offer_data_for_logger( $offer_product, 'Accepted' ); } /** * Enroll to the course for tutor * * @param Obj $order * @param Array $offer_product * * @return void * @since 2.3.3 */ public function enroll_to_tutor_course( $order, $offer_product ){ if( defined('TUTOR_VERSION') ){ if ( is_array($offer_product) && !empty($offer_product)) { $product_id = $offer_product['id']; tutor_utils()->do_enroll( $product_id ); } } } /** * Add offer details to logger after rejected * @param Obj $order * @param Array $offer_product * * @return void * @since 1.6.28 */ public function add_rejected_offer_details_to_logger( $order, $offer_product ){ $this->prepare_offer_data_for_logger( $offer_product, 'Rejected' ); } /** * Prepare offer product data for logger * */ private function prepare_offer_data_for_logger( $offer_product, $status ){ $wp_function = new \WPFunnels\Wpfnl_functions(); if( is_callable(array($wp_function, 'maybe_logger_enabled')) ){ if (class_exists("Wpfnl_Logger")) { if( Wpfnl_functions::maybe_logger_enabled() && Wpfnl_functions::is_wc_active() ) { $product_array[] = [ 'name' => isset($offer_product['name']) ? $offer_product['name'] : '', 'price' => isset($offer_product['price']) ? $offer_product['price'] : '', ]; ob_start(); print_r($product_array); $textual_representation = ob_get_contents(); ob_end_clean(); \Wpfnl_Logger::modify_log_file( 'event',$textual_representation,'Offer Product '.$status ); } } } } /** * Sets the bounce rate for a specific user within a funnel step. * * This function is designed to handle an AJAX request for updating the bounce rate * of a user in the context of a specific funnel step. Bounce rate typically indicates * whether a user engaged with the content on the step or left the page quickly. * * @since 1.9.6 */ public function set_bounce_rate() { // Verify the AJAX request nonce for security. check_ajax_referer( 'wpfnl_public_pro', 'security' ); // Get the funnel ID, step ID, user ID, and user IP from the AJAX request. $funnel_id = filter_input( INPUT_POST, 'funnel_id', FILTER_SANITIZE_NUMBER_INT ); $step_id = filter_input( INPUT_POST, 'step_id', FILTER_SANITIZE_NUMBER_INT ); $user_id = filter_input( INPUT_POST, 'user_id', FILTER_SANITIZE_NUMBER_INT ); $user_ip = Analytics::getInstance()->get_the_user_ip(); // Retrieve the analytics ID for the given funnel, step, user, and IP combination. $analytics_id = $this->get_analytics_id( $funnel_id, $step_id, $user_id, $user_ip ); // If an analytics record is found, update the 'bounced' meta key to 'no'. if ( $analytics_id ) { global $wpdb; $analytics_meta = $wpdb->prefix . 'wpfnl_analytics_meta'; $wpdb->update( $analytics_meta, [ 'analytics_id' => $analytics_id, 'meta_key' => 'bounced', 'meta_value' => 'no', ], [ 'analytics_id' => $analytics_id, 'meta_key' => 'bounced', ] ); } // Send a JSON success response to the AJAX request. wp_send_json_success(); // Terminate the script execution. wp_die(); } /** * Get the analytics ID for a specific user within a funnel step. * * This function queries the database to retrieve the analytics ID that corresponds * to a specific combination of funnel, step, user, and user IP. It is used to uniquely * identify an analytics record for a user's interaction with a funnel step. * * @param int $funnel_id The ID of the funnel. * @param int $step_id The ID of the funnel step. * @param int $user_id The ID of the user. * @param string $user_ip The IP address of the user. * * @return int|false The analytics ID if found, or false if not found. * @since 1.9.6 */ public function get_analytics_id( $funnel_id, $step_id, $user_id, $user_ip ) { global $wpdb; $analytics_table = $wpdb->prefix . 'wpfnl_analytics'; // Prepare and construct the SQL query to retrieve the analytics ID. $query = $wpdb->prepare( 'SELECT `id` FROM %i ', $analytics_table ); $query .= $wpdb->prepare( 'WHERE `funnel_id` = %d ', $funnel_id ); $query .= $wpdb->prepare( 'AND `step_id` = %d ', $step_id ); $query .= $wpdb->prepare( 'AND `user_id` = %d ', $user_id ); $query .= $wpdb->prepare( 'AND `user_ip` = %s ', $user_ip ); $query .= 'ORDER BY `id` DESC'; // Execute the query and retrieve the analytics ID. return $wpdb->get_var( $query ); } /** * Match mail mint automation condition * * @param bool $result * @param array $condition * @param array $automation_data * * @return bool * @since 2.3.4 */ public function mint_automation_condition( $result, $condition, $automation_data ){ if( !$result ){ if( isset( $automation_data['data']['action'], $condition['param'] , $condition['condition_value'], $condition['value'] ) ){ $targeted_value = $automation_data['data']['action']; $value = $condition['value']; $class_name = $condition['param']; $function_name = $condition['condition_value']; $class_name = "WPFunnelsPro\\Automation\\Condition\\".ucfirst($class_name); if ( class_exists(ucfirst($class_name)) ) { $instance = new $class_name( $value , $targeted_value ); $result = $instance->$function_name(); } } } return $result; } } public/index.php000064400000000032147600245720007644 0ustar00 $vendorDir . '/composer/InstalledVersions.php', 'WPFunnelsProWebHooks\\Events\\Wpfnl_Pro_Webhook_Checkout' => $baseDir . '/includes/core/webhook/events/class-wpfunnels-pro-webhook-checkout.php', 'WPFunnelsProWebHooks\\Events\\Wpfnl_Pro_Webhook_Offer' => $baseDir . '/includes/core/webhook/events/class-wpfunnels-pro-webhook-offer.php', 'WPFunnelsProWebHooks\\Events\\Wpfnl_Pro_Webhook_Optin' => $baseDir . '/includes/core/webhook/events/class-wofunnels-pro-webhook-optin.php', 'WPFunnelsProWebHooks\\Events\\Wpfnl_Pro_Webhook_Orderbump' => $baseDir . '/includes/core/webhook/events/class-wpfunnels-pro-webhook-order-bump.php', 'WPFunnelsProWebHooks\\Functions\\Wpfnl_Pro_Webhook_Functions' => $baseDir . '/includes/core/webhook/classes/class-wpfunnels-pro-webhook-functions.php', 'WPFunnelsPro\\AbTesting\\Backup_Ab_Testing' => $baseDir . '/includes/core/ab-testing/class-wpfnl-pro-ab-testing-backup.php', 'WPFunnelsPro\\AbTesting\\Backup_Ab_Testing_Hook' => $baseDir . '/admin/modules/ab-testing/class-wpfnl-ab-testing-hook-backup.php', 'WPFunnelsPro\\AbTesting\\Wpfnl_Ab_Testing' => $baseDir . '/includes/core/ab-testing/class-wpfnl-pro-ab-testing.php', 'WPFunnelsPro\\AbTesting\\Wpfnl_Ab_Testing_Hook' => $baseDir . '/admin/modules/ab-testing/class-wpfnl-ab-testing-hook.php', 'WPFunnelsPro\\Addons' => $baseDir . '/includes/core/class-wpfnl-pro-addons.php', 'WPFunnelsPro\\Admin\\Modules\\Steps\\Checkout\\CheckoutFields' => $baseDir . '/admin/modules/steps/checkout/checkout-fields/wpfnl-checkout-fields.php', 'WPFunnelsPro\\Admin\\Modules\\Steps\\Checkout\\Module' => $baseDir . '/admin/modules/steps/checkout/class-wpfnl-checkout.php', 'WPFunnelsPro\\Admin\\Modules\\Steps\\Downsell\\Module' => $baseDir . '/admin/modules/steps/downsell/class-wpfnl-downsell.php', 'WPFunnelsPro\\Admin\\Modules\\Steps\\Upsell\\Module' => $baseDir . '/admin/modules/steps/upsell/class-wpfnl-upsell.php', 'WPFunnelsPro\\Admin\\OfferProduct\\Wpfnl_Pro_OfferProduct' => $baseDir . '/admin/modules/type/abstruct-wpfnl-pro-offer-product.php', 'WPFunnelsPro\\Admin\\OfferProduct\\Wpfunnels_Pro_Wc_OfferProduct' => $baseDir . '/admin/modules/type/sales-funnel/class-wpfnl-admin-woocommerce.php', 'WPFunnelsPro\\Admin\\Wpfnl_Pro_Admin' => $baseDir . '/admin/class-wpfnl-pro-admin.php', 'WPFunnelsPro\\AnalyticsController\\Lead' => $baseDir . '/includes/core/rest-api/controllers/analytics/LeadController.php', 'WPFunnelsPro\\AnalyticsController\\Wc' => $baseDir . '/includes/core/rest-api/controllers/analytics/WcController.php', 'WPFunnelsPro\\Automation\\Condition\\Downsell' => $baseDir . '/includes/core/mint-automation/conditions/downsell.php', 'WPFunnelsPro\\Automation\\Condition\\Ob' => $baseDir . '/includes/core/mint-automation/conditions/order-bump.php', 'WPFunnelsPro\\Automation\\Condition\\Upsell' => $baseDir . '/includes/core/mint-automation/conditions/upsell.php', 'WPFunnelsPro\\Compatibility\\ChainedProduct' => $baseDir . '/includes/core/compatibility/class-wpfnl-pro-chained-product.php', 'WPFunnelsPro\\Compatibility\\WcHpos' => $baseDir . '/includes/core/compatibility/class-wpfnl-pro-wc-hpos.php', 'WPFunnelsPro\\Db\\Wpfnl_Pro_Db' => $baseDir . '/admin/class-wpfnl-db.php', 'WPFunnelsPro\\Export\\Wpfnl_Export' => $baseDir . '/includes/core/import-export/class-wpfnl-pro-export-funnel.php', 'WPFunnelsPro\\Filters\\Wpfnl_Pro_Hooks' => $baseDir . '/includes/core/class-wpfnl-pro-hooks.php', 'WPFunnelsPro\\Frontend\\Gateways\\API\\Wpfnl_Pro_API_Base' => $baseDir . '/public/gateways/class-wpfnl-pro-api-base.php', 'WPFunnelsPro\\Frontend\\Gateways\\API\\Wpfnl_Pro_Gateway' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Authorize_Net' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-authorize-net.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Bacs' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-bacs.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Cheque' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-cheque.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Cod' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-cod.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Mollie' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-mollie-credit-card.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Mollie_Idea' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-mollie-ideal.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_OfficeGuy' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-officeguy.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_PayFast' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-payfast.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Paypal' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-paypal-standard.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Paypal_Express' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-paypal-express.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Paypal_WooCommerce' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Square' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-square.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Mollie_Helper' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-mollie-helper.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Woocommerce_Payments' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-woocommerce-payments.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Stripe_payment_process' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-stripe.php', 'WPFunnelsPro\\Frontend\\Modules\\Analytics\\Analytics' => $baseDir . '/public/modules/analytics/class-wpfnl-pro-analytics.php', 'WPFunnelsPro\\Frontend\\Modules\\Checkout\\EditField\\WPFunnel_Edit_field' => $baseDir . '/public/modules/checkout/classes/class-wpfnl-pro-edit-field.php', 'WPFunnelsPro\\Frontend\\Modules\\Checkout\\Module' => $baseDir . '/public/modules/checkout/class-wpfnl-pro-checkout.php', 'WPFunnelsPro\\Frontend\\Modules\\Gateways\\Exception\\Wpfnl_Payment_Gateway_Exception' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-exception.php', 'WPFunnelsPro\\Frontend\\Modules\\Gateways\\Payment_Gateways_Factory' => $baseDir . '/public/gateways/class-wpfnl-pro-gateway-factory.php', 'WPFunnelsPro\\Frontend\\Modules\\Thankyou\\Module' => $baseDir . '/public/modules/thank-you/class-wpfnl-pro-thankyou.php', 'WPFunnelsPro\\Frontend\\Modules\\Upsell\\Module' => $baseDir . '/public/modules/upsell/class-wpfnl-upsell.php', 'WPFunnelsPro\\Frontend\\Modules\\Webhook\\Wpfnl_Pro_Webhook' => $baseDir . '/public/modules/webhook/class-wpfnl-pro-webhook.php', 'WPFunnelsPro\\Frontend\\Modules\\Webhook\\Wpfnl_Pro_Webhook_Mapping' => $baseDir . '/public/modules/webhook/classes/class-wpfunnels-pro-webhook-mapping.php', 'WPFunnelsPro\\Frontend\\SkipOffer' => $baseDir . '/public/classes/skip-offer/class-wpfnl-skip-offer.php', 'WPFunnelsPro\\Frontend\\Wpfnl_Pro_Public' => $baseDir . '/public/class-wpfnl-pro-public.php', 'WPFunnelsPro\\Import\\Wpfnl_Import' => $baseDir . '/includes/core/import-export/class-wpfnl-pro-import-funnel.php', 'WPFunnelsPro\\Integration\\Affiliate\\Wpfnl_Pro_Integration_WPAffiliate' => $baseDir . '/includes/core/integrations/affiliate/class-wpfnl-pro-wp-affiliate-integration.php', 'WPFunnelsPro\\Integration\\Wpfnl_Pro_Integration_Fluent_Forms' => $baseDir . '/includes/core/integrations/class-wpfnl-pro-integration-fluent-forms.php', 'WPFunnelsPro\\Integration\\Wpfnl_Pro_Integrations_Manager' => $baseDir . '/includes/core/integrations/Manager.php', 'WPFunnelsPro\\Integrations\\CRM' => $baseDir . '/public/classes/integrations/crm-integrations/crm/abstract-class-wpfnl-pro-crm.php', 'WPFunnelsPro\\Integrations\\CRM\\CookieHandler' => $baseDir . '/public/classes/integrations/crm-integrations/handlers/class-wpfnl-pro-cookie.php', 'WPFunnelsPro\\Integrations\\CRM\\Data\\CookieData' => $baseDir . '/public/classes/integrations/crm-integrations/data/class-wpfnl-pro-automation-cookie-data.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\Event' => $baseDir . '/public/classes/integrations/crm-integrations/events/class-wpfnl-pro-automation-events.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\MainOrder' => $baseDir . '/public/classes/integrations/crm-integrations/events/class-wpfnl-mainorder-events.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\OfferEvent' => $baseDir . '/public/classes/integrations/crm-integrations/events/class-wpfnl-offer-events.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\OptinSubmit' => $baseDir . '/public/classes/integrations/crm-integrations/events/class-wpfnl-optin-submit.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\OrderBump' => $baseDir . '/public/classes/integrations/crm-integrations/events/class-wpfnl-orderbump-events.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\TriggerCTA' => $baseDir . '/public/classes/integrations/crm-integrations/events/class-wpfnl-cta-events.php', 'WPFunnelsPro\\Integrations\\CRM\\TriggerHandler' => $baseDir . '/public/classes/integrations/crm-integrations/handlers/class-wpfnl-pro-trigger.php', 'WPFunnelsPro\\Integrations\\CRM\\TriggerObject' => $baseDir . '/public/classes/integrations/crm-integrations/handlers/AutomationTriggerObject.php', 'WPFunnelsPro\\Integrations\\FluentCRM' => $baseDir . '/public/classes/integrations/crm-integrations/crm/class-wpfnl-pro-fluent-crm.php', 'WPFunnelsPro\\MailMint\\Automation' => $baseDir . '/includes/core/mint-automation/class-wpfnl-pro-automation.php', 'WPFunnelsPro\\Mint\\Backup_Mint_Hook' => $baseDir . '/admin/modules/mint/class-wpfnl-mint-hook-backup.php', 'WPFunnelsPro\\Mint\\Wpfnl_Mint_Hook' => $baseDir . '/admin/modules/mint/class-wpfnl-mint-hook.php', 'WPFunnelsPro\\Modules\\Frontend\\Checkout\\Single\\Wpfnl_Single_Product' => $baseDir . '/public/modules/checkout/classes/class-wpfnl-single-product.php', 'WPFunnelsPro\\Modules\\Frontend\\Checkout\\Variable\\Wpfnl_Variable_Product' => $baseDir . '/public/modules/checkout/classes/class-wpfnl-variable-product.php', 'WPFunnelsPro\\Modules\\Wpfnl_Pro_Modules_Manager' => $baseDir . '/includes/core/class-wpfnl-pro-module-manager.php', 'WPFunnelsPro\\Notice\\Notices' => $baseDir . '/admin/classes/class-wpfnl-pro-notices.php', 'WPFunnelsPro\\OfferProduct\\Wpfnl_Offer_Product' => $baseDir . '/includes/core/classes/class-wpfnl-pro-offer-product.php', 'WPFunnelsPro\\Offers\\Offer_Subscription' => $baseDir . '/includes/core/classes/class-wpfnl-pro-subscription.php', 'WPFunnelsPro\\Order\\Wpfnl_Order_Refund' => $baseDir . '/admin/classes/class-wpfnl-refund.php', 'WPFunnelsPro\\OrdersMeta\\OrderMeta' => $baseDir . '/admin/classes/class-wpfnl-order-meta.php', 'WPFunnelsPro\\Orders\\Orders' => $baseDir . '/includes/core/classes/class-wpfnl-pro-orders.php', 'WPFunnelsPro\\ReplaceOrder\\OrderReplacement' => $baseDir . '/admin/modules/steps/replace-order/class-replace-order.php', 'WPFunnelsPro\\Report\\Reporting' => $baseDir . '/includes/core/Reporting/Reporting.php', 'WPFunnelsPro\\Session\\Wpfnl_Pro_Session' => $baseDir . '/includes/core/class-wpfnl-pro-sessions.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Pro_Shortcodes' => $baseDir . '/includes/core/shortcodes/class-wpfnl-pro-shortcodes.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Button' => $baseDir . '/includes/core/shortcodes/class-wpfnl-pro-offer-button.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Description' => $baseDir . '/includes/core/shortcodes/class-wpfnl-pro-offer-description.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Image' => $baseDir . '/includes/core/shortcodes/class-wpfnl-pro-offer-image.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Price' => $baseDir . '/includes/core/shortcodes/class-wpfnl-pro-offer-price.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Title' => $baseDir . '/includes/core/shortcodes/class-wpfnl-pro-offer-title.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Widget' => $baseDir . '/includes/core/shortcodes/class-wpfnl-pro-offer-product-widget.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Variable_product' => $baseDir . '/includes/core/shortcodes/class-wpfnl-pro-variable-product.php', 'WPFunnelsPro\\Tracking\\GTM' => $baseDir . '/public/classes/tracking/gtm/class-wpfnl-pro-gtm.php', 'WPFunnelsPro\\Tracking\\Pixel\\Facebook_Pixel_Integration' => $baseDir . '/public/classes/tracking/pixel/class-facebook-pixel-integration.php', 'WPFunnelsPro\\Widgets\\Bricks\\OfferButton' => $baseDir . '/includes/core/widgets/bricks/OfferButton.php', 'WPFunnelsPro\\Widgets\\DiviModules\\Manager' => $baseDir . '/includes/core/widgets/divi-modules/Manager.php', 'WPFunnelsPro\\Widgets\\DiviModules\\Modules\\WPFNL_Lms_Pay_Button' => $baseDir . '/includes/core/widgets/divi-modules/includes/modules/LmsPayButton/LmsPayButton.php', 'WPFunnelsPro\\Widgets\\DiviModules\\Modules\\WPFNL_Offer_Button' => $baseDir . '/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php', 'WPFunnelsPro\\Widgets\\DiviModules\\WPFNL_DiviModules' => $baseDir . '/includes/core/widgets/divi-modules/includes/DiviModules.php', 'WPFunnelsPro\\Widgets\\Elementor\\Manager' => $baseDir . '/includes/core/widgets/elementor/Manager.php', 'WPFunnelsPro\\Widgets\\Elementor\\Offer_Button' => $baseDir . '/includes/core/widgets/elementor/elements/offer-button.php', 'WPFunnelsPro\\Widgets\\Elementor\\Offer_Product_Widget' => $baseDir . '/includes/core/widgets/elementor/elements/offer-product-widget.php', 'WPFunnelsPro\\Widgets\\Elementor\\Upsell_Downsell' => $baseDir . '/includes/core/widgets/elementor/elements/upsell-downsell.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\AbstractBlock' => $baseDir . '/includes/core/widgets/block/block-types/AbstractBlock.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\AbstractDynamicBlock' => $baseDir . '/includes/core/widgets/block/block-types/AbstractDynamicBlock.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\LmsOfferButton' => $baseDir . '/includes/core/widgets/block/block-types/LmsOfferButton.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\OfferButton' => $baseDir . '/includes/core/widgets/block/block-types/OfferButton.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\OfferPrice' => $baseDir . '/includes/core/widgets/block/block-types/OfferPrice.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\OfferProduct' => $baseDir . '/includes/core/widgets/block/block-types/OfferProduct.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\OfferTitle' => $baseDir . '/includes/core/widgets/block/block-types/OfferTitle.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\Manager' => $baseDir . '/includes/core/widgets/block/Manager.php', 'WPFunnelsPro\\Widgets\\Oxygen\\Elements' => $baseDir . '/includes/core/widgets/oxygen/elements/abstract-class-wpfnl-oxygen-elements.php', 'WPFunnelsPro\\Widgets\\Oxygen\\Manager' => $baseDir . '/includes/core/widgets/oxygen/Manager.php', 'WPFunnelsPro\\Widgets\\Oxygen\\OfferButton' => $baseDir . '/includes/core/widgets/oxygen/elements/OfferButton/OfferButton.php', 'WPFunnelsPro\\Widgets\\Wpfnl_Pro_Widgets_Manager' => $baseDir . '/includes/core/widgets/Manager.php', 'WPFunnelsPro\\Wpfnl_Pro_Dependency' => $baseDir . '/includes/core/class-wpfnl-pro-dependency.php', 'WPFunnelsPro\\Wpfnl_Pro_Licensing' => $baseDir . '/includes/utils/class-wpfnl-pro-licensing.php', 'WPFunnelsPro\\Wpfnl_Pro_Updater' => $baseDir . '/includes/utils/class-wpfnl-pro-updater.php', 'WPFunnelsPro\\Wpfnl_Pro_functions' => $baseDir . '/includes/utils/class-wpfnl-pro-functions.php', 'WPFunnels\\Rest\\AnalyticsController\\TimeInterval' => $baseDir . '/includes/core/rest-api/controllers/TimeInterval.php', 'WPFunnels\\Rest\\Controllers\\AbTestingController' => $baseDir . '/includes/core/rest-api/controllers/AbTestingController.php', 'WPFunnels\\Rest\\Controllers\\AnalyticsController' => $baseDir . '/includes/core/rest-api/controllers/AnalyticsController.php', 'WPFunnels\\Rest\\Controllers\\AutomationController' => $baseDir . '/includes/core/rest-api/controllers/AutomationController.php', 'WPFunnels\\Rest\\Controllers\\ImportExportController' => $baseDir . '/includes/core/rest-api/controllers/ImportExportController.php', 'WPFunnels\\Rest\\Controllers\\MintController' => $baseDir . '/includes/core/rest-api/controllers/MintController.php', 'WPFunnels\\Rest\\Controllers\\OfferController' => $baseDir . '/includes/core/rest-api/controllers/OfferController.php', 'WPFunnels\\Rest\\Controllers\\WebhookController' => $baseDir . '/includes/core/rest-api/controllers/WebhookController.php', 'Wpfnl_Analytics_Factory' => $baseDir . '/includes/core/rest-api/controllers/analytics/Analyticsfactory.php', 'Wpfnl_DateTime' => $baseDir . '/includes/core/rest-api/controllers/WpfnlTimeInterval.php', 'Wpfnl_Pro_Activator' => $baseDir . '/includes/utils/class-wpfnl-pro-activator.php', 'Wpfnl_Pro_Deactivator' => $baseDir . '/includes/utils/class-wpfnl-pro-deactivator.php', 'Wpfnl_Pro_Loader' => $baseDir . '/includes/utils/class-wpfnl-pro-loader.php', 'Wpfnl_Pro_OfferProduct_Factory' => $baseDir . '/admin/modules/type/sales-funnel/class-wpfnl-type-factory.php', 'Wpfnl_Pro_i18n' => $baseDir . '/includes/utils/class-wpfnl-pro-i18n.php', 'Wpfunnels\\Wpfunnels_AES\\Wpfunnels_Aes' => $baseDir . '/includes/core/aes-encryption/class-wpfunnels-aes.php', 'Wpfunnels\\Wpfunnels_AES_Encription\\Wpfunnels_Aes_Ctr' => $baseDir . '/includes/core/aes-encryption/class-wpfunnels-aes-counter.php', ); vendor/composer/autoload_namespaces.php000064400000000213147600245720014413 0ustar00register(true); return $loader; } } vendor/composer/autoload_static.php000064400000050067147600245720013577 0ustar00 __DIR__ . '/..' . '/composer/InstalledVersions.php', 'WPFunnelsProWebHooks\\Events\\Wpfnl_Pro_Webhook_Checkout' => __DIR__ . '/../..' . '/includes/core/webhook/events/class-wpfunnels-pro-webhook-checkout.php', 'WPFunnelsProWebHooks\\Events\\Wpfnl_Pro_Webhook_Offer' => __DIR__ . '/../..' . '/includes/core/webhook/events/class-wpfunnels-pro-webhook-offer.php', 'WPFunnelsProWebHooks\\Events\\Wpfnl_Pro_Webhook_Optin' => __DIR__ . '/../..' . '/includes/core/webhook/events/class-wofunnels-pro-webhook-optin.php', 'WPFunnelsProWebHooks\\Events\\Wpfnl_Pro_Webhook_Orderbump' => __DIR__ . '/../..' . '/includes/core/webhook/events/class-wpfunnels-pro-webhook-order-bump.php', 'WPFunnelsProWebHooks\\Functions\\Wpfnl_Pro_Webhook_Functions' => __DIR__ . '/../..' . '/includes/core/webhook/classes/class-wpfunnels-pro-webhook-functions.php', 'WPFunnelsPro\\AbTesting\\Backup_Ab_Testing' => __DIR__ . '/../..' . '/includes/core/ab-testing/class-wpfnl-pro-ab-testing-backup.php', 'WPFunnelsPro\\AbTesting\\Backup_Ab_Testing_Hook' => __DIR__ . '/../..' . '/admin/modules/ab-testing/class-wpfnl-ab-testing-hook-backup.php', 'WPFunnelsPro\\AbTesting\\Wpfnl_Ab_Testing' => __DIR__ . '/../..' . '/includes/core/ab-testing/class-wpfnl-pro-ab-testing.php', 'WPFunnelsPro\\AbTesting\\Wpfnl_Ab_Testing_Hook' => __DIR__ . '/../..' . '/admin/modules/ab-testing/class-wpfnl-ab-testing-hook.php', 'WPFunnelsPro\\Addons' => __DIR__ . '/../..' . '/includes/core/class-wpfnl-pro-addons.php', 'WPFunnelsPro\\Admin\\Modules\\Steps\\Checkout\\CheckoutFields' => __DIR__ . '/../..' . '/admin/modules/steps/checkout/checkout-fields/wpfnl-checkout-fields.php', 'WPFunnelsPro\\Admin\\Modules\\Steps\\Checkout\\Module' => __DIR__ . '/../..' . '/admin/modules/steps/checkout/class-wpfnl-checkout.php', 'WPFunnelsPro\\Admin\\Modules\\Steps\\Downsell\\Module' => __DIR__ . '/../..' . '/admin/modules/steps/downsell/class-wpfnl-downsell.php', 'WPFunnelsPro\\Admin\\Modules\\Steps\\Upsell\\Module' => __DIR__ . '/../..' . '/admin/modules/steps/upsell/class-wpfnl-upsell.php', 'WPFunnelsPro\\Admin\\OfferProduct\\Wpfnl_Pro_OfferProduct' => __DIR__ . '/../..' . '/admin/modules/type/abstruct-wpfnl-pro-offer-product.php', 'WPFunnelsPro\\Admin\\OfferProduct\\Wpfunnels_Pro_Wc_OfferProduct' => __DIR__ . '/../..' . '/admin/modules/type/sales-funnel/class-wpfnl-admin-woocommerce.php', 'WPFunnelsPro\\Admin\\Wpfnl_Pro_Admin' => __DIR__ . '/../..' . '/admin/class-wpfnl-pro-admin.php', 'WPFunnelsPro\\AnalyticsController\\Lead' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/analytics/LeadController.php', 'WPFunnelsPro\\AnalyticsController\\Wc' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/analytics/WcController.php', 'WPFunnelsPro\\Automation\\Condition\\Downsell' => __DIR__ . '/../..' . '/includes/core/mint-automation/conditions/downsell.php', 'WPFunnelsPro\\Automation\\Condition\\Ob' => __DIR__ . '/../..' . '/includes/core/mint-automation/conditions/order-bump.php', 'WPFunnelsPro\\Automation\\Condition\\Upsell' => __DIR__ . '/../..' . '/includes/core/mint-automation/conditions/upsell.php', 'WPFunnelsPro\\Compatibility\\ChainedProduct' => __DIR__ . '/../..' . '/includes/core/compatibility/class-wpfnl-pro-chained-product.php', 'WPFunnelsPro\\Compatibility\\WcHpos' => __DIR__ . '/../..' . '/includes/core/compatibility/class-wpfnl-pro-wc-hpos.php', 'WPFunnelsPro\\Db\\Wpfnl_Pro_Db' => __DIR__ . '/../..' . '/admin/class-wpfnl-db.php', 'WPFunnelsPro\\Export\\Wpfnl_Export' => __DIR__ . '/../..' . '/includes/core/import-export/class-wpfnl-pro-export-funnel.php', 'WPFunnelsPro\\Filters\\Wpfnl_Pro_Hooks' => __DIR__ . '/../..' . '/includes/core/class-wpfnl-pro-hooks.php', 'WPFunnelsPro\\Frontend\\Gateways\\API\\Wpfnl_Pro_API_Base' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-api-base.php', 'WPFunnelsPro\\Frontend\\Gateways\\API\\Wpfnl_Pro_Gateway' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Authorize_Net' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-authorize-net.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Bacs' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-bacs.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Cheque' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-cheque.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Cod' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-cod.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Mollie' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-mollie-credit-card.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Mollie_Idea' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-mollie-ideal.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_OfficeGuy' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-officeguy.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_PayFast' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-payfast.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Paypal' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-paypal-standard.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Paypal_Express' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-paypal-express.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Paypal_WooCommerce' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-paypal-woocommerce.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Gateway_Square' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-square.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Mollie_Helper' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-mollie-helper.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Pro_Woocommerce_Payments' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-woocommerce-payments.php', 'WPFunnelsPro\\Frontend\\Gateways\\Wpfnl_Stripe_payment_process' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-stripe.php', 'WPFunnelsPro\\Frontend\\Modules\\Analytics\\Analytics' => __DIR__ . '/../..' . '/public/modules/analytics/class-wpfnl-pro-analytics.php', 'WPFunnelsPro\\Frontend\\Modules\\Checkout\\EditField\\WPFunnel_Edit_field' => __DIR__ . '/../..' . '/public/modules/checkout/classes/class-wpfnl-pro-edit-field.php', 'WPFunnelsPro\\Frontend\\Modules\\Checkout\\Module' => __DIR__ . '/../..' . '/public/modules/checkout/class-wpfnl-pro-checkout.php', 'WPFunnelsPro\\Frontend\\Modules\\Gateways\\Exception\\Wpfnl_Payment_Gateway_Exception' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-exception.php', 'WPFunnelsPro\\Frontend\\Modules\\Gateways\\Payment_Gateways_Factory' => __DIR__ . '/../..' . '/public/gateways/class-wpfnl-pro-gateway-factory.php', 'WPFunnelsPro\\Frontend\\Modules\\Thankyou\\Module' => __DIR__ . '/../..' . '/public/modules/thank-you/class-wpfnl-pro-thankyou.php', 'WPFunnelsPro\\Frontend\\Modules\\Upsell\\Module' => __DIR__ . '/../..' . '/public/modules/upsell/class-wpfnl-upsell.php', 'WPFunnelsPro\\Frontend\\Modules\\Webhook\\Wpfnl_Pro_Webhook' => __DIR__ . '/../..' . '/public/modules/webhook/class-wpfnl-pro-webhook.php', 'WPFunnelsPro\\Frontend\\Modules\\Webhook\\Wpfnl_Pro_Webhook_Mapping' => __DIR__ . '/../..' . '/public/modules/webhook/classes/class-wpfunnels-pro-webhook-mapping.php', 'WPFunnelsPro\\Frontend\\SkipOffer' => __DIR__ . '/../..' . '/public/classes/skip-offer/class-wpfnl-skip-offer.php', 'WPFunnelsPro\\Frontend\\Wpfnl_Pro_Public' => __DIR__ . '/../..' . '/public/class-wpfnl-pro-public.php', 'WPFunnelsPro\\Import\\Wpfnl_Import' => __DIR__ . '/../..' . '/includes/core/import-export/class-wpfnl-pro-import-funnel.php', 'WPFunnelsPro\\Integration\\Affiliate\\Wpfnl_Pro_Integration_WPAffiliate' => __DIR__ . '/../..' . '/includes/core/integrations/affiliate/class-wpfnl-pro-wp-affiliate-integration.php', 'WPFunnelsPro\\Integration\\Wpfnl_Pro_Integration_Fluent_Forms' => __DIR__ . '/../..' . '/includes/core/integrations/class-wpfnl-pro-integration-fluent-forms.php', 'WPFunnelsPro\\Integration\\Wpfnl_Pro_Integrations_Manager' => __DIR__ . '/../..' . '/includes/core/integrations/Manager.php', 'WPFunnelsPro\\Integrations\\CRM' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/crm/abstract-class-wpfnl-pro-crm.php', 'WPFunnelsPro\\Integrations\\CRM\\CookieHandler' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/handlers/class-wpfnl-pro-cookie.php', 'WPFunnelsPro\\Integrations\\CRM\\Data\\CookieData' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/data/class-wpfnl-pro-automation-cookie-data.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\Event' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/events/class-wpfnl-pro-automation-events.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\MainOrder' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/events/class-wpfnl-mainorder-events.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\OfferEvent' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/events/class-wpfnl-offer-events.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\OptinSubmit' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/events/class-wpfnl-optin-submit.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\OrderBump' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/events/class-wpfnl-orderbump-events.php', 'WPFunnelsPro\\Integrations\\CRM\\Event\\TriggerCTA' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/events/class-wpfnl-cta-events.php', 'WPFunnelsPro\\Integrations\\CRM\\TriggerHandler' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/handlers/class-wpfnl-pro-trigger.php', 'WPFunnelsPro\\Integrations\\CRM\\TriggerObject' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/handlers/AutomationTriggerObject.php', 'WPFunnelsPro\\Integrations\\FluentCRM' => __DIR__ . '/../..' . '/public/classes/integrations/crm-integrations/crm/class-wpfnl-pro-fluent-crm.php', 'WPFunnelsPro\\MailMint\\Automation' => __DIR__ . '/../..' . '/includes/core/mint-automation/class-wpfnl-pro-automation.php', 'WPFunnelsPro\\Mint\\Backup_Mint_Hook' => __DIR__ . '/../..' . '/admin/modules/mint/class-wpfnl-mint-hook-backup.php', 'WPFunnelsPro\\Mint\\Wpfnl_Mint_Hook' => __DIR__ . '/../..' . '/admin/modules/mint/class-wpfnl-mint-hook.php', 'WPFunnelsPro\\Modules\\Frontend\\Checkout\\Single\\Wpfnl_Single_Product' => __DIR__ . '/../..' . '/public/modules/checkout/classes/class-wpfnl-single-product.php', 'WPFunnelsPro\\Modules\\Frontend\\Checkout\\Variable\\Wpfnl_Variable_Product' => __DIR__ . '/../..' . '/public/modules/checkout/classes/class-wpfnl-variable-product.php', 'WPFunnelsPro\\Modules\\Wpfnl_Pro_Modules_Manager' => __DIR__ . '/../..' . '/includes/core/class-wpfnl-pro-module-manager.php', 'WPFunnelsPro\\Notice\\Notices' => __DIR__ . '/../..' . '/admin/classes/class-wpfnl-pro-notices.php', 'WPFunnelsPro\\OfferProduct\\Wpfnl_Offer_Product' => __DIR__ . '/../..' . '/includes/core/classes/class-wpfnl-pro-offer-product.php', 'WPFunnelsPro\\Offers\\Offer_Subscription' => __DIR__ . '/../..' . '/includes/core/classes/class-wpfnl-pro-subscription.php', 'WPFunnelsPro\\Order\\Wpfnl_Order_Refund' => __DIR__ . '/../..' . '/admin/classes/class-wpfnl-refund.php', 'WPFunnelsPro\\OrdersMeta\\OrderMeta' => __DIR__ . '/../..' . '/admin/classes/class-wpfnl-order-meta.php', 'WPFunnelsPro\\Orders\\Orders' => __DIR__ . '/../..' . '/includes/core/classes/class-wpfnl-pro-orders.php', 'WPFunnelsPro\\ReplaceOrder\\OrderReplacement' => __DIR__ . '/../..' . '/admin/modules/steps/replace-order/class-replace-order.php', 'WPFunnelsPro\\Report\\Reporting' => __DIR__ . '/../..' . '/includes/core/Reporting/Reporting.php', 'WPFunnelsPro\\Session\\Wpfnl_Pro_Session' => __DIR__ . '/../..' . '/includes/core/class-wpfnl-pro-sessions.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Pro_Shortcodes' => __DIR__ . '/../..' . '/includes/core/shortcodes/class-wpfnl-pro-shortcodes.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Button' => __DIR__ . '/../..' . '/includes/core/shortcodes/class-wpfnl-pro-offer-button.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Description' => __DIR__ . '/../..' . '/includes/core/shortcodes/class-wpfnl-pro-offer-description.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Image' => __DIR__ . '/../..' . '/includes/core/shortcodes/class-wpfnl-pro-offer-image.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Price' => __DIR__ . '/../..' . '/includes/core/shortcodes/class-wpfnl-pro-offer-price.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Title' => __DIR__ . '/../..' . '/includes/core/shortcodes/class-wpfnl-pro-offer-title.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Offer_Widget' => __DIR__ . '/../..' . '/includes/core/shortcodes/class-wpfnl-pro-offer-product-widget.php', 'WPFunnelsPro\\Shortcodes\\Wpfnl_Shortcode_Variable_product' => __DIR__ . '/../..' . '/includes/core/shortcodes/class-wpfnl-pro-variable-product.php', 'WPFunnelsPro\\Tracking\\GTM' => __DIR__ . '/../..' . '/public/classes/tracking/gtm/class-wpfnl-pro-gtm.php', 'WPFunnelsPro\\Tracking\\Pixel\\Facebook_Pixel_Integration' => __DIR__ . '/../..' . '/public/classes/tracking/pixel/class-facebook-pixel-integration.php', 'WPFunnelsPro\\Widgets\\Bricks\\OfferButton' => __DIR__ . '/../..' . '/includes/core/widgets/bricks/OfferButton.php', 'WPFunnelsPro\\Widgets\\DiviModules\\Manager' => __DIR__ . '/../..' . '/includes/core/widgets/divi-modules/Manager.php', 'WPFunnelsPro\\Widgets\\DiviModules\\Modules\\WPFNL_Lms_Pay_Button' => __DIR__ . '/../..' . '/includes/core/widgets/divi-modules/includes/modules/LmsPayButton/LmsPayButton.php', 'WPFunnelsPro\\Widgets\\DiviModules\\Modules\\WPFNL_Offer_Button' => __DIR__ . '/../..' . '/includes/core/widgets/divi-modules/includes/modules/OfferButton/OfferButton.php', 'WPFunnelsPro\\Widgets\\DiviModules\\WPFNL_DiviModules' => __DIR__ . '/../..' . '/includes/core/widgets/divi-modules/includes/DiviModules.php', 'WPFunnelsPro\\Widgets\\Elementor\\Manager' => __DIR__ . '/../..' . '/includes/core/widgets/elementor/Manager.php', 'WPFunnelsPro\\Widgets\\Elementor\\Offer_Button' => __DIR__ . '/../..' . '/includes/core/widgets/elementor/elements/offer-button.php', 'WPFunnelsPro\\Widgets\\Elementor\\Offer_Product_Widget' => __DIR__ . '/../..' . '/includes/core/widgets/elementor/elements/offer-product-widget.php', 'WPFunnelsPro\\Widgets\\Elementor\\Upsell_Downsell' => __DIR__ . '/../..' . '/includes/core/widgets/elementor/elements/upsell-downsell.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\AbstractBlock' => __DIR__ . '/../..' . '/includes/core/widgets/block/block-types/AbstractBlock.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\AbstractDynamicBlock' => __DIR__ . '/../..' . '/includes/core/widgets/block/block-types/AbstractDynamicBlock.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\LmsOfferButton' => __DIR__ . '/../..' . '/includes/core/widgets/block/block-types/LmsOfferButton.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\OfferButton' => __DIR__ . '/../..' . '/includes/core/widgets/block/block-types/OfferButton.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\OfferPrice' => __DIR__ . '/../..' . '/includes/core/widgets/block/block-types/OfferPrice.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\OfferProduct' => __DIR__ . '/../..' . '/includes/core/widgets/block/block-types/OfferProduct.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\BlockTypes\\OfferTitle' => __DIR__ . '/../..' . '/includes/core/widgets/block/block-types/OfferTitle.php', 'WPFunnelsPro\\Widgets\\Gutenberg\\Manager' => __DIR__ . '/../..' . '/includes/core/widgets/block/Manager.php', 'WPFunnelsPro\\Widgets\\Oxygen\\Elements' => __DIR__ . '/../..' . '/includes/core/widgets/oxygen/elements/abstract-class-wpfnl-oxygen-elements.php', 'WPFunnelsPro\\Widgets\\Oxygen\\Manager' => __DIR__ . '/../..' . '/includes/core/widgets/oxygen/Manager.php', 'WPFunnelsPro\\Widgets\\Oxygen\\OfferButton' => __DIR__ . '/../..' . '/includes/core/widgets/oxygen/elements/OfferButton/OfferButton.php', 'WPFunnelsPro\\Widgets\\Wpfnl_Pro_Widgets_Manager' => __DIR__ . '/../..' . '/includes/core/widgets/Manager.php', 'WPFunnelsPro\\Wpfnl_Pro_Dependency' => __DIR__ . '/../..' . '/includes/core/class-wpfnl-pro-dependency.php', 'WPFunnelsPro\\Wpfnl_Pro_Licensing' => __DIR__ . '/../..' . '/includes/utils/class-wpfnl-pro-licensing.php', 'WPFunnelsPro\\Wpfnl_Pro_Updater' => __DIR__ . '/../..' . '/includes/utils/class-wpfnl-pro-updater.php', 'WPFunnelsPro\\Wpfnl_Pro_functions' => __DIR__ . '/../..' . '/includes/utils/class-wpfnl-pro-functions.php', 'WPFunnels\\Rest\\AnalyticsController\\TimeInterval' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/TimeInterval.php', 'WPFunnels\\Rest\\Controllers\\AbTestingController' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/AbTestingController.php', 'WPFunnels\\Rest\\Controllers\\AnalyticsController' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/AnalyticsController.php', 'WPFunnels\\Rest\\Controllers\\AutomationController' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/AutomationController.php', 'WPFunnels\\Rest\\Controllers\\ImportExportController' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/ImportExportController.php', 'WPFunnels\\Rest\\Controllers\\MintController' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/MintController.php', 'WPFunnels\\Rest\\Controllers\\OfferController' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/OfferController.php', 'WPFunnels\\Rest\\Controllers\\WebhookController' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/WebhookController.php', 'Wpfnl_Analytics_Factory' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/analytics/Analyticsfactory.php', 'Wpfnl_DateTime' => __DIR__ . '/../..' . '/includes/core/rest-api/controllers/WpfnlTimeInterval.php', 'Wpfnl_Pro_Activator' => __DIR__ . '/../..' . '/includes/utils/class-wpfnl-pro-activator.php', 'Wpfnl_Pro_Deactivator' => __DIR__ . '/../..' . '/includes/utils/class-wpfnl-pro-deactivator.php', 'Wpfnl_Pro_Loader' => __DIR__ . '/../..' . '/includes/utils/class-wpfnl-pro-loader.php', 'Wpfnl_Pro_OfferProduct_Factory' => __DIR__ . '/../..' . '/admin/modules/type/sales-funnel/class-wpfnl-type-factory.php', 'Wpfnl_Pro_i18n' => __DIR__ . '/../..' . '/includes/utils/class-wpfnl-pro-i18n.php', 'Wpfunnels\\Wpfunnels_AES\\Wpfunnels_Aes' => __DIR__ . '/../..' . '/includes/core/aes-encryption/class-wpfunnels-aes.php', 'Wpfunnels\\Wpfunnels_AES_Encription\\Wpfunnels_Aes_Ctr' => __DIR__ . '/../..' . '/includes/core/aes-encryption/class-wpfunnels-aes-counter.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->classMap = ComposerStaticInit7f94130c3ca17e5c0581f2c733d7f7e6::$classMap; }, null, ClassLoader::class); } } vendor/composer/ClassLoader.php000064400000037304147600245720012613 0ustar00 * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier * @author Jordi Boggiano * @see https://www.php-fig.org/psr/psr-0/ * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { /** @var ?string */ private $vendorDir; // PSR-4 /** * @var array[] * @psalm-var array> */ private $prefixLengthsPsr4 = array(); /** * @var array[] * @psalm-var array> */ private $prefixDirsPsr4 = array(); /** * @var array[] * @psalm-var array */ private $fallbackDirsPsr4 = array(); // PSR-0 /** * @var array[] * @psalm-var array> */ private $prefixesPsr0 = array(); /** * @var array[] * @psalm-var array */ private $fallbackDirsPsr0 = array(); /** @var bool */ private $useIncludePath = false; /** * @var string[] * @psalm-var array */ private $classMap = array(); /** @var bool */ private $classMapAuthoritative = false; /** * @var bool[] * @psalm-var array */ private $missingClasses = array(); /** @var ?string */ private $apcuPrefix; /** * @var self[] */ private static $registeredLoaders = array(); /** * @param ?string $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; } /** * @return string[] */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } /** * @return array[] * @psalm-return array> */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } /** * @return array[] * @psalm-return array */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } /** * @return array[] * @psalm-return array */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } /** * @return string[] Array of classname => path * @psalm-return array */ public function getClassMap() { return $this->classMap; } /** * @param string[] $classMap Class to filename map * @psalm-param array $classMap * * @return void */ public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /** * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * * @param string $prefix The prefix * @param string[]|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( (array) $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, (array) $paths ); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = (array) $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( (array) $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], (array) $paths ); } } /** * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param string[]|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( (array) $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, (array) $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], (array) $paths ); } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param string[]|string $paths The PSR-0 base directories * * @return void */ public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param string[]|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * * @return void */ public function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } /** * Turns on searching the include path for class files. * * @param bool $useIncludePath * * @return void */ public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } /** * Can be used to check if the autoloader uses the include path to check * for classes. * * @return bool */ public function getUseIncludePath() { return $this->useIncludePath; } /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative * * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Should class lookup fail if not found in the current class map? * * @return bool */ public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } /** * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix * * @return void */ public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** * The APCu prefix in use, or null if APCu caching is not enabled. * * @return string|null */ public function getApcuPrefix() { return $this->apcuPrefix; } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not * * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); if (null === $this->vendorDir) { return; } if ($prepend) { self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; } else { unset(self::$registeredLoaders[$this->vendorDir]); self::$registeredLoaders[$this->vendorDir] = $this; } } /** * Unregisters this instance as an autoloader. * * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); if (null !== $this->vendorDir) { unset(self::$registeredLoaders[$this->vendorDir]); } } /** * Loads the given class or interface. * * @param string $class The name of the class * @return true|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } return null; } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; } /** * Returns the currently registered loaders indexed by their corresponding vendor directories. * * @return self[] */ public static function getRegisteredLoaders() { return self::$registeredLoaders; } /** * @param string $class * @param string $ext * @return string|false */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } } /** * Scope isolated include. * * Prevents access to $this/self from included files. * * @param string $file * @return void * @private */ function includeFile($file) { include $file; } vendor/composer/installed.json000064400000004056147600245720012556 0ustar00{ "packages": [ { "name": "woocommerce/action-scheduler", "version": "3.8.0", "version_normalized": "3.8.0.0", "source": { "type": "git", "url": "https://github.com/woocommerce/action-scheduler.git", "reference": "99cd7981f51c98883082534d4852491858d72834" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/99cd7981f51c98883082534d4852491858d72834", "reference": "99cd7981f51c98883082534d4852491858d72834", "shasum": "" }, "require": { "php": ">=5.6" }, "require-dev": { "phpunit/phpunit": "^7.5", "woocommerce/woocommerce-sniffs": "0.1.0", "wp-cli/wp-cli": "~2.5.0", "yoast/phpunit-polyfills": "^2.0" }, "time": "2024-05-22T13:50:29+00:00", "type": "wordpress-plugin", "extra": { "scripts-description": { "test": "Run unit tests", "phpcs": "Analyze code against the WordPress coding standards with PHP_CodeSniffer", "phpcbf": "Fix coding standards warnings/errors automatically with PHP Code Beautifier" } }, "installation-source": "dist", "notification-url": "https://packagist.org/downloads/", "license": [ "GPL-3.0-or-later" ], "description": "Action Scheduler for WordPress and WooCommerce", "homepage": "https://actionscheduler.org/", "support": { "issues": "https://github.com/woocommerce/action-scheduler/issues", "source": "https://github.com/woocommerce/action-scheduler/tree/3.8.0" }, "install-path": "../woocommerce/action-scheduler" } ], "dev": false, "dev-package-names": [] } vendor/composer/installed.php000064400000002245147600245720012372 0ustar00 array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'reference' => '7ce0da5a5f0b02bb9a68de2fd9155e8ea289da94', 'name' => 'wpfunnels/wpfunnels-pro', 'dev' => false, ), 'versions' => array( 'woocommerce/action-scheduler' => array( 'pretty_version' => '3.8.0', 'version' => '3.8.0.0', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../woocommerce/action-scheduler', 'aliases' => array(), 'reference' => '99cd7981f51c98883082534d4852491858d72834', 'dev_requirement' => false, ), 'wpfunnels/wpfunnels-pro' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'reference' => '7ce0da5a5f0b02bb9a68de2fd9155e8ea289da94', 'dev_requirement' => false, ), ), ); vendor/composer/InstalledVersions.php000064400000035234147600245720014067 0ustar00 * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Autoload\ClassLoader; use Composer\Semver\VersionParser; /** * This class is copied in every Composer installed project and available to all * * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * * To require its presence, you can require `composer-runtime-api ^2.0` * * @final */ class InstalledVersions { /** * @var mixed[]|null * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null */ private static $installed; /** * @var bool|null */ private static $canGetVendors; /** * @var array[] * @psalm-var array}> */ private static $installedByVendor = array(); /** * Returns a list of all package names which are present, either by being installed, replaced or provided * * @return string[] * @psalm-return list */ public static function getInstalledPackages() { $packages = array(); foreach (self::getInstalled() as $installed) { $packages[] = array_keys($installed['versions']); } if (1 === \count($packages)) { return $packages[0]; } return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); } /** * Returns a list of all package names with a specific type e.g. 'library' * * @param string $type * @return string[] * @psalm-return list */ public static function getInstalledPackagesByType($type) { $packagesByType = array(); foreach (self::getInstalled() as $installed) { foreach ($installed['versions'] as $name => $package) { if (isset($package['type']) && $package['type'] === $type) { $packagesByType[] = $name; } } } return $packagesByType; } /** * Checks whether the given package is installed * * This also returns true if the package name is provided or replaced by another package * * @param string $packageName * @param bool $includeDevRequirements * @return bool */ public static function isInstalled($packageName, $includeDevRequirements = true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); } } return false; } /** * Checks whether the given package satisfies a version constraint * * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: * * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') * * @param VersionParser $parser Install composer/semver to have access to this class and functionality * @param string $packageName * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package * @return bool */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { $constraint = $parser->parseConstraints($constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); } /** * Returns a version constraint representing all the range(s) which are installed for a given package * * It is easier to use this via isInstalled() with the $constraint argument if you need to check * whether a given version of a package is installed, and not just whether it exists * * @param string $packageName * @return string Version constraint usable with composer/semver */ public static function getVersionRanges($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } $ranges = array(); if (isset($installed['versions'][$packageName]['pretty_version'])) { $ranges[] = $installed['versions'][$packageName]['pretty_version']; } if (array_key_exists('aliases', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); } if (array_key_exists('replaced', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); } if (array_key_exists('provided', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); } return implode(' || ', $ranges); } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['version'])) { return null; } return $installed['versions'][$packageName]['version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getPrettyVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['pretty_version'])) { return null; } return $installed['versions'][$packageName]['pretty_version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference */ public static function getReference($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['reference'])) { return null; } return $installed['versions'][$packageName]['reference']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. */ public static function getInstallPath($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @return array * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} */ public static function getRootPackage() { $installed = self::getInstalled(); return $installed[0]['root']; } /** * Returns the raw installed.php data for custom implementations * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} */ public static function getRawData() { @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { self::$installed = include __DIR__ . '/installed.php'; } else { self::$installed = array(); } } return self::$installed; } /** * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] * @psalm-return list}> */ public static function getAllRawData() { return self::getInstalled(); } /** * Lets you reload the static array from another file * * This is only useful for complex integrations in which a project needs to use * this class but then also needs to execute another project's autoloader in process, * and wants to ensure both projects have access to their version of installed.php. * * A typical case would be PHPUnit, where it would need to make sure it reads all * the data it needs from this class, then call reload() with * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure * the project in which it runs can then also use this class safely, without * interference between PHPUnit's dependencies and the project's dependencies. * * @param array[] $data A vendor/composer/installed.php data set * @return void * * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data */ public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); } /** * @return array[] * @psalm-return list}> */ private static function getInstalled() { if (null === self::$canGetVendors) { self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); } $installed = array(); if (self::$canGetVendors) { foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { self::$installed = $installed[count($installed) - 1]; } } } } if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { self::$installed = require __DIR__ . '/installed.php'; } else { self::$installed = array(); } } $installed[] = self::$installed; return $installed; } } vendor/composer/LICENSE000064400000002056147600245720010707 0ustar00 Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Clean_Command.php000064400000007340147600245720025561 0ustar00] * : The maximum number of actions to delete per batch. Defaults to 20. * * [--batches=] * : Limit execution to a number of batches. Defaults to 0, meaning batches will continue all eligible actions are deleted. * * [--status=] * : Only clean actions with the specified status. Defaults to Canceled, Completed. Define multiple statuses as a comma separated string (without spaces), e.g. `--status=complete,failed,canceled` * * [--before=] * : Only delete actions with scheduled date older than this. Defaults to 31 days. e.g `--before='7 days ago'`, `--before='02-Feb-2020 20:20:20'` * * [--pause=] * : The number of seconds to pause between batches. Default no pause. * * @param array $args Positional arguments. * @param array $assoc_args Keyed arguments. * @throws \WP_CLI\ExitException When an error occurs. * * @subcommand clean */ public function clean( $args, $assoc_args ) { // Handle passed arguments. $batch = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 20 ) ); $batches = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) ); $status = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'status', '' ) ); $status = array_filter( array_map( 'trim', $status ) ); $before = \WP_CLI\Utils\get_flag_value( $assoc_args, 'before', '' ); $sleep = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 ); $batches_completed = 0; $actions_deleted = 0; $unlimited = $batches === 0; try { $lifespan = as_get_datetime_object( $before ); } catch ( Exception $e ) { $lifespan = null; } try { // Custom queue cleaner instance. $cleaner = new ActionScheduler_QueueCleaner( null, $batch ); // Clean actions for as long as possible. while ( $unlimited || $batches_completed < $batches ) { if ( $sleep && $batches_completed > 0 ) { sleep( $sleep ); } $deleted = count( $cleaner->clean_actions( $status, $lifespan, null,'CLI' ) ); if ( $deleted <= 0 ) { break; } $actions_deleted += $deleted; $batches_completed++; $this->print_success( $deleted ); } } catch ( Exception $e ) { $this->print_error( $e ); } $this->print_total_batches( $batches_completed ); if ( $batches_completed > 1 ) { $this->print_success( $actions_deleted ); } } /** * Print WP CLI message about how many batches of actions were processed. * * @param int $batches_processed */ protected function print_total_batches( int $batches_processed ) { WP_CLI::log( sprintf( /* translators: %d refers to the total number of batches processed */ _n( '%d batch processed.', '%d batches processed.', $batches_processed, 'action-scheduler' ), $batches_processed ) ); } /** * Convert an exception into a WP CLI error. * * @param Exception $e The error object. * * @throws \WP_CLI\ExitException */ protected function print_error( Exception $e ) { WP_CLI::error( sprintf( /* translators: %s refers to the exception error message */ __( 'There was an error deleting an action: %s', 'action-scheduler' ), $e->getMessage() ) ); } /** * Print a success message with the number of completed actions. * * @param int $actions_deleted */ protected function print_success( int $actions_deleted ) { WP_CLI::success( sprintf( /* translators: %d refers to the total number of actions deleted */ _n( '%d action deleted.', '%d actions deleted.', $actions_deleted, 'action-scheduler' ), $actions_deleted ) ); } } vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php000064400000014217147600245720025360 0ustar00run_cleanup(); $this->add_hooks(); // Check to make sure there aren't too many concurrent processes running. if ( $this->has_maximum_concurrent_batches() ) { if ( $force ) { WP_CLI::warning( __( 'There are too many concurrent batches, but the run is forced to continue.', 'action-scheduler' ) ); } else { WP_CLI::error( __( 'There are too many concurrent batches.', 'action-scheduler' ) ); } } // Stake a claim and store it. $this->claim = $this->store->stake_claim( $batch_size, null, $hooks, $group ); $this->monitor->attach( $this->claim ); $this->actions = $this->claim->get_actions(); return count( $this->actions ); } /** * Add our hooks to the appropriate actions. * * @author Jeremy Pry */ protected function add_hooks() { add_action( 'action_scheduler_before_execute', array( $this, 'before_execute' ) ); add_action( 'action_scheduler_after_execute', array( $this, 'after_execute' ), 10, 2 ); add_action( 'action_scheduler_failed_execution', array( $this, 'action_failed' ), 10, 2 ); } /** * Set up the WP CLI progress bar. * * @author Jeremy Pry */ protected function setup_progress_bar() { $count = count( $this->actions ); $this->progress_bar = new ProgressBar( /* translators: %d: amount of actions */ sprintf( _n( 'Running %d action', 'Running %d actions', $count, 'action-scheduler' ), $count ), $count ); } /** * Process actions in the queue. * * @author Jeremy Pry * * @param string $context Optional runner context. Default 'WP CLI'. * * @return int The number of actions processed. */ public function run( $context = 'WP CLI' ) { do_action( 'action_scheduler_before_process_queue' ); $this->setup_progress_bar(); foreach ( $this->actions as $action_id ) { // Error if we lost the claim. if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $this->claim->get_id() ) ) ) { WP_CLI::warning( __( 'The claim has been lost. Aborting current batch.', 'action-scheduler' ) ); break; } $this->process_action( $action_id, $context ); $this->progress_bar->tick(); } $completed = $this->progress_bar->current(); $this->progress_bar->finish(); $this->store->release_claim( $this->claim ); do_action( 'action_scheduler_after_process_queue' ); return $completed; } /** * Handle WP CLI message when the action is starting. * * @author Jeremy Pry * * @param $action_id */ public function before_execute( $action_id ) { /* translators: %s refers to the action ID */ WP_CLI::log( sprintf( __( 'Started processing action %s', 'action-scheduler' ), $action_id ) ); } /** * Handle WP CLI message when the action has completed. * * @author Jeremy Pry * * @param int $action_id * @param null|ActionScheduler_Action $action The instance of the action. Default to null for backward compatibility. */ public function after_execute( $action_id, $action = null ) { // backward compatibility if ( null === $action ) { $action = $this->store->fetch_action( $action_id ); } /* translators: 1: action ID 2: hook name */ WP_CLI::log( sprintf( __( 'Completed processing action %1$s with hook: %2$s', 'action-scheduler' ), $action_id, $action->get_hook() ) ); } /** * Handle WP CLI message when the action has failed. * * @author Jeremy Pry * * @param int $action_id * @param Exception $exception * @throws \WP_CLI\ExitException With failure message. */ public function action_failed( $action_id, $exception ) { WP_CLI::error( /* translators: 1: action ID 2: exception message */ sprintf( __( 'Error processing action %1$s: %2$s', 'action-scheduler' ), $action_id, $exception->getMessage() ), false ); } /** * Sleep and help avoid hitting memory limit * * @param int $sleep_time Amount of seconds to sleep * @deprecated 3.0.0 */ protected function stop_the_insanity( $sleep_time = 0 ) { _deprecated_function( 'ActionScheduler_WPCLI_QueueRunner::stop_the_insanity', '3.0.0', 'ActionScheduler_DataController::free_memory' ); ActionScheduler_DataController::free_memory(); } /** * Maybe trigger the stop_the_insanity() method to free up memory. */ protected function maybe_stop_the_insanity() { // The value returned by progress_bar->current() might be padded. Remove padding, and convert to int. $current_iteration = intval( trim( $this->progress_bar->current() ) ); if ( 0 === $current_iteration % 50 ) { $this->stop_the_insanity(); } } } vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php000064400000015244147600245720026517 0ustar00init(); $obj->register_tables( true ); WP_CLI::success( sprintf( /* translators: %s refers to the schema name*/ __( 'Registered schema for %s', 'action-scheduler' ), $classname ) ); } } } /** * Run the Action Scheduler * * ## OPTIONS * * [--batch-size=] * : The maximum number of actions to run. Defaults to 100. * * [--batches=] * : Limit execution to a number of batches. Defaults to 0, meaning batches will continue being executed until all actions are complete. * * [--cleanup-batch-size=] * : The maximum number of actions to clean up. Defaults to the value of --batch-size. * * [--hooks=] * : Only run actions with the specified hook. Omitting this option runs actions with any hook. Define multiple hooks as a comma separated string (without spaces), e.g. `--hooks=hook_one,hook_two,hook_three` * * [--group=] * : Only run actions from the specified group. Omitting this option runs actions from all groups. * * [--exclude-groups=] * : Run actions from all groups except the specified group(s). Define multiple groups as a comma separated string (without spaces), e.g. '--group_a,group_b'. This option is ignored when `--group` is used. * * [--free-memory-on=] * : The number of actions to process between freeing memory. 0 disables freeing memory. Default 50. * * [--pause=] * : The number of seconds to pause when freeing memory. Default no pause. * * [--force] * : Whether to force execution despite the maximum number of concurrent processes being exceeded. * * @param array $args Positional arguments. * @param array $assoc_args Keyed arguments. * @throws \WP_CLI\ExitException When an error occurs. * * @subcommand run */ public function run( $args, $assoc_args ) { // Handle passed arguments. $batch = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 100 ) ); $batches = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) ); $clean = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'cleanup-batch-size', $batch ) ); $hooks = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'hooks', '' ) ); $hooks = array_filter( array_map( 'trim', $hooks ) ); $group = \WP_CLI\Utils\get_flag_value( $assoc_args, 'group', '' ); $exclude_groups = \WP_CLI\Utils\get_flag_value( $assoc_args, 'exclude-groups', '' ); $free_on = \WP_CLI\Utils\get_flag_value( $assoc_args, 'free-memory-on', 50 ); $sleep = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 ); $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force', false ); ActionScheduler_DataController::set_free_ticks( $free_on ); ActionScheduler_DataController::set_sleep_time( $sleep ); $batches_completed = 0; $actions_completed = 0; $unlimited = $batches === 0; if ( is_callable( [ ActionScheduler::store(), 'set_claim_filter' ] ) ) { $exclude_groups = $this->parse_comma_separated_string( $exclude_groups ); if ( ! empty( $exclude_groups ) ) { ActionScheduler::store()->set_claim_filter('exclude-groups', $exclude_groups ); } } try { // Custom queue cleaner instance. $cleaner = new ActionScheduler_QueueCleaner( null, $clean ); // Get the queue runner instance $runner = new ActionScheduler_WPCLI_QueueRunner( null, null, $cleaner ); // Determine how many tasks will be run in the first batch. $total = $runner->setup( $batch, $hooks, $group, $force ); // Run actions for as long as possible. while ( $total > 0 ) { $this->print_total_actions( $total ); $actions_completed += $runner->run(); $batches_completed++; // Maybe set up tasks for the next batch. $total = ( $unlimited || $batches_completed < $batches ) ? $runner->setup( $batch, $hooks, $group, $force ) : 0; } } catch ( Exception $e ) { $this->print_error( $e ); } $this->print_total_batches( $batches_completed ); $this->print_success( $actions_completed ); } /** * Converts a string of comma-separated values into an array of those same values. * * @param string $string The string of one or more comma separated values. * * @return array */ private function parse_comma_separated_string( $string ): array { return array_filter( str_getcsv( $string ) ); } /** * Print WP CLI message about how many actions are about to be processed. * * @author Jeremy Pry * * @param int $total */ protected function print_total_actions( $total ) { WP_CLI::log( sprintf( /* translators: %d refers to how many scheduled tasks were found to run */ _n( 'Found %d scheduled task', 'Found %d scheduled tasks', $total, 'action-scheduler' ), $total ) ); } /** * Print WP CLI message about how many batches of actions were processed. * * @author Jeremy Pry * * @param int $batches_completed */ protected function print_total_batches( $batches_completed ) { WP_CLI::log( sprintf( /* translators: %d refers to the total number of batches executed */ _n( '%d batch executed.', '%d batches executed.', $batches_completed, 'action-scheduler' ), $batches_completed ) ); } /** * Convert an exception into a WP CLI error. * * @author Jeremy Pry * * @param Exception $e The error object. * * @throws \WP_CLI\ExitException */ protected function print_error( Exception $e ) { WP_CLI::error( sprintf( /* translators: %s refers to the exception error message */ __( 'There was an error running the action scheduler: %s', 'action-scheduler' ), $e->getMessage() ) ); } /** * Print a success message with the number of completed actions. * * @author Jeremy Pry * * @param int $actions_completed */ protected function print_success( $actions_completed ) { WP_CLI::success( sprintf( /* translators: %d refers to the total number of tasks completed */ _n( '%d scheduled task completed.', '%d scheduled tasks completed.', $actions_completed, 'action-scheduler' ), $actions_completed ) ); } } vendor/woocommerce/action-scheduler/classes/WP_CLI/Migration_Command.php000064400000011304147600245720022371 0ustar00 'Migrates actions to the DB tables store', 'synopsis' => [ [ 'type' => 'assoc', 'name' => 'batch-size', 'optional' => true, 'default' => 100, 'description' => 'The number of actions to process in each batch', ], [ 'type' => 'assoc', 'name' => 'free-memory-on', 'optional' => true, 'default' => 50, 'description' => 'The number of actions to process between freeing memory. 0 disables freeing memory', ], [ 'type' => 'assoc', 'name' => 'pause', 'optional' => true, 'default' => 0, 'description' => 'The number of seconds to pause when freeing memory', ], [ 'type' => 'flag', 'name' => 'dry-run', 'optional' => true, 'description' => 'Reports on the actions that would have been migrated, but does not change any data', ], ], ] ); } /** * Process the data migration. * * @param array $positional_args Required for WP CLI. Not used in migration. * @param array $assoc_args Optional arguments. * * @return void */ public function migrate( $positional_args, $assoc_args ) { $this->init_logging(); $config = $this->get_migration_config( $assoc_args ); $runner = new Runner( $config ); $runner->init_destination(); $batch_size = isset( $assoc_args[ 'batch-size' ] ) ? (int) $assoc_args[ 'batch-size' ] : 100; $free_on = isset( $assoc_args[ 'free-memory-on' ] ) ? (int) $assoc_args[ 'free-memory-on' ] : 50; $sleep = isset( $assoc_args[ 'pause' ] ) ? (int) $assoc_args[ 'pause' ] : 0; \ActionScheduler_DataController::set_free_ticks( $free_on ); \ActionScheduler_DataController::set_sleep_time( $sleep ); do { $actions_processed = $runner->run( $batch_size ); $this->total_processed += $actions_processed; } while ( $actions_processed > 0 ); if ( ! $config->get_dry_run() ) { // let the scheduler know that there's nothing left to do $scheduler = new Scheduler(); $scheduler->mark_complete(); } WP_CLI::success( sprintf( '%s complete. %d actions processed.', $config->get_dry_run() ? 'Dry run' : 'Migration', $this->total_processed ) ); } /** * Build the config object used to create the Runner * * @param array $args Optional arguments. * * @return ActionScheduler\Migration\Config */ private function get_migration_config( $args ) { $args = wp_parse_args( $args, [ 'dry-run' => false, ] ); $config = Controller::instance()->get_migration_config_object(); $config->set_dry_run( ! empty( $args[ 'dry-run' ] ) ); return $config; } /** * Hook command line logging into migration actions. */ private function init_logging() { add_action( 'action_scheduler/migrate_action_dry_run', function ( $action_id ) { WP_CLI::debug( sprintf( 'Dry-run: migrated action %d', $action_id ) ); }, 10, 1 ); add_action( 'action_scheduler/no_action_to_migrate', function ( $action_id ) { WP_CLI::debug( sprintf( 'No action found to migrate for ID %d', $action_id ) ); }, 10, 1 ); add_action( 'action_scheduler/migrate_action_failed', function ( $action_id ) { WP_CLI::warning( sprintf( 'Failed migrating action with ID %d', $action_id ) ); }, 10, 1 ); add_action( 'action_scheduler/migrate_action_incomplete', function ( $source_id, $destination_id ) { WP_CLI::warning( sprintf( 'Unable to remove source action with ID %d after migrating to new ID %d', $source_id, $destination_id ) ); }, 10, 2 ); add_action( 'action_scheduler/migrated_action', function ( $source_id, $destination_id ) { WP_CLI::debug( sprintf( 'Migrated source action with ID %d to new store with ID %d', $source_id, $destination_id ) ); }, 10, 2 ); add_action( 'action_scheduler/migration_batch_starting', function ( $batch ) { WP_CLI::debug( 'Beginning migration of batch: ' . print_r( $batch, true ) ); }, 10, 1 ); add_action( 'action_scheduler/migration_batch_complete', function ( $batch ) { WP_CLI::log( sprintf( 'Completed migration of %d actions', count( $batch ) ) ); }, 10, 1 ); } } vendor/woocommerce/action-scheduler/classes/WP_CLI/ProgressBar.php000064400000004720147600245720021237 0ustar00total_ticks = 0; $this->message = $message; $this->count = $count; $this->interval = $interval; } /** * Increment the progress bar ticks. */ public function tick() { if ( null === $this->progress_bar ) { $this->setup_progress_bar(); } $this->progress_bar->tick(); $this->total_ticks++; do_action( 'action_scheduler/progress_tick', $this->total_ticks ); } /** * Get the progress bar tick count. * * @return int */ public function current() { return $this->progress_bar ? $this->progress_bar->current() : 0; } /** * Finish the current progress bar. */ public function finish() { if ( null !== $this->progress_bar ) { $this->progress_bar->finish(); } $this->progress_bar = null; } /** * Set the message used when creating the progress bar. * * @param string $message The message to be used when the next progress bar is created. */ public function set_message( $message ) { $this->message = $message; } /** * Set the count for a new progress bar. * * @param integer $count The total number of ticks expected to complete. */ public function set_count( $count ) { $this->count = $count; $this->finish(); } /** * Set up the progress bar. */ protected function setup_progress_bar() { $this->progress_bar = \WP_CLI\Utils\make_progress_bar( $this->message, $this->count, $this->interval ); } } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler.php000064400000023042147600245720023031 0ustar00init() (or it's parent method) set this itself, * once it has initialized, however that would cause problems in cases where a custom data store is in * use and it has not yet been updated to follow that same logic. */ function () { self::$data_store_initialized = true; /** * Fires when Action Scheduler is ready: it is safe to use the procedural API after this point. * * @since 3.5.5 */ do_action( 'action_scheduler_init' ); }, 1 ); } else { $admin_view->init(); $store->init(); $logger->init(); $runner->init(); self::$data_store_initialized = true; /** * Fires when Action Scheduler is ready: it is safe to use the procedural API after this point. * * @since 3.5.5 */ do_action( 'action_scheduler_init' ); } if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) { require_once( self::plugin_path( 'deprecated/functions.php' ) ); } if ( defined( 'WP_CLI' ) && WP_CLI ) { WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Scheduler_command' ); WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Clean_Command' ); if ( ! ActionScheduler_DataController::is_migration_complete() && Controller::instance()->allow_migration() ) { $command = new Migration_Command(); $command->register(); } } /** * Handle WP comment cleanup after migration. */ if ( is_a( $logger, 'ActionScheduler_DBLogger' ) && ActionScheduler_DataController::is_migration_complete() && ActionScheduler_WPCommentCleaner::has_logs() ) { ActionScheduler_WPCommentCleaner::init(); } add_action( 'action_scheduler/migration_complete', 'ActionScheduler_WPCommentCleaner::maybe_schedule_cleanup' ); } /** * Check whether the AS data store has been initialized. * * @param string $function_name The name of the function being called. Optional. Default `null`. * @return bool */ public static function is_initialized( $function_name = null ) { if ( ! self::$data_store_initialized && ! empty( $function_name ) ) { $message = sprintf( /* translators: %s function name. */ __( '%s() was called before the Action Scheduler data store was initialized', 'action-scheduler' ), esc_attr( $function_name ) ); _doing_it_wrong( $function_name, $message, '3.1.6' ); } return self::$data_store_initialized; } /** * Determine if the class is one of our abstract classes. * * @since 3.0.0 * * @param string $class The class name. * * @return bool */ protected static function is_class_abstract( $class ) { static $abstracts = array( 'ActionScheduler' => true, 'ActionScheduler_Abstract_ListTable' => true, 'ActionScheduler_Abstract_QueueRunner' => true, 'ActionScheduler_Abstract_Schedule' => true, 'ActionScheduler_Abstract_RecurringSchedule' => true, 'ActionScheduler_Lock' => true, 'ActionScheduler_Logger' => true, 'ActionScheduler_Abstract_Schema' => true, 'ActionScheduler_Store' => true, 'ActionScheduler_TimezoneHelper' => true, ); return isset( $abstracts[ $class ] ) && $abstracts[ $class ]; } /** * Determine if the class is one of our migration classes. * * @since 3.0.0 * * @param string $class The class name. * * @return bool */ protected static function is_class_migration( $class ) { static $migration_segments = array( 'ActionMigrator' => true, 'BatchFetcher' => true, 'DBStoreMigrator' => true, 'DryRun' => true, 'LogMigrator' => true, 'Config' => true, 'Controller' => true, 'Runner' => true, 'Scheduler' => true, ); $segments = explode( '_', $class ); $segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class; return isset( $migration_segments[ $segment ] ) && $migration_segments[ $segment ]; } /** * Determine if the class is one of our WP CLI classes. * * @since 3.0.0 * * @param string $class The class name. * * @return bool */ protected static function is_class_cli( $class ) { static $cli_segments = array( 'QueueRunner' => true, 'Command' => true, 'ProgressBar' => true, ); $segments = explode( '_', $class ); $segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class; return isset( $cli_segments[ $segment ] ) && $cli_segments[ $segment ]; } final public function __clone() { trigger_error("Singleton. No cloning allowed!", E_USER_ERROR); } final public function __wakeup() { trigger_error("Singleton. No serialization allowed!", E_USER_ERROR); } final private function __construct() {} /** Deprecated **/ public static function get_datetime_object( $when = null, $timezone = 'UTC' ) { _deprecated_function( __METHOD__, '2.0', 'wcs_add_months()' ); return as_get_datetime_object( $when, $timezone ); } /** * Issue deprecated warning if an Action Scheduler function is called in the shutdown hook. * * @param string $function_name The name of the function being called. * @deprecated 3.1.6. */ public static function check_shutdown_hook( $function_name ) { _deprecated_function( __FUNCTION__, '3.1.6' ); } } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php000064400000061053147600245720026623 0ustar00 value pair. The * key must much the table column name and the value is the label, which is * automatically translated. * * @var array */ protected $columns = array(); /** * Defines the row-actions. It expects an array where the key * is the column name and the value is an array of actions. * * The array of actions are key => value, where key is the method name * (with the prefix row_action_) and the value is the label * and title. * * @var array */ protected $row_actions = array(); /** * The Primary key of our table * * @var string */ protected $ID = 'ID'; /** * Enables sorting, it expects an array * of columns (the column names are the values) * * @var array */ protected $sort_by = array(); /** * The default sort order * * @var string */ protected $filter_by = array(); /** * The status name => count combinations for this table's items. Used to display status filters. * * @var array */ protected $status_counts = array(); /** * Notices to display when loading the table. Array of arrays of form array( 'class' => {updated|error}, 'message' => 'This is the notice text display.' ). * * @var array */ protected $admin_notices = array(); /** * Localised string displayed in the

element above the able. * * @var string */ protected $table_header; /** * Enables bulk actions. It must be an array where the key is the action name * and the value is the label (which is translated automatically). It is important * to notice that it will check that the method exists (`bulk_$name`) and will throw * an exception if it does not exists. * * This class will automatically check if the current request has a bulk action, will do the * validations and afterwards will execute the bulk method, with two arguments. The first argument * is the array with primary keys, the second argument is a string with a list of the primary keys, * escaped and ready to use (with `IN`). * * @var array */ protected $bulk_actions = array(); /** * Makes translation easier, it basically just wraps * `_x` with some default (the package name). * * @param string $text The new text to translate. * @param string $context The context of the text. * @return string|void The translated text. * * @deprecated 3.0.0 Use `_x()` instead. */ protected function translate( $text, $context = '' ) { return $text; } /** * Reads `$this->bulk_actions` and returns an array that WP_List_Table understands. It * also validates that the bulk method handler exists. It throws an exception because * this is a library meant for developers and missing a bulk method is a development-time error. * * @return array * * @throws RuntimeException Throws RuntimeException when the bulk action does not have a callback method. */ protected function get_bulk_actions() { $actions = array(); foreach ( $this->bulk_actions as $action => $label ) { if ( ! is_callable( array( $this, 'bulk_' . $action ) ) ) { throw new RuntimeException( "The bulk action $action does not have a callback method" ); } $actions[ $action ] = $label; } return $actions; } /** * Checks if the current request has a bulk action. If that is the case it will validate and will * execute the bulk method handler. Regardless if the action is valid or not it will redirect to * the previous page removing the current arguments that makes this request a bulk action. */ protected function process_bulk_action() { global $wpdb; // Detect when a bulk action is being triggered. $action = $this->current_action(); if ( ! $action ) { return; } check_admin_referer( 'bulk-' . $this->_args['plural'] ); $method = 'bulk_' . $action; if ( array_key_exists( $action, $this->bulk_actions ) && is_callable( array( $this, $method ) ) && ! empty( $_GET['ID'] ) && is_array( $_GET['ID'] ) ) { $ids_sql = '(' . implode( ',', array_fill( 0, count( $_GET['ID'] ), '%s' ) ) . ')'; $id = array_map( 'absint', $_GET['ID'] ); $this->$method( $id, $wpdb->prepare( $ids_sql, $id ) ); //phpcs:ignore WordPress.DB.PreparedSQL } if ( isset( $_SERVER['REQUEST_URI'] ) ) { wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce', 'ID', 'action', 'action2' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ); exit; } } /** * Default code for deleting entries. * validated already by process_bulk_action() * * @param array $ids ids of the items to delete. * @param string $ids_sql the sql for the ids. * @return void */ protected function bulk_delete( array $ids, $ids_sql ) { $store = ActionScheduler::store(); foreach ( $ids as $action_id ) { $store->delete( $action_id ); } } /** * Prepares the _column_headers property which is used by WP_Table_List at rendering. * It merges the columns and the sortable columns. */ protected function prepare_column_headers() { $this->_column_headers = array( $this->get_columns(), get_hidden_columns( $this->screen ), $this->get_sortable_columns(), ); } /** * Reads $this->sort_by and returns the columns name in a format that WP_Table_List * expects */ public function get_sortable_columns() { $sort_by = array(); foreach ( $this->sort_by as $column ) { $sort_by[ $column ] = array( $column, true ); } return $sort_by; } /** * Returns the columns names for rendering. It adds a checkbox for selecting everything * as the first column */ public function get_columns() { $columns = array_merge( array( 'cb' => '' ), $this->columns ); return $columns; } /** * Get prepared LIMIT clause for items query * * @global wpdb $wpdb * * @return string Prepared LIMIT clause for items query. */ protected function get_items_query_limit() { global $wpdb; $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page ); return $wpdb->prepare( 'LIMIT %d', $per_page ); } /** * Returns the number of items to offset/skip for this current view. * * @return int */ protected function get_items_offset() { $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page ); $current_page = $this->get_pagenum(); if ( 1 < $current_page ) { $offset = $per_page * ( $current_page - 1 ); } else { $offset = 0; } return $offset; } /** * Get prepared OFFSET clause for items query * * @global wpdb $wpdb * * @return string Prepared OFFSET clause for items query. */ protected function get_items_query_offset() { global $wpdb; return $wpdb->prepare( 'OFFSET %d', $this->get_items_offset() ); } /** * Prepares the ORDER BY sql statement. It uses `$this->sort_by` to know which * columns are sortable. This requests validates the orderby $_GET parameter is a valid * column and sortable. It will also use order (ASC|DESC) using DESC by default. */ protected function get_items_query_order() { if ( empty( $this->sort_by ) ) { return ''; } $orderby = esc_sql( $this->get_request_orderby() ); $order = esc_sql( $this->get_request_order() ); return "ORDER BY {$orderby} {$order}"; } /** * Querystring arguments to persist between form submissions. * * @since 3.7.3 * * @return string[] */ protected function get_request_query_args_to_persist() { return array_merge( $this->sort_by, array( 'page', 'status', 'tab', ) ); } /** * Return the sortable column specified for this request to order the results by, if any. * * @return string */ protected function get_request_orderby() { $valid_sortable_columns = array_values( $this->sort_by ); if ( ! empty( $_GET['orderby'] ) && in_array( $_GET['orderby'], $valid_sortable_columns, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended $orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended } else { $orderby = $valid_sortable_columns[0]; } return $orderby; } /** * Return the sortable column order specified for this request. * * @return string */ protected function get_request_order() { if ( ! empty( $_GET['order'] ) && 'desc' === strtolower( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended $order = 'DESC'; } else { $order = 'ASC'; } return $order; } /** * Return the status filter for this request, if any. * * @return string */ protected function get_request_status() { $status = ( ! empty( $_GET['status'] ) ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended return $status; } /** * Return the search filter for this request, if any. * * @return string */ protected function get_request_search_query() { $search_query = ( ! empty( $_GET['s'] ) ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended return $search_query; } /** * Process and return the columns name. This is meant for using with SQL, this means it * always includes the primary key. * * @return array */ protected function get_table_columns() { $columns = array_keys( $this->columns ); if ( ! in_array( $this->ID, $columns, true ) ) { $columns[] = $this->ID; } return $columns; } /** * Check if the current request is doing a "full text" search. If that is the case * prepares the SQL to search texts using LIKE. * * If the current request does not have any search or if this list table does not support * that feature it will return an empty string. * * @return string */ protected function get_items_query_search() { global $wpdb; if ( empty( $_GET['s'] ) || empty( $this->search_by ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended return ''; } $search_string = sanitize_text_field( wp_unslash( $_GET['s'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended $filter = array(); foreach ( $this->search_by as $column ) { $wild = '%'; $sql_like = $wild . $wpdb->esc_like( $search_string ) . $wild; $filter[] = $wpdb->prepare( '`' . $column . '` LIKE %s', $sql_like ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.NotPrepared } return implode( ' OR ', $filter ); } /** * Prepares the SQL to filter rows by the options defined at `$this->filter_by`. Before trusting * any data sent by the user it validates that it is a valid option. */ protected function get_items_query_filters() { global $wpdb; if ( ! $this->filter_by || empty( $_GET['filter_by'] ) || ! is_array( $_GET['filter_by'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended return ''; } $filter = array(); foreach ( $this->filter_by as $column => $options ) { if ( empty( $_GET['filter_by'][ $column ] ) || empty( $options[ $_GET['filter_by'][ $column ] ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended continue; } $filter[] = $wpdb->prepare( "`$column` = %s", sanitize_text_field( wp_unslash( $_GET['filter_by'][ $column ] ) ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.InterpolatedNotPrepared } return implode( ' AND ', $filter ); } /** * Prepares the data to feed WP_Table_List. * * This has the core for selecting, sorting and filting data. To keep the code simple * its logic is split among many methods (get_items_query_*). * * Beside populating the items this function will also count all the records that matches * the filtering criteria and will do fill the pagination variables. */ public function prepare_items() { global $wpdb; $this->process_bulk_action(); $this->process_row_actions(); if ( ! empty( $_REQUEST['_wp_http_referer'] && ! empty( $_SERVER['REQUEST_URI'] ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ); exit; } $this->prepare_column_headers(); $limit = $this->get_items_query_limit(); $offset = $this->get_items_query_offset(); $order = $this->get_items_query_order(); $where = array_filter( array( $this->get_items_query_search(), $this->get_items_query_filters(), ) ); $columns = '`' . implode( '`, `', $this->get_table_columns() ) . '`'; if ( ! empty( $where ) ) { $where = 'WHERE (' . implode( ') AND (', $where ) . ')'; } else { $where = ''; } $sql = "SELECT $columns FROM {$this->table_name} {$where} {$order} {$limit} {$offset}"; $this->set_items( $wpdb->get_results( $sql, ARRAY_A ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $query_count = "SELECT COUNT({$this->ID}) FROM {$this->table_name} {$where}"; $total_items = $wpdb->get_var( $query_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page ); $this->set_pagination_args( array( 'total_items' => $total_items, 'per_page' => $per_page, 'total_pages' => ceil( $total_items / $per_page ), ) ); } /** * Display the table. * * @param string $which The name of the table. */ public function extra_tablenav( $which ) { if ( ! $this->filter_by || 'top' !== $which ) { return; } echo '
'; foreach ( $this->filter_by as $id => $options ) { $default = ! empty( $_GET['filter_by'][ $id ] ) ? sanitize_text_field( wp_unslash( $_GET['filter_by'][ $id ] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( empty( $options[ $default ] ) ) { $default = ''; } echo ''; } submit_button( esc_html__( 'Filter', 'action-scheduler' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) ); echo '
'; } /** * Set the data for displaying. It will attempt to unserialize (There is a chance that some columns * are serialized). This can be override in child classes for futher data transformation. * * @param array $items Items array. */ protected function set_items( array $items ) { $this->items = array(); foreach ( $items as $item ) { $this->items[ $item[ $this->ID ] ] = array_map( 'maybe_unserialize', $item ); } } /** * Renders the checkbox for each row, this is the first column and it is named ID regardless * of how the primary key is named (to keep the code simpler). The bulk actions will do the proper * name transformation though using `$this->ID`. * * @param array $row The row to render. */ public function column_cb( $row ) { return ''; } /** * Renders the row-actions. * * This method renders the action menu, it reads the definition from the $row_actions property, * and it checks that the row action method exists before rendering it. * * @param array $row Row to be rendered. * @param string $column_name Column name. * @return string */ protected function maybe_render_actions( $row, $column_name ) { if ( empty( $this->row_actions[ $column_name ] ) ) { return; } $row_id = $row[ $this->ID ]; $actions = '
'; $action_count = 0; foreach ( $this->row_actions[ $column_name ] as $action_key => $action ) { $action_count++; if ( ! method_exists( $this, 'row_action_' . $action_key ) ) { continue; } $action_link = ! empty( $action['link'] ) ? $action['link'] : add_query_arg( array( 'row_action' => $action_key, 'row_id' => $row_id, 'nonce' => wp_create_nonce( $action_key . '::' . $row_id ), ) ); $span_class = ! empty( $action['class'] ) ? $action['class'] : $action_key; $separator = ( $action_count < count( $this->row_actions[ $column_name ] ) ) ? ' | ' : ''; $actions .= sprintf( '', esc_attr( $span_class ) ); $actions .= sprintf( '%3$s', esc_url( $action_link ), esc_attr( $action['desc'] ), esc_html( $action['name'] ) ); $actions .= sprintf( '%s', $separator ); } $actions .= '
'; return $actions; } /** * Process the bulk actions. * * @return void */ protected function process_row_actions() { $parameters = array( 'row_action', 'row_id', 'nonce' ); foreach ( $parameters as $parameter ) { if ( empty( $_REQUEST[ $parameter ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } } $action = sanitize_text_field( wp_unslash( $_REQUEST['row_action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated $row_id = sanitize_text_field( wp_unslash( $_REQUEST['row_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated $nonce = sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated $method = 'row_action_' . $action; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( wp_verify_nonce( $nonce, $action . '::' . $row_id ) && method_exists( $this, $method ) ) { $this->$method( sanitize_text_field( wp_unslash( $row_id ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } if ( isset( $_SERVER['REQUEST_URI'] ) ) { wp_safe_redirect( remove_query_arg( array( 'row_id', 'row_action', 'nonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ); exit; } } /** * Default column formatting, it will escape everythig for security. * * @param array $item The item array. * @param string $column_name Column name to display. * * @return string */ public function column_default( $item, $column_name ) { $column_html = esc_html( $item[ $column_name ] ); $column_html .= $this->maybe_render_actions( $item, $column_name ); return $column_html; } /** * Display the table heading and search query, if any */ protected function display_header() { echo '

' . esc_attr( $this->table_header ) . '

'; if ( $this->get_request_search_query() ) { /* translators: %s: search query */ echo '' . esc_attr( sprintf( __( 'Search results for "%s"', 'action-scheduler' ), $this->get_request_search_query() ) ) . ''; } echo '
'; } /** * Display the table heading and search query, if any */ protected function display_admin_notices() { foreach ( $this->admin_notices as $notice ) { echo '
'; echo '

' . wp_kses_post( $notice['message'] ) . '

'; echo '
'; } } /** * Prints the available statuses so the user can click to filter. */ protected function display_filter_by_status() { $status_list_items = array(); $request_status = $this->get_request_status(); // Helper to set 'all' filter when not set on status counts passed in. if ( ! isset( $this->status_counts['all'] ) ) { $all_count = array_sum( $this->status_counts ); if ( isset( $this->status_counts['past-due'] ) ) { $all_count -= $this->status_counts['past-due']; } $this->status_counts = array( 'all' => $all_count ) + $this->status_counts; } // Translated status labels. $status_labels = ActionScheduler_Store::instance()->get_status_labels(); $status_labels['all'] = esc_html_x( 'All', 'status labels', 'action-scheduler' ); $status_labels['past-due'] = esc_html_x( 'Past-due', 'status labels', 'action-scheduler' ); foreach ( $this->status_counts as $status_slug => $count ) { if ( 0 === $count ) { continue; } if ( $status_slug === $request_status || ( empty( $request_status ) && 'all' === $status_slug ) ) { $status_list_item = '
  • %3$s (%4$d)
  • '; } else { $status_list_item = '
  • %3$s (%4$d)
  • '; } $status_name = isset( $status_labels[ $status_slug ] ) ? $status_labels[ $status_slug ] : ucfirst( $status_slug ); $status_filter_url = ( 'all' === $status_slug ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_slug ); $status_filter_url = remove_query_arg( array( 'paged', 's' ), $status_filter_url ); $status_list_items[] = sprintf( $status_list_item, esc_attr( $status_slug ), esc_url( $status_filter_url ), esc_html( $status_name ), absint( $count ) ); } if ( $status_list_items ) { echo '
      '; echo implode( " | \n", $status_list_items ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '
    '; } } /** * Renders the table list, we override the original class to render the table inside a form * and to render any needed HTML (like the search box). By doing so the callee of a function can simple * forget about any extra HTML. */ protected function display_table() { echo ''; foreach ( $this->get_request_query_args_to_persist() as $arg ) { $arg_value = isset( $_GET[ $arg ] ) ? sanitize_text_field( wp_unslash( $_GET[ $arg ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! $arg_value ) { continue; } echo ''; } if ( ! empty( $this->search_by ) ) { echo $this->search_box( $this->get_search_box_button_text(), 'plugin' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } parent::display(); echo ''; } /** * Process any pending actions. */ public function process_actions() { $this->process_bulk_action(); $this->process_row_actions(); if ( ! empty( $_REQUEST['_wp_http_referer'] ) && ! empty( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended // _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ); exit; } } /** * Render the list table page, including header, notices, status filters and table. */ public function display_page() { $this->prepare_items(); echo '
    '; $this->display_header(); $this->display_admin_notices(); $this->display_filter_by_status(); $this->display_table(); echo '
    '; } /** * Get the text to display in the search box on the list table. */ protected function get_search_box_placeholder() { return esc_html__( 'Search', 'action-scheduler' ); } /** * Gets the screen per_page option name. * * @return string */ protected function get_per_page_option_name() { return $this->package . '_items_per_page'; } } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php000064400000032131147600245720027211 0ustar00created_time = microtime( true ); $this->store = $store ? $store : ActionScheduler_Store::instance(); $this->monitor = $monitor ? $monitor : new ActionScheduler_FatalErrorMonitor( $this->store ); $this->cleaner = $cleaner ? $cleaner : new ActionScheduler_QueueCleaner( $this->store ); } /** * Process an individual action. * * @param int $action_id The action ID to process. * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' * Generally, this should be capitalised and not localised as it's a proper noun. */ public function process_action( $action_id, $context = '' ) { // Temporarily override the error handler while we process the current action. set_error_handler( /** * Temporary error handler which can catch errors and convert them into exceptions. This faciliates more * robust error handling across all supported PHP versions. * * @throws Exception * * @param int $type Error level expressed as an integer. * @param string $message Error message. */ function ( $type, $message ) { throw new Exception( $message ); }, E_USER_ERROR | E_RECOVERABLE_ERROR ); /* * The nested try/catch structure is required because we potentially need to convert thrown errors into * exceptions (and an exception thrown from a catch block cannot be caught by a later catch block in the *same* * structure). */ try { try { $valid_action = false; do_action( 'action_scheduler_before_execute', $action_id, $context ); if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) { do_action( 'action_scheduler_execution_ignored', $action_id, $context ); return; } $valid_action = true; do_action( 'action_scheduler_begin_execute', $action_id, $context ); $action = $this->store->fetch_action( $action_id ); $this->store->log_execution( $action_id ); $action->execute(); do_action( 'action_scheduler_after_execute', $action_id, $action, $context ); $this->store->mark_complete( $action_id ); } catch ( Throwable $e ) { // Throwable is defined when executing under PHP 7.0 and up. We convert it to an exception, for // compatibility with ActionScheduler_Logger. throw new Exception( $e->getMessage(), $e->getCode(), $e ); } } catch ( Exception $e ) { // This catch block exists for compatibility with PHP 5.6. $this->handle_action_error( $action_id, $e, $context, $valid_action ); } finally { restore_error_handler(); } if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) && $action->get_schedule()->is_recurring() ) { $this->schedule_next_instance( $action, $action_id ); } } /** * Marks actions as either having failed execution or failed validation, as appropriate. * * @param int $action_id Action ID. * @param Exception $e Exception instance. * @param string $context Execution context. * @param bool $valid_action If the action is valid. * * @return void */ private function handle_action_error( $action_id, $e, $context, $valid_action ) { if ( $valid_action ) { $this->store->mark_failure( $action_id ); /** * Runs when action execution fails. * * @param int $action_id Action ID. * @param Exception $e Exception instance. * @param string $context Execution context. */ do_action( 'action_scheduler_failed_execution', $action_id, $e, $context ); } else { /** * Runs when action validation fails. * * @param int $action_id Action ID. * @param Exception $e Exception instance. * @param string $context Execution context. */ do_action( 'action_scheduler_failed_validation', $action_id, $e, $context ); } } /** * Schedule the next instance of the action if necessary. * * @param ActionScheduler_Action $action * @param int $action_id */ protected function schedule_next_instance( ActionScheduler_Action $action, $action_id ) { // If a recurring action has been consistently failing, we may wish to stop rescheduling it. if ( ActionScheduler_Store::STATUS_FAILED === $this->store->get_status( $action_id ) && $this->recurring_action_is_consistently_failing( $action, $action_id ) ) { ActionScheduler_Logger::instance()->log( $action_id, __( 'This action appears to be consistently failing. A new instance will not be scheduled.', 'action-scheduler' ) ); return; } try { ActionScheduler::factory()->repeat( $action ); } catch ( Exception $e ) { do_action( 'action_scheduler_failed_to_schedule_next_instance', $action_id, $e, $action ); } } /** * Determine if the specified recurring action has been consistently failing. * * @param ActionScheduler_Action $action The recurring action to be rescheduled. * @param int $action_id The ID of the recurring action. * * @return bool */ private function recurring_action_is_consistently_failing( ActionScheduler_Action $action, $action_id ) { /** * Controls the failure threshold for recurring actions. * * Before rescheduling a recurring action, we look at its status. If it failed, we then check if all of the most * recent actions (upto the threshold set by this filter) sharing the same hook have also failed: if they have, * that is considered consistent failure and a new instance of the action will not be scheduled. * * @param int $failure_threshold Number of actions of the same hook to examine for failure. Defaults to 5. */ $consistent_failure_threshold = (int) apply_filters( 'action_scheduler_recurring_action_failure_threshold', 5 ); // This query should find the earliest *failing* action (for the hook we are interested in) within our threshold. $query_args = array( 'hook' => $action->get_hook(), 'status' => ActionScheduler_Store::STATUS_FAILED, 'date' => date_create( 'now', timezone_open( 'UTC' ) )->format( 'Y-m-d H:i:s' ), 'date_compare' => '<', 'per_page' => 1, 'offset' => $consistent_failure_threshold - 1 ); $first_failing_action_id = $this->store->query_actions( $query_args ); // If we didn't retrieve an action ID, then there haven't been enough failures for us to worry about. if ( empty( $first_failing_action_id ) ) { return false; } // Now let's fetch the first action (having the same hook) of *any status* within the same window. unset( $query_args['status'] ); $first_action_id_with_the_same_hook = $this->store->query_actions( $query_args ); /** * If a recurring action is assessed as consistently failing, it will not be rescheduled. This hook provides a * way to observe and optionally override that assessment. * * @param bool $is_consistently_failing If the action is considered to be consistently failing. * @param ActionScheduler_Action $action The action being assessed. */ return (bool) apply_filters( 'action_scheduler_recurring_action_is_consistently_failing', $first_action_id_with_the_same_hook === $first_failing_action_id, $action ); } /** * Run the queue cleaner. * * @author Jeremy Pry */ protected function run_cleanup() { $this->cleaner->clean( 10 * $this->get_time_limit() ); } /** * Get the number of concurrent batches a runner allows. * * @return int */ public function get_allowed_concurrent_batches() { return apply_filters( 'action_scheduler_queue_runner_concurrent_batches', 1 ); } /** * Check if the number of allowed concurrent batches is met or exceeded. * * @return bool */ public function has_maximum_concurrent_batches() { return $this->store->get_claim_count() >= $this->get_allowed_concurrent_batches(); } /** * Get the maximum number of seconds a batch can run for. * * @return int The number of seconds. */ protected function get_time_limit() { $time_limit = 30; // Apply deprecated filter from deprecated get_maximum_execution_time() method if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) { _deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' ); $time_limit = apply_filters( 'action_scheduler_maximum_execution_time', $time_limit ); } return absint( apply_filters( 'action_scheduler_queue_runner_time_limit', $time_limit ) ); } /** * Get the number of seconds the process has been running. * * @return int The number of seconds. */ protected function get_execution_time() { $execution_time = microtime( true ) - $this->created_time; // Get the CPU time if the hosting environment uses it rather than wall-clock time to calculate a process's execution time. if ( function_exists( 'getrusage' ) && apply_filters( 'action_scheduler_use_cpu_execution_time', defined( 'PANTHEON_ENVIRONMENT' ) ) ) { $resource_usages = getrusage(); if ( isset( $resource_usages['ru_stime.tv_usec'], $resource_usages['ru_stime.tv_usec'] ) ) { $execution_time = $resource_usages['ru_stime.tv_sec'] + ( $resource_usages['ru_stime.tv_usec'] / 1000000 ); } } return $execution_time; } /** * Check if the host's max execution time is (likely) to be exceeded if processing more actions. * * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action * @return bool */ protected function time_likely_to_be_exceeded( $processed_actions ) { $execution_time = $this->get_execution_time(); $max_execution_time = $this->get_time_limit(); // Safety against division by zero errors. if ( 0 === $processed_actions ) { return $execution_time >= $max_execution_time; } $time_per_action = $execution_time / $processed_actions; $estimated_time = $execution_time + ( $time_per_action * 3 ); $likely_to_be_exceeded = $estimated_time > $max_execution_time; return apply_filters( 'action_scheduler_maximum_execution_time_likely_to_be_exceeded', $likely_to_be_exceeded, $this, $processed_actions, $execution_time, $max_execution_time ); } /** * Get memory limit * * Based on WP_Background_Process::get_memory_limit() * * @return int */ protected function get_memory_limit() { if ( function_exists( 'ini_get' ) ) { $memory_limit = ini_get( 'memory_limit' ); } else { $memory_limit = '128M'; // Sensible default, and minimum required by WooCommerce } if ( ! $memory_limit || -1 === $memory_limit || '-1' === $memory_limit ) { // Unlimited, set to 32GB. $memory_limit = '32G'; } return ActionScheduler_Compatibility::convert_hr_to_bytes( $memory_limit ); } /** * Memory exceeded * * Ensures the batch process never exceeds 90% of the maximum WordPress memory. * * Based on WP_Background_Process::memory_exceeded() * * @return bool */ protected function memory_exceeded() { $memory_limit = $this->get_memory_limit() * 0.90; $current_memory = memory_get_usage( true ); $memory_exceeded = $current_memory >= $memory_limit; return apply_filters( 'action_scheduler_memory_exceeded', $memory_exceeded, $this ); } /** * See if the batch limits have been exceeded, which is when memory usage is almost at * the maximum limit, or the time to process more actions will exceed the max time limit. * * Based on WC_Background_Process::batch_limits_exceeded() * * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action * @return bool */ protected function batch_limits_exceeded( $processed_actions ) { return $this->memory_exceeded() || $this->time_likely_to_be_exceeded( $processed_actions ); } /** * Process actions in the queue. * * @author Jeremy Pry * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' * Generally, this should be capitalised and not localised as it's a proper noun. * @return int The number of actions processed. */ abstract public function run( $context = '' ); } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_RecurringSchedule.php000064400000006146147600245720030357 0ustar00start - and logic to calculate the next run date after * that - @see $this->calculate_next(). The $first_date property also keeps a record of when the very * first instance of this chain of schedules ran. * * @var DateTime */ private $first_date = NULL; /** * Timestamp equivalent of @see $this->first_date * * @var int */ protected $first_timestamp = NULL; /** * The recurrance between each time an action is run using this schedule. * Used to calculate the start date & time. Can be a number of seconds, in the * case of ActionScheduler_IntervalSchedule, or a cron expression, as in the * case of ActionScheduler_CronSchedule. Or something else. * * @var mixed */ protected $recurrence; /** * @param DateTime $date The date & time to run the action. * @param mixed $recurrence The data used to determine the schedule's recurrance. * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance. */ public function __construct( DateTime $date, $recurrence, DateTime $first = null ) { parent::__construct( $date ); $this->first_date = empty( $first ) ? $date : $first; $this->recurrence = $recurrence; } /** * @return bool */ public function is_recurring() { return true; } /** * Get the date & time of the first schedule in this recurring series. * * @return DateTime|null */ public function get_first_date() { return clone $this->first_date; } /** * @return string */ public function get_recurrence() { return $this->recurrence; } /** * For PHP 5.2 compat, since DateTime objects can't be serialized * @return array */ public function __sleep() { $sleep_params = parent::__sleep(); $this->first_timestamp = $this->first_date->getTimestamp(); return array_merge( $sleep_params, array( 'first_timestamp', 'recurrence' ) ); } /** * Unserialize recurring schedules serialized/stored prior to AS 3.0.0 * * Prior to Action Scheduler 3.0.0, schedules used different property names to refer * to equivalent data. For example, ActionScheduler_IntervalSchedule::start_timestamp * was the same as ActionScheduler_SimpleSchedule::timestamp. This was addressed in * Action Scheduler 3.0.0, where properties and property names were aligned for better * inheritance. To maintain backward compatibility with scheduled serialized and stored * prior to 3.0, we need to correctly map the old property names. */ public function __wakeup() { parent::__wakeup(); if ( $this->first_timestamp > 0 ) { $this->first_date = as_get_datetime_object( $this->first_timestamp ); } else { $this->first_date = $this->get_date(); } } } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schedule.php000064400000003426147600245720026474 0ustar00scheduled_date * * @var int */ protected $scheduled_timestamp = NULL; /** * @param DateTime $date The date & time to run the action. */ public function __construct( DateTime $date ) { $this->scheduled_date = $date; } /** * Check if a schedule should recur. * * @return bool */ abstract public function is_recurring(); /** * Calculate when the next instance of this schedule would run based on a given date & time. * * @param DateTime $after * @return DateTime */ abstract protected function calculate_next( DateTime $after ); /** * Get the next date & time when this schedule should run after a given date & time. * * @param DateTime $after * @return DateTime|null */ public function get_next( DateTime $after ) { $after = clone $after; if ( $after > $this->scheduled_date ) { $after = $this->calculate_next( $after ); return $after; } return clone $this->scheduled_date; } /** * Get the date & time the schedule is set to run. * * @return DateTime|null */ public function get_date() { return $this->scheduled_date; } /** * For PHP 5.2 compat, since DateTime objects can't be serialized * @return array */ public function __sleep() { $this->scheduled_timestamp = $this->scheduled_date->getTimestamp(); return array( 'scheduled_timestamp', ); } public function __wakeup() { $this->scheduled_date = as_get_datetime_object( $this->scheduled_timestamp ); unset( $this->scheduled_timestamp ); } } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php000064400000011165147600245720026137 0ustar00tables as $table ) { $wpdb->tables[] = $table; $name = $this->get_full_table_name( $table ); $wpdb->$table = $name; } // create the tables if ( $this->schema_update_required() || $force_update ) { foreach ( $this->tables as $table ) { /** * Allow custom processing before updating a table schema. * * @param string $table Name of table being updated. * @param string $db_version Existing version of the table being updated. */ do_action( 'action_scheduler_before_schema_update', $table, $this->db_version ); $this->update_table( $table ); } $this->mark_schema_update_complete(); } } /** * @param string $table The name of the table * * @return string The CREATE TABLE statement, suitable for passing to dbDelta */ abstract protected function get_table_definition( $table ); /** * Determine if the database schema is out of date * by comparing the integer found in $this->schema_version * with the option set in the WordPress options table * * @return bool */ private function schema_update_required() { $option_name = 'schema-' . static::class; $this->db_version = get_option( $option_name, 0 ); // Check for schema option stored by the Action Scheduler Custom Tables plugin in case site has migrated from that plugin with an older schema if ( 0 === $this->db_version ) { $plugin_option_name = 'schema-'; switch ( static::class ) { case 'ActionScheduler_StoreSchema': $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Store_Table_Maker'; break; case 'ActionScheduler_LoggerSchema': $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Logger_Table_Maker'; break; } $this->db_version = get_option( $plugin_option_name, 0 ); delete_option( $plugin_option_name ); } return version_compare( $this->db_version, $this->schema_version, '<' ); } /** * Update the option in WordPress to indicate that * our schema is now up to date * * @return void */ private function mark_schema_update_complete() { $option_name = 'schema-' . static::class; // work around race conditions and ensure that our option updates $value_to_save = (string) $this->schema_version . '.0.' . time(); update_option( $option_name, $value_to_save ); } /** * Update the schema for the given table * * @param string $table The name of the table to update * * @return void */ private function update_table( $table ) { require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $definition = $this->get_table_definition( $table ); if ( $definition ) { $updated = dbDelta( $definition ); foreach ( $updated as $updated_table => $update_description ) { if ( strpos( $update_description, 'Created table' ) === 0 ) { do_action( 'action_scheduler/created_table', $updated_table, $table ); } } } } /** * @param string $table * * @return string The full name of the table, including the * table prefix for the current blog */ protected function get_full_table_name( $table ) { return $GLOBALS['wpdb']->prefix . $table; } /** * Confirms that all of the tables registered by this schema class have been created. * * @return bool */ public function tables_exist() { global $wpdb; $tables_exist = true; foreach ( $this->tables as $table_name ) { $table_name = $wpdb->prefix . $table_name; $pattern = str_replace( '_', '\\_', $table_name ); $existing_table = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $pattern ) ); if ( $existing_table !== $table_name ) { $tables_exist = false; break; } } return $tables_exist; } } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Lock.php000064400000003312147600245720023777 0ustar00get_expiration( $lock_type ) >= time() ); } /** * Set a lock. * * To prevent race conditions, implementations should avoid setting the lock if the lock is already held. * * @param string $lock_type A string to identify different lock types. * @return bool */ abstract public function set( $lock_type ); /** * If a lock is set, return the timestamp it was set to expiry. * * @param string $lock_type A string to identify different lock types. * @return bool|int False if no lock is set, otherwise the timestamp for when the lock is set to expire. */ abstract public function get_expiration( $lock_type ); /** * Get the amount of time to set for a given lock. 60 seconds by default. * * @param string $lock_type A string to identify different lock types. * @return int */ protected function get_duration( $lock_type ) { return apply_filters( 'action_scheduler_lock_duration', self::$lock_duration, $lock_type ); } /** * @return ActionScheduler_Lock */ public static function instance() { if ( empty( self::$locker ) ) { $class = apply_filters( 'action_scheduler_lock_class', 'ActionScheduler_OptionLock' ); self::$locker = new $class(); } return self::$locker; } } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Logger.php000064400000014257147600245720024340 0ustar00hook_stored_action(); add_action( 'action_scheduler_canceled_action', array( $this, 'log_canceled_action' ), 10, 1 ); add_action( 'action_scheduler_begin_execute', array( $this, 'log_started_action' ), 10, 2 ); add_action( 'action_scheduler_after_execute', array( $this, 'log_completed_action' ), 10, 3 ); add_action( 'action_scheduler_failed_execution', array( $this, 'log_failed_action' ), 10, 3 ); add_action( 'action_scheduler_failed_action', array( $this, 'log_timed_out_action' ), 10, 2 ); add_action( 'action_scheduler_unexpected_shutdown', array( $this, 'log_unexpected_shutdown' ), 10, 2 ); add_action( 'action_scheduler_reset_action', array( $this, 'log_reset_action' ), 10, 1 ); add_action( 'action_scheduler_execution_ignored', array( $this, 'log_ignored_action' ), 10, 2 ); add_action( 'action_scheduler_failed_fetch_action', array( $this, 'log_failed_fetch_action' ), 10, 2 ); add_action( 'action_scheduler_failed_to_schedule_next_instance', array( $this, 'log_failed_schedule_next_instance' ), 10, 2 ); add_action( 'action_scheduler_bulk_cancel_actions', array( $this, 'bulk_log_cancel_actions' ), 10, 1 ); } public function hook_stored_action() { add_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) ); } public function unhook_stored_action() { remove_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) ); } public function log_stored_action( $action_id ) { $this->log( $action_id, __( 'action created', 'action-scheduler' ) ); } public function log_canceled_action( $action_id ) { $this->log( $action_id, __( 'action canceled', 'action-scheduler' ) ); } public function log_started_action( $action_id, $context = '' ) { if ( ! empty( $context ) ) { /* translators: %s: context */ $message = sprintf( __( 'action started via %s', 'action-scheduler' ), $context ); } else { $message = __( 'action started', 'action-scheduler' ); } $this->log( $action_id, $message ); } public function log_completed_action( $action_id, $action = NULL, $context = '' ) { if ( ! empty( $context ) ) { /* translators: %s: context */ $message = sprintf( __( 'action complete via %s', 'action-scheduler' ), $context ); } else { $message = __( 'action complete', 'action-scheduler' ); } $this->log( $action_id, $message ); } public function log_failed_action( $action_id, Exception $exception, $context = '' ) { if ( ! empty( $context ) ) { /* translators: 1: context 2: exception message */ $message = sprintf( __( 'action failed via %1$s: %2$s', 'action-scheduler' ), $context, $exception->getMessage() ); } else { /* translators: %s: exception message */ $message = sprintf( __( 'action failed: %s', 'action-scheduler' ), $exception->getMessage() ); } $this->log( $action_id, $message ); } public function log_timed_out_action( $action_id, $timeout ) { /* translators: %s: amount of time */ $this->log( $action_id, sprintf( __( 'action marked as failed after %s seconds. Unknown error occurred. Check server, PHP and database error logs to diagnose cause.', 'action-scheduler' ), $timeout ) ); } public function log_unexpected_shutdown( $action_id, $error ) { if ( ! empty( $error ) ) { /* translators: 1: error message 2: filename 3: line */ $this->log( $action_id, sprintf( __( 'unexpected shutdown: PHP Fatal error %1$s in %2$s on line %3$s', 'action-scheduler' ), $error['message'], $error['file'], $error['line'] ) ); } } public function log_reset_action( $action_id ) { $this->log( $action_id, __( 'action reset', 'action-scheduler' ) ); } public function log_ignored_action( $action_id, $context = '' ) { if ( ! empty( $context ) ) { /* translators: %s: context */ $message = sprintf( __( 'action ignored via %s', 'action-scheduler' ), $context ); } else { $message = __( 'action ignored', 'action-scheduler' ); } $this->log( $action_id, $message ); } /** * @param string $action_id * @param Exception|NULL $exception The exception which occured when fetching the action. NULL by default for backward compatibility. * * @return ActionScheduler_LogEntry[] */ public function log_failed_fetch_action( $action_id, Exception $exception = NULL ) { if ( ! is_null( $exception ) ) { /* translators: %s: exception message */ $log_message = sprintf( __( 'There was a failure fetching this action: %s', 'action-scheduler' ), $exception->getMessage() ); } else { $log_message = __( 'There was a failure fetching this action', 'action-scheduler' ); } $this->log( $action_id, $log_message ); } public function log_failed_schedule_next_instance( $action_id, Exception $exception ) { /* translators: %s: exception message */ $this->log( $action_id, sprintf( __( 'There was a failure scheduling the next instance of this action: %s', 'action-scheduler' ), $exception->getMessage() ) ); } /** * Bulk add cancel action log entries. * * Implemented here for backward compatibility. Should be implemented in parent loggers * for more performant bulk logging. * * @param array $action_ids List of action ID. */ public function bulk_log_cancel_actions( $action_ids ) { if ( empty( $action_ids ) ) { return; } foreach ( $action_ids as $action_id ) { $this->log_canceled_action( $action_id ); } } } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Store.php000064400000032056147600245720024212 0ustar00 null, 'status' => self::STATUS_PENDING, 'group' => '', ) ); // These params are fixed for this method. $params['hook'] = $hook; $params['orderby'] = 'date'; $params['per_page'] = 1; if ( ! empty( $params['status'] ) ) { if ( self::STATUS_PENDING === $params['status'] ) { $params['order'] = 'ASC'; // Find the next action that matches. } else { $params['order'] = 'DESC'; // Find the most recent action that matches. } } $results = $this->query_actions( $params ); return empty( $results ) ? null : $results[0]; } /** * Query for action count or list of action IDs. * * @since 3.3.0 $query['status'] accepts array of statuses instead of a single status. * * @param array $query { * Query filtering options. * * @type string $hook The name of the actions. Optional. * @type string|array $status The status or statuses of the actions. Optional. * @type array $args The args array of the actions. Optional. * @type DateTime $date The scheduled date of the action. Used in UTC timezone. Optional. * @type string $date_compare Operator for selecting by $date param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='. * @type DateTime $modified The last modified date of the action. Used in UTC timezone. Optional. * @type string $modified_compare Operator for comparing $modified param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='. * @type string $group The group the action belongs to. Optional. * @type bool|int $claimed TRUE to find claimed actions, FALSE to find unclaimed actions, an int to find a specific claim ID. Optional. * @type int $per_page Number of results to return. Defaults to 5. * @type int $offset The query pagination offset. Defaults to 0. * @type int $orderby Accepted values are 'hook', 'group', 'modified', 'date' or 'none'. Defaults to 'date'. * @type string $order Accepted values are 'ASC' or 'DESC'. Defaults to 'ASC'. * } * @param string $query_type Whether to select or count the results. Default, select. * * @return string|array|null The IDs of actions matching the query. Null on failure. */ abstract public function query_actions( $query = array(), $query_type = 'select' ); /** * Run query to get a single action ID. * * @since 3.3.0 * * @see ActionScheduler_Store::query_actions for $query arg usage but 'per_page' and 'offset' can't be used. * * @param array $query Query parameters. * * @return int|null */ public function query_action( $query ) { $query['per_page'] = 1; $query['offset'] = 0; $results = $this->query_actions( $query ); if ( empty( $results ) ) { return null; } else { return (int) $results[0]; } } /** * Get a count of all actions in the store, grouped by status * * @return array */ abstract public function action_counts(); /** * Get additional action counts. * * - add past-due actions * * @return array */ public function extra_action_counts() { $extra_actions = array(); $pastdue_action_counts = ( int ) $this->query_actions( array( 'status' => self::STATUS_PENDING, 'date' => as_get_datetime_object(), ), 'count' ); if ( $pastdue_action_counts ) { $extra_actions['past-due'] = $pastdue_action_counts; } /** * Allows 3rd party code to add extra action counts (used in filters in the list table). * * @since 3.5.0 * @param $extra_actions array Array with format action_count_identifier => action count. */ return apply_filters( 'action_scheduler_extra_action_counts', $extra_actions ); } /** * @param string $action_id */ abstract public function cancel_action( $action_id ); /** * @param string $action_id */ abstract public function delete_action( $action_id ); /** * @param string $action_id * * @return DateTime The date the action is schedule to run, or the date that it ran. */ abstract public function get_date( $action_id ); /** * @param int $max_actions * @param DateTime $before_date Claim only actions schedule before the given date. Defaults to now. * @param array $hooks Claim only actions with a hook or hooks. * @param string $group Claim only actions in the given group. * * @return ActionScheduler_ActionClaim */ abstract public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ); /** * @return int */ abstract public function get_claim_count(); /** * @param ActionScheduler_ActionClaim $claim */ abstract public function release_claim( ActionScheduler_ActionClaim $claim ); /** * @param string $action_id */ abstract public function unclaim_action( $action_id ); /** * @param string $action_id */ abstract public function mark_failure( $action_id ); /** * @param string $action_id */ abstract public function log_execution( $action_id ); /** * @param string $action_id */ abstract public function mark_complete( $action_id ); /** * @param string $action_id * * @return string */ abstract public function get_status( $action_id ); /** * @param string $action_id * @return mixed */ abstract public function get_claim_id( $action_id ); /** * @param string $claim_id * @return array */ abstract public function find_actions_by_claim_id( $claim_id ); /** * @param string $comparison_operator * @return string */ protected function validate_sql_comparator( $comparison_operator ) { if ( in_array( $comparison_operator, array('!=', '>', '>=', '<', '<=', '=') ) ) { return $comparison_operator; } return '='; } /** * Get the time MySQL formated date/time string for an action's (next) scheduled date. * * @param ActionScheduler_Action $action * @param DateTime $scheduled_date (optional) * @return string */ protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) { $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date; if ( ! $next ) { $next = date_create(); } $next->setTimezone( new DateTimeZone( 'UTC' ) ); return $next->format( 'Y-m-d H:i:s' ); } /** * Get the time MySQL formated date/time string for an action's (next) scheduled date. * * @param ActionScheduler_Action $action * @param DateTime $scheduled_date (optional) * @return string */ protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) { $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date; if ( ! $next ) { $next = date_create(); } ActionScheduler_TimezoneHelper::set_local_timezone( $next ); return $next->format( 'Y-m-d H:i:s' ); } /** * Validate that we could decode action arguments. * * @param mixed $args The decoded arguments. * @param int $action_id The action ID. * * @throws ActionScheduler_InvalidActionException When the decoded arguments are invalid. */ protected function validate_args( $args, $action_id ) { // Ensure we have an array of args. if ( ! is_array( $args ) ) { throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id ); } // Validate JSON decoding if possible. if ( function_exists( 'json_last_error' ) && JSON_ERROR_NONE !== json_last_error() ) { throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id, $args ); } } /** * Validate a ActionScheduler_Schedule object. * * @param mixed $schedule The unserialized ActionScheduler_Schedule object. * @param int $action_id The action ID. * * @throws ActionScheduler_InvalidActionException When the schedule is invalid. */ protected function validate_schedule( $schedule, $action_id ) { if ( empty( $schedule ) || ! is_a( $schedule, 'ActionScheduler_Schedule' ) ) { throw ActionScheduler_InvalidActionException::from_schedule( $action_id, $schedule ); } } /** * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4. * * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However, * with custom tables, we use an indexed VARCHAR column instead. * * @param ActionScheduler_Action $action Action to be validated. * @throws InvalidArgumentException When json encoded args is too long. */ protected function validate_action( ActionScheduler_Action $action ) { if ( strlen( wp_json_encode( $action->get_args() ) ) > static::$max_args_length ) { // translators: %d is a number (maximum length of action arguments). throw new InvalidArgumentException( sprintf( __( 'ActionScheduler_Action::$args too long. To ensure the args column can be indexed, action args should not be more than %d characters when encoded as JSON.', 'action-scheduler' ), static::$max_args_length ) ); } } /** * Cancel pending actions by hook. * * @since 3.0.0 * * @param string $hook Hook name. * * @return void */ public function cancel_actions_by_hook( $hook ) { $action_ids = true; while ( ! empty( $action_ids ) ) { $action_ids = $this->query_actions( array( 'hook' => $hook, 'status' => self::STATUS_PENDING, 'per_page' => 1000, 'orderby' => 'none', ) ); $this->bulk_cancel_actions( $action_ids ); } } /** * Cancel pending actions by group. * * @since 3.0.0 * * @param string $group Group slug. * * @return void */ public function cancel_actions_by_group( $group ) { $action_ids = true; while ( ! empty( $action_ids ) ) { $action_ids = $this->query_actions( array( 'group' => $group, 'status' => self::STATUS_PENDING, 'per_page' => 1000, 'orderby' => 'none', ) ); $this->bulk_cancel_actions( $action_ids ); } } /** * Cancel a set of action IDs. * * @since 3.0.0 * * @param array $action_ids List of action IDs. * * @return void */ private function bulk_cancel_actions( $action_ids ) { foreach ( $action_ids as $action_id ) { $this->cancel_action( $action_id ); } do_action( 'action_scheduler_bulk_cancel_actions', $action_ids ); } /** * @return array */ public function get_status_labels() { return array( self::STATUS_COMPLETE => __( 'Complete', 'action-scheduler' ), self::STATUS_PENDING => __( 'Pending', 'action-scheduler' ), self::STATUS_RUNNING => __( 'In-progress', 'action-scheduler' ), self::STATUS_FAILED => __( 'Failed', 'action-scheduler' ), self::STATUS_CANCELED => __( 'Canceled', 'action-scheduler' ), ); } /** * Check if there are any pending scheduled actions due to run. * * @param ActionScheduler_Action $action * @param DateTime $scheduled_date (optional) * @return string */ public function has_pending_actions_due() { $pending_actions = $this->query_actions( array( 'date' => as_get_datetime_object(), 'status' => ActionScheduler_Store::STATUS_PENDING, 'orderby' => 'none', ) ); return ! empty( $pending_actions ); } /** * Callable initialization function optionally overridden in derived classes. */ public function init() {} /** * Callable function to mark an action as migrated optionally overridden in derived classes. */ public function mark_migrated( $action_id ) {} /** * @return ActionScheduler_Store */ public static function instance() { if ( empty( self::$store ) ) { $class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS ); self::$store = new $class(); } return self::$store; } } vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php000064400000011060147600245720026040 0ustar00format( 'U' ) ); } if ( get_option( 'timezone_string' ) ) { $date->setTimezone( new DateTimeZone( self::get_local_timezone_string() ) ); } else { $date->setUtcOffset( self::get_local_timezone_offset() ); } return $date; } /** * Helper to retrieve the timezone string for a site until a WP core method exists * (see https://core.trac.wordpress.org/ticket/24730). * * Adapted from wc_timezone_string() and https://secure.php.net/manual/en/function.timezone-name-from-abbr.php#89155. * * If no timezone string is set, and its not possible to match the UTC offset set for the site to a timezone * string, then an empty string will be returned, and the UTC offset should be used to set a DateTime's * timezone. * * @since 2.1.0 * @return string PHP timezone string for the site or empty if no timezone string is available. */ protected static function get_local_timezone_string( $reset = false ) { // If site timezone string exists, return it. $timezone = get_option( 'timezone_string' ); if ( $timezone ) { return $timezone; } // Get UTC offset, if it isn't set then return UTC. $utc_offset = intval( get_option( 'gmt_offset', 0 ) ); if ( 0 === $utc_offset ) { return 'UTC'; } // Adjust UTC offset from hours to seconds. $utc_offset *= 3600; // Attempt to guess the timezone string from the UTC offset. $timezone = timezone_name_from_abbr( '', $utc_offset ); if ( $timezone ) { return $timezone; } // Last try, guess timezone string manually. foreach ( timezone_abbreviations_list() as $abbr ) { foreach ( $abbr as $city ) { if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- we are actually interested in the runtime timezone. return $city['timezone_id']; } } } // No timezone string return ''; } /** * Get timezone offset in seconds. * * @since 2.1.0 * @return float */ protected static function get_local_timezone_offset() { $timezone = get_option( 'timezone_string' ); if ( $timezone ) { $timezone_object = new DateTimeZone( $timezone ); return $timezone_object->getOffset( new DateTime( 'now' ) ); } else { return floatval( get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS; } } /** * @deprecated 2.1.0 */ public static function get_local_timezone( $reset = FALSE ) { _deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' ); if ( $reset ) { self::$local_timezone = NULL; } if ( !isset(self::$local_timezone) ) { $tzstring = get_option('timezone_string'); if ( empty($tzstring) ) { $gmt_offset = get_option('gmt_offset'); if ( $gmt_offset == 0 ) { $tzstring = 'UTC'; } else { $gmt_offset *= HOUR_IN_SECONDS; $tzstring = timezone_name_from_abbr( '', $gmt_offset, 1 ); // If there's no timezone string, try again with no DST. if ( false === $tzstring ) { $tzstring = timezone_name_from_abbr( '', $gmt_offset, 0 ); } // Try mapping to the first abbreviation we can find. if ( false === $tzstring ) { $is_dst = date( 'I' ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- we are actually interested in the runtime timezone. foreach ( timezone_abbreviations_list() as $abbr ) { foreach ( $abbr as $city ) { if ( $city['dst'] == $is_dst && $city['offset'] == $gmt_offset ) { // If there's no valid timezone ID, keep looking. if ( null === $city['timezone_id'] ) { continue; } $tzstring = $city['timezone_id']; break 2; } } } } // If we still have no valid string, then fall back to UTC. if ( false === $tzstring ) { $tzstring = 'UTC'; } } } self::$local_timezone = new DateTimeZone($tzstring); } return self::$local_timezone; } } vendor/woocommerce/action-scheduler/classes/actions/ActionScheduler_Action.php000064400000005711147600245720024003 0ustar00set_hook($hook); $this->set_schedule($schedule); $this->set_args($args); $this->set_group($group); } /** * Executes the action. * * If no callbacks are registered, an exception will be thrown and the action will not be * fired. This is useful to help detect cases where the code responsible for setting up * a scheduled action no longer exists. * * @throws Exception If no callbacks are registered for this action. */ public function execute() { $hook = $this->get_hook(); if ( ! has_action( $hook ) ) { throw new Exception( sprintf( /* translators: 1: action hook. */ __( 'Scheduled action for %1$s will not be executed as no callbacks are registered.', 'action-scheduler' ), $hook ) ); } do_action_ref_array( $hook, array_values( $this->get_args() ) ); } /** * @param string $hook */ protected function set_hook( $hook ) { $this->hook = $hook; } public function get_hook() { return $this->hook; } protected function set_schedule( ActionScheduler_Schedule $schedule ) { $this->schedule = $schedule; } /** * @return ActionScheduler_Schedule */ public function get_schedule() { return $this->schedule; } protected function set_args( array $args ) { $this->args = $args; } public function get_args() { return $this->args; } /** * @param string $group */ protected function set_group( $group ) { $this->group = $group; } /** * @return string */ public function get_group() { return $this->group; } /** * @return bool If the action has been finished */ public function is_finished() { return FALSE; } /** * Sets the priority of the action. * * @param int $priority Priority level (lower is higher priority). Should be in the range 0-255. * * @return void */ public function set_priority( $priority ) { if ( $priority < 0 ) { $priority = 0; } elseif ( $priority > 255 ) { $priority = 255; } $this->priority = (int) $priority; } /** * Gets the action priority. * * @return int */ public function get_priority() { return $this->priority; } } vendor/woocommerce/action-scheduler/classes/actions/ActionScheduler_CanceledAction.php000064400000001316147600245720025417 0ustar00set_schedule( new ActionScheduler_NullSchedule() ); } } } vendor/woocommerce/action-scheduler/classes/actions/ActionScheduler_FinishedAction.php000064400000000350147600245720025447 0ustar00set_schedule( new ActionScheduler_NullSchedule() ); } public function execute() { // don't execute } } vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_DBLogger.php000064400000010600147600245720024772 0ustar00format( 'Y-m-d H:i:s' ); ActionScheduler_TimezoneHelper::set_local_timezone( $date ); $date_local = $date->format( 'Y-m-d H:i:s' ); /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort global $wpdb; $wpdb->insert( $wpdb->actionscheduler_logs, array( 'action_id' => $action_id, 'message' => $message, 'log_date_gmt' => $date_gmt, 'log_date_local' => $date_local, ), array( '%d', '%s', '%s', '%s' ) ); return $wpdb->insert_id; } /** * Retrieve an action log entry. * * @param int $entry_id Log entry ID. * * @return ActionScheduler_LogEntry */ public function get_entry( $entry_id ) { /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort global $wpdb; $entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE log_id=%d", $entry_id ) ); return $this->create_entry_from_db_record( $entry ); } /** * Create an action log entry from a database record. * * @param object $record Log entry database record object. * * @return ActionScheduler_LogEntry */ private function create_entry_from_db_record( $record ) { if ( empty( $record ) ) { return new ActionScheduler_NullLogEntry(); } if ( is_null( $record->log_date_gmt ) ) { $date = as_get_datetime_object( ActionScheduler_StoreSchema::DEFAULT_DATE ); } else { $date = as_get_datetime_object( $record->log_date_gmt ); } return new ActionScheduler_LogEntry( $record->action_id, $record->message, $date ); } /** * Retrieve an action's log entries from the database. * * @param int $action_id Action ID. * * @return ActionScheduler_LogEntry[] */ public function get_logs( $action_id ) { /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort global $wpdb; $records = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE action_id=%d", $action_id ) ); return array_map( array( $this, 'create_entry_from_db_record' ), $records ); } /** * Initialize the data store. * * @codeCoverageIgnore */ public function init() { $table_maker = new ActionScheduler_LoggerSchema(); $table_maker->init(); $table_maker->register_tables(); parent::init(); add_action( 'action_scheduler_deleted_action', array( $this, 'clear_deleted_action_logs' ), 10, 1 ); } /** * Delete the action logs for an action. * * @param int $action_id Action ID. */ public function clear_deleted_action_logs( $action_id ) { /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort global $wpdb; $wpdb->delete( $wpdb->actionscheduler_logs, array( 'action_id' => $action_id ), array( '%d' ) ); } /** * Bulk add cancel action log entries. * * @param array $action_ids List of action ID. */ public function bulk_log_cancel_actions( $action_ids ) { if ( empty( $action_ids ) ) { return; } /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort global $wpdb; $date = as_get_datetime_object(); $date_gmt = $date->format( 'Y-m-d H:i:s' ); ActionScheduler_TimezoneHelper::set_local_timezone( $date ); $date_local = $date->format( 'Y-m-d H:i:s' ); $message = __( 'action canceled', 'action-scheduler' ); $format = '(%d, ' . $wpdb->prepare( '%s, %s, %s', $message, $date_gmt, $date_local ) . ')'; $sql_query = "INSERT {$wpdb->actionscheduler_logs} (action_id, message, log_date_gmt, log_date_local) VALUES "; $value_rows = array(); foreach ( $action_ids as $action_id ) { $value_rows[] = $wpdb->prepare( $format, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } $sql_query .= implode( ',', $value_rows ); $wpdb->query( $sql_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } } vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php000064400000112160147600245720024653 0ustar00 '', 'hooks' => '', 'exclude-groups' => '', ]; /** * Initialize the data store * * @codeCoverageIgnore */ public function init() { $table_maker = new ActionScheduler_StoreSchema(); $table_maker->init(); $table_maker->register_tables(); } /** * Save an action, checks if this is a unique action before actually saving. * * @param ActionScheduler_Action $action Action object. * @param \DateTime $scheduled_date Optional schedule date. Default null. * * @return int Action ID. * @throws RuntimeException Throws exception when saving the action fails. */ public function save_unique_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null ) { return $this->save_action_to_db( $action, $scheduled_date, true ); } /** * Save an action. Can save duplicate action as well, prefer using `save_unique_action` instead. * * @param ActionScheduler_Action $action Action object. * @param \DateTime $scheduled_date Optional schedule date. Default null. * * @return int Action ID. * @throws RuntimeException Throws exception when saving the action fails. */ public function save_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null ) { return $this->save_action_to_db( $action, $scheduled_date, false ); } /** * Save an action. * * @param ActionScheduler_Action $action Action object. * @param ?DateTime $date Optional schedule date. Default null. * @param bool $unique Whether the action should be unique. * * @return int Action ID. * @throws RuntimeException Throws exception when saving the action fails. */ private function save_action_to_db( ActionScheduler_Action $action, DateTime $date = null, $unique = false ) { global $wpdb; try { $this->validate_action( $action ); $data = array( 'hook' => $action->get_hook(), 'status' => ( $action->is_finished() ? self::STATUS_COMPLETE : self::STATUS_PENDING ), 'scheduled_date_gmt' => $this->get_scheduled_date_string( $action, $date ), 'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ), 'schedule' => serialize( $action->get_schedule() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize 'group_id' => current( $this->get_group_ids( $action->get_group() ) ), 'priority' => $action->get_priority(), ); $args = wp_json_encode( $action->get_args() ); if ( strlen( $args ) <= static::$max_index_length ) { $data['args'] = $args; } else { $data['args'] = $this->hash_args( $args ); $data['extended_args'] = $args; } $insert_sql = $this->build_insert_sql( $data, $unique ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $insert_sql should be already prepared. $wpdb->query( $insert_sql ); $action_id = $wpdb->insert_id; if ( is_wp_error( $action_id ) ) { throw new \RuntimeException( $action_id->get_error_message() ); } elseif ( empty( $action_id ) ) { if ( $unique ) { return 0; } throw new \RuntimeException( $wpdb->last_error ? $wpdb->last_error : __( 'Database error.', 'action-scheduler' ) ); } do_action( 'action_scheduler_stored_action', $action_id ); return $action_id; } catch ( \Exception $e ) { /* translators: %s: error message */ throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 ); } } /** * Helper function to build insert query. * * @param array $data Row data for action. * @param bool $unique Whether the action should be unique. * * @return string Insert query. */ private function build_insert_sql( array $data, $unique ) { global $wpdb; $columns = array_keys( $data ); $values = array_values( $data ); $placeholders = array_map( array( $this, 'get_placeholder_for_column' ), $columns ); $table_name = ! empty( $wpdb->actionscheduler_actions ) ? $wpdb->actionscheduler_actions : $wpdb->prefix . 'actionscheduler_actions'; $column_sql = '`' . implode( '`, `', $columns ) . '`'; $placeholder_sql = implode( ', ', $placeholders ); $where_clause = $this->build_where_clause_for_insert( $data, $table_name, $unique ); // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $column_sql and $where_clause are already prepared. $placeholder_sql is hardcoded. $insert_query = $wpdb->prepare( " INSERT INTO $table_name ( $column_sql ) SELECT $placeholder_sql FROM DUAL WHERE ( $where_clause ) IS NULL", $values ); // phpcs:enable return $insert_query; } /** * Helper method to build where clause for action insert statement. * * @param array $data Row data for action. * @param string $table_name Action table name. * @param bool $unique Where action should be unique. * * @return string Where clause to be used with insert. */ private function build_where_clause_for_insert( $data, $table_name, $unique ) { global $wpdb; if ( ! $unique ) { return 'SELECT NULL FROM DUAL'; } $pending_statuses = array( ActionScheduler_Store::STATUS_PENDING, ActionScheduler_Store::STATUS_RUNNING, ); $pending_status_placeholders = implode( ', ', array_fill( 0, count( $pending_statuses ), '%s' ) ); // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $pending_status_placeholders is hardcoded. $where_clause = $wpdb->prepare( " SELECT action_id FROM $table_name WHERE status IN ( $pending_status_placeholders ) AND hook = %s AND `group_id` = %d ", array_merge( $pending_statuses, array( $data['hook'], $data['group_id'], ) ) ); // phpcs:enable return "$where_clause" . ' LIMIT 1'; } /** * Helper method to get $wpdb->prepare placeholder for a given column name. * * @param string $column_name Name of column in actions table. * * @return string Placeholder to use for given column. */ private function get_placeholder_for_column( $column_name ) { $string_columns = array( 'hook', 'status', 'scheduled_date_gmt', 'scheduled_date_local', 'args', 'schedule', 'last_attempt_gmt', 'last_attempt_local', 'extended_args', ); return in_array( $column_name, $string_columns ) ? '%s' : '%d'; } /** * Generate a hash from json_encoded $args using MD5 as this isn't for security. * * @param string $args JSON encoded action args. * @return string */ protected function hash_args( $args ) { return md5( $args ); } /** * Get action args query param value from action args. * * @param array $args Action args. * @return string */ protected function get_args_for_query( $args ) { $encoded = wp_json_encode( $args ); if ( strlen( $encoded ) <= static::$max_index_length ) { return $encoded; } return $this->hash_args( $encoded ); } /** * Get a group's ID based on its name/slug. * * @param string|array $slugs The string name of a group, or names for several groups. * @param bool $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group. * * @return array The group IDs, if they exist or were successfully created. May be empty. */ protected function get_group_ids( $slugs, $create_if_not_exists = true ) { $slugs = (array) $slugs; $group_ids = array(); if ( empty( $slugs ) ) { return array(); } /** @var \wpdb $wpdb */ global $wpdb; foreach ( $slugs as $slug ) { $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) ); if ( empty( $group_id ) && $create_if_not_exists ) { $group_id = $this->create_group( $slug ); } if ( $group_id ) { $group_ids[] = $group_id; } } return $group_ids; } /** * Create an action group. * * @param string $slug Group slug. * * @return int Group ID. */ protected function create_group( $slug ) { /** @var \wpdb $wpdb */ global $wpdb; $wpdb->insert( $wpdb->actionscheduler_groups, array( 'slug' => $slug ) ); return (int) $wpdb->insert_id; } /** * Retrieve an action. * * @param int $action_id Action ID. * * @return ActionScheduler_Action */ public function fetch_action( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $data = $wpdb->get_row( $wpdb->prepare( "SELECT a.*, g.slug AS `group` FROM {$wpdb->actionscheduler_actions} a LEFT JOIN {$wpdb->actionscheduler_groups} g ON a.group_id=g.group_id WHERE a.action_id=%d", $action_id ) ); if ( empty( $data ) ) { return $this->get_null_action(); } if ( ! empty( $data->extended_args ) ) { $data->args = $data->extended_args; unset( $data->extended_args ); } // Convert NULL dates to zero dates. $date_fields = array( 'scheduled_date_gmt', 'scheduled_date_local', 'last_attempt_gmt', 'last_attempt_gmt', ); foreach ( $date_fields as $date_field ) { if ( is_null( $data->$date_field ) ) { $data->$date_field = ActionScheduler_StoreSchema::DEFAULT_DATE; } } try { $action = $this->make_action_from_db_record( $data ); } catch ( ActionScheduler_InvalidActionException $exception ) { do_action( 'action_scheduler_failed_fetch_action', $action_id, $exception ); return $this->get_null_action(); } return $action; } /** * Create a null action. * * @return ActionScheduler_NullAction */ protected function get_null_action() { return new ActionScheduler_NullAction(); } /** * Create an action from a database record. * * @param object $data Action database record. * * @return ActionScheduler_Action|ActionScheduler_CanceledAction|ActionScheduler_FinishedAction */ protected function make_action_from_db_record( $data ) { $hook = $data->hook; $args = json_decode( $data->args, true ); $schedule = unserialize( $data->schedule ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize $this->validate_args( $args, $data->action_id ); $this->validate_schedule( $schedule, $data->action_id ); if ( empty( $schedule ) ) { $schedule = new ActionScheduler_NullSchedule(); } $group = $data->group ? $data->group : ''; return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group, $data->priority ); } /** * Returns the SQL statement to query (or count) actions. * * @since 3.3.0 $query['status'] accepts array of statuses instead of a single status. * * @param array $query Filtering options. * @param string $select_or_count Whether the SQL should select and return the IDs or just the row count. * * @return string SQL statement already properly escaped. * @throws InvalidArgumentException If the query is invalid. */ protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) { if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) { throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'action-scheduler' ) ); } $query = wp_parse_args( $query, array( 'hook' => '', 'args' => null, 'partial_args_matching' => 'off', // can be 'like' or 'json' 'date' => null, 'date_compare' => '<=', 'modified' => null, 'modified_compare' => '<=', 'group' => '', 'status' => '', 'claimed' => null, 'per_page' => 5, 'offset' => 0, 'orderby' => 'date', 'order' => 'ASC', ) ); /** @var \wpdb $wpdb */ global $wpdb; $db_server_info = is_callable( array( $wpdb, 'db_server_info' ) ) ? $wpdb->db_server_info() : $wpdb->db_version(); if ( false !== strpos( $db_server_info, 'MariaDB' ) ) { $supports_json = version_compare( PHP_VERSION_ID >= 80016 ? $wpdb->db_version() : preg_replace( '/[^0-9.].*/', '', str_replace( '5.5.5-', '', $db_server_info ) ), '10.2', '>=' ); } else { $supports_json = version_compare( $wpdb->db_version(), '5.7', '>=' ); } $sql = ( 'count' === $select_or_count ) ? 'SELECT count(a.action_id)' : 'SELECT a.action_id'; $sql .= " FROM {$wpdb->actionscheduler_actions} a"; $sql_params = array(); if ( ! empty( $query['group'] ) || 'group' === $query['orderby'] ) { $sql .= " LEFT JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id"; } $sql .= " WHERE 1=1"; if ( ! empty( $query['group'] ) ) { $sql .= " AND g.slug=%s"; $sql_params[] = $query['group']; } if ( ! empty( $query['hook'] ) ) { $sql .= " AND a.hook=%s"; $sql_params[] = $query['hook']; } if ( ! is_null( $query['args'] ) ) { switch ( $query['partial_args_matching'] ) { case 'json': if ( ! $supports_json ) { throw new \RuntimeException( __( 'JSON partial matching not supported in your environment. Please check your MySQL/MariaDB version.', 'action-scheduler' ) ); } $supported_types = array( 'integer' => '%d', 'boolean' => '%s', 'double' => '%f', 'string' => '%s', ); foreach ( $query['args'] as $key => $value ) { $value_type = gettype( $value ); if ( 'boolean' === $value_type ) { $value = $value ? 'true' : 'false'; } $placeholder = isset( $supported_types[ $value_type ] ) ? $supported_types[ $value_type ] : false; if ( ! $placeholder ) { throw new \RuntimeException( sprintf( /* translators: %s: provided value type */ __( 'The value type for the JSON partial matching is not supported. Must be either integer, boolean, double or string. %s type provided.', 'action-scheduler' ), $value_type ) ); } $sql .= ' AND JSON_EXTRACT(a.args, %s)='.$placeholder; $sql_params[] = '$.'.$key; $sql_params[] = $value; } break; case 'like': foreach ( $query['args'] as $key => $value ) { $sql .= ' AND a.args LIKE %s'; $json_partial = $wpdb->esc_like( trim( wp_json_encode( array( $key => $value ) ), '{}' ) ); $sql_params[] = "%{$json_partial}%"; } break; case 'off': $sql .= " AND a.args=%s"; $sql_params[] = $this->get_args_for_query( $query['args'] ); break; default: throw new \RuntimeException( __( 'Unknown partial args matching value.', 'action-scheduler' ) ); } } if ( $query['status'] ) { $statuses = (array) $query['status']; $placeholders = array_fill( 0, count( $statuses ), '%s' ); $sql .= ' AND a.status IN (' . join( ', ', $placeholders ) . ')'; $sql_params = array_merge( $sql_params, array_values( $statuses ) ); } if ( $query['date'] instanceof \DateTime ) { $date = clone $query['date']; $date->setTimezone( new \DateTimeZone( 'UTC' ) ); $date_string = $date->format( 'Y-m-d H:i:s' ); $comparator = $this->validate_sql_comparator( $query['date_compare'] ); $sql .= " AND a.scheduled_date_gmt $comparator %s"; $sql_params[] = $date_string; } if ( $query['modified'] instanceof \DateTime ) { $modified = clone $query['modified']; $modified->setTimezone( new \DateTimeZone( 'UTC' ) ); $date_string = $modified->format( 'Y-m-d H:i:s' ); $comparator = $this->validate_sql_comparator( $query['modified_compare'] ); $sql .= " AND a.last_attempt_gmt $comparator %s"; $sql_params[] = $date_string; } if ( true === $query['claimed'] ) { $sql .= ' AND a.claim_id != 0'; } elseif ( false === $query['claimed'] ) { $sql .= ' AND a.claim_id = 0'; } elseif ( ! is_null( $query['claimed'] ) ) { $sql .= ' AND a.claim_id = %d'; $sql_params[] = $query['claimed']; } if ( ! empty( $query['search'] ) ) { $sql .= ' AND (a.hook LIKE %s OR (a.extended_args IS NULL AND a.args LIKE %s) OR a.extended_args LIKE %s'; for ( $i = 0; $i < 3; $i++ ) { $sql_params[] = sprintf( '%%%s%%', $query['search'] ); } $search_claim_id = (int) $query['search']; if ( $search_claim_id ) { $sql .= ' OR a.claim_id = %d'; $sql_params[] = $search_claim_id; } $sql .= ')'; } if ( 'select' === $select_or_count ) { if ( 'ASC' === strtoupper( $query['order'] ) ) { $order = 'ASC'; } else { $order = 'DESC'; } switch ( $query['orderby'] ) { case 'hook': $sql .= " ORDER BY a.hook $order"; break; case 'group': $sql .= " ORDER BY g.slug $order"; break; case 'modified': $sql .= " ORDER BY a.last_attempt_gmt $order"; break; case 'none': break; case 'action_id': $sql .= " ORDER BY a.action_id $order"; break; case 'date': default: $sql .= " ORDER BY a.scheduled_date_gmt $order"; break; } if ( $query['per_page'] > 0 ) { $sql .= ' LIMIT %d, %d'; $sql_params[] = $query['offset']; $sql_params[] = $query['per_page']; } } if ( ! empty( $sql_params ) ) { $sql = $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } return $sql; } /** * Query for action count or list of action IDs. * * @since 3.3.0 $query['status'] accepts array of statuses instead of a single status. * * @see ActionScheduler_Store::query_actions for $query arg usage. * * @param array $query Query filtering options. * @param string $query_type Whether to select or count the results. Defaults to select. * * @return string|array|null The IDs of actions matching the query. Null on failure. */ public function query_actions( $query = array(), $query_type = 'select' ) { /** @var wpdb $wpdb */ global $wpdb; $sql = $this->get_query_actions_sql( $query, $query_type ); return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoSql, WordPress.DB.DirectDatabaseQuery.NoCaching } /** * Get a count of all actions in the store, grouped by status. * * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status. */ public function action_counts() { global $wpdb; $sql = "SELECT a.status, count(a.status) as 'count'"; $sql .= " FROM {$wpdb->actionscheduler_actions} a"; $sql .= ' GROUP BY a.status'; $actions_count_by_status = array(); $action_stati_and_labels = $this->get_status_labels(); foreach ( $wpdb->get_results( $sql ) as $action_data ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared // Ignore any actions with invalid status. if ( array_key_exists( $action_data->status, $action_stati_and_labels ) ) { $actions_count_by_status[ $action_data->status ] = $action_data->count; } } return $actions_count_by_status; } /** * Cancel an action. * * @param int $action_id Action ID. * * @return void * @throws \InvalidArgumentException If the action update failed. */ public function cancel_action( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $updated = $wpdb->update( $wpdb->actionscheduler_actions, array( 'status' => self::STATUS_CANCELED ), array( 'action_id' => $action_id ), array( '%s' ), array( '%d' ) ); if ( false === $updated ) { /* translators: %s: action ID */ throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); } do_action( 'action_scheduler_canceled_action', $action_id ); } /** * Cancel pending actions by hook. * * @since 3.0.0 * * @param string $hook Hook name. * * @return void */ public function cancel_actions_by_hook( $hook ) { $this->bulk_cancel_actions( array( 'hook' => $hook ) ); } /** * Cancel pending actions by group. * * @param string $group Group slug. * * @return void */ public function cancel_actions_by_group( $group ) { $this->bulk_cancel_actions( array( 'group' => $group ) ); } /** * Bulk cancel actions. * * @since 3.0.0 * * @param array $query_args Query parameters. */ protected function bulk_cancel_actions( $query_args ) { /** @var \wpdb $wpdb */ global $wpdb; if ( ! is_array( $query_args ) ) { return; } // Don't cancel actions that are already canceled. if ( isset( $query_args['status'] ) && self::STATUS_CANCELED === $query_args['status'] ) { return; } $action_ids = true; $query_args = wp_parse_args( $query_args, array( 'per_page' => 1000, 'status' => self::STATUS_PENDING, 'orderby' => 'none', ) ); while ( $action_ids ) { $action_ids = $this->query_actions( $query_args ); if ( empty( $action_ids ) ) { break; } $format = array_fill( 0, count( $action_ids ), '%d' ); $query_in = '(' . implode( ',', $format ) . ')'; $parameters = $action_ids; array_unshift( $parameters, self::STATUS_CANCELED ); $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->actionscheduler_actions} SET status = %s WHERE action_id IN {$query_in}", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $parameters ) ); do_action( 'action_scheduler_bulk_cancel_actions', $action_ids ); } } /** * Delete an action. * * @param int $action_id Action ID. * @throws \InvalidArgumentException If the action deletion failed. */ public function delete_action( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $deleted = $wpdb->delete( $wpdb->actionscheduler_actions, array( 'action_id' => $action_id ), array( '%d' ) ); if ( empty( $deleted ) ) { throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment } do_action( 'action_scheduler_deleted_action', $action_id ); } /** * Get the schedule date for an action. * * @param string $action_id Action ID. * * @return \DateTime The local date the action is scheduled to run, or the date that it ran. */ public function get_date( $action_id ) { $date = $this->get_date_gmt( $action_id ); ActionScheduler_TimezoneHelper::set_local_timezone( $date ); return $date; } /** * Get the GMT schedule date for an action. * * @param int $action_id Action ID. * * @throws \InvalidArgumentException If action cannot be identified. * @return \DateTime The GMT date the action is scheduled to run, or the date that it ran. */ protected function get_date_gmt( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) ); if ( empty( $record ) ) { throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment } if ( self::STATUS_PENDING === $record->status ) { return as_get_datetime_object( $record->scheduled_date_gmt ); } else { return as_get_datetime_object( $record->last_attempt_gmt ); } } /** * Stake a claim on actions. * * @param int $max_actions Maximum number of action to include in claim. * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now. * @param array $hooks Hooks to filter for. * @param string $group Group to filter for. * * @return ActionScheduler_ActionClaim */ public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) { $claim_id = $this->generate_claim_id(); $this->claim_before_date = $before_date; $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group ); $action_ids = $this->find_actions_by_claim_id( $claim_id ); $this->claim_before_date = null; return new ActionScheduler_ActionClaim( $claim_id, $action_ids ); } /** * Generate a new action claim. * * @return int Claim ID. */ protected function generate_claim_id() { /** @var \wpdb $wpdb */ global $wpdb; $now = as_get_datetime_object(); $wpdb->insert( $wpdb->actionscheduler_claims, array( 'date_created_gmt' => $now->format( 'Y-m-d H:i:s' ) ) ); return $wpdb->insert_id; } /** * Set a claim filter. * * @param string $filter_name Claim filter name. * @param mixed $filter_values Values to filter. * @return void */ public function set_claim_filter( $filter_name, $filter_values ) { if ( isset( $this->claim_filters[ $filter_name ] ) ) { $this->claim_filters[ $filter_name ] = $filter_values; } } /** * Get the claim filter value. * * @param string $filter_name Claim filter name. * @return mixed */ public function get_claim_filter( $filter_name ) { if ( isset( $this->claim_filters[ $filter_name ] ) ) { return $this->claim_filters[ $filter_name ]; } return ''; } /** * Mark actions claimed. * * @param string $claim_id Claim Id. * @param int $limit Number of action to include in claim. * @param \DateTime $before_date Should use UTC timezone. * @param array $hooks Hooks to filter for. * @param string $group Group to filter for. * * @return int The number of actions that were claimed. * @throws \InvalidArgumentException Throws InvalidArgumentException if group doesn't exist. * @throws \RuntimeException Throws RuntimeException if unable to claim action. */ protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) { /** @var \wpdb $wpdb */ global $wpdb; $now = as_get_datetime_object(); $date = is_null( $before_date ) ? $now : clone $before_date; // can't use $wpdb->update() because of the <= condition. $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s"; $params = array( $claim_id, $now->format( 'Y-m-d H:i:s' ), current_time( 'mysql' ), ); // Set claim filters. if ( ! empty( $hooks ) ) { $this->set_claim_filter( 'hooks', $hooks ); } else { $hooks = $this->get_claim_filter( 'hooks' ); } if ( ! empty( $group ) ) { $this->set_claim_filter( 'group', $group ); } else { $group = $this->get_claim_filter( 'group' ); } $where = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s'; $params[] = $date->format( 'Y-m-d H:i:s' ); $params[] = self::STATUS_PENDING; if ( ! empty( $hooks ) ) { $placeholders = array_fill( 0, count( $hooks ), '%s' ); $where .= ' AND hook IN (' . join( ', ', $placeholders ) . ')'; $params = array_merge( $params, array_values( $hooks ) ); } $group_operator = 'IN'; if ( empty( $group ) ) { $group = $this->get_claim_filter( 'exclude-groups' ); $group_operator = 'NOT IN'; } if ( ! empty( $group ) ) { $group_ids = $this->get_group_ids( $group, false ); // throw exception if no matching group(s) found, this matches ActionScheduler_wpPostStore's behaviour. if ( empty( $group_ids ) ) { throw new InvalidArgumentException( sprintf( /* translators: %s: group name(s) */ _n( 'The group "%s" does not exist.', 'The groups "%s" do not exist.', is_array( $group ) ? count( $group ) : 1, 'action-scheduler' ), $group ) ); } $id_list = implode( ',', array_map( 'intval', $group_ids ) ); $where .= " AND group_id {$group_operator} ( $id_list )"; } /** * Sets the order-by clause used in the action claim query. * * @since 3.4.0 * * @param string $order_by_sql */ $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC' ); $params[] = $limit; $sql = $wpdb->prepare( "{$update} {$where} {$order} LIMIT %d", $params ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders $rows_affected = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching if ( false === $rows_affected ) { $error = empty( $wpdb->last_error ) ? _x( 'unknown', 'database error', 'action-scheduler' ) : $wpdb->last_error; throw new \RuntimeException( sprintf( /* translators: %s database error. */ __( 'Unable to claim actions. Database error: %s.', 'action-scheduler' ), $error ) ); } return (int) $rows_affected; } /** * Get the number of active claims. * * @return int */ public function get_claim_count() { global $wpdb; $sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)"; $sql = $wpdb->prepare( $sql, array( self::STATUS_PENDING, self::STATUS_RUNNING ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } /** * Return an action's claim ID, as stored in the claim_id column. * * @param string $action_id Action ID. * @return mixed */ public function get_claim_id( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } /** * Retrieve the action IDs of action in a claim. * * @param int $claim_id Claim ID. * @return int[] */ public function find_actions_by_claim_id( $claim_id ) { /** @var \wpdb $wpdb */ global $wpdb; $action_ids = array(); $before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object(); $cut_off = $before_date->format( 'Y-m-d H:i:s' ); $sql = $wpdb->prepare( "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC", $claim_id ); // Verify that the scheduled date for each action is within the expected bounds (in some unusual // cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify). foreach ( $wpdb->get_results( $sql ) as $claimed_action ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ( $claimed_action->scheduled_date_gmt <= $cut_off ) { $action_ids[] = absint( $claimed_action->action_id ); } } return $action_ids; } /** * Release actions from a claim and delete the claim. * * @param ActionScheduler_ActionClaim $claim Claim object. */ public function release_claim( ActionScheduler_ActionClaim $claim ) { /** @var \wpdb $wpdb */ global $wpdb; /** * Deadlock warning: This function modifies actions to release them from claims that have been processed. Earlier, we used to it in a atomic query, i.e. we would update all actions belonging to a particular claim_id with claim_id = 0. * While this was functionally correct, it would cause deadlock, since this update query will hold a lock on the claim_id_.. index on the action table. * This allowed the possibility of a race condition, where the claimer query is also running at the same time, then the claimer query will also try to acquire a lock on the claim_id_.. index, and in this case if claim release query has already progressed to the point of acquiring the lock, but have not updated yet, it would cause a deadlock. * * We resolve this by getting all the actions_id that we want to release claim from in a separate query, and then releasing the claim on each of them. This way, our lock is acquired on the action_id index instead of the claim_id index. Note that the lock on claim_id will still be acquired, but it will only when we actually make the update, rather than when we select the actions. */ $action_ids = $wpdb->get_col( $wpdb->prepare( "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d", $claim->get_id() ) ); $row_updates = 0; if ( count( $action_ids ) > 0 ) { $action_id_string = implode( ',', array_map( 'absint', $action_ids ) ); $row_updates = $wpdb->query( "UPDATE {$wpdb->actionscheduler_actions} SET claim_id = 0 WHERE action_id IN ({$action_id_string})" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared } $wpdb->delete( $wpdb->actionscheduler_claims, array( 'claim_id' => $claim->get_id() ), array( '%d' ) ); if ( $row_updates < count( $action_ids ) ) { throw new RuntimeException( sprintf( // translators: %d is an id. __( 'Unable to release actions from claim id %d.', 'action-scheduler' ), $claim->get_id() ) ); } } /** * Remove the claim from an action. * * @param int $action_id Action ID. * * @return void */ public function unclaim_action( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $wpdb->update( $wpdb->actionscheduler_actions, array( 'claim_id' => 0 ), array( 'action_id' => $action_id ), array( '%s' ), array( '%d' ) ); } /** * Mark an action as failed. * * @param int $action_id Action ID. * @throws \InvalidArgumentException Throw an exception if action was not updated. */ public function mark_failure( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $updated = $wpdb->update( $wpdb->actionscheduler_actions, array( 'status' => self::STATUS_FAILED ), array( 'action_id' => $action_id ), array( '%s' ), array( '%d' ) ); if ( empty( $updated ) ) { throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment } } /** * Add execution message to action log. * * @throws Exception If the action status cannot be updated to self::STATUS_RUNNING ('in-progress'). * * @param int $action_id Action ID. * * @return void */ public function log_execution( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d"; $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $status_updated = $wpdb->query( $sql ); if ( ! $status_updated ) { throw new Exception( sprintf( /* translators: 1: action ID. 2: status slug. */ __( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ), $action_id, self::STATUS_RUNNING ) ); } } /** * Mark an action as complete. * * @param int $action_id Action ID. * * @return void * @throws \InvalidArgumentException Throw an exception if action was not updated. */ public function mark_complete( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $updated = $wpdb->update( $wpdb->actionscheduler_actions, array( 'status' => self::STATUS_COMPLETE, 'last_attempt_gmt' => current_time( 'mysql', true ), 'last_attempt_local' => current_time( 'mysql' ), ), array( 'action_id' => $action_id ), array( '%s' ), array( '%d' ) ); if ( empty( $updated ) ) { throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment } /** * Fires after a scheduled action has been completed. * * @since 3.4.2 * * @param int $action_id Action ID. */ do_action( 'action_scheduler_completed_action', $action_id ); } /** * Get an action's status. * * @param int $action_id Action ID. * * @return string * @throws \InvalidArgumentException Throw an exception if not status was found for action_id. * @throws \RuntimeException Throw an exception if action status could not be retrieved. */ public function get_status( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $sql = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $status = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ( null === $status ) { throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) ); } elseif ( empty( $status ) ) { throw new \RuntimeException( __( 'Unknown status found for action.', 'action-scheduler' ) ); } else { return $status; } } } vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_HybridStore.php000064400000027366147600245720025624 0ustar00demarkation_id = (int) get_option( self::DEMARKATION_OPTION, 0 ); if ( empty( $config ) ) { $config = Controller::instance()->get_migration_config_object(); } $this->primary_store = $config->get_destination_store(); $this->secondary_store = $config->get_source_store(); $this->migration_runner = new Runner( $config ); } /** * Initialize the table data store tables. * * @codeCoverageIgnore */ public function init() { add_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10, 2 ); $this->primary_store->init(); $this->secondary_store->init(); remove_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10 ); } /** * When the actions table is created, set its autoincrement * value to be one higher than the posts table to ensure that * there are no ID collisions. * * @param string $table_name * @param string $table_suffix * * @return void * @codeCoverageIgnore */ public function set_autoincrement( $table_name, $table_suffix ) { if ( ActionScheduler_StoreSchema::ACTIONS_TABLE === $table_suffix ) { if ( empty( $this->demarkation_id ) ) { $this->demarkation_id = $this->set_demarkation_id(); } /** @var \wpdb $wpdb */ global $wpdb; /** * A default date of '0000-00-00 00:00:00' is invalid in MySQL 5.7 when configured with * sql_mode including both STRICT_TRANS_TABLES and NO_ZERO_DATE. */ $default_date = new DateTime( 'tomorrow' ); $null_action = new ActionScheduler_NullAction(); $date_gmt = $this->get_scheduled_date_string( $null_action, $default_date ); $date_local = $this->get_scheduled_date_string_local( $null_action, $default_date ); $row_count = $wpdb->insert( $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE}, [ 'action_id' => $this->demarkation_id, 'hook' => '', 'status' => '', 'scheduled_date_gmt' => $date_gmt, 'scheduled_date_local' => $date_local, 'last_attempt_gmt' => $date_gmt, 'last_attempt_local' => $date_local, ] ); if ( $row_count > 0 ) { $wpdb->delete( $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE}, [ 'action_id' => $this->demarkation_id ] ); } } } /** * Store the demarkation id in WP options. * * @param int $id The ID to set as the demarkation point between the two stores * Leave null to use the next ID from the WP posts table. * * @return int The new ID. * * @codeCoverageIgnore */ private function set_demarkation_id( $id = null ) { if ( empty( $id ) ) { /** @var \wpdb $wpdb */ global $wpdb; $id = (int) $wpdb->get_var( "SELECT MAX(ID) FROM $wpdb->posts" ); $id ++; } update_option( self::DEMARKATION_OPTION, $id ); return $id; } /** * Find the first matching action from the secondary store. * If it exists, migrate it to the primary store immediately. * After it migrates, the secondary store will logically contain * the next matching action, so return the result thence. * * @param string $hook * @param array $params * * @return string */ public function find_action( $hook, $params = [] ) { $found_unmigrated_action = $this->secondary_store->find_action( $hook, $params ); if ( ! empty( $found_unmigrated_action ) ) { $this->migrate( [ $found_unmigrated_action ] ); } return $this->primary_store->find_action( $hook, $params ); } /** * Find actions matching the query in the secondary source first. * If any are found, migrate them immediately. Then the secondary * store will contain the canonical results. * * @param array $query * @param string $query_type Whether to select or count the results. Default, select. * * @return int[] */ public function query_actions( $query = [], $query_type = 'select' ) { $found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' ); if ( ! empty( $found_unmigrated_actions ) ) { $this->migrate( $found_unmigrated_actions ); } return $this->primary_store->query_actions( $query, $query_type ); } /** * Get a count of all actions in the store, grouped by status * * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status. */ public function action_counts() { $unmigrated_actions_count = $this->secondary_store->action_counts(); $migrated_actions_count = $this->primary_store->action_counts(); $actions_count_by_status = array(); foreach ( $this->get_status_labels() as $status_key => $status_label ) { $count = 0; if ( isset( $unmigrated_actions_count[ $status_key ] ) ) { $count += $unmigrated_actions_count[ $status_key ]; } if ( isset( $migrated_actions_count[ $status_key ] ) ) { $count += $migrated_actions_count[ $status_key ]; } $actions_count_by_status[ $status_key ] = $count; } $actions_count_by_status = array_filter( $actions_count_by_status ); return $actions_count_by_status; } /** * If any actions would have been claimed by the secondary store, * migrate them immediately, then ask the primary store for the * canonical claim. * * @param int $max_actions * @param DateTime|null $before_date * * @return ActionScheduler_ActionClaim */ public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) { $claim = $this->secondary_store->stake_claim( $max_actions, $before_date, $hooks, $group ); $claimed_actions = $claim->get_actions(); if ( ! empty( $claimed_actions ) ) { $this->migrate( $claimed_actions ); } $this->secondary_store->release_claim( $claim ); return $this->primary_store->stake_claim( $max_actions, $before_date, $hooks, $group ); } /** * Migrate a list of actions to the table data store. * * @param array $action_ids List of action IDs. */ private function migrate( $action_ids ) { $this->migration_runner->migrate_actions( $action_ids ); } /** * Save an action to the primary store. * * @param ActionScheduler_Action $action Action object to be saved. * @param DateTime $date Optional. Schedule date. Default null. * * @return int The action ID */ public function save_action( ActionScheduler_Action $action, DateTime $date = null ) { return $this->primary_store->save_action( $action, $date ); } /** * Retrieve an existing action whether migrated or not. * * @param int $action_id Action ID. */ public function fetch_action( $action_id ) { $store = $this->get_store_from_action_id( $action_id, true ); if ( $store ) { return $store->fetch_action( $action_id ); } else { return new ActionScheduler_NullAction(); } } /** * Cancel an existing action whether migrated or not. * * @param int $action_id Action ID. */ public function cancel_action( $action_id ) { $store = $this->get_store_from_action_id( $action_id ); if ( $store ) { $store->cancel_action( $action_id ); } } /** * Delete an existing action whether migrated or not. * * @param int $action_id Action ID. */ public function delete_action( $action_id ) { $store = $this->get_store_from_action_id( $action_id ); if ( $store ) { $store->delete_action( $action_id ); } } /** * Get the schedule date an existing action whether migrated or not. * * @param int $action_id Action ID. */ public function get_date( $action_id ) { $store = $this->get_store_from_action_id( $action_id ); if ( $store ) { return $store->get_date( $action_id ); } else { return null; } } /** * Mark an existing action as failed whether migrated or not. * * @param int $action_id Action ID. */ public function mark_failure( $action_id ) { $store = $this->get_store_from_action_id( $action_id ); if ( $store ) { $store->mark_failure( $action_id ); } } /** * Log the execution of an existing action whether migrated or not. * * @param int $action_id Action ID. */ public function log_execution( $action_id ) { $store = $this->get_store_from_action_id( $action_id ); if ( $store ) { $store->log_execution( $action_id ); } } /** * Mark an existing action complete whether migrated or not. * * @param int $action_id Action ID. */ public function mark_complete( $action_id ) { $store = $this->get_store_from_action_id( $action_id ); if ( $store ) { $store->mark_complete( $action_id ); } } /** * Get an existing action status whether migrated or not. * * @param int $action_id Action ID. */ public function get_status( $action_id ) { $store = $this->get_store_from_action_id( $action_id ); if ( $store ) { return $store->get_status( $action_id ); } return null; } /** * Return which store an action is stored in. * * @param int $action_id ID of the action. * @param bool $primary_first Optional flag indicating search the primary store first. * @return ActionScheduler_Store */ protected function get_store_from_action_id( $action_id, $primary_first = false ) { if ( $primary_first ) { $stores = [ $this->primary_store, $this->secondary_store, ]; } elseif ( $action_id < $this->demarkation_id ) { $stores = [ $this->secondary_store, $this->primary_store, ]; } else { $stores = [ $this->primary_store, ]; } foreach ( $stores as $store ) { $action = $store->fetch_action( $action_id ); if ( ! is_a( $action, 'ActionScheduler_NullAction' ) ) { return $store; } } return null; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * All claim-related functions should operate solely * on the primary store. * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Get the claim count from the table data store. */ public function get_claim_count() { return $this->primary_store->get_claim_count(); } /** * Retrieve the claim ID for an action from the table data store. * * @param int $action_id Action ID. */ public function get_claim_id( $action_id ) { return $this->primary_store->get_claim_id( $action_id ); } /** * Release a claim in the table data store. * * @param ActionScheduler_ActionClaim $claim Claim object. */ public function release_claim( ActionScheduler_ActionClaim $claim ) { $this->primary_store->release_claim( $claim ); } /** * Release claims on an action in the table data store. * * @param int $action_id Action ID. */ public function unclaim_action( $action_id ) { $this->primary_store->unclaim_action( $action_id ); } /** * Retrieve a list of action IDs by claim. * * @param int $claim_id Claim ID. */ public function find_actions_by_claim_id( $claim_id ) { return $this->primary_store->find_actions_by_claim_id( $claim_id ); } } vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_wpCommentLogger.php000064400000015233147600245720026465 0ustar00create_wp_comment( $action_id, $message, $date ); return $comment_id; } protected function create_wp_comment( $action_id, $message, DateTime $date ) { $comment_date_gmt = $date->format('Y-m-d H:i:s'); ActionScheduler_TimezoneHelper::set_local_timezone( $date ); $comment_data = array( 'comment_post_ID' => $action_id, 'comment_date' => $date->format('Y-m-d H:i:s'), 'comment_date_gmt' => $comment_date_gmt, 'comment_author' => self::AGENT, 'comment_content' => $message, 'comment_agent' => self::AGENT, 'comment_type' => self::TYPE, ); return wp_insert_comment($comment_data); } /** * @param string $entry_id * * @return ActionScheduler_LogEntry */ public function get_entry( $entry_id ) { $comment = $this->get_comment( $entry_id ); if ( empty($comment) || $comment->comment_type != self::TYPE ) { return new ActionScheduler_NullLogEntry(); } $date = as_get_datetime_object( $comment->comment_date_gmt ); ActionScheduler_TimezoneHelper::set_local_timezone( $date ); return new ActionScheduler_LogEntry( $comment->comment_post_ID, $comment->comment_content, $date ); } /** * @param string $action_id * * @return ActionScheduler_LogEntry[] */ public function get_logs( $action_id ) { $status = 'all'; if ( get_post_status($action_id) == 'trash' ) { $status = 'post-trashed'; } $comments = get_comments(array( 'post_id' => $action_id, 'orderby' => 'comment_date_gmt', 'order' => 'ASC', 'type' => self::TYPE, 'status' => $status, )); $logs = array(); foreach ( $comments as $c ) { $entry = $this->get_entry( $c ); if ( !empty($entry) ) { $logs[] = $entry; } } return $logs; } protected function get_comment( $comment_id ) { return get_comment( $comment_id ); } /** * @param WP_Comment_Query $query */ public function filter_comment_queries( $query ) { foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) { if ( !empty($query->query_vars[$key]) ) { return; // don't slow down queries that wouldn't include action_log comments anyway } } $query->query_vars['action_log_filter'] = TRUE; add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 ); } /** * @param array $clauses * @param WP_Comment_Query $query * * @return array */ public function filter_comment_query_clauses( $clauses, $query ) { if ( !empty($query->query_vars['action_log_filter']) ) { $clauses['where'] .= $this->get_where_clause(); } return $clauses; } /** * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not * the WP_Comment_Query class handled by @see self::filter_comment_queries(). * * @param string $where * @param WP_Query $query * * @return string */ public function filter_comment_feed( $where, $query ) { if ( is_comment_feed() ) { $where .= $this->get_where_clause(); } return $where; } /** * Return a SQL clause to exclude Action Scheduler comments. * * @return string */ protected function get_where_clause() { global $wpdb; return sprintf( " AND {$wpdb->comments}.comment_type != '%s'", self::TYPE ); } /** * Remove action log entries from wp_count_comments() * * @param array $stats * @param int $post_id * * @return object */ public function filter_comment_count( $stats, $post_id ) { global $wpdb; if ( 0 === $post_id ) { $stats = $this->get_comment_count(); } return $stats; } /** * Retrieve the comment counts from our cache, or the database if the cached version isn't set. * * @return object */ protected function get_comment_count() { global $wpdb; $stats = get_transient( 'as_comment_count' ); if ( ! $stats ) { $stats = array(); $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A ); $total = 0; $stats = array(); $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' ); foreach ( (array) $count as $row ) { // Don't count post-trashed toward totals if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) { $total += $row['num_comments']; } if ( isset( $approved[ $row['comment_approved'] ] ) ) { $stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments']; } } $stats['total_comments'] = $total; $stats['all'] = $total; foreach ( $approved as $key ) { if ( empty( $stats[ $key ] ) ) { $stats[ $key ] = 0; } } $stats = (object) $stats; set_transient( 'as_comment_count', $stats ); } return $stats; } /** * Delete comment count cache whenever there is new comment or the status of a comment changes. Cache * will be regenerated next time ActionScheduler_wpCommentLogger::filter_comment_count() is called. */ public function delete_comment_count_cache() { delete_transient( 'as_comment_count' ); } /** * @codeCoverageIgnore */ public function init() { add_action( 'action_scheduler_before_process_queue', array( $this, 'disable_comment_counting' ), 10, 0 ); add_action( 'action_scheduler_after_process_queue', array( $this, 'enable_comment_counting' ), 10, 0 ); parent::init(); add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 ); add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 ); // Delete comments count cache whenever there is a new comment or a comment status changes add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) ); add_action( 'wp_set_comment_status', array( $this, 'delete_comment_count_cache' ) ); } public function disable_comment_counting() { wp_defer_comment_counting(true); } public function enable_comment_counting() { wp_defer_comment_counting(false); } } vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php000064400000105374147600245720025673 0ustar00validate_action( $action ); $post_array = $this->create_post_array( $action, $scheduled_date ); $post_id = $this->save_post_array( $post_array ); $this->save_post_schedule( $post_id, $action->get_schedule() ); $this->save_action_group( $post_id, $action->get_group() ); do_action( 'action_scheduler_stored_action', $post_id ); return $post_id; } catch ( Exception $e ) { /* translators: %s: action error message */ throw new RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 ); } } /** * Create post array. * * @param ActionScheduler_Action $action Scheduled Action. * @param DateTime $scheduled_date Scheduled Date. * * @return array Returns an array of post data. */ protected function create_post_array( ActionScheduler_Action $action, DateTime $scheduled_date = null ) { $post = array( 'post_type' => self::POST_TYPE, 'post_title' => $action->get_hook(), 'post_content' => wp_json_encode( $action->get_args() ), 'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ), 'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ), 'post_date' => $this->get_scheduled_date_string_local( $action, $scheduled_date ), ); return $post; } /** * Save post array. * * @param array $post_array Post array. * @return int Returns the post ID. * @throws RuntimeException Throws an exception if the action could not be saved. */ protected function save_post_array( $post_array ) { add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 ); add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 ); $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ); if ( $has_kses ) { // Prevent KSES from corrupting JSON in post_content. kses_remove_filters(); } $post_id = wp_insert_post( $post_array ); if ( $has_kses ) { kses_init_filters(); } remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 ); remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 ); if ( is_wp_error( $post_id ) || empty( $post_id ) ) { throw new RuntimeException( __( 'Unable to save action.', 'action-scheduler' ) ); } return $post_id; } /** * Filter insert post data. * * @param array $postdata Post data to filter. * * @return array */ public function filter_insert_post_data( $postdata ) { if ( self::POST_TYPE === $postdata['post_type'] ) { $postdata['post_author'] = 0; if ( 'future' === $postdata['post_status'] ) { $postdata['post_status'] = 'publish'; } } return $postdata; } /** * Create a (probably unique) post name for scheduled actions in a more performant manner than wp_unique_post_slug(). * * When an action's post status is transitioned to something other than 'draft', 'pending' or 'auto-draft, like 'publish' * or 'failed' or 'trash', WordPress will find a unique slug (stored in post_name column) using the wp_unique_post_slug() * function. This is done to ensure URL uniqueness. The approach taken by wp_unique_post_slug() is to iterate over existing * post_name values that match, and append a number 1 greater than the largest. This makes sense when manually creating a * post from the Edit Post screen. It becomes a bottleneck when automatically processing thousands of actions, with a * database containing thousands of related post_name values. * * WordPress 5.1 introduces the 'pre_wp_unique_post_slug' filter for plugins to address this issue. * * We can short-circuit WordPress's wp_unique_post_slug() approach using the 'pre_wp_unique_post_slug' filter. This * method is available to be used as a callback on that filter. It provides a more scalable approach to generating a * post_name/slug that is probably unique. Because Action Scheduler never actually uses the post_name field, or an * action's slug, being probably unique is good enough. * * For more backstory on this issue, see: * - https://github.com/woocommerce/action-scheduler/issues/44 and * - https://core.trac.wordpress.org/ticket/21112 * * @param string $override_slug Short-circuit return value. * @param string $slug The desired slug (post_name). * @param int $post_ID Post ID. * @param string $post_status The post status. * @param string $post_type Post type. * @return string */ public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) { if ( self::POST_TYPE === $post_type ) { $override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false ); } return $override_slug; } /** * Save post schedule. * * @param int $post_id Post ID of the scheduled action. * @param string $schedule Schedule to save. * * @return void */ protected function save_post_schedule( $post_id, $schedule ) { update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule ); } /** * Save action group. * * @param int $post_id Post ID. * @param string $group Group to save. * @return void */ protected function save_action_group( $post_id, $group ) { if ( empty( $group ) ) { wp_set_object_terms( $post_id, array(), self::GROUP_TAXONOMY, false ); } else { wp_set_object_terms( $post_id, array( $group ), self::GROUP_TAXONOMY, false ); } } /** * Fetch actions. * * @param int $action_id Action ID. * @return object */ public function fetch_action( $action_id ) { $post = $this->get_post( $action_id ); if ( empty( $post ) || self::POST_TYPE !== $post->post_type ) { return $this->get_null_action(); } try { $action = $this->make_action_from_post( $post ); } catch ( ActionScheduler_InvalidActionException $exception ) { do_action( 'action_scheduler_failed_fetch_action', $post->ID, $exception ); return $this->get_null_action(); } return $action; } /** * Get post. * * @param string $action_id - Action ID. * @return WP_Post|null */ protected function get_post( $action_id ) { if ( empty( $action_id ) ) { return null; } return get_post( $action_id ); } /** * Get NULL action. * * @return ActionScheduler_NullAction */ protected function get_null_action() { return new ActionScheduler_NullAction(); } /** * Make action from post. * * @param WP_Post $post Post object. * @return WP_Post */ protected function make_action_from_post( $post ) { $hook = $post->post_title; $args = json_decode( $post->post_content, true ); $this->validate_args( $args, $post->ID ); $schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true ); $this->validate_schedule( $schedule, $post->ID ); $group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array( 'fields' => 'names' ) ); $group = empty( $group ) ? '' : reset( $group ); return ActionScheduler::factory()->get_stored_action( $this->get_action_status_by_post_status( $post->post_status ), $hook, $args, $schedule, $group ); } /** * Get action status by post status. * * @param string $post_status Post status. * * @throws InvalidArgumentException Throw InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels(). * @return string */ protected function get_action_status_by_post_status( $post_status ) { switch ( $post_status ) { case 'publish': $action_status = self::STATUS_COMPLETE; break; case 'trash': $action_status = self::STATUS_CANCELED; break; default: if ( ! array_key_exists( $post_status, $this->get_status_labels() ) ) { throw new InvalidArgumentException( sprintf( 'Invalid post status: "%s". No matching action status available.', $post_status ) ); } $action_status = $post_status; break; } return $action_status; } /** * Get post status by action status. * * @param string $action_status Action status. * * @throws InvalidArgumentException Throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels(). * @return string */ protected function get_post_status_by_action_status( $action_status ) { switch ( $action_status ) { case self::STATUS_COMPLETE: $post_status = 'publish'; break; case self::STATUS_CANCELED: $post_status = 'trash'; break; default: if ( ! array_key_exists( $action_status, $this->get_status_labels() ) ) { throw new InvalidArgumentException( sprintf( 'Invalid action status: "%s".', $action_status ) ); } $post_status = $action_status; break; } return $post_status; } /** * Returns the SQL statement to query (or count) actions. * * @param array $query - Filtering options. * @param string $select_or_count - Whether the SQL should select and return the IDs or just the row count. * * @throws InvalidArgumentException - Throw InvalidArgumentException if $select_or_count not count or select. * @return string SQL statement. The returned SQL is already properly escaped. */ protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) { if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) { throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) ); } $query = wp_parse_args( $query, array( 'hook' => '', 'args' => null, 'date' => null, 'date_compare' => '<=', 'modified' => null, 'modified_compare' => '<=', 'group' => '', 'status' => '', 'claimed' => null, 'per_page' => 5, 'offset' => 0, 'orderby' => 'date', 'order' => 'ASC', 'search' => '', ) ); /** * Global wpdb object. * * @var wpdb $wpdb */ global $wpdb; $sql = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID '; $sql .= "FROM {$wpdb->posts} p"; $sql_params = array(); if ( empty( $query['group'] ) && 'group' === $query['orderby'] ) { $sql .= " LEFT JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID"; $sql .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id"; $sql .= " LEFT JOIN {$wpdb->terms} t ON tt.term_id=t.term_id"; } elseif ( ! empty( $query['group'] ) ) { $sql .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID"; $sql .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id"; $sql .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id"; $sql .= ' AND t.slug=%s'; $sql_params[] = $query['group']; } $sql .= ' WHERE post_type=%s'; $sql_params[] = self::POST_TYPE; if ( $query['hook'] ) { $sql .= ' AND p.post_title=%s'; $sql_params[] = $query['hook']; } if ( ! is_null( $query['args'] ) ) { $sql .= ' AND p.post_content=%s'; $sql_params[] = wp_json_encode( $query['args'] ); } if ( $query['status'] ) { $post_statuses = array_map( array( $this, 'get_post_status_by_action_status' ), (array) $query['status'] ); $placeholders = array_fill( 0, count( $post_statuses ), '%s' ); $sql .= ' AND p.post_status IN (' . join( ', ', $placeholders ) . ')'; $sql_params = array_merge( $sql_params, array_values( $post_statuses ) ); } if ( $query['date'] instanceof DateTime ) { $date = clone $query['date']; $date->setTimezone( new DateTimeZone( 'UTC' ) ); $date_string = $date->format( 'Y-m-d H:i:s' ); $comparator = $this->validate_sql_comparator( $query['date_compare'] ); $sql .= " AND p.post_date_gmt $comparator %s"; $sql_params[] = $date_string; } if ( $query['modified'] instanceof DateTime ) { $modified = clone $query['modified']; $modified->setTimezone( new DateTimeZone( 'UTC' ) ); $date_string = $modified->format( 'Y-m-d H:i:s' ); $comparator = $this->validate_sql_comparator( $query['modified_compare'] ); $sql .= " AND p.post_modified_gmt $comparator %s"; $sql_params[] = $date_string; } if ( true === $query['claimed'] ) { $sql .= " AND p.post_password != ''"; } elseif ( false === $query['claimed'] ) { $sql .= " AND p.post_password = ''"; } elseif ( ! is_null( $query['claimed'] ) ) { $sql .= ' AND p.post_password = %s'; $sql_params[] = $query['claimed']; } if ( ! empty( $query['search'] ) ) { $sql .= ' AND (p.post_title LIKE %s OR p.post_content LIKE %s OR p.post_password LIKE %s)'; for ( $i = 0; $i < 3; $i++ ) { $sql_params[] = sprintf( '%%%s%%', $query['search'] ); } } if ( 'select' === $select_or_count ) { switch ( $query['orderby'] ) { case 'hook': $orderby = 'p.post_title'; break; case 'group': $orderby = 't.name'; break; case 'status': $orderby = 'p.post_status'; break; case 'modified': $orderby = 'p.post_modified'; break; case 'claim_id': $orderby = 'p.post_password'; break; case 'schedule': case 'date': default: $orderby = 'p.post_date_gmt'; break; } if ( 'ASC' === strtoupper( $query['order'] ) ) { $order = 'ASC'; } else { $order = 'DESC'; } $sql .= " ORDER BY $orderby $order"; if ( $query['per_page'] > 0 ) { $sql .= ' LIMIT %d, %d'; $sql_params[] = $query['offset']; $sql_params[] = $query['per_page']; } } return $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } /** * Query for action count or list of action IDs. * * @since 3.3.0 $query['status'] accepts array of statuses instead of a single status. * * @see ActionScheduler_Store::query_actions for $query arg usage. * * @param array $query Query filtering options. * @param string $query_type Whether to select or count the results. Defaults to select. * * @return string|array|null The IDs of actions matching the query. Null on failure. */ public function query_actions( $query = array(), $query_type = 'select' ) { /** * Global $wpdb object. * * @var wpdb $wpdb */ global $wpdb; $sql = $this->get_query_actions_sql( $query, $query_type ); return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared } /** * Get a count of all actions in the store, grouped by status * * @return array */ public function action_counts() { $action_counts_by_status = array(); $action_stati_and_labels = $this->get_status_labels(); $posts_count_by_status = (array) wp_count_posts( self::POST_TYPE, 'readable' ); foreach ( $posts_count_by_status as $post_status_name => $count ) { try { $action_status_name = $this->get_action_status_by_post_status( $post_status_name ); } catch ( Exception $e ) { // Ignore any post statuses that aren't for actions. continue; } if ( array_key_exists( $action_status_name, $action_stati_and_labels ) ) { $action_counts_by_status[ $action_status_name ] = $count; } } return $action_counts_by_status; } /** * Cancel action. * * @param int $action_id Action ID. * * @throws InvalidArgumentException If $action_id is not identified. */ public function cancel_action( $action_id ) { $post = get_post( $action_id ); if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) { /* translators: %s is the action ID */ throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); } do_action( 'action_scheduler_canceled_action', $action_id ); add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 ); wp_trash_post( $action_id ); remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 ); } /** * Delete action. * * @param int $action_id Action ID. * @return void * @throws InvalidArgumentException If action is not identified. */ public function delete_action( $action_id ) { $post = get_post( $action_id ); if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) { /* translators: %s is the action ID */ throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); } do_action( 'action_scheduler_deleted_action', $action_id ); wp_delete_post( $action_id, true ); } /** * Get date for claim id. * * @param int $action_id Action ID. * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran. */ public function get_date( $action_id ) { $next = $this->get_date_gmt( $action_id ); return ActionScheduler_TimezoneHelper::set_local_timezone( $next ); } /** * Get Date GMT. * * @param int $action_id Action ID. * * @throws InvalidArgumentException If $action_id is not identified. * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran. */ public function get_date_gmt( $action_id ) { $post = get_post( $action_id ); if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) { /* translators: %s is the action ID */ throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); } if ( 'publish' === $post->post_status ) { return as_get_datetime_object( $post->post_modified_gmt ); } else { return as_get_datetime_object( $post->post_date_gmt ); } } /** * Stake claim. * * @param int $max_actions Maximum number of actions. * @param DateTime $before_date Jobs must be schedule before this date. Defaults to now. * @param array $hooks Claim only actions with a hook or hooks. * @param string $group Claim only actions in the given group. * * @return ActionScheduler_ActionClaim * @throws RuntimeException When there is an error staking a claim. * @throws InvalidArgumentException When the given group is not valid. */ public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) { $this->claim_before_date = $before_date; $claim_id = $this->generate_claim_id(); $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group ); $action_ids = $this->find_actions_by_claim_id( $claim_id ); $this->claim_before_date = null; return new ActionScheduler_ActionClaim( $claim_id, $action_ids ); } /** * Get claim count. * * @return int */ public function get_claim_count() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT post_password) FROM {$wpdb->posts} WHERE post_password != '' AND post_type = %s AND post_status IN ('in-progress','pending')", array( self::POST_TYPE ) ) ); } /** * Generate claim id. * * @return string */ protected function generate_claim_id() { $claim_id = md5( microtime( true ) . wp_rand( 0, 1000 ) ); return substr( $claim_id, 0, 20 ); // to fit in db field with 20 char limit. } /** * Claim actions. * * @param string $claim_id Claim ID. * @param int $limit Limit. * @param DateTime $before_date Should use UTC timezone. * @param array $hooks Claim only actions with a hook or hooks. * @param string $group Claim only actions in the given group. * * @return int The number of actions that were claimed. * @throws RuntimeException When there is a database error. */ protected function claim_actions( $claim_id, $limit, DateTime $before_date = null, $hooks = array(), $group = '' ) { // Set up initial variables. $date = null === $before_date ? as_get_datetime_object() : clone $before_date; $limit_ids = ! empty( $group ); $ids = $limit_ids ? $this->get_actions_by_group( $group, $limit, $date ) : array(); // If limiting by IDs and no posts found, then return early since we have nothing to update. if ( $limit_ids && 0 === count( $ids ) ) { return 0; } /** * Global wpdb object. * * @var wpdb $wpdb */ global $wpdb; /* * Build up custom query to update the affected posts. Parameters are built as a separate array * to make it easier to identify where they are in the query. * * We can't use $wpdb->update() here because of the "ID IN ..." clause. */ $update = "UPDATE {$wpdb->posts} SET post_password = %s, post_modified_gmt = %s, post_modified = %s"; $params = array( $claim_id, current_time( 'mysql', true ), current_time( 'mysql' ), ); // Build initial WHERE clause. $where = "WHERE post_type = %s AND post_status = %s AND post_password = ''"; $params[] = self::POST_TYPE; $params[] = ActionScheduler_Store::STATUS_PENDING; if ( ! empty( $hooks ) ) { $placeholders = array_fill( 0, count( $hooks ), '%s' ); $where .= ' AND post_title IN (' . join( ', ', $placeholders ) . ')'; $params = array_merge( $params, array_values( $hooks ) ); } /* * Add the IDs to the WHERE clause. IDs not escaped because they came directly from a prior DB query. * * If we're not limiting by IDs, then include the post_date_gmt clause. */ if ( $limit_ids ) { $where .= ' AND ID IN (' . join( ',', $ids ) . ')'; } else { $where .= ' AND post_date_gmt <= %s'; $params[] = $date->format( 'Y-m-d H:i:s' ); } // Add the ORDER BY clause and,ms limit. $order = 'ORDER BY menu_order ASC, post_date_gmt ASC, ID ASC LIMIT %d'; $params[] = $limit; // Run the query and gather results. $rows_affected = $wpdb->query( $wpdb->prepare( "{$update} {$where} {$order}", $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare if ( false === $rows_affected ) { throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) ); } return (int) $rows_affected; } /** * Get IDs of actions within a certain group and up to a certain date/time. * * @param string $group The group to use in finding actions. * @param int $limit The number of actions to retrieve. * @param DateTime $date DateTime object representing cutoff time for actions. Actions retrieved will be * up to and including this DateTime. * * @return array IDs of actions in the appropriate group and before the appropriate time. * @throws InvalidArgumentException When the group does not exist. */ protected function get_actions_by_group( $group, $limit, DateTime $date ) { // Ensure the group exists before continuing. if ( ! term_exists( $group, self::GROUP_TAXONOMY ) ) { /* translators: %s is the group name */ throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) ); } // Set up a query for post IDs to use later. $query = new WP_Query(); $query_args = array( 'fields' => 'ids', 'post_type' => self::POST_TYPE, 'post_status' => ActionScheduler_Store::STATUS_PENDING, 'has_password' => false, 'posts_per_page' => $limit * 3, 'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters 'no_found_rows' => true, 'orderby' => array( 'menu_order' => 'ASC', 'date' => 'ASC', 'ID' => 'ASC', ), 'date_query' => array( 'column' => 'post_date_gmt', 'before' => $date->format( 'Y-m-d H:i' ), 'inclusive' => true, ), 'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery array( 'taxonomy' => self::GROUP_TAXONOMY, 'field' => 'slug', 'terms' => $group, 'include_children' => false, ), ), ); return $query->query( $query_args ); } /** * Find actions by claim ID. * * @param string $claim_id Claim ID. * @return array */ public function find_actions_by_claim_id( $claim_id ) { /** * Global wpdb object. * * @var wpdb $wpdb */ global $wpdb; $action_ids = array(); $before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object(); $cut_off = $before_date->format( 'Y-m-d H:i:s' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $results = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_date_gmt FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s", array( self::POST_TYPE, $claim_id, ) ) ); // Verify that the scheduled date for each action is within the expected bounds (in some unusual // cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify). foreach ( $results as $claimed_action ) { if ( $claimed_action->post_date_gmt <= $cut_off ) { $action_ids[] = absint( $claimed_action->ID ); } } return $action_ids; } /** * Release claim. * * @param ActionScheduler_ActionClaim $claim Claim object to release. * @return void * @throws RuntimeException When the claim is not unlocked. */ public function release_claim( ActionScheduler_ActionClaim $claim ) { $action_ids = $this->find_actions_by_claim_id( $claim->get_id() ); if ( empty( $action_ids ) ) { return; // nothing to do. } $action_id_string = implode( ',', array_map( 'intval', $action_ids ) ); /** * Global wpdb object. * * @var wpdb $wpdb */ global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $result = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID IN ($action_id_string) AND post_password = %s", //phpcs:ignore array( $claim->get_id(), ) ) ); if ( false === $result ) { /* translators: %s: claim ID */ throw new RuntimeException( sprintf( __( 'Unable to unlock claim %s. Database error.', 'action-scheduler' ), $claim->get_id() ) ); } } /** * Unclaim action. * * @param string $action_id Action ID. * @throws RuntimeException When unable to unlock claim on action ID. */ public function unclaim_action( $action_id ) { /** * Global wpdb object. * * @var wpdb $wpdb */ global $wpdb; //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $result = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID = %d AND post_type = %s", $action_id, self::POST_TYPE ) ); if ( false === $result ) { /* translators: %s: action ID */ throw new RuntimeException( sprintf( __( 'Unable to unlock claim on action %s. Database error.', 'action-scheduler' ), $action_id ) ); } } /** * Mark failure on action. * * @param int $action_id Action ID. * * @return void * @throws RuntimeException When unable to mark failure on action ID. */ public function mark_failure( $action_id ) { /** * Global wpdb object. * * @var wpdb $wpdb */ global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $result = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_status = %s WHERE ID = %d AND post_type = %s", self::STATUS_FAILED, $action_id, self::POST_TYPE ) ); if ( false === $result ) { /* translators: %s: action ID */ throw new RuntimeException( sprintf( __( 'Unable to mark failure on action %s. Database error.', 'action-scheduler' ), $action_id ) ); } } /** * Return an action's claim ID, as stored in the post password column * * @param int $action_id Action ID. * @return mixed */ public function get_claim_id( $action_id ) { return $this->get_post_column( $action_id, 'post_password' ); } /** * Return an action's status, as stored in the post status column * * @param int $action_id Action ID. * * @return mixed * @throws InvalidArgumentException When the action ID is invalid. */ public function get_status( $action_id ) { $status = $this->get_post_column( $action_id, 'post_status' ); if ( null === $status ) { throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) ); } return $this->get_action_status_by_post_status( $status ); } /** * Get post column * * @param string $action_id Action ID. * @param string $column_name Column Name. * * @return string|null */ private function get_post_column( $action_id, $column_name ) { /** * Global wpdb object. * * @var wpdb $wpdb */ global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching return $wpdb->get_var( $wpdb->prepare( "SELECT {$column_name} FROM {$wpdb->posts} WHERE ID=%d AND post_type=%s", // phpcs:ignore $action_id, self::POST_TYPE ) ); } /** * Log Execution. * * @throws Exception If the action status cannot be updated to self::STATUS_RUNNING ('in-progress'). * * @param string $action_id Action ID. */ public function log_execution( $action_id ) { /** * Global wpdb object. * * @var wpdb $wpdb */ global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $status_updated = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s", self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id, self::POST_TYPE ) ); if ( ! $status_updated ) { throw new Exception( sprintf( /* translators: 1: action ID. 2: status slug. */ __( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ), $action_id, self::STATUS_RUNNING ) ); } } /** * Record that an action was completed. * * @param string $action_id ID of the completed action. * * @throws InvalidArgumentException When the action ID is invalid. * @throws RuntimeException When there was an error executing the action. */ public function mark_complete( $action_id ) { $post = get_post( $action_id ); if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) { /* translators: %s is the action ID */ throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); } add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 ); add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 ); $result = wp_update_post( array( 'ID' => $action_id, 'post_status' => 'publish', ), true ); remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 ); remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 ); if ( is_wp_error( $result ) ) { throw new RuntimeException( $result->get_error_message() ); } /** * Fires after a scheduled action has been completed. * * @since 3.4.2 * * @param int $action_id Action ID. */ do_action( 'action_scheduler_completed_action', $action_id ); } /** * Mark action as migrated when there is an error deleting the action. * * @param int $action_id Action ID. */ public function mark_migrated( $action_id ) { wp_update_post( array( 'ID' => $action_id, 'post_status' => 'migrated', ) ); } /** * Determine whether the post store can be migrated. * * @param [type] $setting - Setting value. * @return bool */ public function migration_dependencies_met( $setting ) { global $wpdb; $dependencies_met = get_transient( self::DEPENDENCIES_MET ); if ( empty( $dependencies_met ) ) { $maximum_args_length = apply_filters( 'action_scheduler_maximum_args_length', 191 ); $found_action = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND CHAR_LENGTH(post_content) > %d LIMIT 1", $maximum_args_length, self::POST_TYPE ) ); $dependencies_met = $found_action ? 'no' : 'yes'; set_transient( self::DEPENDENCIES_MET, $dependencies_met, DAY_IN_SECONDS ); } return 'yes' === $dependencies_met ? $setting : false; } /** * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4. * * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However, * as we prepare to move to custom tables, and can use an indexed VARCHAR column instead, we want to warn * developers of this impending requirement. * * @param ActionScheduler_Action $action Action object. */ protected function validate_action( ActionScheduler_Action $action ) { try { parent::validate_action( $action ); } catch ( Exception $e ) { /* translators: %s is the error message */ $message = sprintf( __( '%s Support for strings longer than this will be removed in a future version.', 'action-scheduler' ), $e->getMessage() ); _doing_it_wrong( 'ActionScheduler_Action::$args', esc_html( $message ), '2.1.0' ); } } /** * (@codeCoverageIgnore) */ public function init() { add_filter( 'action_scheduler_migration_dependencies_met', array( $this, 'migration_dependencies_met' ) ); $post_type_registrar = new ActionScheduler_wpPostStore_PostTypeRegistrar(); $post_type_registrar->register(); $post_status_registrar = new ActionScheduler_wpPostStore_PostStatusRegistrar(); $post_status_registrar->register(); $taxonomy_registrar = new ActionScheduler_wpPostStore_TaxonomyRegistrar(); $taxonomy_registrar->register(); } } woocommerce/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostStatusRegistrar.php000064400000003445147600245720031704 0ustar00vendorpost_status_args(), $this->post_status_running_labels() ) ); register_post_status( ActionScheduler_Store::STATUS_FAILED, array_merge( $this->post_status_args(), $this->post_status_failed_labels() ) ); } /** * Build the args array for the post type definition * * @return array */ protected function post_status_args() { $args = array( 'public' => false, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, ); return apply_filters( 'action_scheduler_post_status_args', $args ); } /** * Build the args array for the post type definition * * @return array */ protected function post_status_failed_labels() { $labels = array( 'label' => _x( 'Failed', 'post', 'action-scheduler' ), /* translators: %s: count */ 'label_count' => _n_noop( 'Failed (%s)', 'Failed (%s)', 'action-scheduler' ), ); return apply_filters( 'action_scheduler_post_status_failed_labels', $labels ); } /** * Build the args array for the post type definition * * @return array */ protected function post_status_running_labels() { $labels = array( 'label' => _x( 'In-Progress', 'post', 'action-scheduler' ), /* translators: %s: count */ 'label_count' => _n_noop( 'In-Progress (%s)', 'In-Progress (%s)', 'action-scheduler' ), ); return apply_filters( 'action_scheduler_post_status_running_labels', $labels ); } } woocommerce/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_PostTypeRegistrar.php000064400000003415147600245720031337 0ustar00vendorpost_type_args() ); } /** * Build the args array for the post type definition * * @return array */ protected function post_type_args() { $args = array( 'label' => __( 'Scheduled Actions', 'action-scheduler' ), 'description' => __( 'Scheduled actions are hooks triggered on a cetain date and time.', 'action-scheduler' ), 'public' => false, 'map_meta_cap' => true, 'hierarchical' => false, 'supports' => array('title', 'editor','comments'), 'rewrite' => false, 'query_var' => false, 'can_export' => true, 'ep_mask' => EP_NONE, 'labels' => array( 'name' => __( 'Scheduled Actions', 'action-scheduler' ), 'singular_name' => __( 'Scheduled Action', 'action-scheduler' ), 'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'action-scheduler' ), 'add_new' => __( 'Add', 'action-scheduler' ), 'add_new_item' => __( 'Add New Scheduled Action', 'action-scheduler' ), 'edit' => __( 'Edit', 'action-scheduler' ), 'edit_item' => __( 'Edit Scheduled Action', 'action-scheduler' ), 'new_item' => __( 'New Scheduled Action', 'action-scheduler' ), 'view' => __( 'View Action', 'action-scheduler' ), 'view_item' => __( 'View Action', 'action-scheduler' ), 'search_items' => __( 'Search Scheduled Actions', 'action-scheduler' ), 'not_found' => __( 'No actions found', 'action-scheduler' ), 'not_found_in_trash' => __( 'No actions found in trash', 'action-scheduler' ), ), ); $args = apply_filters('action_scheduler_post_type_args', $args); return $args; } } woocommerce/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore_TaxonomyRegistrar.php000064400000001212147600245720031357 0ustar00vendortaxonomy_args() ); } protected function taxonomy_args() { $args = array( 'label' => __( 'Action Group', 'action-scheduler' ), 'public' => false, 'hierarchical' => false, 'show_admin_column' => true, 'query_var' => false, 'rewrite' => false, ); $args = apply_filters( 'action_scheduler_taxonomy_args', $args ); return $args; } } vendor/woocommerce/action-scheduler/classes/migration/ActionMigrator.php000064400000007135147600245720022707 0ustar00source = $source_store; $this->destination = $destination_store; $this->log_migrator = $log_migrator; } /** * Migrate an action. * * @param int $source_action_id Action ID. * * @return int 0|new action ID */ public function migrate( $source_action_id ) { try { $action = $this->source->fetch_action( $source_action_id ); $status = $this->source->get_status( $source_action_id ); } catch ( \Exception $e ) { $action = null; $status = ''; } if ( is_null( $action ) || empty( $status ) || ! $action->get_schedule()->get_date() ) { // null action or empty status means the fetch operation failed or the action didn't exist // null schedule means it's missing vital data // delete it and move on try { $this->source->delete_action( $source_action_id ); } catch ( \Exception $e ) { // nothing to do, it didn't exist in the first place } do_action( 'action_scheduler/no_action_to_migrate', $source_action_id, $this->source, $this->destination ); return 0; } try { // Make sure the last attempt date is set correctly for completed and failed actions $last_attempt_date = ( $status !== \ActionScheduler_Store::STATUS_PENDING ) ? $this->source->get_date( $source_action_id ) : null; $destination_action_id = $this->destination->save_action( $action, null, $last_attempt_date ); } catch ( \Exception $e ) { do_action( 'action_scheduler/migrate_action_failed', $source_action_id, $this->source, $this->destination ); return 0; // could not save the action in the new store } try { switch ( $status ) { case \ActionScheduler_Store::STATUS_FAILED : $this->destination->mark_failure( $destination_action_id ); break; case \ActionScheduler_Store::STATUS_CANCELED : $this->destination->cancel_action( $destination_action_id ); break; } $this->log_migrator->migrate( $source_action_id, $destination_action_id ); $this->source->delete_action( $source_action_id ); $test_action = $this->source->fetch_action( $source_action_id ); if ( ! is_a( $test_action, 'ActionScheduler_NullAction' ) ) { // translators: %s is an action ID. throw new \RuntimeException( sprintf( __( 'Unable to remove source migrated action %s', 'action-scheduler' ), $source_action_id ) ); } do_action( 'action_scheduler/migrated_action', $source_action_id, $destination_action_id, $this->source, $this->destination ); return $destination_action_id; } catch ( \Exception $e ) { // could not delete from the old store $this->source->mark_migrated( $source_action_id ); do_action( 'action_scheduler/migrate_action_incomplete', $source_action_id, $destination_action_id, $this->source, $this->destination ); do_action( 'action_scheduler/migrated_action', $source_action_id, $destination_action_id, $this->source, $this->destination ); return $destination_action_id; } } } vendor/woocommerce/action-scheduler/classes/migration/ActionScheduler_DBStoreMigrator.php000064400000003333147600245720026124 0ustar00 $this->get_scheduled_date_string( $action, $last_attempt_date ), 'last_attempt_local' => $this->get_scheduled_date_string_local( $action, $last_attempt_date ), ]; $wpdb->update( $wpdb->actionscheduler_actions, $data, array( 'action_id' => $action_id ), array( '%s', '%s' ), array( '%d' ) ); } return $action_id; } catch ( \Exception $e ) { // translators: %s is an error message. throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 ); } } } vendor/woocommerce/action-scheduler/classes/migration/BatchFetcher.php000064400000003222147600245720022300 0ustar00store = $source_store; } /** * Retrieve a list of actions. * * @param int $count The number of actions to retrieve * * @return int[] A list of action IDs */ public function fetch( $count = 10 ) { foreach ( $this->get_query_strategies( $count ) as $query ) { $action_ids = $this->store->query_actions( $query ); if ( ! empty( $action_ids ) ) { return $action_ids; } } return []; } /** * Generate a list of prioritized of action search parameters. * * @param int $count Number of actions to find. * * @return array */ private function get_query_strategies( $count ) { $now = as_get_datetime_object(); $args = [ 'date' => $now, 'per_page' => $count, 'offset' => 0, 'orderby' => 'date', 'order' => 'ASC', ]; $priorities = [ Store::STATUS_PENDING, Store::STATUS_FAILED, Store::STATUS_CANCELED, Store::STATUS_COMPLETE, Store::STATUS_RUNNING, '', // any other unanticipated status ]; foreach ( $priorities as $status ) { yield wp_parse_args( [ 'status' => $status, 'date_compare' => '<=', ], $args ); yield wp_parse_args( [ 'status' => $status, 'date_compare' => '>=', ], $args ); } } }vendor/woocommerce/action-scheduler/classes/migration/Config.php000064400000006773147600245720021201 0ustar00source_store ) ) { throw new \RuntimeException( __( 'Source store must be configured before running a migration', 'action-scheduler' ) ); } return $this->source_store; } /** * Set the configured source store. * * @param ActionScheduler_Store $store Source store object. */ public function set_source_store( Store $store ) { $this->source_store = $store; } /** * Get the configured source loger. * * @return ActionScheduler_Logger */ public function get_source_logger() { if ( empty( $this->source_logger ) ) { throw new \RuntimeException( __( 'Source logger must be configured before running a migration', 'action-scheduler' ) ); } return $this->source_logger; } /** * Set the configured source logger. * * @param ActionScheduler_Logger $logger */ public function set_source_logger( Logger $logger ) { $this->source_logger = $logger; } /** * Get the configured destination store. * * @return ActionScheduler_Store */ public function get_destination_store() { if ( empty( $this->destination_store ) ) { throw new \RuntimeException( __( 'Destination store must be configured before running a migration', 'action-scheduler' ) ); } return $this->destination_store; } /** * Set the configured destination store. * * @param ActionScheduler_Store $store */ public function set_destination_store( Store $store ) { $this->destination_store = $store; } /** * Get the configured destination logger. * * @return ActionScheduler_Logger */ public function get_destination_logger() { if ( empty( $this->destination_logger ) ) { throw new \RuntimeException( __( 'Destination logger must be configured before running a migration', 'action-scheduler' ) ); } return $this->destination_logger; } /** * Set the configured destination logger. * * @param ActionScheduler_Logger $logger */ public function set_destination_logger( Logger $logger ) { $this->destination_logger = $logger; } /** * Get flag indicating whether it's a dry run. * * @return bool */ public function get_dry_run() { return $this->dry_run; } /** * Set flag indicating whether it's a dry run. * * @param bool $dry_run */ public function set_dry_run( $dry_run ) { $this->dry_run = (bool) $dry_run; } /** * Get progress bar object. * * @return ActionScheduler\WPCLI\ProgressBar */ public function get_progress_bar() { return $this->progress_bar; } /** * Set progress bar object. * * @param ActionScheduler\WPCLI\ProgressBar $progress_bar */ public function set_progress_bar( ProgressBar $progress_bar ) { $this->progress_bar = $progress_bar; } } vendor/woocommerce/action-scheduler/classes/migration/Controller.php000064400000014155147600245720022110 0ustar00migration_scheduler = $migration_scheduler; $this->store_classname = ''; } /** * Set the action store class name. * * @param string $class Classname of the store class. * * @return string */ public function get_store_class( $class ) { if ( \ActionScheduler_DataController::is_migration_complete() ) { return \ActionScheduler_DataController::DATASTORE_CLASS; } elseif ( \ActionScheduler_Store::DEFAULT_CLASS !== $class ) { $this->store_classname = $class; return $class; } else { return 'ActionScheduler_HybridStore'; } } /** * Set the action logger class name. * * @param string $class Classname of the logger class. * * @return string */ public function get_logger_class( $class ) { \ActionScheduler_Store::instance(); if ( $this->has_custom_datastore() ) { $this->logger_classname = $class; return $class; } else { return \ActionScheduler_DataController::LOGGER_CLASS; } } /** * Get flag indicating whether a custom datastore is in use. * * @return bool */ public function has_custom_datastore() { return (bool) $this->store_classname; } /** * Set up the background migration process. * * @return void */ public function schedule_migration() { $logging_tables = new ActionScheduler_LoggerSchema(); $store_tables = new ActionScheduler_StoreSchema(); /* * In some unusual cases, the expected tables may not have been created. In such cases * we do not schedule a migration as doing so will lead to fatal error conditions. * * In such cases the user will likely visit the Tools > Scheduled Actions screen to * investigate, and will see appropriate messaging (this step also triggers an attempt * to rebuild any missing tables). * * @see https://github.com/woocommerce/action-scheduler/issues/653 */ if ( ActionScheduler_DataController::is_migration_complete() || $this->migration_scheduler->is_migration_scheduled() || ! $store_tables->tables_exist() || ! $logging_tables->tables_exist() ) { return; } $this->migration_scheduler->schedule_migration(); } /** * Get the default migration config object * * @return ActionScheduler\Migration\Config */ public function get_migration_config_object() { static $config = null; if ( ! $config ) { $source_store = $this->store_classname ? new $this->store_classname() : new \ActionScheduler_wpPostStore(); $source_logger = $this->logger_classname ? new $this->logger_classname() : new \ActionScheduler_wpCommentLogger(); $config = new Config(); $config->set_source_store( $source_store ); $config->set_source_logger( $source_logger ); $config->set_destination_store( new \ActionScheduler_DBStoreMigrator() ); $config->set_destination_logger( new \ActionScheduler_DBLogger() ); if ( defined( 'WP_CLI' ) && WP_CLI ) { $config->set_progress_bar( new ProgressBar( '', 0 ) ); } } return apply_filters( 'action_scheduler/migration_config', $config ); } /** * Hook dashboard migration notice. */ public function hook_admin_notices() { if ( ! $this->allow_migration() || \ActionScheduler_DataController::is_migration_complete() ) { return; } add_action( 'admin_notices', array( $this, 'display_migration_notice' ), 10, 0 ); } /** * Show a dashboard notice that migration is in progress. */ public function display_migration_notice() { printf( '

    %s

    ', esc_html__( 'Action Scheduler migration in progress. The list of scheduled actions may be incomplete.', 'action-scheduler' ) ); } /** * Add store classes. Hook migration. */ private function hook() { add_filter( 'action_scheduler_store_class', array( $this, 'get_store_class' ), 100, 1 ); add_filter( 'action_scheduler_logger_class', array( $this, 'get_logger_class' ), 100, 1 ); add_action( 'init', array( $this, 'maybe_hook_migration' ) ); add_action( 'wp_loaded', array( $this, 'schedule_migration' ) ); // Action Scheduler may be displayed as a Tools screen or WooCommerce > Status administration screen add_action( 'load-tools_page_action-scheduler', array( $this, 'hook_admin_notices' ), 10, 0 ); add_action( 'load-woocommerce_page_wc-status', array( $this, 'hook_admin_notices' ), 10, 0 ); } /** * Possibly hook the migration scheduler action. * * @author Jeremy Pry */ public function maybe_hook_migration() { if ( ! $this->allow_migration() || \ActionScheduler_DataController::is_migration_complete() ) { return; } $this->migration_scheduler->hook(); } /** * Allow datastores to enable migration to AS tables. */ public function allow_migration() { if ( ! \ActionScheduler_DataController::dependencies_met() ) { return false; } if ( null === $this->migrate_custom_store ) { $this->migrate_custom_store = apply_filters( 'action_scheduler_migrate_data_store', false ); } return ( ! $this->has_custom_datastore() ) || $this->migrate_custom_store; } /** * Proceed with the migration if the dependencies have been met. */ public static function init() { if ( \ActionScheduler_DataController::dependencies_met() ) { self::instance()->hook(); } } /** * Singleton factory. */ public static function instance() { if ( ! isset( self::$instance ) ) { self::$instance = new static( new Scheduler() ); } return self::$instance; } } vendor/woocommerce/action-scheduler/classes/migration/DryRun_ActionMigrator.php000064400000000741147600245720024206 0ustar00source = $source_logger; $this->destination = $destination_Logger; } /** * Migrate an action log. * * @param int $source_action_id Source logger object. * @param int $destination_action_id Destination logger object. */ public function migrate( $source_action_id, $destination_action_id ) { $logs = $this->source->get_logs( $source_action_id ); foreach ( $logs as $log ) { if ( $log->get_action_id() == $source_action_id ) { $this->destination->log( $destination_action_id, $log->get_message(), $log->get_date() ); } } } } vendor/woocommerce/action-scheduler/classes/migration/Runner.php000064400000007230147600245720021232 0ustar00source_store = $config->get_source_store(); $this->destination_store = $config->get_destination_store(); $this->source_logger = $config->get_source_logger(); $this->destination_logger = $config->get_destination_logger(); $this->batch_fetcher = new BatchFetcher( $this->source_store ); if ( $config->get_dry_run() ) { $this->log_migrator = new DryRun_LogMigrator( $this->source_logger, $this->destination_logger ); $this->action_migrator = new DryRun_ActionMigrator( $this->source_store, $this->destination_store, $this->log_migrator ); } else { $this->log_migrator = new LogMigrator( $this->source_logger, $this->destination_logger ); $this->action_migrator = new ActionMigrator( $this->source_store, $this->destination_store, $this->log_migrator ); } if ( defined( 'WP_CLI' ) && WP_CLI ) { $this->progress_bar = $config->get_progress_bar(); } } /** * Run migration batch. * * @param int $batch_size Optional batch size. Default 10. * * @return int Size of batch processed. */ public function run( $batch_size = 10 ) { $batch = $this->batch_fetcher->fetch( $batch_size ); $batch_size = count( $batch ); if ( ! $batch_size ) { return 0; } if ( $this->progress_bar ) { /* translators: %d: amount of actions */ $this->progress_bar->set_message( sprintf( _n( 'Migrating %d action', 'Migrating %d actions', $batch_size, 'action-scheduler' ), $batch_size ) ); $this->progress_bar->set_count( $batch_size ); } $this->migrate_actions( $batch ); return $batch_size; } /** * Migration a batch of actions. * * @param array $action_ids List of action IDs to migrate. */ public function migrate_actions( array $action_ids ) { do_action( 'action_scheduler/migration_batch_starting', $action_ids ); \ActionScheduler::logger()->unhook_stored_action(); $this->destination_logger->unhook_stored_action(); foreach ( $action_ids as $source_action_id ) { $destination_action_id = $this->action_migrator->migrate( $source_action_id ); if ( $destination_action_id ) { $this->destination_logger->log( $destination_action_id, sprintf( /* translators: 1: source action ID 2: source store class 3: destination action ID 4: destination store class */ __( 'Migrated action with ID %1$d in %2$s to ID %3$d in %4$s', 'action-scheduler' ), $source_action_id, get_class( $this->source_store ), $destination_action_id, get_class( $this->destination_store ) ) ); } if ( $this->progress_bar ) { $this->progress_bar->tick(); } } if ( $this->progress_bar ) { $this->progress_bar->finish(); } \ActionScheduler::logger()->hook_stored_action(); do_action( 'action_scheduler/migration_batch_complete', $action_ids ); } /** * Initialize destination store and logger. */ public function init_destination() { $this->destination_store->init(); $this->destination_logger->init(); } } vendor/woocommerce/action-scheduler/classes/migration/Scheduler.php000064400000005542147600245720021703 0ustar00get_migration_runner(); $count = $migration_runner->run( $this->get_batch_size() ); if ( $count === 0 ) { $this->mark_complete(); } else { $this->schedule_migration( time() + $this->get_schedule_interval() ); } } /** * Mark the migration complete. */ public function mark_complete() { $this->unschedule_migration(); \ActionScheduler_DataController::mark_migration_complete(); do_action( 'action_scheduler/migration_complete' ); } /** * Get a flag indicating whether the migration is scheduled. * * @return bool Whether there is a pending action in the store to handle the migration */ public function is_migration_scheduled() { $next = as_next_scheduled_action( self::HOOK ); return ! empty( $next ); } /** * Schedule the migration. * * @param int $when Optional timestamp to run the next migration batch. Defaults to now. * * @return string The action ID */ public function schedule_migration( $when = 0 ) { $next = as_next_scheduled_action( self::HOOK ); if ( ! empty( $next ) ) { return $next; } if ( empty( $when ) ) { $when = time() + MINUTE_IN_SECONDS; } return as_schedule_single_action( $when, self::HOOK, array(), self::GROUP ); } /** * Remove the scheduled migration action. */ public function unschedule_migration() { as_unschedule_action( self::HOOK, null, self::GROUP ); } /** * Get migration batch schedule interval. * * @return int Seconds between migration runs. Defaults to 0 seconds to allow chaining migration via Async Runners. */ private function get_schedule_interval() { return (int) apply_filters( 'action_scheduler/migration_interval', 0 ); } /** * Get migration batch size. * * @return int Number of actions to migrate in each batch. Defaults to 250. */ private function get_batch_size() { return (int) apply_filters( 'action_scheduler/migration_batch_size', 250 ); } /** * Get migration runner object. * * @return Runner */ private function get_migration_runner() { $config = Controller::instance()->get_migration_config_object(); return new Runner( $config ); } } vendor/woocommerce/action-scheduler/classes/schedules/ActionScheduler_CanceledSchedule.php000064400000002705147600245720026260 0ustar00__wakeup() for details. **/ private $timestamp = NULL; /** * @param DateTime $after * * @return DateTime|null */ public function calculate_next( DateTime $after ) { return null; } /** * Cancelled actions should never have a next schedule, even if get_next() * is called with $after < $this->scheduled_date. * * @param DateTime $after * @return DateTime|null */ public function get_next( DateTime $after ) { return null; } /** * @return bool */ public function is_recurring() { return false; } /** * Unserialize recurring schedules serialized/stored prior to AS 3.0.0 * * Prior to Action Scheduler 3.0.0, schedules used different property names to refer * to equivalent data. For example, ActionScheduler_IntervalSchedule::start_timestamp * was the same as ActionScheduler_SimpleSchedule::timestamp. Action Scheduler 3.0.0 * aligned properties and property names for better inheritance. To maintain backward * compatibility with schedules serialized and stored prior to 3.0, we need to correctly * map the old property names with matching visibility. */ public function __wakeup() { if ( ! is_null( $this->timestamp ) ) { $this->scheduled_timestamp = $this->timestamp; unset( $this->timestamp ); } parent::__wakeup(); } } vendor/woocommerce/action-scheduler/classes/schedules/ActionScheduler_CronSchedule.php000064400000007201147600245720025457 0ustar00__wakeup() for details. **/ private $start_timestamp = NULL; /** * Deprecated property @see $this->__wakeup() for details. **/ private $cron = NULL; /** * Wrapper for parent constructor to accept a cron expression string and map it to a CronExpression for this * objects $recurrence property. * * @param DateTime $start The date & time to run the action at or after. If $start aligns with the CronSchedule passed via $recurrence, it will be used. If it does not align, the first matching date after it will be used. * @param CronExpression|string $recurrence The CronExpression used to calculate the schedule's next instance. * @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance. */ public function __construct( DateTime $start, $recurrence, DateTime $first = null ) { if ( ! is_a( $recurrence, 'CronExpression' ) ) { $recurrence = CronExpression::factory( $recurrence ); } // For backward compatibility, we need to make sure the date is set to the first matching cron date, not whatever date is passed in. Importantly, by passing true as the 3rd param, if $start matches the cron expression, then it will be used. This was previously handled in the now deprecated next() method. $date = $recurrence->getNextRunDate( $start, 0, true ); // parent::__construct() will set this to $date by default, but that may be different to $start now. $first = empty( $first ) ? $start : $first; parent::__construct( $date, $recurrence, $first ); } /** * Calculate when an instance of this schedule would start based on a given * date & time using its the CronExpression. * * @param DateTime $after * @return DateTime */ protected function calculate_next( DateTime $after ) { return $this->recurrence->getNextRunDate( $after, 0, false ); } /** * @return string */ public function get_recurrence() { return strval( $this->recurrence ); } /** * Serialize cron schedules with data required prior to AS 3.0.0 * * Prior to Action Scheduler 3.0.0, reccuring schedules used different property names to * refer to equivalent data. For example, ActionScheduler_IntervalSchedule::start_timestamp * was the same as ActionScheduler_SimpleSchedule::timestamp. Action Scheduler 3.0.0 * aligned properties and property names for better inheritance. To guard against the * possibility of infinite loops if downgrading to Action Scheduler < 3.0.0, we need to * also store the data with the old property names so if it's unserialized in AS < 3.0, * the schedule doesn't end up with a null recurrence. * * @return array */ public function __sleep() { $sleep_params = parent::__sleep(); $this->start_timestamp = $this->scheduled_timestamp; $this->cron = $this->recurrence; return array_merge( $sleep_params, array( 'start_timestamp', 'cron' ) ); } /** * Unserialize cron schedules serialized/stored prior to AS 3.0.0 * * For more background, @see ActionScheduler_Abstract_RecurringSchedule::__wakeup(). */ public function __wakeup() { if ( is_null( $this->scheduled_timestamp ) && ! is_null( $this->start_timestamp ) ) { $this->scheduled_timestamp = $this->start_timestamp; unset( $this->start_timestamp ); } if ( is_null( $this->recurrence ) && ! is_null( $this->cron ) ) { $this->recurrence = $this->cron; unset( $this->cron ); } parent::__wakeup(); } } vendor/woocommerce/action-scheduler/classes/schedules/ActionScheduler_IntervalSchedule.php000064400000004752147600245720026352 0ustar00__wakeup() for details. **/ private $start_timestamp = NULL; /** * Deprecated property @see $this->__wakeup() for details. **/ private $interval_in_seconds = NULL; /** * Calculate when this schedule should start after a given date & time using * the number of seconds between recurrences. * * @param DateTime $after * @return DateTime */ protected function calculate_next( DateTime $after ) { $after->modify( '+' . (int) $this->get_recurrence() . ' seconds' ); return $after; } /** * @return int */ public function interval_in_seconds() { _deprecated_function( __METHOD__, '3.0.0', '(int)ActionScheduler_Abstract_RecurringSchedule::get_recurrence()' ); return (int) $this->get_recurrence(); } /** * Serialize interval schedules with data required prior to AS 3.0.0 * * Prior to Action Scheduler 3.0.0, reccuring schedules used different property names to * refer to equivalent data. For example, ActionScheduler_IntervalSchedule::start_timestamp * was the same as ActionScheduler_SimpleSchedule::timestamp. Action Scheduler 3.0.0 * aligned properties and property names for better inheritance. To guard against the * possibility of infinite loops if downgrading to Action Scheduler < 3.0.0, we need to * also store the data with the old property names so if it's unserialized in AS < 3.0, * the schedule doesn't end up with a null/false/0 recurrence. * * @return array */ public function __sleep() { $sleep_params = parent::__sleep(); $this->start_timestamp = $this->scheduled_timestamp; $this->interval_in_seconds = $this->recurrence; return array_merge( $sleep_params, array( 'start_timestamp', 'interval_in_seconds' ) ); } /** * Unserialize interval schedules serialized/stored prior to AS 3.0.0 * * For more background, @see ActionScheduler_Abstract_RecurringSchedule::__wakeup(). */ public function __wakeup() { if ( is_null( $this->scheduled_timestamp ) && ! is_null( $this->start_timestamp ) ) { $this->scheduled_timestamp = $this->start_timestamp; unset( $this->start_timestamp ); } if ( is_null( $this->recurrence ) && ! is_null( $this->interval_in_seconds ) ) { $this->recurrence = $this->interval_in_seconds; unset( $this->interval_in_seconds ); } parent::__wakeup(); } } vendor/woocommerce/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php000064400000001177147600245720025476 0ustar00scheduled_date = null; } /** * This schedule has no scheduled DateTime, so we need to override the parent __sleep() * @return array */ public function __sleep() { return array(); } public function __wakeup() { $this->scheduled_date = null; } } vendor/woocommerce/action-scheduler/classes/schedules/ActionScheduler_Schedule.php000064400000000406147600245720024635 0ustar00__wakeup() for details. **/ private $timestamp = NULL; /** * @param DateTime $after * * @return DateTime|null */ public function calculate_next( DateTime $after ) { return null; } /** * @return bool */ public function is_recurring() { return false; } /** * Serialize schedule with data required prior to AS 3.0.0 * * Prior to Action Scheduler 3.0.0, schedules used different property names to refer * to equivalent data. For example, ActionScheduler_IntervalSchedule::start_timestamp * was the same as ActionScheduler_SimpleSchedule::timestamp. Action Scheduler 3.0.0 * aligned properties and property names for better inheritance. To guard against the * scheduled date for single actions always being seen as "now" if downgrading to * Action Scheduler < 3.0.0, we need to also store the data with the old property names * so if it's unserialized in AS < 3.0, the schedule doesn't end up with a null recurrence. * * @return array */ public function __sleep() { $sleep_params = parent::__sleep(); $this->timestamp = $this->scheduled_timestamp; return array_merge( $sleep_params, array( 'timestamp', ) ); } /** * Unserialize recurring schedules serialized/stored prior to AS 3.0.0 * * Prior to Action Scheduler 3.0.0, schedules used different property names to refer * to equivalent data. For example, ActionScheduler_IntervalSchedule::start_timestamp * was the same as ActionScheduler_SimpleSchedule::timestamp. Action Scheduler 3.0.0 * aligned properties and property names for better inheritance. To maintain backward * compatibility with schedules serialized and stored prior to 3.0, we need to correctly * map the old property names with matching visibility. */ public function __wakeup() { if ( is_null( $this->scheduled_timestamp ) && ! is_null( $this->timestamp ) ) { $this->scheduled_timestamp = $this->timestamp; unset( $this->timestamp ); } parent::__wakeup(); } } vendor/woocommerce/action-scheduler/classes/schema/ActionScheduler_LoggerSchema.php000064400000005462147600245720024731 0ustar00tables = [ self::LOG_TABLE, ]; } /** * Performs additional setup work required to support this schema. */ public function init() { add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_3_0' ), 10, 2 ); } protected function get_table_definition( $table ) { global $wpdb; $table_name = $wpdb->$table; $charset_collate = $wpdb->get_charset_collate(); switch ( $table ) { case self::LOG_TABLE: $default_date = ActionScheduler_StoreSchema::DEFAULT_DATE; return "CREATE TABLE $table_name ( log_id bigint(20) unsigned NOT NULL auto_increment, action_id bigint(20) unsigned NOT NULL, message text NOT NULL, log_date_gmt datetime NULL default '{$default_date}', log_date_local datetime NULL default '{$default_date}', PRIMARY KEY (log_id), KEY action_id (action_id), KEY log_date_gmt (log_date_gmt) ) $charset_collate"; default: return ''; } } /** * Update the logs table schema, allowing datetime fields to be NULL. * * This is needed because the NOT NULL constraint causes a conflict with some versions of MySQL * configured with sql_mode=NO_ZERO_DATE, which can for instance lead to tables not being created. * * Most other schema updates happen via ActionScheduler_Abstract_Schema::update_table(), however * that method relies on dbDelta() and this change is not possible when using that function. * * @param string $table Name of table being updated. * @param string $db_version The existing schema version of the table. */ public function update_schema_3_0( $table, $db_version ) { global $wpdb; if ( 'actionscheduler_logs' !== $table || version_compare( $db_version, '3', '>=' ) ) { return; } // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $table_name = $wpdb->prefix . 'actionscheduler_logs'; $table_list = $wpdb->get_col( "SHOW TABLES LIKE '{$table_name}'" ); $default_date = ActionScheduler_StoreSchema::DEFAULT_DATE; if ( ! empty( $table_list ) ) { $query = " ALTER TABLE {$table_name} MODIFY COLUMN log_date_gmt datetime NULL default '{$default_date}', MODIFY COLUMN log_date_local datetime NULL default '{$default_date}' "; $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared } } vendor/woocommerce/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php000064400000011507147600245720024603 0ustar00tables = [ self::ACTIONS_TABLE, self::CLAIMS_TABLE, self::GROUPS_TABLE, ]; } /** * Performs additional setup work required to support this schema. */ public function init() { add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_5_0' ), 10, 2 ); } protected function get_table_definition( $table ) { global $wpdb; $table_name = $wpdb->$table; $charset_collate = $wpdb->get_charset_collate(); $max_index_length = 191; // @see wp_get_db_schema() $hook_status_scheduled_date_gmt_max_index_length = $max_index_length - 20 - 8; // - status, - scheduled_date_gmt $default_date = self::DEFAULT_DATE; switch ( $table ) { case self::ACTIONS_TABLE: return "CREATE TABLE {$table_name} ( action_id bigint(20) unsigned NOT NULL auto_increment, hook varchar(191) NOT NULL, status varchar(20) NOT NULL, scheduled_date_gmt datetime NULL default '{$default_date}', scheduled_date_local datetime NULL default '{$default_date}', priority tinyint unsigned NOT NULL default '10', args varchar($max_index_length), schedule longtext, group_id bigint(20) unsigned NOT NULL default '0', attempts int(11) NOT NULL default '0', last_attempt_gmt datetime NULL default '{$default_date}', last_attempt_local datetime NULL default '{$default_date}', claim_id bigint(20) unsigned NOT NULL default '0', extended_args varchar(8000) DEFAULT NULL, PRIMARY KEY (action_id), KEY hook_status_scheduled_date_gmt (hook($hook_status_scheduled_date_gmt_max_index_length), status, scheduled_date_gmt), KEY status_scheduled_date_gmt (status, scheduled_date_gmt), KEY scheduled_date_gmt (scheduled_date_gmt), KEY args (args($max_index_length)), KEY group_id (group_id), KEY last_attempt_gmt (last_attempt_gmt), KEY `claim_id_status_scheduled_date_gmt` (`claim_id`, `status`, `scheduled_date_gmt`) ) $charset_collate"; case self::CLAIMS_TABLE: return "CREATE TABLE {$table_name} ( claim_id bigint(20) unsigned NOT NULL auto_increment, date_created_gmt datetime NULL default '{$default_date}', PRIMARY KEY (claim_id), KEY date_created_gmt (date_created_gmt) ) $charset_collate"; case self::GROUPS_TABLE: return "CREATE TABLE {$table_name} ( group_id bigint(20) unsigned NOT NULL auto_increment, slug varchar(255) NOT NULL, PRIMARY KEY (group_id), KEY slug (slug($max_index_length)) ) $charset_collate"; default: return ''; } } /** * Update the actions table schema, allowing datetime fields to be NULL. * * This is needed because the NOT NULL constraint causes a conflict with some versions of MySQL * configured with sql_mode=NO_ZERO_DATE, which can for instance lead to tables not being created. * * Most other schema updates happen via ActionScheduler_Abstract_Schema::update_table(), however * that method relies on dbDelta() and this change is not possible when using that function. * * @param string $table Name of table being updated. * @param string $db_version The existing schema version of the table. */ public function update_schema_5_0( $table, $db_version ) { global $wpdb; if ( 'actionscheduler_actions' !== $table || version_compare( $db_version, '5', '>=' ) ) { return; } // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $table_name = $wpdb->prefix . 'actionscheduler_actions'; $table_list = $wpdb->get_col( "SHOW TABLES LIKE '{$table_name}'" ); $default_date = self::DEFAULT_DATE; if ( ! empty( $table_list ) ) { $query = " ALTER TABLE {$table_name} MODIFY COLUMN scheduled_date_gmt datetime NULL default '{$default_date}', MODIFY COLUMN scheduled_date_local datetime NULL default '{$default_date}', MODIFY COLUMN last_attempt_gmt datetime NULL default '{$default_date}', MODIFY COLUMN last_attempt_local datetime NULL default '{$default_date}' "; $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_ActionClaim.php000064400000000566147600245720023314 0ustar00id = $id; $this->action_ids = $action_ids; } public function get_id() { return $this->id; } public function get_actions() { return $this->action_ids; } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_ActionFactory.php000064400000037373147600245720023704 0ustar00= 6 ? (int) func_get_arg( 5 ) : 10; switch ( $status ) { case ActionScheduler_Store::STATUS_PENDING: $action_class = 'ActionScheduler_Action'; break; case ActionScheduler_Store::STATUS_CANCELED: $action_class = 'ActionScheduler_CanceledAction'; if ( ! is_null( $schedule ) && ! is_a( $schedule, 'ActionScheduler_CanceledSchedule' ) && ! is_a( $schedule, 'ActionScheduler_NullSchedule' ) ) { $schedule = new ActionScheduler_CanceledSchedule( $schedule->get_date() ); } break; default: $action_class = 'ActionScheduler_FinishedAction'; break; } $action_class = apply_filters( 'action_scheduler_stored_action_class', $action_class, $status, $hook, $args, $schedule, $group ); $action = new $action_class( $hook, $args, $schedule, $group ); $action->set_priority( $priority ); /** * Allow 3rd party code to change the instantiated action for a given hook, args, schedule and group. * * @param ActionScheduler_Action $action The instantiated action. * @param string $hook The instantiated action's hook. * @param array $args The instantiated action's args. * @param ActionScheduler_Schedule $schedule The instantiated action's schedule. * @param string $group The instantiated action's group. * @param int $priority The action priority. */ return apply_filters( 'action_scheduler_stored_action_instance', $action, $hook, $args, $schedule, $group, $priority ); } /** * Enqueue an action to run one time, as soon as possible (rather a specific scheduled time). * * This method creates a new action using the NullSchedule. In practice, this results in an action scheduled to * execute "now". Therefore, it will generally run as soon as possible but is not prioritized ahead of other actions * that are already past-due. * * @param string $hook The hook to trigger when this action runs. * @param array $args Args to pass when the hook is triggered. * @param string $group A group to put the action in. * * @return int The ID of the stored action. */ public function async( $hook, $args = array(), $group = '' ) { return $this->async_unique( $hook, $args, $group, false ); } /** * Same as async, but also supports $unique param. * * @param string $hook The hook to trigger when this action runs. * @param array $args Args to pass when the hook is triggered. * @param string $group A group to put the action in. * @param bool $unique Whether to ensure the action is unique. * * @return int The ID of the stored action. */ public function async_unique( $hook, $args = array(), $group = '', $unique = true ) { $schedule = new ActionScheduler_NullSchedule(); $action = new ActionScheduler_Action( $hook, $args, $schedule, $group ); return $unique ? $this->store_unique_action( $action, $unique ) : $this->store( $action ); } /** * Create single action. * * @param string $hook The hook to trigger when this action runs. * @param array $args Args to pass when the hook is triggered. * @param int $when Unix timestamp when the action will run. * @param string $group A group to put the action in. * * @return int The ID of the stored action. */ public function single( $hook, $args = array(), $when = null, $group = '' ) { return $this->single_unique( $hook, $args, $when, $group, false ); } /** * Create single action only if there is no pending or running action with same name and params. * * @param string $hook The hook to trigger when this action runs. * @param array $args Args to pass when the hook is triggered. * @param int $when Unix timestamp when the action will run. * @param string $group A group to put the action in. * @param bool $unique Whether action scheduled should be unique. * * @return int The ID of the stored action. */ public function single_unique( $hook, $args = array(), $when = null, $group = '', $unique = true ) { $date = as_get_datetime_object( $when ); $schedule = new ActionScheduler_SimpleSchedule( $date ); $action = new ActionScheduler_Action( $hook, $args, $schedule, $group ); return $unique ? $this->store_unique_action( $action ) : $this->store( $action ); } /** * Create the first instance of an action recurring on a given interval. * * @param string $hook The hook to trigger when this action runs. * @param array $args Args to pass when the hook is triggered. * @param int $first Unix timestamp for the first run. * @param int $interval Seconds between runs. * @param string $group A group to put the action in. * * @return int The ID of the stored action. */ public function recurring( $hook, $args = array(), $first = null, $interval = null, $group = '' ) { return $this->recurring_unique( $hook, $args, $first, $interval, $group, false ); } /** * Create the first instance of an action recurring on a given interval only if there is no pending or running action with same name and params. * * @param string $hook The hook to trigger when this action runs. * @param array $args Args to pass when the hook is triggered. * @param int $first Unix timestamp for the first run. * @param int $interval Seconds between runs. * @param string $group A group to put the action in. * @param bool $unique Whether action scheduled should be unique. * * @return int The ID of the stored action. */ public function recurring_unique( $hook, $args = array(), $first = null, $interval = null, $group = '', $unique = true ) { if ( empty( $interval ) ) { return $this->single_unique( $hook, $args, $first, $group, $unique ); } $date = as_get_datetime_object( $first ); $schedule = new ActionScheduler_IntervalSchedule( $date, $interval ); $action = new ActionScheduler_Action( $hook, $args, $schedule, $group ); return $unique ? $this->store_unique_action( $action ) : $this->store( $action ); } /** * Create the first instance of an action recurring on a Cron schedule. * * @param string $hook The hook to trigger when this action runs. * @param array $args Args to pass when the hook is triggered. * @param int $base_timestamp The first instance of the action will be scheduled * to run at a time calculated after this timestamp matching the cron * expression. This can be used to delay the first instance of the action. * @param int $schedule A cron definition string. * @param string $group A group to put the action in. * * @return int The ID of the stored action. */ public function cron( $hook, $args = array(), $base_timestamp = null, $schedule = null, $group = '' ) { return $this->cron_unique( $hook, $args, $base_timestamp, $schedule, $group, false ); } /** * Create the first instance of an action recurring on a Cron schedule only if there is no pending or running action with same name and params. * * @param string $hook The hook to trigger when this action runs. * @param array $args Args to pass when the hook is triggered. * @param int $base_timestamp The first instance of the action will be scheduled * to run at a time calculated after this timestamp matching the cron * expression. This can be used to delay the first instance of the action. * @param int $schedule A cron definition string. * @param string $group A group to put the action in. * @param bool $unique Whether action scheduled should be unique. * * @return int The ID of the stored action. **/ public function cron_unique( $hook, $args = array(), $base_timestamp = null, $schedule = null, $group = '', $unique = true ) { if ( empty( $schedule ) ) { return $this->single_unique( $hook, $args, $base_timestamp, $group, $unique ); } $date = as_get_datetime_object( $base_timestamp ); $cron = CronExpression::factory( $schedule ); $schedule = new ActionScheduler_CronSchedule( $date, $cron ); $action = new ActionScheduler_Action( $hook, $args, $schedule, $group ); return $unique ? $this->store_unique_action( $action ) : $this->store( $action ); } /** * Create a successive instance of a recurring or cron action. * * Importantly, the action will be rescheduled to run based on the current date/time. * That means when the action is scheduled to run in the past, the next scheduled date * will be pushed forward. For example, if a recurring action set to run every hour * was scheduled to run 5 seconds ago, it will be next scheduled for 1 hour in the * future, which is 1 hour and 5 seconds from when it was last scheduled to run. * * Alternatively, if the action is scheduled to run in the future, and is run early, * likely via manual intervention, then its schedule will change based on the time now. * For example, if a recurring action set to run every day, and is run 12 hours early, * it will run again in 24 hours, not 36 hours. * * This slippage is less of an issue with Cron actions, as the specific run time can * be set for them to run, e.g. 1am each day. In those cases, and entire period would * need to be missed before there was any change is scheduled, e.g. in the case of an * action scheduled for 1am each day, the action would need to run an entire day late. * * @param ActionScheduler_Action $action The existing action. * * @return string The ID of the stored action * @throws InvalidArgumentException If $action is not a recurring action. */ public function repeat( $action ) { $schedule = $action->get_schedule(); $next = $schedule->get_next( as_get_datetime_object() ); if ( is_null( $next ) || ! $schedule->is_recurring() ) { throw new InvalidArgumentException( __( 'Invalid action - must be a recurring action.', 'action-scheduler' ) ); } $schedule_class = get_class( $schedule ); $new_schedule = new $schedule( $next, $schedule->get_recurrence(), $schedule->get_first_date() ); $new_action = new ActionScheduler_Action( $action->get_hook(), $action->get_args(), $new_schedule, $action->get_group() ); $new_action->set_priority( $action->get_priority() ); return $this->store( $new_action ); } /** * Creates a scheduled action. * * This general purpose method can be used in place of specific methods such as async(), * async_unique(), single() or single_unique(), etc. * * @internal Not intended for public use, should not be overriden by subclasses. * * @param array $options { * Describes the action we wish to schedule. * * @type string $type Must be one of 'async', 'cron', 'recurring', or 'single'. * @type string $hook The hook to be executed. * @type array $arguments Arguments to be passed to the callback. * @type string $group The action group. * @type bool $unique If the action should be unique. * @type int $when Timestamp. Indicates when the action, or first instance of the action in the case * of recurring or cron actions, becomes due. * @type int|string $pattern Recurrence pattern. This is either an interval in seconds for recurring actions * or a cron expression for cron actions. * @type int $priority Lower values means higher priority. Should be in the range 0-255. * } * * @return int The action ID. Zero if there was an error scheduling the action. */ public function create( array $options = array() ) { $defaults = array( 'type' => 'single', 'hook' => '', 'arguments' => array(), 'group' => '', 'unique' => false, 'when' => time(), 'pattern' => null, 'priority' => 10, ); $options = array_merge( $defaults, $options ); // Cron/recurring actions without a pattern are treated as single actions (this gives calling code the ability // to use functions like as_schedule_recurring_action() to schedule recurring as well as single actions). if ( ( 'cron' === $options['type'] || 'recurring' === $options['type'] ) && empty( $options['pattern'] ) ) { $options['type'] = 'single'; } switch ( $options['type'] ) { case 'async': $schedule = new ActionScheduler_NullSchedule(); break; case 'cron': $date = as_get_datetime_object( $options['when'] ); $cron = CronExpression::factory( $options['pattern'] ); $schedule = new ActionScheduler_CronSchedule( $date, $cron ); break; case 'recurring': $date = as_get_datetime_object( $options['when'] ); $schedule = new ActionScheduler_IntervalSchedule( $date, $options['pattern'] ); break; case 'single': $date = as_get_datetime_object( $options['when'] ); $schedule = new ActionScheduler_SimpleSchedule( $date ); break; default: error_log( "Unknown action type '{$options['type']}' specified when trying to create an action for '{$options['hook']}'." ); return 0; } $action = new ActionScheduler_Action( $options['hook'], $options['arguments'], $schedule, $options['group'] ); $action->set_priority( $options['priority'] ); $action_id = 0; try { $action_id = $options['unique'] ? $this->store_unique_action( $action ) : $this->store( $action ); } catch ( Exception $e ) { error_log( sprintf( /* translators: %1$s is the name of the hook to be enqueued, %2$s is the exception message. */ __( 'Caught exception while enqueuing action "%1$s": %2$s', 'action-scheduler' ), $options['hook'], $e->getMessage() ) ); } return $action_id; } /** * Save action to database. * * @param ActionScheduler_Action $action Action object to save. * * @return int The ID of the stored action */ protected function store( ActionScheduler_Action $action ) { $store = ActionScheduler_Store::instance(); return $store->save_action( $action ); } /** * Store action if it's unique. * * @param ActionScheduler_Action $action Action object to store. * * @return int ID of the created action. Will be 0 if action was not created. */ protected function store_unique_action( ActionScheduler_Action $action ) { $store = ActionScheduler_Store::instance(); if ( method_exists( $store, 'save_unique_action' ) ) { return $store->save_unique_action( $action ); } else { /** * Fallback to non-unique action if the store doesn't support unique actions. * We try to save the action as unique, accepting that there might be a race condition. * This is likely still better than givinig up on unique actions entirely. */ $existing_action_id = (int) $store->find_action( $action->get_hook(), array( 'args' => $action->get_args(), 'status' => ActionScheduler_Store::STATUS_PENDING, 'group' => $action->get_group(), ) ); if ( $existing_action_id > 0 ) { return 0; } return $store->save_action( $action ); } } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_AdminView.php000064400000021706147600245720023013 0ustar00render(); } /** * Registers action-scheduler into WooCommerce > System status. * * @param array $tabs An associative array of tab key => label. * @return array $tabs An associative array of tab key => label, including Action Scheduler's tabs */ public function register_system_status_tab( array $tabs ) { $tabs['action-scheduler'] = __( 'Scheduled Actions', 'action-scheduler' ); return $tabs; } /** * Include Action Scheduler's administration under the Tools menu. * * A menu under the Tools menu is important for backward compatibility (as that's * where it started), and also provides more convenient access than the WooCommerce * System Status page, and for sites where WooCommerce isn't active. */ public function register_menu() { $hook_suffix = add_submenu_page( 'tools.php', __( 'Scheduled Actions', 'action-scheduler' ), __( 'Scheduled Actions', 'action-scheduler' ), 'manage_options', 'action-scheduler', array( $this, 'render_admin_ui' ) ); add_action( 'load-' . $hook_suffix , array( $this, 'process_admin_ui' ) ); } /** * Triggers processing of any pending actions. */ public function process_admin_ui() { $this->get_list_table(); } /** * Renders the Admin UI */ public function render_admin_ui() { $table = $this->get_list_table(); $table->display_page(); } /** * Get the admin UI object and process any requested actions. * * @return ActionScheduler_ListTable */ protected function get_list_table() { if ( null === $this->list_table ) { $this->list_table = new ActionScheduler_ListTable( ActionScheduler::store(), ActionScheduler::logger(), ActionScheduler::runner() ); $this->list_table->process_actions(); } return $this->list_table; } /** * Action: admin_notices * * Maybe check past-due actions, and print notice. * * @uses $this->check_pastdue_actions() */ public function maybe_check_pastdue_actions() { # Filter to prevent checking actions (ex: inappropriate user). if ( ! apply_filters( 'action_scheduler_check_pastdue_actions', current_user_can( 'manage_options' ) ) ) { return; } # Get last check transient. $last_check = get_transient( 'action_scheduler_last_pastdue_actions_check' ); # If transient exists, we're within interval, so bail. if ( ! empty( $last_check ) ) { return; } # Perform the check. $this->check_pastdue_actions(); } /** * Check past-due actions, and print notice. * * @todo update $link_url to "Past-due" filter when released (see issue #510, PR #511) */ protected function check_pastdue_actions() { # Set thresholds. $threshold_seconds = ( int ) apply_filters( 'action_scheduler_pastdue_actions_seconds', DAY_IN_SECONDS ); $threshhold_min = ( int ) apply_filters( 'action_scheduler_pastdue_actions_min', 1 ); // Set fallback value for past-due actions count. $num_pastdue_actions = 0; // Allow third-parties to preempt the default check logic. $check = apply_filters( 'action_scheduler_pastdue_actions_check_pre', null ); // If no third-party preempted and there are no past-due actions, return early. if ( ! is_null( $check ) ) { return; } # Scheduled actions query arguments. $query_args = array( 'date' => as_get_datetime_object( time() - $threshold_seconds ), 'status' => ActionScheduler_Store::STATUS_PENDING, 'per_page' => $threshhold_min, ); # If no third-party preempted, run default check. if ( is_null( $check ) ) { $store = ActionScheduler_Store::instance(); $num_pastdue_actions = ( int ) $store->query_actions( $query_args, 'count' ); # Check if past-due actions count is greater than or equal to threshold. $check = ( $num_pastdue_actions >= $threshhold_min ); $check = ( bool ) apply_filters( 'action_scheduler_pastdue_actions_check', $check, $num_pastdue_actions, $threshold_seconds, $threshhold_min ); } # If check failed, set transient and abort. if ( ! boolval( $check ) ) { $interval = apply_filters( 'action_scheduler_pastdue_actions_check_interval', round( $threshold_seconds / 4 ), $threshold_seconds ); set_transient( 'action_scheduler_last_pastdue_actions_check', time(), $interval ); return; } $actions_url = add_query_arg( array( 'page' => 'action-scheduler', 'status' => 'past-due', 'order' => 'asc', ), admin_url( 'tools.php' ) ); # Print notice. echo '

    '; printf( // translators: 1) is the number of affected actions, 2) is a link to an admin screen. _n( 'Action Scheduler: %1$d past-due action found; something may be wrong. Read documentation »', 'Action Scheduler: %1$d past-due actions found; something may be wrong. Read documentation »', $num_pastdue_actions, 'action-scheduler' ), $num_pastdue_actions, esc_attr( esc_url( $actions_url ) ) ); echo '

    '; # Facilitate third-parties to evaluate and print notices. do_action( 'action_scheduler_pastdue_actions_extra_notices', $query_args ); } /** * Provide more information about the screen and its data in the help tab. */ public function add_help_tabs() { $screen = get_current_screen(); if ( ! $screen || self::$screen_id != $screen->id ) { return; } $as_version = ActionScheduler_Versions::instance()->latest_version(); $screen->add_help_tab( array( 'id' => 'action_scheduler_about', 'title' => __( 'About', 'action-scheduler' ), 'content' => // translators: %s is the Action Scheduler version. '

    ' . sprintf( __( 'About Action Scheduler %s', 'action-scheduler' ), $as_version ) . '

    ' . '

    ' . __( 'Action Scheduler is a scalable, traceable job queue for background processing large sets of actions. Action Scheduler works by triggering an action hook to run at some time in the future. Scheduled actions can also be scheduled to run on a recurring schedule.', 'action-scheduler' ) . '

    ', ) ); $screen->add_help_tab( array( 'id' => 'action_scheduler_columns', 'title' => __( 'Columns', 'action-scheduler' ), 'content' => '

    ' . __( 'Scheduled Action Columns', 'action-scheduler' ) . '

    ' . '
      ' . sprintf( '
    • %1$s: %2$s
    • ', __( 'Hook', 'action-scheduler' ), __( 'Name of the action hook that will be triggered.', 'action-scheduler' ) ) . sprintf( '
    • %1$s: %2$s
    • ', __( 'Status', 'action-scheduler' ), __( 'Action statuses are Pending, Complete, Canceled, Failed', 'action-scheduler' ) ) . sprintf( '
    • %1$s: %2$s
    • ', __( 'Arguments', 'action-scheduler' ), __( 'Optional data array passed to the action hook.', 'action-scheduler' ) ) . sprintf( '
    • %1$s: %2$s
    • ', __( 'Group', 'action-scheduler' ), __( 'Optional action group.', 'action-scheduler' ) ) . sprintf( '
    • %1$s: %2$s
    • ', __( 'Recurrence', 'action-scheduler' ), __( 'The action\'s schedule frequency.', 'action-scheduler' ) ) . sprintf( '
    • %1$s: %2$s
    • ', __( 'Scheduled', 'action-scheduler' ), __( 'The date/time the action is/was scheduled to run.', 'action-scheduler' ) ) . sprintf( '
    • %1$s: %2$s
    • ', __( 'Log', 'action-scheduler' ), __( 'Activity log for the action.', 'action-scheduler' ) ) . '
    ', ) ); } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_AsyncRequest_QueueRunner.php000064400000004255147600245720026114 0ustar00store = $store; } /** * Handle async requests * * Run a queue, and maybe dispatch another async request to run another queue * if there are still pending actions after completing a queue in this request. */ protected function handle() { do_action( 'action_scheduler_run_queue', 'Async Request' ); // run a queue in the same way as WP Cron, but declare the Async Request context $sleep_seconds = $this->get_sleep_seconds(); if ( $sleep_seconds ) { sleep( $sleep_seconds ); } $this->maybe_dispatch(); } /** * If the async request runner is needed and allowed to run, dispatch a request. */ public function maybe_dispatch() { if ( ! $this->allow() ) { return; } $this->dispatch(); ActionScheduler_QueueRunner::instance()->unhook_dispatch_async_request(); } /** * Only allow async requests when needed. * * Also allow 3rd party code to disable running actions via async requests. */ protected function allow() { if ( ! has_action( 'action_scheduler_run_queue' ) || ActionScheduler::runner()->has_maximum_concurrent_batches() || ! $this->store->has_pending_actions_due() ) { $allow = false; } else { $allow = true; } return apply_filters( 'action_scheduler_allow_async_request_runner', $allow ); } /** * Chaining async requests can crash MySQL. A brief sleep call in PHP prevents that. */ protected function get_sleep_seconds() { return apply_filters( 'action_scheduler_async_request_sleep_seconds', 5, $this ); } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_Compatibility.php000064400000007144147600245720023741 0ustar00 $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) { if ( false !== @ini_set( 'memory_limit', $filtered_limit ) ) { return $filtered_limit; } else { return false; } } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) { if ( false !== @ini_set( 'memory_limit', $wp_max_limit ) ) { return $wp_max_limit; } else { return false; } } return false; } /** * Attempts to raise the PHP timeout for time intensive processes. * * Only allows raising the existing limit and prevents lowering it. Wrapper for wc_set_time_limit(), when available. * * @param int $limit The time limit in seconds. */ public static function raise_time_limit( $limit = 0 ) { $limit = (int) $limit; $max_execution_time = (int) ini_get( 'max_execution_time' ); // If the max execution time is already set to zero (unlimited), there is no reason to make a further change. if ( 0 === $max_execution_time ) { return; } // Whichever of $max_execution_time or $limit is higher is the amount by which we raise the time limit. $raise_by = 0 === $limit || $limit > $max_execution_time ? $limit : $max_execution_time; if ( function_exists( 'wc_set_time_limit' ) ) { wc_set_time_limit( $raise_by ); } elseif ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved @set_time_limit( $raise_by ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_DataController.php000064400000012245147600245720024043 0ustar00=' ); return $php_support && apply_filters( 'action_scheduler_migration_dependencies_met', true ); } /** * Get a flag indicating whether the migration is complete. * * @return bool Whether the flag has been set marking the migration as complete */ public static function is_migration_complete() { return get_option( self::STATUS_FLAG ) === self::STATUS_COMPLETE; } /** * Mark the migration as complete. */ public static function mark_migration_complete() { update_option( self::STATUS_FLAG, self::STATUS_COMPLETE ); } /** * Unmark migration when a plugin is de-activated. Will not work in case of silent activation, for example in an update. * We do this to mitigate the bug of lost actions which happens if there was an AS 2.x to AS 3.x migration in the past, but that plugin is now * deactivated and the site was running on AS 2.x again. */ public static function mark_migration_incomplete() { delete_option( self::STATUS_FLAG ); } /** * Set the action store class name. * * @param string $class Classname of the store class. * * @return string */ public static function set_store_class( $class ) { return self::DATASTORE_CLASS; } /** * Set the action logger class name. * * @param string $class Classname of the logger class. * * @return string */ public static function set_logger_class( $class ) { return self::LOGGER_CLASS; } /** * Set the sleep time in seconds. * * @param integer $sleep_time The number of seconds to pause before resuming operation. */ public static function set_sleep_time( $sleep_time ) { self::$sleep_time = (int) $sleep_time; } /** * Set the tick count required for freeing memory. * * @param integer $free_ticks The number of ticks to free memory on. */ public static function set_free_ticks( $free_ticks ) { self::$free_ticks = (int) $free_ticks; } /** * Free memory if conditions are met. * * @param int $ticks Current tick count. */ public static function maybe_free_memory( $ticks ) { if ( self::$free_ticks && 0 === $ticks % self::$free_ticks ) { self::free_memory(); } } /** * Reduce memory footprint by clearing the database query and object caches. */ public static function free_memory() { if ( 0 < self::$sleep_time ) { /* translators: %d: amount of time */ \WP_CLI::warning( sprintf( _n( 'Stopped the insanity for %d second', 'Stopped the insanity for %d seconds', self::$sleep_time, 'action-scheduler' ), self::$sleep_time ) ); sleep( self::$sleep_time ); } \WP_CLI::warning( __( 'Attempting to reduce used memory...', 'action-scheduler' ) ); /** * @var $wpdb \wpdb * @var $wp_object_cache \WP_Object_Cache */ global $wpdb, $wp_object_cache; $wpdb->queries = array(); if ( ! is_a( $wp_object_cache, 'WP_Object_Cache' ) ) { return; } $wp_object_cache->group_ops = array(); $wp_object_cache->stats = array(); $wp_object_cache->memcache_debug = array(); $wp_object_cache->cache = array(); if ( is_callable( array( $wp_object_cache, '__remoteset' ) ) ) { call_user_func( array( $wp_object_cache, '__remoteset' ) ); // important } } /** * Connect to table datastores if migration is complete. * Otherwise, proceed with the migration if the dependencies have been met. */ public static function init() { if ( self::is_migration_complete() ) { add_filter( 'action_scheduler_store_class', array( 'ActionScheduler_DataController', 'set_store_class' ), 100 ); add_filter( 'action_scheduler_logger_class', array( 'ActionScheduler_DataController', 'set_logger_class' ), 100 ); add_action( 'deactivate_plugin', array( 'ActionScheduler_DataController', 'mark_migration_incomplete' ) ); } elseif ( self::dependencies_met() ) { Controller::init(); } add_action( 'action_scheduler/progress_tick', array( 'ActionScheduler_DataController', 'maybe_free_memory' ) ); } /** * Singleton factory. */ public static function instance() { if ( ! isset( self::$instance ) ) { self::$instance = new static(); } return self::$instance; } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_DateTime.php000064400000003206147600245720022617 0ustar00format( 'U' ); } /** * Set the UTC offset. * * This represents a fixed offset instead of a timezone setting. * * @param $offset */ public function setUtcOffset( $offset ) { $this->utcOffset = intval( $offset ); } /** * Returns the timezone offset. * * @return int * @link http://php.net/manual/en/datetime.getoffset.php */ #[\ReturnTypeWillChange] public function getOffset() { return $this->utcOffset ? $this->utcOffset : parent::getOffset(); } /** * Set the TimeZone associated with the DateTime * * @param DateTimeZone $timezone * * @return static * @link http://php.net/manual/en/datetime.settimezone.php */ #[\ReturnTypeWillChange] public function setTimezone( $timezone ) { $this->utcOffset = 0; parent::setTimezone( $timezone ); return $this; } /** * Get the timestamp with the WordPress timezone offset added or subtracted. * * @since 3.0.0 * @return int */ public function getOffsetTimestamp() { return $this->getTimestamp() + $this->getOffset(); } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_Exception.php000064400000000317147600245720023061 0ustar00store = $store; } public function attach( ActionScheduler_ActionClaim $claim ) { $this->claim = $claim; add_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) ); add_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0, 1 ); add_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0, 0 ); add_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0, 0 ); add_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0, 0 ); } public function detach() { $this->claim = NULL; $this->untrack_action(); remove_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) ); remove_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0 ); remove_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0 ); remove_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0 ); remove_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0 ); } public function track_current_action( $action_id ) { $this->action_id = $action_id; } public function untrack_action() { $this->action_id = 0; } public function handle_unexpected_shutdown() { if ( $error = error_get_last() ) { if ( in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ) ) ) { if ( !empty($this->action_id) ) { $this->store->mark_failure( $this->action_id ); do_action( 'action_scheduler_unexpected_shutdown', $this->action_id, $error ); } } $this->store->release_claim( $this->claim ); } } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_InvalidActionException.php000064400000002375147600245720025534 0ustar00 label). * * @var array */ protected $columns = array(); /** * Actions (name => label). * * @var array */ protected $row_actions = array(); /** * The active data stores * * @var ActionScheduler_Store */ protected $store; /** * A logger to use for getting action logs to display * * @var ActionScheduler_Logger */ protected $logger; /** * A ActionScheduler_QueueRunner runner instance (or child class) * * @var ActionScheduler_QueueRunner */ protected $runner; /** * Bulk actions. The key of the array is the method name of the implementation: * * bulk_(array $ids, string $sql_in). * * See the comments in the parent class for further details * * @var array */ protected $bulk_actions = array(); /** * Flag variable to render our notifications, if any, once. * * @var bool */ protected static $did_notification = false; /** * Array of seconds for common time periods, like week or month, alongside an internationalised string representation, i.e. "Day" or "Days" * * @var array */ private static $time_periods; /** * Sets the current data store object into `store->action` and initialises the object. * * @param ActionScheduler_Store $store * @param ActionScheduler_Logger $logger * @param ActionScheduler_QueueRunner $runner */ public function __construct( ActionScheduler_Store $store, ActionScheduler_Logger $logger, ActionScheduler_QueueRunner $runner ) { $this->store = $store; $this->logger = $logger; $this->runner = $runner; $this->table_header = __( 'Scheduled Actions', 'action-scheduler' ); $this->bulk_actions = array( 'delete' => __( 'Delete', 'action-scheduler' ), ); $this->columns = array( 'hook' => __( 'Hook', 'action-scheduler' ), 'status' => __( 'Status', 'action-scheduler' ), 'args' => __( 'Arguments', 'action-scheduler' ), 'group' => __( 'Group', 'action-scheduler' ), 'recurrence' => __( 'Recurrence', 'action-scheduler' ), 'schedule' => __( 'Scheduled Date', 'action-scheduler' ), 'log_entries' => __( 'Log', 'action-scheduler' ), ); $this->sort_by = array( 'schedule', 'hook', 'group', ); $this->search_by = array( 'hook', 'args', 'claim_id', ); $request_status = $this->get_request_status(); if ( empty( $request_status ) ) { $this->sort_by[] = 'status'; } elseif ( in_array( $request_status, array( 'in-progress', 'failed' ) ) ) { $this->columns += array( 'claim_id' => __( 'Claim ID', 'action-scheduler' ) ); $this->sort_by[] = 'claim_id'; } $this->row_actions = array( 'hook' => array( 'run' => array( 'name' => __( 'Run', 'action-scheduler' ), 'desc' => __( 'Process the action now as if it were run as part of a queue', 'action-scheduler' ), ), 'cancel' => array( 'name' => __( 'Cancel', 'action-scheduler' ), 'desc' => __( 'Cancel the action now to avoid it being run in future', 'action-scheduler' ), 'class' => 'cancel trash', ), ), ); self::$time_periods = array( array( 'seconds' => YEAR_IN_SECONDS, /* translators: %s: amount of time */ 'names' => _n_noop( '%s year', '%s years', 'action-scheduler' ), ), array( 'seconds' => MONTH_IN_SECONDS, /* translators: %s: amount of time */ 'names' => _n_noop( '%s month', '%s months', 'action-scheduler' ), ), array( 'seconds' => WEEK_IN_SECONDS, /* translators: %s: amount of time */ 'names' => _n_noop( '%s week', '%s weeks', 'action-scheduler' ), ), array( 'seconds' => DAY_IN_SECONDS, /* translators: %s: amount of time */ 'names' => _n_noop( '%s day', '%s days', 'action-scheduler' ), ), array( 'seconds' => HOUR_IN_SECONDS, /* translators: %s: amount of time */ 'names' => _n_noop( '%s hour', '%s hours', 'action-scheduler' ), ), array( 'seconds' => MINUTE_IN_SECONDS, /* translators: %s: amount of time */ 'names' => _n_noop( '%s minute', '%s minutes', 'action-scheduler' ), ), array( 'seconds' => 1, /* translators: %s: amount of time */ 'names' => _n_noop( '%s second', '%s seconds', 'action-scheduler' ), ), ); parent::__construct( array( 'singular' => 'action-scheduler', 'plural' => 'action-scheduler', 'ajax' => false, ) ); add_screen_option( 'per_page', array( 'default' => $this->items_per_page, ) ); add_filter( 'set_screen_option_' . $this->get_per_page_option_name(), array( $this, 'set_items_per_page_option' ), 10, 3 ); set_screen_options(); } /** * Handles setting the items_per_page option for this screen. * * @param mixed $status Default false (to skip saving the current option). * @param string $option Screen option name. * @param int $value Screen option value. * @return int */ public function set_items_per_page_option( $status, $option, $value ) { return $value; } /** * Convert an interval of seconds into a two part human friendly string. * * The WordPress human_time_diff() function only calculates the time difference to one degree, meaning * even if an action is 1 day and 11 hours away, it will display "1 day". This function goes one step * further to display two degrees of accuracy. * * Inspired by the Crontrol::interval() function by Edward Dale: https://wordpress.org/plugins/wp-crontrol/ * * @param int $interval A interval in seconds. * @param int $periods_to_include Depth of time periods to include, e.g. for an interval of 70, and $periods_to_include of 2, both minutes and seconds would be included. With a value of 1, only minutes would be included. * @return string A human friendly string representation of the interval. */ private static function human_interval( $interval, $periods_to_include = 2 ) { if ( $interval <= 0 ) { return __( 'Now!', 'action-scheduler' ); } $output = ''; for ( $time_period_index = 0, $periods_included = 0, $seconds_remaining = $interval; $time_period_index < count( self::$time_periods ) && $seconds_remaining > 0 && $periods_included < $periods_to_include; $time_period_index++ ) { $periods_in_interval = floor( $seconds_remaining / self::$time_periods[ $time_period_index ]['seconds'] ); if ( $periods_in_interval > 0 ) { if ( ! empty( $output ) ) { $output .= ' '; } $output .= sprintf( translate_nooped_plural( self::$time_periods[ $time_period_index ]['names'], $periods_in_interval, 'action-scheduler' ), $periods_in_interval ); $seconds_remaining -= $periods_in_interval * self::$time_periods[ $time_period_index ]['seconds']; $periods_included++; } } return $output; } /** * Returns the recurrence of an action or 'Non-repeating'. The output is human readable. * * @param ActionScheduler_Action $action * * @return string */ protected function get_recurrence( $action ) { $schedule = $action->get_schedule(); if ( $schedule->is_recurring() && method_exists( $schedule, 'get_recurrence' ) ) { $recurrence = $schedule->get_recurrence(); if ( is_numeric( $recurrence ) ) { /* translators: %s: time interval */ return sprintf( __( 'Every %s', 'action-scheduler' ), self::human_interval( $recurrence ) ); } else { return $recurrence; } } return __( 'Non-repeating', 'action-scheduler' ); } /** * Serializes the argument of an action to render it in a human friendly format. * * @param array $row The array representation of the current row of the table * * @return string */ public function column_args( array $row ) { if ( empty( $row['args'] ) ) { return apply_filters( 'action_scheduler_list_table_column_args', '', $row ); } $row_html = '
      '; foreach ( $row['args'] as $key => $value ) { $row_html .= sprintf( '
    • %s => %s
    • ', esc_html( var_export( $key, true ) ), esc_html( var_export( $value, true ) ) ); } $row_html .= '
    '; return apply_filters( 'action_scheduler_list_table_column_args', $row_html, $row ); } /** * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal. * * @param array $row Action array. * @return string */ public function column_log_entries( array $row ) { $log_entries_html = '
      '; $timezone = new DateTimezone( 'UTC' ); foreach ( $row['log_entries'] as $log_entry ) { $log_entries_html .= $this->get_log_entry_html( $log_entry, $timezone ); } $log_entries_html .= '
    '; return $log_entries_html; } /** * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal. * * @param ActionScheduler_LogEntry $log_entry * @param DateTimezone $timezone * @return string */ protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) { $date = $log_entry->get_date(); $date->setTimezone( $timezone ); return sprintf( '
  • %s
    %s
  • ', esc_html( $date->format( 'Y-m-d H:i:s O' ) ), esc_html( $log_entry->get_message() ) ); } /** * Only display row actions for pending actions. * * @param array $row Row to render * @param string $column_name Current row * * @return string */ protected function maybe_render_actions( $row, $column_name ) { if ( 'pending' === strtolower( $row[ 'status_name' ] ) ) { return parent::maybe_render_actions( $row, $column_name ); } return ''; } /** * Renders admin notifications * * Notifications: * 1. When the maximum number of tasks are being executed simultaneously. * 2. Notifications when a task is manually executed. * 3. Tables are missing. */ public function display_admin_notices() { global $wpdb; if ( ( is_a( $this->store, 'ActionScheduler_HybridStore' ) || is_a( $this->store, 'ActionScheduler_DBStore' ) ) && apply_filters( 'action_scheduler_enable_recreate_data_store', true ) ) { $table_list = array( 'actionscheduler_actions', 'actionscheduler_logs', 'actionscheduler_groups', 'actionscheduler_claims', ); $found_tables = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}actionscheduler%'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared foreach ( $table_list as $table_name ) { if ( ! in_array( $wpdb->prefix . $table_name, $found_tables ) ) { $this->admin_notices[] = array( 'class' => 'error', 'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).' , 'action-scheduler' ), ); $this->recreate_tables(); parent::display_admin_notices(); return; } } } if ( $this->runner->has_maximum_concurrent_batches() ) { $claim_count = $this->store->get_claim_count(); $this->admin_notices[] = array( 'class' => 'updated', 'message' => sprintf( /* translators: %s: amount of claims */ _n( 'Maximum simultaneous queues already in progress (%s queue). No additional queues will begin processing until the current queues are complete.', 'Maximum simultaneous queues already in progress (%s queues). No additional queues will begin processing until the current queues are complete.', $claim_count, 'action-scheduler' ), $claim_count ), ); } elseif ( $this->store->has_pending_actions_due() ) { $async_request_lock_expiration = ActionScheduler::lock()->get_expiration( 'async-request-runner' ); // No lock set or lock expired if ( false === $async_request_lock_expiration || $async_request_lock_expiration < time() ) { $in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) ); /* translators: %s: process URL */ $async_request_message = sprintf( __( 'A new queue has begun processing. View actions in-progress »', 'action-scheduler' ), esc_url( $in_progress_url ) ); } else { /* translators: %d: seconds */ $async_request_message = sprintf( __( 'The next queue will begin processing in approximately %d seconds.', 'action-scheduler' ), $async_request_lock_expiration - time() ); } $this->admin_notices[] = array( 'class' => 'notice notice-info', 'message' => $async_request_message, ); } $notification = get_transient( 'action_scheduler_admin_notice' ); if ( is_array( $notification ) ) { delete_transient( 'action_scheduler_admin_notice' ); $action = $this->store->fetch_action( $notification['action_id'] ); $action_hook_html = '' . $action->get_hook() . ''; if ( 1 == $notification['success'] ) { $class = 'updated'; switch ( $notification['row_action_type'] ) { case 'run' : /* translators: %s: action HTML */ $action_message_html = sprintf( __( 'Successfully executed action: %s', 'action-scheduler' ), $action_hook_html ); break; case 'cancel' : /* translators: %s: action HTML */ $action_message_html = sprintf( __( 'Successfully canceled action: %s', 'action-scheduler' ), $action_hook_html ); break; default : /* translators: %s: action HTML */ $action_message_html = sprintf( __( 'Successfully processed change for action: %s', 'action-scheduler' ), $action_hook_html ); break; } } else { $class = 'error'; /* translators: 1: action HTML 2: action ID 3: error message */ $action_message_html = sprintf( __( 'Could not process change for action: "%1$s" (ID: %2$d). Error: %3$s', 'action-scheduler' ), $action_hook_html, esc_html( $notification['action_id'] ), esc_html( $notification['error_message'] ) ); } $action_message_html = apply_filters( 'action_scheduler_admin_notice_html', $action_message_html, $action, $notification ); $this->admin_notices[] = array( 'class' => $class, 'message' => $action_message_html, ); } parent::display_admin_notices(); } /** * Prints the scheduled date in a human friendly format. * * @param array $row The array representation of the current row of the table * * @return string */ public function column_schedule( $row ) { return $this->get_schedule_display_string( $row['schedule'] ); } /** * Get the scheduled date in a human friendly format. * * @param ActionScheduler_Schedule $schedule * @return string */ protected function get_schedule_display_string( ActionScheduler_Schedule $schedule ) { $schedule_display_string = ''; if ( is_a( $schedule, 'ActionScheduler_NullSchedule' ) ) { return __( 'async', 'action-scheduler' ); } if ( ! method_exists( $schedule, 'get_date' ) || ! $schedule->get_date() ) { return '0000-00-00 00:00:00'; } $next_timestamp = $schedule->get_date()->getTimestamp(); $schedule_display_string .= $schedule->get_date()->format( 'Y-m-d H:i:s O' ); $schedule_display_string .= '
    '; if ( gmdate( 'U' ) > $next_timestamp ) { /* translators: %s: date interval */ $schedule_display_string .= sprintf( __( ' (%s ago)', 'action-scheduler' ), self::human_interval( gmdate( 'U' ) - $next_timestamp ) ); } else { /* translators: %s: date interval */ $schedule_display_string .= sprintf( __( ' (%s)', 'action-scheduler' ), self::human_interval( $next_timestamp - gmdate( 'U' ) ) ); } return $schedule_display_string; } /** * Bulk delete * * Deletes actions based on their ID. This is the handler for the bulk delete. It assumes the data * properly validated by the callee and it will delete the actions without any extra validation. * * @param array $ids * @param string $ids_sql Inherited and unused */ protected function bulk_delete( array $ids, $ids_sql ) { foreach ( $ids as $id ) { try { $this->store->delete_action( $id ); } catch ( Exception $e ) { // A possible reason for an exception would include a scenario where the same action is deleted by a // concurrent request. error_log( sprintf( /* translators: 1: action ID 2: exception message. */ __( 'Action Scheduler was unable to delete action %1$d. Reason: %2$s', 'action-scheduler' ), $id, $e->getMessage() ) ); } } } /** * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their * parameters are valid. * * @param int $action_id */ protected function row_action_cancel( $action_id ) { $this->process_row_action( $action_id, 'cancel' ); } /** * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their * parameters are valid. * * @param int $action_id */ protected function row_action_run( $action_id ) { $this->process_row_action( $action_id, 'run' ); } /** * Force the data store schema updates. */ protected function recreate_tables() { if ( is_a( $this->store, 'ActionScheduler_HybridStore' ) ) { $store = $this->store; } else { $store = new ActionScheduler_HybridStore(); } add_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10, 2 ); $store_schema = new ActionScheduler_StoreSchema(); $logger_schema = new ActionScheduler_LoggerSchema(); $store_schema->register_tables( true ); $logger_schema->register_tables( true ); remove_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10 ); } /** * Implements the logic behind processing an action once an action link is clicked on the list table. * * @param int $action_id * @param string $row_action_type The type of action to perform on the action. */ protected function process_row_action( $action_id, $row_action_type ) { try { switch ( $row_action_type ) { case 'run' : $this->runner->process_action( $action_id, 'Admin List Table' ); break; case 'cancel' : $this->store->cancel_action( $action_id ); break; } $success = 1; $error_message = ''; } catch ( Exception $e ) { $success = 0; $error_message = $e->getMessage(); } set_transient( 'action_scheduler_admin_notice', compact( 'action_id', 'success', 'error_message', 'row_action_type' ), 30 ); } /** * {@inheritDoc} */ public function prepare_items() { $this->prepare_column_headers(); $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page ); $query = array( 'per_page' => $per_page, 'offset' => $this->get_items_offset(), 'status' => $this->get_request_status(), 'orderby' => $this->get_request_orderby(), 'order' => $this->get_request_order(), 'search' => $this->get_request_search_query(), ); /** * Change query arguments to query for past-due actions. * Past-due actions have the 'pending' status and are in the past. * This is needed because registering 'past-due' as a status is overkill. */ if ( 'past-due' === $this->get_request_status() ) { $query['status'] = ActionScheduler_Store::STATUS_PENDING; $query['date'] = as_get_datetime_object(); } $this->items = array(); $total_items = $this->store->query_actions( $query, 'count' ); $status_labels = $this->store->get_status_labels(); foreach ( $this->store->query_actions( $query ) as $action_id ) { try { $action = $this->store->fetch_action( $action_id ); } catch ( Exception $e ) { continue; } if ( is_a( $action, 'ActionScheduler_NullAction' ) ) { continue; } $this->items[ $action_id ] = array( 'ID' => $action_id, 'hook' => $action->get_hook(), 'status_name' => $this->store->get_status( $action_id ), 'status' => $status_labels[ $this->store->get_status( $action_id ) ], 'args' => $action->get_args(), 'group' => $action->get_group(), 'log_entries' => $this->logger->get_logs( $action_id ), 'claim_id' => $this->store->get_claim_id( $action_id ), 'recurrence' => $this->get_recurrence( $action ), 'schedule' => $action->get_schedule(), ); } $this->set_pagination_args( array( 'total_items' => $total_items, 'per_page' => $per_page, 'total_pages' => ceil( $total_items / $per_page ), ) ); } /** * Prints the available statuses so the user can click to filter. */ protected function display_filter_by_status() { $this->status_counts = $this->store->action_counts() + $this->store->extra_action_counts(); parent::display_filter_by_status(); } /** * Get the text to display in the search box on the list table. */ protected function get_search_box_button_text() { return __( 'Search hook, args and claim ID', 'action-scheduler' ); } /** * {@inheritDoc} */ protected function get_per_page_option_name() { return str_replace( '-', '_', $this->screen->id ) . '_per_page'; } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_LogEntry.php000064400000003344147600245720022671 0ustar00comment_type * to ActionScheduler_LogEntry::__construct(), goodness knows why, and the Follow-up Emails plugin * hard-codes loading its own version of ActionScheduler_wpCommentLogger with that out-dated method, * goodness knows why, so we need to guard against that here instead of using a DateTime type declaration * for the constructor's 3rd param of $date and causing a fatal error with older versions of FUE. */ if ( null !== $date && ! is_a( $date, 'DateTime' ) ) { _doing_it_wrong( __METHOD__, 'The third parameter must be a valid DateTime instance, or null.', '2.0.0' ); $date = null; } $this->action_id = $action_id; $this->message = $message; $this->date = $date ? $date : new Datetime; } /** * Returns the date when this log entry was created * * @return Datetime */ public function get_date() { return $this->date; } public function get_action_id() { return $this->action_id; } public function get_message() { return $this->message; } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_NullLogEntry.php000064400000000333147600245720023517 0ustar00maybe_dispatch_async_request() uses a lock to avoid * calling ActionScheduler_QueueRunner->has_maximum_concurrent_batches() every time the 'shutdown', * hook is triggered, because that method calls ActionScheduler_QueueRunner->store->get_claim_count() * to find the current number of claims in the database. * * @param string $lock_type A string to identify different lock types. * @bool True if lock value has changed, false if not or if set failed. */ public function set( $lock_type ) { global $wpdb; $lock_key = $this->get_key( $lock_type ); $existing_lock_value = $this->get_existing_lock( $lock_type ); $new_lock_value = $this->new_lock_value( $lock_type ); // The lock may not exist yet, or may have been deleted. if ( empty( $existing_lock_value ) ) { return (bool) $wpdb->insert( $wpdb->options, array( 'option_name' => $lock_key, 'option_value' => $new_lock_value, 'autoload' => 'no', ) ); } if ( $this->get_expiration_from( $existing_lock_value ) >= time() ) { return false; } // Otherwise, try to obtain the lock. return (bool) $wpdb->update( $wpdb->options, array( 'option_value' => $new_lock_value ), array( 'option_name' => $lock_key, 'option_value' => $existing_lock_value, ) ); } /** * If a lock is set, return the timestamp it was set to expiry. * * @param string $lock_type A string to identify different lock types. * @return bool|int False if no lock is set, otherwise the timestamp for when the lock is set to expire. */ public function get_expiration( $lock_type ) { return $this->get_expiration_from( $this->get_existing_lock( $lock_type ) ); } /** * Given the lock string, derives the lock expiration timestamp (or false if it cannot be determined). * * @param string $lock_value String containing a timestamp, or pipe-separated combination of unique value and timestamp. * * @return false|int */ private function get_expiration_from( $lock_value ) { $lock_string = explode( '|', $lock_value ); // Old style lock? if ( count( $lock_string ) === 1 && is_numeric( $lock_string[0] ) ) { return (int) $lock_string[0]; } // New style lock? if ( count( $lock_string ) === 2 && is_numeric( $lock_string[1] ) ) { return (int) $lock_string[1]; } return false; } /** * Get the key to use for storing the lock in the transient * * @param string $lock_type A string to identify different lock types. * @return string */ protected function get_key( $lock_type ) { return sprintf( 'action_scheduler_lock_%s', $lock_type ); } /** * Supplies the existing lock value, or an empty string if not set. * * @param string $lock_type A string to identify different lock types. * * @return string */ private function get_existing_lock( $lock_type ) { global $wpdb; // Now grab the existing lock value, if there is one. return (string) $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s", $this->get_key( $lock_type ) ) ); } /** * Supplies a lock value consisting of a unique value and the current timestamp, which are separated by a pipe * character. * * Example: (string) "649de012e6b262.09774912|1688068114" * * @param string $lock_type A string to identify different lock types. * * @return string */ private function new_lock_value( $lock_type ) { return uniqid( '', true ) . '|' . ( time() + $this->get_duration( $lock_type ) ); } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueCleaner.php000064400000017334147600245720023510 0ustar00store = $store ? $store : ActionScheduler_Store::instance(); $this->batch_size = $batch_size; } /** * Default queue cleaner process used by queue runner. * * @return array */ public function delete_old_actions() { /** * Filter the minimum scheduled date age for action deletion. * * @param int $retention_period Minimum scheduled age in seconds of the actions to be deleted. */ $lifespan = apply_filters( 'action_scheduler_retention_period', $this->month_in_seconds ); try { $cutoff = as_get_datetime_object( $lifespan . ' seconds ago' ); } catch ( Exception $e ) { _doing_it_wrong( __METHOD__, sprintf( /* Translators: %s is the exception message. */ esc_html__( 'It was not possible to determine a valid cut-off time: %s.', 'action-scheduler' ), esc_html( $e->getMessage() ) ), '3.5.5' ); return array(); } /** * Filter the statuses when cleaning the queue. * * @param string[] $default_statuses_to_purge Action statuses to clean. */ $statuses_to_purge = (array) apply_filters( 'action_scheduler_default_cleaner_statuses', $this->default_statuses_to_purge ); return $this->clean_actions( $statuses_to_purge, $cutoff, $this->get_batch_size() ); } /** * Delete selected actions limited by status and date. * * @param string[] $statuses_to_purge List of action statuses to purge. Defaults to canceled, complete. * @param DateTime $cutoff_date Date limit for selecting actions. Defaults to 31 days ago. * @param int|null $batch_size Maximum number of actions per status to delete. Defaults to 20. * @param string $context Calling process context. Defaults to `old`. * @return array Actions deleted. */ public function clean_actions( array $statuses_to_purge, DateTime $cutoff_date, $batch_size = null, $context = 'old' ) { $batch_size = $batch_size !== null ? $batch_size : $this->batch_size; $cutoff = $cutoff_date !== null ? $cutoff_date : as_get_datetime_object( $this->month_in_seconds . ' seconds ago' ); $lifespan = time() - $cutoff->getTimestamp(); if ( empty( $statuses_to_purge ) ) { $statuses_to_purge = $this->default_statuses_to_purge; } $deleted_actions = []; foreach ( $statuses_to_purge as $status ) { $actions_to_delete = $this->store->query_actions( array( 'status' => $status, 'modified' => $cutoff, 'modified_compare' => '<=', 'per_page' => $batch_size, 'orderby' => 'none', ) ); $deleted_actions = array_merge( $deleted_actions, $this->delete_actions( $actions_to_delete, $lifespan, $context ) ); } return $deleted_actions; } /** * @param int[] $actions_to_delete List of action IDs to delete. * @param int $lifespan Minimum scheduled age in seconds of the actions being deleted. * @param string $context Context of the delete request. * @return array Deleted action IDs. */ private function delete_actions( array $actions_to_delete, $lifespan = null, $context = 'old' ) { $deleted_actions = []; if ( $lifespan === null ) { $lifespan = $this->month_in_seconds; } foreach ( $actions_to_delete as $action_id ) { try { $this->store->delete_action( $action_id ); $deleted_actions[] = $action_id; } catch ( Exception $e ) { /** * Notify 3rd party code of exceptions when deleting a completed action older than the retention period * * This hook provides a way for 3rd party code to log or otherwise handle exceptions relating to their * actions. * * @param int $action_id The scheduled actions ID in the data store * @param Exception $e The exception thrown when attempting to delete the action from the data store * @param int $lifespan The retention period, in seconds, for old actions * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch * @since 2.0.0 * */ do_action( "action_scheduler_failed_{$context}_action_deletion", $action_id, $e, $lifespan, count( $actions_to_delete ) ); } } return $deleted_actions; } /** * Unclaim pending actions that have not been run within a given time limit. * * When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed * as a parameter is 10x the time limit used for queue processing. * * @param int $time_limit The number of seconds to allow a queue to run before unclaiming its pending actions. Default 300 (5 minutes). */ public function reset_timeouts( $time_limit = 300 ) { $timeout = apply_filters( 'action_scheduler_timeout_period', $time_limit ); if ( $timeout < 0 ) { return; } $cutoff = as_get_datetime_object($timeout.' seconds ago'); $actions_to_reset = $this->store->query_actions( array( 'status' => ActionScheduler_Store::STATUS_PENDING, 'modified' => $cutoff, 'modified_compare' => '<=', 'claimed' => true, 'per_page' => $this->get_batch_size(), 'orderby' => 'none', ) ); foreach ( $actions_to_reset as $action_id ) { $this->store->unclaim_action( $action_id ); do_action( 'action_scheduler_reset_action', $action_id ); } } /** * Mark actions that have been running for more than a given time limit as failed, based on * the assumption some uncatachable and unloggable fatal error occurred during processing. * * When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed * as a parameter is 10x the time limit used for queue processing. * * @param int $time_limit The number of seconds to allow an action to run before it is considered to have failed. Default 300 (5 minutes). */ public function mark_failures( $time_limit = 300 ) { $timeout = apply_filters( 'action_scheduler_failure_period', $time_limit ); if ( $timeout < 0 ) { return; } $cutoff = as_get_datetime_object($timeout.' seconds ago'); $actions_to_reset = $this->store->query_actions( array( 'status' => ActionScheduler_Store::STATUS_RUNNING, 'modified' => $cutoff, 'modified_compare' => '<=', 'per_page' => $this->get_batch_size(), 'orderby' => 'none', ) ); foreach ( $actions_to_reset as $action_id ) { $this->store->mark_failure( $action_id ); do_action( 'action_scheduler_failed_action', $action_id, $timeout ); } } /** * Do all of the cleaning actions. * * @param int $time_limit The number of seconds to use as the timeout and failure period. Default 300 (5 minutes). * @author Jeremy Pry */ public function clean( $time_limit = 300 ) { $this->delete_old_actions(); $this->reset_timeouts( $time_limit ); $this->mark_failures( $time_limit ); } /** * Get the batch size for cleaning the queue. * * @author Jeremy Pry * @return int */ protected function get_batch_size() { /** * Filter the batch size when cleaning the queue. * * @param int $batch_size The number of actions to clean in one batch. */ return absint( apply_filters( 'action_scheduler_cleanup_batch_size', $this->batch_size ) ); } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueRunner.php000064400000021501147600245720023377 0ustar00store ); } $this->async_request = $async_request; } /** * @codeCoverageIgnore */ public function init() { add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) ); // Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param $next_timestamp = wp_next_scheduled( self::WP_CRON_HOOK ); if ( $next_timestamp ) { wp_unschedule_event( $next_timestamp, self::WP_CRON_HOOK ); } $cron_context = array( 'WP Cron' ); if ( ! wp_next_scheduled( self::WP_CRON_HOOK, $cron_context ) ) { $schedule = apply_filters( 'action_scheduler_run_schedule', self::WP_CRON_SCHEDULE ); wp_schedule_event( time(), $schedule, self::WP_CRON_HOOK, $cron_context ); } add_action( self::WP_CRON_HOOK, array( self::instance(), 'run' ) ); $this->hook_dispatch_async_request(); } /** * Hook check for dispatching an async request. */ public function hook_dispatch_async_request() { add_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) ); } /** * Unhook check for dispatching an async request. */ public function unhook_dispatch_async_request() { remove_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) ); } /** * Check if we should dispatch an async request to process actions. * * This method is attached to 'shutdown', so is called frequently. To avoid slowing down * the site, it mitigates the work performed in each request by: * 1. checking if it's in the admin context and then * 2. haven't run on the 'shutdown' hook within the lock time (60 seconds by default) * 3. haven't exceeded the number of allowed batches. * * The order of these checks is important, because they run from a check on a value: * 1. in memory - is_admin() maps to $GLOBALS or the WP_ADMIN constant * 2. in memory - transients use autoloaded options by default * 3. from a database query - has_maximum_concurrent_batches() run the query * $this->store->get_claim_count() to find the current number of claims in the DB. * * If all of these conditions are met, then we request an async runner check whether it * should dispatch a request to process pending actions. */ public function maybe_dispatch_async_request() { // Only start an async queue at most once every 60 seconds. if ( is_admin() && ! ActionScheduler::lock()->is_locked( 'async-request-runner' ) && ActionScheduler::lock()->set( 'async-request-runner' ) ) { $this->async_request->maybe_dispatch(); } } /** * Process actions in the queue. Attached to self::WP_CRON_HOOK i.e. 'action_scheduler_run_queue' * * The $context param of this method defaults to 'WP Cron', because prior to Action Scheduler 3.0.0 * that was the only context in which this method was run, and the self::WP_CRON_HOOK hook had no context * passed along with it. New code calling this method directly, or by triggering the self::WP_CRON_HOOK, * should set a context as the first parameter. For an example of this, refer to the code seen in * @see ActionScheduler_AsyncRequest_QueueRunner::handle() * * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' * Generally, this should be capitalised and not localised as it's a proper noun. * @return int The number of actions processed. */ public function run( $context = 'WP Cron' ) { ActionScheduler_Compatibility::raise_memory_limit(); ActionScheduler_Compatibility::raise_time_limit( $this->get_time_limit() ); do_action( 'action_scheduler_before_process_queue' ); $this->run_cleanup(); $this->processed_actions_count = 0; if ( false === $this->has_maximum_concurrent_batches() ) { $batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 ); do { $processed_actions_in_batch = $this->do_batch( $batch_size, $context ); $this->processed_actions_count += $processed_actions_in_batch; } while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $this->processed_actions_count ) ); // keep going until we run out of actions, time, or memory } do_action( 'action_scheduler_after_process_queue' ); return $this->processed_actions_count; } /** * Process a batch of actions pending in the queue. * * Actions are processed by claiming a set of pending actions then processing each one until either the batch * size is completed, or memory or time limits are reached, defined by @see $this->batch_limits_exceeded(). * * @param int $size The maximum number of actions to process in the batch. * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' * Generally, this should be capitalised and not localised as it's a proper noun. * @return int The number of actions processed. */ protected function do_batch( $size = 100, $context = '' ) { $claim = $this->store->stake_claim($size); $this->monitor->attach($claim); $processed_actions = 0; foreach ( $claim->get_actions() as $action_id ) { // bail if we lost the claim if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $claim->get_id() ) ) ) { break; } $this->process_action( $action_id, $context ); $processed_actions++; if ( $this->batch_limits_exceeded( $processed_actions + $this->processed_actions_count ) ) { break; } } $this->store->release_claim($claim); $this->monitor->detach(); $this->clear_caches(); return $processed_actions; } /** * Flush the cache if possible (intended for use after a batch of actions has been processed). * * This is useful because running large batches can eat up memory and because invalid data can accrue in the * runtime cache, which may lead to unexpected results. */ protected function clear_caches() { /* * Calling wp_cache_flush_runtime() lets us clear the runtime cache without invalidating the external object * cache, so we will always prefer this method (as compared to calling wp_cache_flush()) when it is available. * * However, this function was only introduced in WordPress 6.0. Additionally, the preferred way of detecting if * it is supported changed in WordPress 6.1 so we use two different methods to decide if we should utilize it. */ $flushing_runtime_cache_explicitly_supported = function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_runtime' ); $flushing_runtime_cache_implicitly_supported = ! function_exists( 'wp_cache_supports' ) && function_exists( 'wp_cache_flush_runtime' ); if ( $flushing_runtime_cache_explicitly_supported || $flushing_runtime_cache_implicitly_supported ) { wp_cache_flush_runtime(); } elseif ( ! wp_using_ext_object_cache() /** * When an external object cache is in use, and when wp_cache_flush_runtime() is not available, then * normally the cache will not be flushed after processing a batch of actions (to avoid a performance * penalty for other processes). * * This filter makes it possible to override this behavior and always flush the cache, even if an external * object cache is in use. * * @since 1.0 * * @param bool $flush_cache If the cache should be flushed. */ || apply_filters( 'action_scheduler_queue_runner_flush_cache', false ) ) { wp_cache_flush(); } } public function add_wp_cron_schedule( $schedules ) { $schedules['every_minute'] = array( 'interval' => 60, // in seconds 'display' => __( 'Every minute', 'action-scheduler' ), ); return $schedules; } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_Versions.php000064400000002357147600245720022741 0ustar00versions[$version_string]) ) { return FALSE; } $this->versions[$version_string] = $initialization_callback; return TRUE; } public function get_versions() { return $this->versions; } public function latest_version() { $keys = array_keys($this->versions); if ( empty($keys) ) { return false; } uasort( $keys, 'version_compare' ); return end($keys); } public function latest_version_callback() { $latest = $this->latest_version(); if ( empty($latest) || !isset($this->versions[$latest]) ) { return '__return_null'; } return $this->versions[$latest]; } /** * @return ActionScheduler_Versions * @codeCoverageIgnore */ public static function instance() { if ( empty(self::$instance) ) { self::$instance = new self(); } return self::$instance; } /** * @codeCoverageIgnore */ public static function initialize_latest_version() { $self = self::instance(); call_user_func($self->latest_version_callback()); } } vendor/woocommerce/action-scheduler/classes/ActionScheduler_wcSystemStatus.php000064400000012270147600245720024146 0ustar00store = $store; } /** * Display action data, including number of actions grouped by status and the oldest & newest action in each status. * * Helpful to identify issues, like a clogged queue. */ public function render() { $action_counts = $this->store->action_counts(); $status_labels = $this->store->get_status_labels(); $oldest_and_newest = $this->get_oldest_and_newest( array_keys( $status_labels ) ); $this->get_template( $status_labels, $action_counts, $oldest_and_newest ); } /** * Get oldest and newest scheduled dates for a given set of statuses. * * @param array $status_keys Set of statuses to find oldest & newest action for. * @return array */ protected function get_oldest_and_newest( $status_keys ) { $oldest_and_newest = array(); foreach ( $status_keys as $status ) { $oldest_and_newest[ $status ] = array( 'oldest' => '–', 'newest' => '–', ); if ( 'in-progress' === $status ) { continue; } $oldest_and_newest[ $status ]['oldest'] = $this->get_action_status_date( $status, 'oldest' ); $oldest_and_newest[ $status ]['newest'] = $this->get_action_status_date( $status, 'newest' ); } return $oldest_and_newest; } /** * Get oldest or newest scheduled date for a given status. * * @param string $status Action status label/name string. * @param string $date_type Oldest or Newest. * @return DateTime */ protected function get_action_status_date( $status, $date_type = 'oldest' ) { $order = 'oldest' === $date_type ? 'ASC' : 'DESC'; $action = $this->store->query_actions( array( 'claimed' => false, 'status' => $status, 'per_page' => 1, 'order' => $order, ) ); if ( ! empty( $action ) ) { $date_object = $this->store->get_date( $action[0] ); $action_date = $date_object->format( 'Y-m-d H:i:s O' ); } else { $action_date = '–'; } return $action_date; } /** * Get oldest or newest scheduled date for a given status. * * @param array $status_labels Set of statuses to find oldest & newest action for. * @param array $action_counts Number of actions grouped by status. * @param array $oldest_and_newest Date of the oldest and newest action with each status. */ protected function get_template( $status_labels, $action_counts, $oldest_and_newest ) { $as_version = ActionScheduler_Versions::instance()->latest_version(); $as_datastore = get_class( ActionScheduler_Store::instance() ); ?> $count ) { // WC uses the 3rd column for export, so we need to display more data in that (hidden when viewed as part of the table) and add an empty 2nd column. printf( '', esc_html( $status_labels[ $status ] ), esc_html( number_format_i18n( $count ) ), esc_html( $oldest_and_newest[ $status ]['oldest'] ), esc_html( $oldest_and_newest[ $status ]['newest'] ) ); } ?>

     
    %1$s %2$s, Oldest: %3$s, Newest: %4$s%3$s%4$s
    Status administration screen add_action( 'load-tools_page_action-scheduler', array( __CLASS__, 'register_admin_notice' ) ); add_action( 'load-woocommerce_page_wc-status', array( __CLASS__, 'register_admin_notice' ) ); } /** * Determines if there are log entries in the wp comments table. * * Uses the flag set on migration completion set by @see self::maybe_schedule_cleanup(). * * @return boolean Whether there are scheduled action comments in the comments table. */ public static function has_logs() { return 'yes' === get_option( self::$has_logs_option_key ); } /** * Schedules the WP Post comment table cleanup to run in 6 months if it's not already scheduled. * Attached to the migration complete hook 'action_scheduler/migration_complete'. */ public static function maybe_schedule_cleanup() { if ( (bool) get_comments( array( 'type' => ActionScheduler_wpCommentLogger::TYPE, 'number' => 1, 'fields' => 'ids' ) ) ) { update_option( self::$has_logs_option_key, 'yes' ); if ( ! as_next_scheduled_action( self::$cleanup_hook ) ) { as_schedule_single_action( gmdate( 'U' ) + ( 6 * MONTH_IN_SECONDS ), self::$cleanup_hook ); } } } /** * Delete all action comments from the WP Comments table. */ public static function delete_all_action_comments() { global $wpdb; $wpdb->delete( $wpdb->comments, array( 'comment_type' => ActionScheduler_wpCommentLogger::TYPE, 'comment_agent' => ActionScheduler_wpCommentLogger::AGENT ) ); delete_option( self::$has_logs_option_key ); } /** * Registers admin notices about the orphaned action logs. */ public static function register_admin_notice() { add_action( 'admin_notices', array( __CLASS__, 'print_admin_notice' ) ); } /** * Prints details about the orphaned action logs and includes information on where to learn more. */ public static function print_admin_notice() { $next_cleanup_message = ''; $next_scheduled_cleanup_hook = as_next_scheduled_action( self::$cleanup_hook ); if ( $next_scheduled_cleanup_hook ) { /* translators: %s: date interval */ $next_cleanup_message = sprintf( __( 'This data will be deleted in %s.', 'action-scheduler' ), human_time_diff( gmdate( 'U' ), $next_scheduled_cleanup_hook ) ); } $notice = sprintf( /* translators: 1: next cleanup message 2: github issue URL */ __( 'Action Scheduler has migrated data to custom tables; however, orphaned log entries exist in the WordPress Comments table. %1$s Learn more »', 'action-scheduler' ), $next_cleanup_message, 'https://github.com/woocommerce/action-scheduler/issues/368' ); echo '

    ' . wp_kses_post( $notice ) . '

    '; } } vendor/woocommerce/action-scheduler/deprecated/ActionScheduler_Abstract_QueueRunner_Deprecated.php000064400000001523147600245720030007 0ustar00get_date(); $replacement_method = 'get_date()'; } else { $return_value = $this->get_next( $after ); $replacement_method = 'get_next( $after )'; } _deprecated_function( __METHOD__, '3.0.0', __CLASS__ . '::' . $replacement_method ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped return $return_value; } } vendor/woocommerce/action-scheduler/deprecated/ActionScheduler_Store_Deprecated.php000064400000002040147600245720024775 0ustar00mark_failure( $action_id ); } /** * Add base hooks * * @since 2.2.6 */ protected static function hook() { _deprecated_function( __METHOD__, '3.0.0' ); } /** * Remove base hooks * * @since 2.2.6 */ protected static function unhook() { _deprecated_function( __METHOD__, '3.0.0' ); } /** * Get the site's local time. * * @deprecated 2.1.0 * @return DateTimeZone */ protected function get_local_timezone() { _deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' ); return ActionScheduler_TimezoneHelper::get_local_timezone(); } } vendor/woocommerce/action-scheduler/deprecated/functions.php000064400000011766147600245720020454 0ustar00 '' - the name of the action that will be triggered * 'args' => NULL - the args array that will be passed with the action * 'date' => NULL - the scheduled date of the action. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone. * 'date_compare' => '<=' - operator for testing "date". accepted values are '!=', '>', '>=', '<', '<=', '=' * 'modified' => NULL - the date the action was last updated. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone. * 'modified_compare' => '<=' - operator for testing "modified". accepted values are '!=', '>', '>=', '<', '<=', '=' * 'group' => '' - the group the action belongs to * 'status' => '' - ActionScheduler_Store::STATUS_COMPLETE or ActionScheduler_Store::STATUS_PENDING * 'claimed' => NULL - TRUE to find claimed actions, FALSE to find unclaimed actions, a string to find a specific claim ID * 'per_page' => 5 - Number of results to return * 'offset' => 0 * 'orderby' => 'date' - accepted values are 'hook', 'group', 'modified', or 'date' * 'order' => 'ASC' * @param string $return_format OBJECT, ARRAY_A, or ids * * @deprecated 2.1.0 * * @return array */ function wc_get_scheduled_actions( $args = array(), $return_format = OBJECT ) { _deprecated_function( __FUNCTION__, '2.1.0', 'as_get_scheduled_actions()' ); return as_get_scheduled_actions( $args, $return_format ); } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression.php000064400000026536147600245720023232 0ustar00 * @link http://en.wikipedia.org/wiki/Cron */ class CronExpression { const MINUTE = 0; const HOUR = 1; const DAY = 2; const MONTH = 3; const WEEKDAY = 4; const YEAR = 5; /** * @var array CRON expression parts */ private $cronParts; /** * @var CronExpression_FieldFactory CRON field factory */ private $fieldFactory; /** * @var array Order in which to test of cron parts */ private static $order = array(self::YEAR, self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE); /** * Factory method to create a new CronExpression. * * @param string $expression The CRON expression to create. There are * several special predefined values which can be used to substitute the * CRON expression: * * @yearly, @annually) - Run once a year, midnight, Jan. 1 - 0 0 1 1 * * @monthly - Run once a month, midnight, first of month - 0 0 1 * * * @weekly - Run once a week, midnight on Sun - 0 0 * * 0 * @daily - Run once a day, midnight - 0 0 * * * * @hourly - Run once an hour, first minute - 0 * * * * * *@param CronExpression_FieldFactory $fieldFactory (optional) Field factory to use * * @return CronExpression */ public static function factory($expression, CronExpression_FieldFactory $fieldFactory = null) { $mappings = array( '@yearly' => '0 0 1 1 *', '@annually' => '0 0 1 1 *', '@monthly' => '0 0 1 * *', '@weekly' => '0 0 * * 0', '@daily' => '0 0 * * *', '@hourly' => '0 * * * *' ); if (isset($mappings[$expression])) { $expression = $mappings[$expression]; } return new self($expression, $fieldFactory ? $fieldFactory : new CronExpression_FieldFactory()); } /** * Parse a CRON expression * * @param string $expression CRON expression (e.g. '8 * * * *') * @param CronExpression_FieldFactory $fieldFactory Factory to create cron fields */ public function __construct($expression, CronExpression_FieldFactory $fieldFactory) { $this->fieldFactory = $fieldFactory; $this->setExpression($expression); } /** * Set or change the CRON expression * * @param string $value CRON expression (e.g. 8 * * * *) * * @return CronExpression * @throws InvalidArgumentException if not a valid CRON expression */ public function setExpression($value) { $this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY); if (count($this->cronParts) < 5) { throw new InvalidArgumentException( $value . ' is not a valid CRON expression' ); } foreach ($this->cronParts as $position => $part) { $this->setPart($position, $part); } return $this; } /** * Set part of the CRON expression * * @param int $position The position of the CRON expression to set * @param string $value The value to set * * @return CronExpression * @throws InvalidArgumentException if the value is not valid for the part */ public function setPart($position, $value) { if (!$this->fieldFactory->getField($position)->validate($value)) { throw new InvalidArgumentException( 'Invalid CRON field value ' . $value . ' as position ' . $position ); } $this->cronParts[$position] = $value; return $this; } /** * Get a next run date relative to the current date or a specific date * * @param string|DateTime $currentTime (optional) Relative calculation date * @param int $nth (optional) Number of matches to skip before returning a * matching next run date. 0, the default, will return the current * date and time if the next run date falls on the current date and * time. Setting this value to 1 will skip the first match and go to * the second match. Setting this value to 2 will skip the first 2 * matches and so on. * @param bool $allowCurrentDate (optional) Set to TRUE to return the * current date if it matches the cron expression * * @return DateTime * @throws RuntimeException on too many iterations */ public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false) { return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate); } /** * Get a previous run date relative to the current date or a specific date * * @param string|DateTime $currentTime (optional) Relative calculation date * @param int $nth (optional) Number of matches to skip before returning * @param bool $allowCurrentDate (optional) Set to TRUE to return the * current date if it matches the cron expression * * @return DateTime * @throws RuntimeException on too many iterations * @see CronExpression::getNextRunDate */ public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false) { return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate); } /** * Get multiple run dates starting at the current date or a specific date * * @param int $total Set the total number of dates to calculate * @param string|DateTime $currentTime (optional) Relative calculation date * @param bool $invert (optional) Set to TRUE to retrieve previous dates * @param bool $allowCurrentDate (optional) Set to TRUE to return the * current date if it matches the cron expression * * @return array Returns an array of run dates */ public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false) { $matches = array(); for ($i = 0; $i < max(0, $total); $i++) { $matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate); } return $matches; } /** * Get all or part of the CRON expression * * @param string $part (optional) Specify the part to retrieve or NULL to * get the full cron schedule string. * * @return string|null Returns the CRON expression, a part of the * CRON expression, or NULL if the part was specified but not found */ public function getExpression($part = null) { if (null === $part) { return implode(' ', $this->cronParts); } elseif (array_key_exists($part, $this->cronParts)) { return $this->cronParts[$part]; } return null; } /** * Helper method to output the full expression. * * @return string Full CRON expression */ public function __toString() { return $this->getExpression(); } /** * Determine if the cron is due to run based on the current date or a * specific date. This method assumes that the current number of * seconds are irrelevant, and should be called once per minute. * * @param string|DateTime $currentTime (optional) Relative calculation date * * @return bool Returns TRUE if the cron is due to run or FALSE if not */ public function isDue($currentTime = 'now') { if ('now' === $currentTime) { $currentDate = date('Y-m-d H:i'); $currentTime = strtotime($currentDate); } elseif ($currentTime instanceof DateTime) { $currentDate = $currentTime->format('Y-m-d H:i'); $currentTime = strtotime($currentDate); } else { $currentTime = new DateTime($currentTime); $currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), 0); $currentDate = $currentTime->format('Y-m-d H:i'); $currentTime = (int)($currentTime->getTimestamp()); } return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime; } /** * Get the next or previous run date of the expression relative to a date * * @param string|DateTime $currentTime (optional) Relative calculation date * @param int $nth (optional) Number of matches to skip before returning * @param bool $invert (optional) Set to TRUE to go backwards in time * @param bool $allowCurrentDate (optional) Set to TRUE to return the * current date if it matches the cron expression * * @return DateTime * @throws RuntimeException on too many iterations */ protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false) { if ($currentTime instanceof DateTime) { $currentDate = $currentTime; } else { $currentDate = new DateTime($currentTime ? $currentTime : 'now'); $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get())); } $currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0); $nextRun = clone $currentDate; $nth = (int) $nth; // Set a hard limit to bail on an impossible date for ($i = 0; $i < 1000; $i++) { foreach (self::$order as $position) { $part = $this->getExpression($position); if (null === $part) { continue; } $satisfied = false; // Get the field object used to validate this part $field = $this->fieldFactory->getField($position); // Check if this is singular or a list if (strpos($part, ',') === false) { $satisfied = $field->isSatisfiedBy($nextRun, $part); } else { foreach (array_map('trim', explode(',', $part)) as $listPart) { if ($field->isSatisfiedBy($nextRun, $listPart)) { $satisfied = true; break; } } } // If the field is not satisfied, then start over if (!$satisfied) { $field->increment($nextRun, $invert); continue 2; } } // Skip this match if needed if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) { $this->fieldFactory->getField(0)->increment($nextRun, $invert); continue; } return $nextRun; } // @codeCoverageIgnoreStart throw new RuntimeException('Impossible CRON expression'); // @codeCoverageIgnoreEnd } } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression_AbstractField.php000064400000005020147600245720026002 0ustar00 */ abstract class CronExpression_AbstractField implements CronExpression_FieldInterface { /** * Check to see if a field is satisfied by a value * * @param string $dateValue Date value to check * @param string $value Value to test * * @return bool */ public function isSatisfied($dateValue, $value) { if ($this->isIncrementsOfRanges($value)) { return $this->isInIncrementsOfRanges($dateValue, $value); } elseif ($this->isRange($value)) { return $this->isInRange($dateValue, $value); } return $value == '*' || $dateValue == $value; } /** * Check if a value is a range * * @param string $value Value to test * * @return bool */ public function isRange($value) { return strpos($value, '-') !== false; } /** * Check if a value is an increments of ranges * * @param string $value Value to test * * @return bool */ public function isIncrementsOfRanges($value) { return strpos($value, '/') !== false; } /** * Test if a value is within a range * * @param string $dateValue Set date value * @param string $value Value to test * * @return bool */ public function isInRange($dateValue, $value) { $parts = array_map('trim', explode('-', $value, 2)); return $dateValue >= $parts[0] && $dateValue <= $parts[1]; } /** * Test if a value is within an increments of ranges (offset[-to]/step size) * * @param string $dateValue Set date value * @param string $value Value to test * * @return bool */ public function isInIncrementsOfRanges($dateValue, $value) { $parts = array_map('trim', explode('/', $value, 2)); $stepSize = isset($parts[1]) ? $parts[1] : 0; if ($parts[0] == '*' || $parts[0] === '0') { return (int) $dateValue % $stepSize == 0; } $range = explode('-', $parts[0], 2); $offset = $range[0]; $to = isset($range[1]) ? $range[1] : $dateValue; // Ensure that the date value is within the range if ($dateValue < $offset || $dateValue > $to) { return false; } for ($i = $offset; $i <= $to; $i+= $stepSize) { if ($i == $dateValue) { return true; } } return false; } } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression_DayOfMonthField.php000064400000007014147600245720026254 0ustar00 */ class CronExpression_DayOfMonthField extends CronExpression_AbstractField { /** * Get the nearest day of the week for a given day in a month * * @param int $currentYear Current year * @param int $currentMonth Current month * @param int $targetDay Target day of the month * * @return DateTime Returns the nearest date */ private static function getNearestWeekday($currentYear, $currentMonth, $targetDay) { $tday = str_pad($targetDay, 2, '0', STR_PAD_LEFT); $target = new DateTime("$currentYear-$currentMonth-$tday"); $currentWeekday = (int) $target->format('N'); if ($currentWeekday < 6) { return $target; } $lastDayOfMonth = $target->format('t'); foreach (array(-1, 1, -2, 2) as $i) { $adjusted = $targetDay + $i; if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) { $target->setDate($currentYear, $currentMonth, $adjusted); if ($target->format('N') < 6 && $target->format('m') == $currentMonth) { return $target; } } } } /** * {@inheritdoc} */ public function isSatisfiedBy(DateTime $date, $value) { // ? states that the field value is to be skipped if ($value == '?') { return true; } $fieldValue = $date->format('d'); // Check to see if this is the last day of the month if ($value == 'L') { return $fieldValue == $date->format('t'); } // Check to see if this is the nearest weekday to a particular value if (strpos($value, 'W')) { // Parse the target day $targetDay = substr($value, 0, strpos($value, 'W')); // Find out if the current day is the nearest day of the week return $date->format('j') == self::getNearestWeekday( $date->format('Y'), $date->format('m'), $targetDay )->format('j'); } return $this->isSatisfied($date->format('d'), $value); } /** * {@inheritdoc} */ public function increment(DateTime $date, $invert = false) { if ($invert) { $date->modify('previous day'); $date->setTime(23, 59); } else { $date->modify('next day'); $date->setTime(0, 0); } return $this; } /** * {@inheritdoc} */ public function validate($value) { return (bool) preg_match('/[\*,\/\-\?LW0-9A-Za-z]+/', $value); } } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression_DayOfWeekField.php000064400000007521147600245720026065 0ustar00 */ class CronExpression_DayOfWeekField extends CronExpression_AbstractField { /** * {@inheritdoc} */ public function isSatisfiedBy(DateTime $date, $value) { if ($value == '?') { return true; } // Convert text day of the week values to integers $value = str_ireplace( array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'), range(0, 6), $value ); $currentYear = $date->format('Y'); $currentMonth = $date->format('m'); $lastDayOfMonth = $date->format('t'); // Find out if this is the last specific weekday of the month if (strpos($value, 'L')) { $weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L'))); $tdate = clone $date; $tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth); while ($tdate->format('w') != $weekday) { $tdate->setDate($currentYear, $currentMonth, --$lastDayOfMonth); } return $date->format('j') == $lastDayOfMonth; } // Handle # hash tokens if (strpos($value, '#')) { list($weekday, $nth) = explode('#', $value); // Validate the hash fields if ($weekday < 1 || $weekday > 5) { throw new InvalidArgumentException("Weekday must be a value between 1 and 5. {$weekday} given"); } if ($nth > 5) { throw new InvalidArgumentException('There are never more than 5 of a given weekday in a month'); } // The current weekday must match the targeted weekday to proceed if ($date->format('N') != $weekday) { return false; } $tdate = clone $date; $tdate->setDate($currentYear, $currentMonth, 1); $dayCount = 0; $currentDay = 1; while ($currentDay < $lastDayOfMonth + 1) { if ($tdate->format('N') == $weekday) { if (++$dayCount >= $nth) { break; } } $tdate->setDate($currentYear, $currentMonth, ++$currentDay); } return $date->format('j') == $currentDay; } // Handle day of the week values if (strpos($value, '-')) { $parts = explode('-', $value); if ($parts[0] == '7') { $parts[0] = '0'; } elseif ($parts[1] == '0') { $parts[1] = '7'; } $value = implode('-', $parts); } // Test to see which Sunday to use -- 0 == 7 == Sunday $format = in_array(7, str_split($value)) ? 'N' : 'w'; $fieldValue = $date->format($format); return $this->isSatisfied($fieldValue, $value); } /** * {@inheritdoc} */ public function increment(DateTime $date, $invert = false) { if ($invert) { $date->modify('-1 day'); $date->setTime(23, 59, 0); } else { $date->modify('+1 day'); $date->setTime(0, 0, 0); } return $this; } /** * {@inheritdoc} */ public function validate($value) { return (bool) preg_match('/[\*,\/\-0-9A-Z]+/', $value); } } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression_FieldFactory.php000064400000003321147600245720025650 0ustar00 * @link http://en.wikipedia.org/wiki/Cron */ class CronExpression_FieldFactory { /** * @var array Cache of instantiated fields */ private $fields = array(); /** * Get an instance of a field object for a cron expression position * * @param int $position CRON expression position value to retrieve * * @return CronExpression_FieldInterface * @throws InvalidArgumentException if a position is not valid */ public function getField($position) { if (!isset($this->fields[$position])) { switch ($position) { case 0: $this->fields[$position] = new CronExpression_MinutesField(); break; case 1: $this->fields[$position] = new CronExpression_HoursField(); break; case 2: $this->fields[$position] = new CronExpression_DayOfMonthField(); break; case 3: $this->fields[$position] = new CronExpression_MonthField(); break; case 4: $this->fields[$position] = new CronExpression_DayOfWeekField(); break; case 5: $this->fields[$position] = new CronExpression_YearField(); break; default: throw new InvalidArgumentException( $position . ' is not a valid position' ); } } return $this->fields[$position]; } } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression_FieldInterface.php000064400000002162147600245720026143 0ustar00 */ interface CronExpression_FieldInterface { /** * Check if the respective value of a DateTime field satisfies a CRON exp * * @param DateTime $date DateTime object to check * @param string $value CRON expression to test against * * @return bool Returns TRUE if satisfied, FALSE otherwise */ public function isSatisfiedBy(DateTime $date, $value); /** * When a CRON expression is not satisfied, this method is used to increment * or decrement a DateTime object by the unit of the cron field * * @param DateTime $date DateTime object to change * @param bool $invert (optional) Set to TRUE to decrement * * @return CronExpression_FieldInterface */ public function increment(DateTime $date, $invert = false); /** * Validates a CRON expression for a given field * * @param string $value CRON expression value to validate * * @return bool Returns TRUE if valid, FALSE otherwise */ public function validate($value); } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression_HoursField.php000064400000002205147600245720025341 0ustar00 */ class CronExpression_HoursField extends CronExpression_AbstractField { /** * {@inheritdoc} */ public function isSatisfiedBy(DateTime $date, $value) { return $this->isSatisfied($date->format('H'), $value); } /** * {@inheritdoc} */ public function increment(DateTime $date, $invert = false) { // Change timezone to UTC temporarily. This will // allow us to go back or forwards and hour even // if DST will be changed between the hours. $timezone = $date->getTimezone(); $date->setTimezone(new DateTimeZone('UTC')); if ($invert) { $date->modify('-1 hour'); $date->setTime($date->format('H'), 59); } else { $date->modify('+1 hour'); $date->setTime($date->format('H'), 0); } $date->setTimezone($timezone); return $this; } /** * {@inheritdoc} */ public function validate($value) { return (bool) preg_match('/[\*,\/\-0-9]+/', $value); } } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression_MinutesField.php000064400000001371147600245720025670 0ustar00 */ class CronExpression_MinutesField extends CronExpression_AbstractField { /** * {@inheritdoc} */ public function isSatisfiedBy(DateTime $date, $value) { return $this->isSatisfied($date->format('i'), $value); } /** * {@inheritdoc} */ public function increment(DateTime $date, $invert = false) { if ($invert) { $date->modify('-1 minute'); } else { $date->modify('+1 minute'); } return $this; } /** * {@inheritdoc} */ public function validate($value) { return (bool) preg_match('/[\*,\/\-0-9]+/', $value); } } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression_MonthField.php000064400000002567147600245720025341 0ustar00 */ class CronExpression_MonthField extends CronExpression_AbstractField { /** * {@inheritdoc} */ public function isSatisfiedBy(DateTime $date, $value) { // Convert text month values to integers $value = str_ireplace( array( 'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC' ), range(1, 12), $value ); return $this->isSatisfied($date->format('m'), $value); } /** * {@inheritdoc} */ public function increment(DateTime $date, $invert = false) { if ($invert) { // $date->modify('last day of previous month'); // remove for php 5.2 compat $date->modify('previous month'); $date->modify($date->format('Y-m-t')); $date->setTime(23, 59); } else { //$date->modify('first day of next month'); // remove for php 5.2 compat $date->modify('next month'); $date->modify($date->format('Y-m-01')); $date->setTime(0, 0); } return $this; } /** * {@inheritdoc} */ public function validate($value) { return (bool) preg_match('/[\*,\/\-0-9A-Z]+/', $value); } } vendor/woocommerce/action-scheduler/lib/cron-expression/CronExpression_YearField.php000064400000001651147600245720025145 0ustar00 */ class CronExpression_YearField extends CronExpression_AbstractField { /** * {@inheritdoc} */ public function isSatisfiedBy(DateTime $date, $value) { return $this->isSatisfied($date->format('Y'), $value); } /** * {@inheritdoc} */ public function increment(DateTime $date, $invert = false) { if ($invert) { $date->modify('-1 year'); $date->setDate($date->format('Y'), 12, 31); $date->setTime(23, 59, 0); } else { $date->modify('+1 year'); $date->setDate($date->format('Y'), 1, 1); $date->setTime(0, 0, 0); } return $this; } /** * {@inheritdoc} */ public function validate($value) { return (bool) preg_match('/[\*,\/\-0-9]+/', $value); } } vendor/woocommerce/action-scheduler/lib/cron-expression/LICENSE000064400000002112147600245720020525 0ustar00Copyright (c) 2011 Michael Dowling and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vendor/woocommerce/action-scheduler/lib/WP_Async_Request.php000064400000007340147600245720020276 0ustar00identifier = $this->prefix . '_' . $this->action; add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) ); } /** * Set data used during the request * * @param array $data Data. * * @return $this */ public function data( $data ) { $this->data = $data; return $this; } /** * Dispatch the async request * * @return array|WP_Error */ public function dispatch() { $url = add_query_arg( $this->get_query_args(), $this->get_query_url() ); $args = $this->get_post_args(); return wp_remote_post( esc_url_raw( $url ), $args ); } /** * Get query args * * @return array */ protected function get_query_args() { if ( property_exists( $this, 'query_args' ) ) { return $this->query_args; } $args = array( 'action' => $this->identifier, 'nonce' => wp_create_nonce( $this->identifier ), ); /** * Filters the post arguments used during an async request. * * @param array $url */ return apply_filters( $this->identifier . '_query_args', $args ); } /** * Get query URL * * @return string */ protected function get_query_url() { if ( property_exists( $this, 'query_url' ) ) { return $this->query_url; } $url = admin_url( 'admin-ajax.php' ); /** * Filters the post arguments used during an async request. * * @param string $url */ return apply_filters( $this->identifier . '_query_url', $url ); } /** * Get post args * * @return array */ protected function get_post_args() { if ( property_exists( $this, 'post_args' ) ) { return $this->post_args; } $args = array( 'timeout' => 0.01, 'blocking' => false, 'body' => $this->data, 'cookies' => $_COOKIE, 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), ); /** * Filters the post arguments used during an async request. * * @param array $args */ return apply_filters( $this->identifier . '_post_args', $args ); } /** * Maybe handle * * Check for correct nonce and pass to handler. */ public function maybe_handle() { // Don't lock up other requests while processing session_write_close(); check_ajax_referer( $this->identifier, 'nonce' ); $this->handle(); wp_die(); } /** * Handle * * Override this method to perform any actions required * during the async request. */ abstract protected function handle(); } } vendor/woocommerce/action-scheduler/action-scheduler.php000064400000005717147600245720017574 0ustar00. * * @package ActionScheduler */ if ( ! function_exists( 'action_scheduler_register_3_dot_8_dot_0' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION. if ( ! class_exists( 'ActionScheduler_Versions', false ) ) { require_once __DIR__ . '/classes/ActionScheduler_Versions.php'; add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 ); } add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_8_dot_0', 0, 0 ); // WRCS: DEFINED_VERSION. // phpcs:disable Generic.Functions.OpeningFunctionBraceKernighanRitchie.ContentAfterBrace /** * Registers this version of Action Scheduler. */ function action_scheduler_register_3_dot_8_dot_0() { // WRCS: DEFINED_VERSION. $versions = ActionScheduler_Versions::instance(); $versions->register( '3.8.0', 'action_scheduler_initialize_3_dot_8_dot_0' ); // WRCS: DEFINED_VERSION. } // phpcs:disable Generic.Functions.OpeningFunctionBraceKernighanRitchie.ContentAfterBrace /** * Initializes this version of Action Scheduler. */ function action_scheduler_initialize_3_dot_8_dot_0() { // WRCS: DEFINED_VERSION. // A final safety check is required even here, because historic versions of Action Scheduler // followed a different pattern (in some unusual cases, we could reach this point and the // ActionScheduler class is already defined—so we need to guard against that). if ( ! class_exists( 'ActionScheduler', false ) ) { require_once __DIR__ . '/classes/abstracts/ActionScheduler.php'; ActionScheduler::init( __FILE__ ); } } // Support usage in themes - load this version if no plugin has loaded a version yet. if ( did_action( 'plugins_loaded' ) && ! doing_action( 'plugins_loaded' ) && ! class_exists( 'ActionScheduler', false ) ) { action_scheduler_initialize_3_dot_8_dot_0(); // WRCS: DEFINED_VERSION. do_action( 'action_scheduler_pre_theme_init' ); ActionScheduler_Versions::initialize_latest_version(); } } vendor/woocommerce/action-scheduler/changelog.txt000064400000016361147600245720016317 0ustar00*** Changelog *** = 3.8.0 - 2024-05-22 = * Bump "Tested up to" version to 6.5. * Fix typos in perf.md. * Merge Release/3.7.4 into trunk. * Tweak - WP 6.5 compatibility. * Update minimum requirements for WordPress (and PHP). Fix test matrix. = 3.7.4 - 2024-04-05 = * Give a clear description of how the $unique parameter works. * Preserve the tab field if set. * Tweak - WP 6.5 compatibility. = 3.7.3 - 2024-03-20 = * Do not iterate over all of GET when building form in list table. * Fix a few issues reported by PCP (Plugin Check Plugin). * Try to save actions as unique even when the store doesn't support it. * Tweak - WP 6.4 compatibility. * Update "Tested up to" tag to WordPress 6.5. * update version in package-lock.json. = 3.7.2 - 2024-02-14 = * No longer user variables in `_n()` translation function. = 3.7.1 - 2023-12-13 = * update semver to 5.7.2 because of a security vulnerability in 5.7.1. = 3.7.0 - 2023-11-20 = * Important: starting with this release, Action Scheduler follows an L-2 version policy (WordPress, and consequently PHP). * Add extended indexes for hook_status_scheduled_date_gmt and status_sheduled_date_gmt. * Catch and log exceptions thrown when actions can't be created, e.g. under a corrupt database schema. * Tweak - WP 6.4 compatibility. * Update unit tests for upcoming dependency version policy. * make sure hook action_scheduler_failed_execution can access original exception object. * mention dependency version policy in usage.md. = 3.6.4 - 2023-10-11 = * Performance improvements when bulk cancelling actions. * Dev-related fixes. = 3.6.3 - 2023-09-13 = * Use `_doing_it_wrong` in initialization check. = 3.6.2 - 2023-08-09 = * Add guidance about passing arguments. * Atomic option locking. * Improve bulk delete handling. * Include database error in the exception message. * Tweak - WP 6.3 compatibility. = 3.6.1 - 2023-06-14 = * Document new optional `$priority` arg for various API functions. * Document the new `--exclude-groups` WP CLI option. * Document the new `action_scheduler_init` hook. * Ensure actions within each claim are executed in the expected order. * Fix incorrect text domain. * Remove SHOW TABLES usage when checking if tables exist. = 3.6.0 - 2023-05-10 = * Add $unique parameter to function signatures. * Add a cast-to-int for extra safety before forming new DateTime object. * Add a hook allowing exceptions for consistently failing recurring actions. * Add action priorities. * Add init hook. * Always raise the time limit. * Bump minimatch from 3.0.4 to 3.0.8. * Bump yaml from 2.2.1 to 2.2.2. * Defensive coding relating to gaps in declared schedule types. * Do not process an action if it cannot be set to `in-progress`. * Filter view labels (status names) should be translatable | #919. * Fix WPCLI progress messages. * Improve data-store initialization flow. * Improve error handling across all supported PHP versions. * Improve logic for flushing the runtime cache. * Support exclusion of multiple groups. * Update lint-staged and Node/NPM requirements. * add CLI clean command. * add CLI exclude-group filter. * exclude past-due from list table all filter count. * throwing an exception if as_schedule_recurring_action interval param is not of type integer. = 3.5.4 - 2023-01-17 = * Add pre filters during action registration. * Async scheduling. * Calculate timeouts based on total actions. * Correctly order the parameters for `ActionScheduler_ActionFactory`'s calls to `single_unique`. * Fetch action in memory first before releasing claim to avoid deadlock. * PHP 8.2: declare property to fix creation of dynamic property warning. * PHP 8.2: fix "Using ${var} in strings is deprecated, use {$var} instead". * Prevent `undefined variable` warning for `$num_pastdue_actions`. = 3.5.3 - 2022-11-09 = * Query actions with partial match. = 3.5.2 - 2022-09-16 = * Fix - erroneous 3.5.1 release. = 3.5.1 - 2022-09-13 = * Maintenance on A/S docs. * fix: PHP 8.2 deprecated notice. = 3.5.0 - 2022-08-25 = * Add - The active view link within the "Tools > Scheduled Actions" screen is now clickable. * Add - A warning when there are past-due actions. * Enhancement - Added the ability to schedule unique actions via an atomic operation. * Enhancement - Improvements to cache invalidation when processing batches (when running on WordPress 6.0+). * Enhancement - If a recurring action is found to be consistently failing, it will stop being rescheduled. * Enhancement - Adds a new "Past Due" view to the scheduled actions list table. = 3.4.2 - 2022-06-08 = * Fix - Change the include for better linting. * Fix - update: Added Action scheduler completed action hook. = 3.4.1 - 2022-05-24 = * Fix - Change the include for better linting. * Fix - Fix the documented return type. = 3.4.0 - 2021-10-29 = * Enhancement - Number of items per page can now be set for the Scheduled Actions view (props @ovidiul). #771 * Fix - Do not lower the max_execution_time if it is already set to 0 (unlimited) (props @barryhughes). #755 * Fix - Avoid triggering autoloaders during the version resolution process (props @olegabr). #731 & #776 * Dev - ActionScheduler_wcSystemStatus PHPCS fixes (props @ovidiul). #761 * Dev - ActionScheduler_DBLogger.php PHPCS fixes (props @ovidiul). #768 * Dev - Fixed phpcs for ActionScheduler_Schedule_Deprecated (props @ovidiul). #762 * Dev - Improve actions table indicies (props @glagonikas). #774 & #777 * Dev - PHPCS fixes for ActionScheduler_DBStore.php (props @ovidiul). #769 & #778 * Dev - PHPCS Fixes for ActionScheduler_Abstract_ListTable (props @ovidiul). #763 & #779 * Dev - Adds new filter action_scheduler_claim_actions_order_by to allow tuning of the claim query (props @glagonikas). #773 * Dev - PHPCS fixes for ActionScheduler_WpPostStore class (props @ovidiul). #780 = 3.3.0 - 2021-09-15 = * Enhancement - Adds as_has_scheduled_action() to provide a performant way to test for existing actions. #645 * Fix - Improves compatibility with environments where NO_ZERO_DATE is enabled. #519 * Fix - Adds safety checks to guard against errors when our database tables cannot be created. #645 * Dev - Now supports queries that use multiple statuses. #649 * Dev - Minimum requirements for WordPress and PHP bumped (to 5.2 and 5.6 respectively). #723 = 3.2.1 - 2021-06-21 = * Fix - Add extra safety/account for different versions of AS and different loading patterns. #714 * Fix - Handle hidden columns (Tools → Scheduled Actions) | #600. = 3.2.0 - 2021-06-03 = * Fix - Add "no ordering" option to as_next_scheduled_action(). * Fix - Add secondary scheduled date checks when claiming actions (DBStore) | #634. * Fix - Add secondary scheduled date checks when claiming actions (wpPostStore) | #634. * Fix - Adds a new index to the action table, reducing the potential for deadlocks (props: @glagonikas). * Fix - Fix unit tests infrastructure and adapt tests to PHP 8. * Fix - Identify in-use data store. * Fix - Improve test_migration_is_scheduled. * Fix - PHP notice on list table. * Fix - Speed up clean up and batch selects. * Fix - Update pending dependencies. * Fix - [PHP 8.0] Only pass action arg values through to do_action_ref_array(). * Fix - [PHP 8] Set the PHP version to 7.1 in composer.json for PHP 8 compatibility. * Fix - add is_initialized() to docs. * Fix - fix file permissions. * Fix - fixes #664 by replacing __ with esc_html__. = 3.1.6 - 2020-05-12 = * Change log starts. vendor/woocommerce/action-scheduler/functions.php000064400000045107147600245720016350 0ustar00create( array( 'type' => 'async', 'hook' => $hook, 'arguments' => $args, 'group' => $group, 'unique' => $unique, 'priority' => $priority, ) ); } /** * Schedule an action to run one time * * @param int $timestamp When the job will run. * @param string $hook The hook to trigger. * @param array $args Arguments to pass when the hook triggers. * @param string $group The group to assign this job to. * @param bool $unique Whether the action should be unique. It will not be scheduled if another pending or running action has the same hook and group parameters. * @param int $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255. * * @return int The action ID. Zero if there was an error scheduling the action. */ function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return 0; } /** * Provides an opportunity to short-circuit the default process for enqueuing single * actions. * * Returning a value other than null from the filter will short-circuit the normal * process. The expectation in such a scenario is that callbacks will return an integer * representing the scheduled action ID (scheduled using some alternative process) or else * zero. * * @param int|null $pre_option The value to return instead of the option value. * @param int $timestamp When the action will run. * @param string $hook Action hook. * @param array $args Action arguments. * @param string $group Action group. * @param int $priorities Action priority. */ $pre = apply_filters( 'pre_as_schedule_single_action', null, $timestamp, $hook, $args, $group, $priority ); if ( null !== $pre ) { return is_int( $pre ) ? $pre : 0; } return ActionScheduler::factory()->create( array( 'type' => 'single', 'hook' => $hook, 'arguments' => $args, 'when' => $timestamp, 'group' => $group, 'unique' => $unique, 'priority' => $priority, ) ); } /** * Schedule a recurring action * * @param int $timestamp When the first instance of the job will run. * @param int $interval_in_seconds How long to wait between runs. * @param string $hook The hook to trigger. * @param array $args Arguments to pass when the hook triggers. * @param string $group The group to assign this job to. * @param bool $unique Whether the action should be unique. It will not be scheduled if another pending or running action has the same hook and group parameters. * @param int $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255. * * @return int The action ID. Zero if there was an error scheduling the action. */ function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return 0; } $interval = (int) $interval_in_seconds; // We expect an integer and allow it to be passed using float and string types, but otherwise // should reject unexpected values. if ( ! is_numeric( $interval_in_seconds ) || $interval_in_seconds != $interval ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: 1: provided value 2: provided type. */ esc_html__( 'An integer was expected but "%1$s" (%2$s) was received.', 'action-scheduler' ), esc_html( $interval_in_seconds ), esc_html( gettype( $interval_in_seconds ) ) ), '3.6.0' ); return 0; } /** * Provides an opportunity to short-circuit the default process for enqueuing recurring * actions. * * Returning a value other than null from the filter will short-circuit the normal * process. The expectation in such a scenario is that callbacks will return an integer * representing the scheduled action ID (scheduled using some alternative process) or else * zero. * * @param int|null $pre_option The value to return instead of the option value. * @param int $timestamp When the action will run. * @param int $interval_in_seconds How long to wait between runs. * @param string $hook Action hook. * @param array $args Action arguments. * @param string $group Action group. * @param int $priority Action priority. */ $pre = apply_filters( 'pre_as_schedule_recurring_action', null, $timestamp, $interval_in_seconds, $hook, $args, $group, $priority ); if ( null !== $pre ) { return is_int( $pre ) ? $pre : 0; } return ActionScheduler::factory()->create( array( 'type' => 'recurring', 'hook' => $hook, 'arguments' => $args, 'when' => $timestamp, 'pattern' => $interval_in_seconds, 'group' => $group, 'unique' => $unique, 'priority' => $priority, ) ); } /** * Schedule an action that recurs on a cron-like schedule. * * @param int $timestamp The first instance of the action will be scheduled * to run at a time calculated after this timestamp matching the cron * expression. This can be used to delay the first instance of the action. * @param string $schedule A cron-link schedule string. * @see http://en.wikipedia.org/wiki/Cron * * * * * * * * ┬ ┬ ┬ ┬ ┬ ┬ * | | | | | | * | | | | | + year [optional] * | | | | +----- day of week (0 - 7) (Sunday=0 or 7) * | | | +---------- month (1 - 12) * | | +--------------- day of month (1 - 31) * | +-------------------- hour (0 - 23) * +------------------------- min (0 - 59) * @param string $hook The hook to trigger. * @param array $args Arguments to pass when the hook triggers. * @param string $group The group to assign this job to. * @param bool $unique Whether the action should be unique. It will not be scheduled if another pending or running action has the same hook and group parameters. * @param int $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255. * * @return int The action ID. Zero if there was an error scheduling the action. */ function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return 0; } /** * Provides an opportunity to short-circuit the default process for enqueuing cron * actions. * * Returning a value other than null from the filter will short-circuit the normal * process. The expectation in such a scenario is that callbacks will return an integer * representing the scheduled action ID (scheduled using some alternative process) or else * zero. * * @param int|null $pre_option The value to return instead of the option value. * @param int $timestamp When the action will run. * @param string $schedule Cron-like schedule string. * @param string $hook Action hook. * @param array $args Action arguments. * @param string $group Action group. * @param int $priority Action priority. */ $pre = apply_filters( 'pre_as_schedule_cron_action', null, $timestamp, $schedule, $hook, $args, $group, $priority ); if ( null !== $pre ) { return is_int( $pre ) ? $pre : 0; } return ActionScheduler::factory()->create( array( 'type' => 'cron', 'hook' => $hook, 'arguments' => $args, 'when' => $timestamp, 'pattern' => $schedule, 'group' => $group, 'unique' => $unique, 'priority' => $priority, ) ); } /** * Cancel the next occurrence of a scheduled action. * * While only the next instance of a recurring or cron action is unscheduled by this method, that will also prevent * all future instances of that recurring or cron action from being run. Recurring and cron actions are scheduled in * a sequence instead of all being scheduled at once. Each successive occurrence of a recurring action is scheduled * only after the former action is run. If the next instance is never run, because it's unscheduled by this function, * then the following instance will never be scheduled (or exist), which is effectively the same as being unscheduled * by this method also. * * @param string $hook The hook that the job will trigger. * @param array $args Args that would have been passed to the job. * @param string $group The group the job is assigned to. * * @return int|null The scheduled action ID if a scheduled action was found, or null if no matching action found. */ function as_unschedule_action( $hook, $args = array(), $group = '' ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return 0; } $params = array( 'hook' => $hook, 'status' => ActionScheduler_Store::STATUS_PENDING, 'orderby' => 'date', 'order' => 'ASC', 'group' => $group, ); if ( is_array( $args ) ) { $params['args'] = $args; } $action_id = ActionScheduler::store()->query_action( $params ); if ( $action_id ) { try { ActionScheduler::store()->cancel_action( $action_id ); } catch ( Exception $exception ) { ActionScheduler::logger()->log( $action_id, sprintf( /* translators: %1$s is the name of the hook to be cancelled, %2$s is the exception message. */ __( 'Caught exception while cancelling action "%1$s": %2$s', 'action-scheduler' ), $hook, $exception->getMessage() ) ); $action_id = null; } } return $action_id; } /** * Cancel all occurrences of a scheduled action. * * @param string $hook The hook that the job will trigger. * @param array $args Args that would have been passed to the job. * @param string $group The group the job is assigned to. */ function as_unschedule_all_actions( $hook, $args = array(), $group = '' ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return; } if ( empty( $args ) ) { if ( ! empty( $hook ) && empty( $group ) ) { ActionScheduler_Store::instance()->cancel_actions_by_hook( $hook ); return; } if ( ! empty( $group ) && empty( $hook ) ) { ActionScheduler_Store::instance()->cancel_actions_by_group( $group ); return; } } do { $unscheduled_action = as_unschedule_action( $hook, $args, $group ); } while ( ! empty( $unscheduled_action ) ); } /** * Check if there is an existing action in the queue with a given hook, args and group combination. * * An action in the queue could be pending, in-progress or async. If the is pending for a time in * future, its scheduled date will be returned as a timestamp. If it is currently being run, or an * async action sitting in the queue waiting to be processed, in which case boolean true will be * returned. Or there may be no async, in-progress or pending action for this hook, in which case, * boolean false will be the return value. * * @param string $hook Name of the hook to search for. * @param array $args Arguments of the action to be searched. * @param string $group Group of the action to be searched. * * @return int|bool The timestamp for the next occurrence of a pending scheduled action, true for an async or in-progress action or false if there is no matching action. */ function as_next_scheduled_action( $hook, $args = null, $group = '' ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return false; } $params = array( 'hook' => $hook, 'orderby' => 'date', 'order' => 'ASC', 'group' => $group, ); if ( is_array( $args ) ) { $params['args'] = $args; } $params['status'] = ActionScheduler_Store::STATUS_RUNNING; $action_id = ActionScheduler::store()->query_action( $params ); if ( $action_id ) { return true; } $params['status'] = ActionScheduler_Store::STATUS_PENDING; $action_id = ActionScheduler::store()->query_action( $params ); if ( null === $action_id ) { return false; } $action = ActionScheduler::store()->fetch_action( $action_id ); $scheduled_date = $action->get_schedule()->get_date(); if ( $scheduled_date ) { return (int) $scheduled_date->format( 'U' ); } elseif ( null === $scheduled_date ) { // pending async action with NullSchedule. return true; } return false; } /** * Check if there is a scheduled action in the queue but more efficiently than as_next_scheduled_action(). * * It's recommended to use this function when you need to know whether a specific action is currently scheduled * (pending or in-progress). * * @since 3.3.0 * * @param string $hook The hook of the action. * @param array $args Args that have been passed to the action. Null will matches any args. * @param string $group The group the job is assigned to. * * @return bool True if a matching action is pending or in-progress, false otherwise. */ function as_has_scheduled_action( $hook, $args = null, $group = '' ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return false; } $query_args = array( 'hook' => $hook, 'status' => array( ActionScheduler_Store::STATUS_RUNNING, ActionScheduler_Store::STATUS_PENDING ), 'group' => $group, 'orderby' => 'none', ); if ( null !== $args ) { $query_args['args'] = $args; } $action_id = ActionScheduler::store()->query_action( $query_args ); return null !== $action_id; } /** * Find scheduled actions * * @param array $args Possible arguments, with their default values. * 'hook' => '' - the name of the action that will be triggered. * 'args' => NULL - the args array that will be passed with the action. * 'date' => NULL - the scheduled date of the action. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone. * 'date_compare' => '<=' - operator for testing "date". accepted values are '!=', '>', '>=', '<', '<=', '='. * 'modified' => NULL - the date the action was last updated. Expects a DateTime object, a unix timestamp, or a string that can parsed with strtotime(). Used in UTC timezone. * 'modified_compare' => '<=' - operator for testing "modified". accepted values are '!=', '>', '>=', '<', '<=', '='. * 'group' => '' - the group the action belongs to. * 'status' => '' - ActionScheduler_Store::STATUS_COMPLETE or ActionScheduler_Store::STATUS_PENDING. * 'claimed' => NULL - TRUE to find claimed actions, FALSE to find unclaimed actions, a string to find a specific claim ID. * 'per_page' => 5 - Number of results to return. * 'offset' => 0. * 'orderby' => 'date' - accepted values are 'hook', 'group', 'modified', 'date' or 'none'. * 'order' => 'ASC'. * * @param string $return_format OBJECT, ARRAY_A, or ids. * * @return array */ function as_get_scheduled_actions( $args = array(), $return_format = OBJECT ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return array(); } $store = ActionScheduler::store(); foreach ( array( 'date', 'modified' ) as $key ) { if ( isset( $args[ $key ] ) ) { $args[ $key ] = as_get_datetime_object( $args[ $key ] ); } } $ids = $store->query_actions( $args ); if ( 'ids' === $return_format || 'int' === $return_format ) { return $ids; } $actions = array(); foreach ( $ids as $action_id ) { $actions[ $action_id ] = $store->fetch_action( $action_id ); } if ( ARRAY_A == $return_format ) { foreach ( $actions as $action_id => $action_object ) { $actions[ $action_id ] = get_object_vars( $action_object ); } } return $actions; } /** * Helper function to create an instance of DateTime based on a given * string and timezone. By default, will return the current date/time * in the UTC timezone. * * Needed because new DateTime() called without an explicit timezone * will create a date/time in PHP's timezone, but we need to have * assurance that a date/time uses the right timezone (which we almost * always want to be UTC), which means we need to always include the * timezone when instantiating datetimes rather than leaving it up to * the PHP default. * * @param mixed $date_string A date/time string. Valid formats are explained in http://php.net/manual/en/datetime.formats.php. * @param string $timezone A timezone identifier, like UTC or Europe/Lisbon. The list of valid identifiers is available http://php.net/manual/en/timezones.php. * * @return ActionScheduler_DateTime */ function as_get_datetime_object( $date_string = null, $timezone = 'UTC' ) { if ( is_object( $date_string ) && $date_string instanceof DateTime ) { $date = new ActionScheduler_DateTime( $date_string->format( 'Y-m-d H:i:s' ), new DateTimeZone( $timezone ) ); } elseif ( is_numeric( $date_string ) ) { $date = new ActionScheduler_DateTime( '@' . $date_string, new DateTimeZone( $timezone ) ); } else { $date = new ActionScheduler_DateTime( null === $date_string ? 'now' : $date_string, new DateTimeZone( $timezone ) ); } return $date; } vendor/woocommerce/action-scheduler/license.txt000064400000104515147600245720016011 0ustar00 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . vendor/woocommerce/action-scheduler/readme.txt000064400000023705147600245720015625 0ustar00=== Action Scheduler === Contributors: Automattic, wpmuguru, claudiosanches, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, royho, barryhughes-1 Tags: scheduler, cron Stable tag: 3.8.0 License: GPLv3 Requires at least: 6.3 Tested up to: 6.5 Requires PHP: 7.0 Action Scheduler - Job Queue for WordPress == Description == Action Scheduler is a scalable, traceable job queue for background processing large sets of actions in WordPress. It's specially designed to be distributed in WordPress plugins. Action Scheduler works by triggering an action hook to run at some time in the future. Each hook can be scheduled with unique data, to allow callbacks to perform operations on that data. The hook can also be scheduled to run on one or more occassions. Think of it like an extension to `do_action()` which adds the ability to delay and repeat a hook. ## Battle-Tested Background Processing Every month, Action Scheduler processes millions of payments for [Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/), webhooks for [WooCommerce](https://wordpress.org/plugins/woocommerce/), as well as emails and other events for a range of other plugins. It's been seen on live sites processing queues in excess of 50,000 jobs and doing resource intensive operations, like processing payments and creating orders, at a sustained rate of over 10,000 / hour without negatively impacting normal site operations. This is all on infrastructure and WordPress sites outside the control of the plugin author. If your plugin needs background processing, especially of large sets of tasks, Action Scheduler can help. ## Learn More To learn more about how to Action Scheduler works, and how to use it in your plugin, check out the docs on [ActionScheduler.org](https://actionscheduler.org). There you will find: * [Usage guide](https://actionscheduler.org/usage/): instructions on installing and using Action Scheduler * [WP CLI guide](https://actionscheduler.org/wp-cli/): instructions on running Action Scheduler at scale via WP CLI * [API Reference](https://actionscheduler.org/api/): complete reference guide for all API functions * [Administration Guide](https://actionscheduler.org/admin/): guide to managing scheduled actions via the administration screen * [Guide to Background Processing at Scale](https://actionscheduler.org/perf/): instructions for running Action Scheduler at scale via the default WP Cron queue runner ## Credits Action Scheduler is developed and maintained by [Automattic](http://automattic.com/) with significant early development completed by [Flightless](https://flightless.us/). Collaboration is cool. We'd love to work with you to improve Action Scheduler. [Pull Requests](https://github.com/woocommerce/action-scheduler/pulls) welcome. == Changelog == = 3.8.0 - 2024-05-22 = * Bump "Tested up to" version to 6.5. * Fix typos in perf.md. * Merge Release/3.7.4 into trunk. * Tweak - WP 6.5 compatibility. * Update minimum requirements for WordPress (and PHP). Fix test matrix. = 3.7.4 - 2024-04-05 = * Give a clear description of how the $unique parameter works. * Preserve the tab field if set. * Tweak - WP 6.5 compatibility. = 3.7.3 - 2024-03-20 = * Do not iterate over all of GET when building form in list table. * Fix a few issues reported by PCP (Plugin Check Plugin). * Try to save actions as unique even when the store doesn't support it. * Tweak - WP 6.4 compatibility. * Update "Tested up to" tag to WordPress 6.5. * update version in package-lock.json. = 3.7.2 - 2024-02-14 = * No longer user variables in `_n()` translation function. = 3.7.1 - 2023-12-13 = * update semver to 5.7.2 because of a security vulnerability in 5.7.1. = 3.7.0 - 2023-11-20 = * Important: starting with this release, Action Scheduler follows an L-2 version policy (WordPress, and consequently PHP). * Add extended indexes for hook_status_scheduled_date_gmt and status_sheduled_date_gmt. * Catch and log exceptions thrown when actions can't be created, e.g. under a corrupt database schema. * Tweak - WP 6.4 compatibility. * Update unit tests for upcoming dependency version policy. * make sure hook action_scheduler_failed_execution can access original exception object. * mention dependency version policy in usage.md. = 3.6.4 - 2023-10-11 = * Performance improvements when bulk cancelling actions. * Dev-related fixes. = 3.6.3 - 2023-09-13 = * Use `_doing_it_wrong` in initialization check. = 3.6.2 - 2023-08-09 = * Add guidance about passing arguments. * Atomic option locking. * Improve bulk delete handling. * Include database error in the exception message. * Tweak - WP 6.3 compatibility. = 3.6.1 - 2023-06-14 = * Document new optional `$priority` arg for various API functions. * Document the new `--exclude-groups` WP CLI option. * Document the new `action_scheduler_init` hook. * Ensure actions within each claim are executed in the expected order. * Fix incorrect text domain. * Remove SHOW TABLES usage when checking if tables exist. = 3.6.0 - 2023-05-10 = * Add $unique parameter to function signatures. * Add a cast-to-int for extra safety before forming new DateTime object. * Add a hook allowing exceptions for consistently failing recurring actions. * Add action priorities. * Add init hook. * Always raise the time limit. * Bump minimatch from 3.0.4 to 3.0.8. * Bump yaml from 2.2.1 to 2.2.2. * Defensive coding relating to gaps in declared schedule types. * Do not process an action if it cannot be set to `in-progress`. * Filter view labels (status names) should be translatable | #919. * Fix WPCLI progress messages. * Improve data-store initialization flow. * Improve error handling across all supported PHP versions. * Improve logic for flushing the runtime cache. * Support exclusion of multiple groups. * Update lint-staged and Node/NPM requirements. * add CLI clean command. * add CLI exclude-group filter. * exclude past-due from list table all filter count. * throwing an exception if as_schedule_recurring_action interval param is not of type integer. = 3.5.4 - 2023-01-17 = * Add pre filters during action registration. * Async scheduling. * Calculate timeouts based on total actions. * Correctly order the parameters for `ActionScheduler_ActionFactory`'s calls to `single_unique`. * Fetch action in memory first before releasing claim to avoid deadlock. * PHP 8.2: declare property to fix creation of dynamic property warning. * PHP 8.2: fix "Using ${var} in strings is deprecated, use {$var} instead". * Prevent `undefined variable` warning for `$num_pastdue_actions`. = 3.5.3 - 2022-11-09 = * Query actions with partial match. = 3.5.2 - 2022-09-16 = * Fix - erroneous 3.5.1 release. = 3.5.1 - 2022-09-13 = * Maintenance on A/S docs. * fix: PHP 8.2 deprecated notice. = 3.5.0 - 2022-08-25 = * Add - The active view link within the "Tools > Scheduled Actions" screen is now clickable. * Add - A warning when there are past-due actions. * Enhancement - Added the ability to schedule unique actions via an atomic operation. * Enhancement - Improvements to cache invalidation when processing batches (when running on WordPress 6.0+). * Enhancement - If a recurring action is found to be consistently failing, it will stop being rescheduled. * Enhancement - Adds a new "Past Due" view to the scheduled actions list table. = 3.4.2 - 2022-06-08 = * Fix - Change the include for better linting. * Fix - update: Added Action scheduler completed action hook. = 3.4.1 - 2022-05-24 = * Fix - Change the include for better linting. * Fix - Fix the documented return type. = 3.4.0 - 2021-10-29 = * Enhancement - Number of items per page can now be set for the Scheduled Actions view (props @ovidiul). #771 * Fix - Do not lower the max_execution_time if it is already set to 0 (unlimited) (props @barryhughes). #755 * Fix - Avoid triggering autoloaders during the version resolution process (props @olegabr). #731 & #776 * Dev - ActionScheduler_wcSystemStatus PHPCS fixes (props @ovidiul). #761 * Dev - ActionScheduler_DBLogger.php PHPCS fixes (props @ovidiul). #768 * Dev - Fixed phpcs for ActionScheduler_Schedule_Deprecated (props @ovidiul). #762 * Dev - Improve actions table indicies (props @glagonikas). #774 & #777 * Dev - PHPCS fixes for ActionScheduler_DBStore.php (props @ovidiul). #769 & #778 * Dev - PHPCS Fixes for ActionScheduler_Abstract_ListTable (props @ovidiul). #763 & #779 * Dev - Adds new filter action_scheduler_claim_actions_order_by to allow tuning of the claim query (props @glagonikas). #773 * Dev - PHPCS fixes for ActionScheduler_WpPostStore class (props @ovidiul). #780 = 3.3.0 - 2021-09-15 = * Enhancement - Adds as_has_scheduled_action() to provide a performant way to test for existing actions. #645 * Fix - Improves compatibility with environments where NO_ZERO_DATE is enabled. #519 * Fix - Adds safety checks to guard against errors when our database tables cannot be created. #645 * Dev - Now supports queries that use multiple statuses. #649 * Dev - Minimum requirements for WordPress and PHP bumped (to 5.2 and 5.6 respectively). #723 = 3.2.1 - 2021-06-21 = * Fix - Add extra safety/account for different versions of AS and different loading patterns. #714 * Fix - Handle hidden columns (Tools → Scheduled Actions) | #600. = 3.2.0 - 2021-06-03 = * Fix - Add "no ordering" option to as_next_scheduled_action(). * Fix - Add secondary scheduled date checks when claiming actions (DBStore) | #634. * Fix - Add secondary scheduled date checks when claiming actions (wpPostStore) | #634. * Fix - Adds a new index to the action table, reducing the potential for deadlocks (props: @glagonikas). * Fix - Fix unit tests infrastructure and adapt tests to PHP 8. * Fix - Identify in-use data store. * Fix - Improve test_migration_is_scheduled. * Fix - PHP notice on list table. * Fix - Speed up clean up and batch selects. * Fix - Update pending dependencies. * Fix - [PHP 8.0] Only pass action arg values through to do_action_ref_array(). * Fix - [PHP 8] Set the PHP version to 7.1 in composer.json for PHP 8 compatibility. * Fix - add is_initialized() to docs. * Fix - fix file permissions. * Fix - fixes #664 by replacing __ with esc_html__. vendor/autoload.php000064400000000653147600245720010375 0ustar00

    $options ) : ?>
    $options, 'attribute' => $attribute_name, 'product' => $product, 'class' => 'wpfnl-variable-attribute-offer', ) ); ?>
    ' . esc_html__( 'Clear Variations', 'woocommerce' ) . '' ) ) : ''; endforeach; ?>
    woocommerce/templates/single-product/add-to-cart/variation.php000064400000001365147600245720020640 0ustar00 index.php000064400000000032147600245720006366 0ustar00 Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License.phpcs.xml000064400000002620147600245720006412 0ustar00 tests/*.php tests/providers/*.php /phpunit/* /includes/* /includes/* */wordpress/* */build/* */node_modules/* */vendor/* README.txt000064400000040542147600245720006256 0ustar00=== Easiest Funnel Builder For WordPress & WooCommerce by WPFunnels === Contributors: coderexltd, getwpfunnels, coderexco Donate link: https://getwpfunnels.com/ Tags: funnel builder, order bump, sales funnels, lead generation, custom checkout Requires at least: 6.1 Tested up to: 6.7.1 Requires PHP: 7.4 Stable tag: 2.5.6 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Create highly converting sales funnels within WordPress using a visual drag and drop funnel builder, WPFunnels. == Description == Create highly converting sales funnels within WordPress using a visual drag and drop funnel builder – WPFunnels. Whether you are offering courses, selling products or ebooks, offering subscriptions, or simply trying to generate leads, WPFunnels will help you create effective sales funnels. Documentation|Get Free Early Access To Pro| Join our Facebook Community [youtube https://www.youtube.com/watch?v=gG4gtcTHb_Y] And the best part is, WPFunnels comes with go-to sales funnel templates for several niches, which you can import with just a click. This means you can start using sales funnels immediately, without wasting any time. WPFunnels is currently the easiest funnel builder for WordPress & WooCommerce. The plugin comes with a user-friendly interface to help you create complete sales funnels easily, on your own, within minutes. In fact, it is the only WordPress funnel builder where you can visually design your funnel flows on a drag-and-drop canvas – you have complete control over your funnel! 1. Plan Your Sales Funnel On A Drag And Drop Canvas 2. Design Your Funnel Steps Using Your Preferred Page Builder 3. Execute Your Sales Funnels And Boost Your Sales. == Why Use WP Funnels? == WPFunnels is created for entrepreneurs and marketers like yourself, to increase your online sales revenue using sales funnels. Use this reliable WordPress funnel builder to automate your sales process and stay in control of every stage of the buyer’s journey. Guide your prospects through engaging offers and trigger them to purchase more than what they had initially intended. = 1. A Complete Sales Funnel Solution Within WordPress = No need to use multiple tools to create or track your funnels. = Use a single funnel builder to plan, design, and execute complete sales funnels, without leaving your WordPress dashboard. = - A Drag-and-drop canvas for funnel mapping & planning - All the typical steps needed (Landing, Checkout, Thank You) - Design funnel steps using Gutenberg or Elementor Page Builder - Use orderbump offers at the checkout - Trigger one click upsell offers after checkout (Coming soon) - Add Downsell offers after upsell steps (Coming soon) - Conditional redirection to control buyer’s journey - Full analytics of every funnel step (Coming soon) - A/B testing of every step (Coming soon) - Lead generation with form builders (Coming soon) - Major payment processing support – Stripe & Paypal You get to control everything from planning your funnel to launching a live sales campaign in 1 place. >Can't find what you are looking for or have a feature request? Let us know! = 2. A Funnel Builder For EVERY Business In EVERY Niche = This plugin is made for any online business whether you are a * Digital Marketer * Marketing Strategist * Marketing coach * Fitness Trainer * Fitness Product seller * Health Supplement Provider * Legal Advisor * Online Coach * Educational Portal * Counselor * Software Firm * eCommerce Shop * And many more With WP Funnels, you can create highly converting sales funnels no matter what business you are in. >Use order bumps at the checkout or post sale offers using one-click upsell/downsell (coming soon) to increase your sales revenue. = 3. Maximize Your Leads And Online Sales No Matter What Product You Offer = Use a funnel builder that is suitable to generate leads and increase sales for any products you may offer. * Sell Online Courses * Sign More Online Subscriptions * Promote Webninar Registrations * Conduct Lead Generation * Sell WooCommerce Products * Increase eBooks Sales * Sell Supplement Products * Market Event Tickets * Offer Exclusive Services Take your marketing goals to the next level with WPFunnels. = 4. Create Fully Functional Funnels In As Low AS 15 Minutes = WPFunnels comes with tons of pre-built funnel templates that are specialized in different niches. These templates have been created after months of research and development to ensure you get to use a WordPress sales funnel that will make a difference. All you need to do is choose a template, improve the content, and launch your sales funnel campaign. In fact, you can complete the whole process in as low as 15 minutes, even without any exclusive technical skills. It is a funnel builder that is made for both tech-savvy and non-tech marketers who want to boost their revenue. >Besides, you may always take help from our **reliable support team** whenever required. = 5. A Simplified Funnel Builder To Save Your Time And Effort = One of the main purposes of creating WPFunnels was to make your life easier when creating sales funnels. The plugin is designed in a way so that you can build sales funnel without any complications. It’s a reliable funnel builder that is specialized to * Reduce the time required to build funnels * Exclude all unnecessary steps to simplify the process * Enhance funnel planning with a visual representation * Ensure the whole process is possible within WordPress * Make your efforts meaningful and productive * Launch your funnel campaigns ASAP. Your time and effort will not be wasted; start boosting your sales immediately without any hassle. == Exclusive Features Of WP Funnels To Boost Your Sales Process== Check out the amazing features you get with WPFunnels to help you boost sales. **👉 Drag-and-Drop Canvas To Build Funnels Easily [⭐ New Feature]** Plan your funnels without any hassle. Use a visual drag and drop funnel builder to plan and organize your funnel steps. You have full control over choosing the number of steps, what steps to use, using conditional flows, or any funnel navigation. Map your funnels with the drag and drop funnel-building canvas and jumpstart your funnel process in minutes. **👉 Pre-made Sales Funnel Templates To Start Selling Immediately** Design your funnel from scratch or choose from dozens of niche-specific funnel templates that are optimized for high conversion. Get a complete funnel with just a click and start selling immediately > It’s a simplified funnel builder, specialized to make your funnel building process easier than ever. **👉 Gutenberg & Elementor Page Builder Integration** Design every step of the funnel using your favorite page builders. The plugin comes with exclusive Gutenberg blocks & Elementor widgets for every funnel step to help you customize your funnel pages with full control. **👉 Order Bump Offers At The Checkout** Add an attractive order bump offer during the checkout and increase the sales revenue. > [Design your own order bumps](https://getwpfunnels.com/docs/wpfunnels-wordpress-funnel-builder/2-detailed-step-by-step-guide-to-create-successful-sales-funnels/use-an-order-bump-at-the-checkout-page/) and place them anywhere on the checkout page as you like. **👉 Fully Functional With WooCommerce** Easily integrate with WooCommerce to implement sales funnels to your WooCommerce products and increase sales. **👉 One-click Upsell And Downsell Offers (Coming Soon)** Trigger one-time offers or any exclusive offers as upsell or downsell after checkout, to attract your buyers to purchase even more and leverage the buyer's intent to your advantage. **👉 Compatible With Form Builders For Lead Generation Funnels (Coming Soon)** Easily integrate with popular form builders to run lead generation campaigns. Offer a freebie, promote webinars, or simply offer more information and collect leads to qualify them into sales funnels easily. == Exclusive Upcoming Features == We have great plans to expand this plugin so that you can make your business as profitable as possible through online sales. Following are some of our planned future solutions: * Upsell and downsell offers after checkout * A/B testing for every funnel step * Analytics to track the performance of every funnel * Integration with Major LMS plugins * Integration with form builders for lead generation funnels * Hybrid funnel templates and implementations == Features == * Interactive drag-and-drop funnel building canvas * Pre-made niche-specific sales funnel templates * Integrated with Elementor Page Builder * Integrated with Gutenberg Page Editor * Compatible with WooCommerce * Option To Create Funnel From Scratch * Full control on designing each funnel steps * Pre-made templates for Landing Pages * Pre-built templates for the Checkout page * Pre-made templates for Thank You page * Option to use a custom taxonomy * Orderbump offers at the checkout * Customize orderbump design * Custom orderbump position * Custom style and color for checkout fields == Pro Features: == * More Upsell Templates (Coming Soon) * Premium Funnel Templates (Coming Soon) * Upsell and downsell offers after checkout (Coming Soon) * A/B testing for every funnel step (Coming Soon) * Integration with Major LMS plugins (Coming Soon) * Hybrid funnel templates and implementations (Coming Soon) >Get Free Early Access To WPFunnels Pro. == Join The WPFunnels Users Community On Facebook == [JOIN OUR FACEBOOK USERS COMMUNITY](https://www.facebook.com/groups/wpfunnels/): Discuss and learn how others are using WPFunnels and how you can use effectively create sales funnels for your WordPress or WooCommerce shop. Plus, stay up-to-date with our exciting upcoming features. ## Privacy Policy WP Funnels uses [Appsero](https://appsero.com) SDK to collect some telemetry data upon user's confirmation. This helps us to troubleshoot problems faster & make product improvements. Appsero SDK **does not gather any data by default.** The SDK only starts gathering basic telemetry data **when a user allows it via the admin notice**. We collect the data to ensure a great user experience for all our users. Integrating Appsero SDK **DOES NOT IMMEDIATELY** start gathering data, **without confirmation from users in any case.** Learn more about how [Appsero collects and uses this data](https://appsero.com/privacy-policy/). == Installation == 1. Upload the WP Funnels to the `/wp-content/plugins/` directory 1. Activate the plugin through the 'Plugins' menu in WordPress 1. Follow the setup wizard to configure the plugin for use == Frequently Asked Questions == = 1. What makes WP Funnels unique from other funnel builders? = It is the only WordPress funnel builder where you can visually design your funnel flows on a drag-and-drop canvas - you have complete control over your funnel! No need to use multiple tools to create or track your funnels. You get to control everything from planning your funnel to launching a live sales campaign in 1 place. - A Drag-and-drop canvas for funnel mapping - All the typical steps needed (Landing, Checkout, Thank You) - Orderbump offer at the checkout - Upsell and Downsell offers after checkout - Full analytics of every funnel step (Coming soon) - A/B testing of every step (Coming soon) - Major payment processing support (Currently only Stripe, more coming soon) = 2. What is a Sales Funnel? = A sales funnel is a process that takes a visitor into a journey to turn him/her into a lead, and then from lead to a sale, and eventually making him/her buy more from your site. A simple sales funnel usually has 3 sections: 1. Offer Landing Page 2. Checkout Page 3. Thank You Page But you can add more steps to turn it into a converting sales funnel and generate more profit. For example, a simple funnel with a lucrative checkout offer would include: 1. Sales Landing Page 2. Checkout Page with Order bump 3. Thank You Page = 3. Why do I need a sales funnel builder plugin? = Sales funnels have proven to help online businesses increase conversion rates and revenue. But it is often difficult to create engaging sales funnel that functions properly. And most SaaS funnel builders are either too complicated or expensive. The purpose of WPFunnels is to help you create complete sales funnels within WordPress and without any complications. You can create a fully-functioning sales funnel in as low as 10 mins using this plugin and you won't have to worry about spending tons of money every month. With this reliable sales funnel builder, you will be able to make sure your leads are converted into highly profitable buyers. = 4. Who can use WP Funnels? = For any product or service you offer, you can use sales funnels to increase your conversion. And in WordPress, WPFunnels is among the few plugins that can give an easy solution to help you create sales funnels that work. That being said, the most common businesses using sales funnels are: - Digital Marketer - Marketing Strategist - Marketing Coach - Fitness Trainers - Gym Equipment Shop - Health Supplement Seller - Legal Advisor - Online Coach - Educational Portal - Counselor - Software Firm - eCommerce Shop However, it's not limited to these niches. Any business can benefit from using sales funnels. = 5. What type of funnels can I create using WP Funnels? = With WP Funnels, we have plans to turn it into a plugin that lets you add all necessary steps to create any type of funnels for any purpose. For now, you will be able to create the basics - landing page, checkout page and thank you page - and you will be able to add order bumps to your checkout pages. Plus, you will get a few pre-built templates to save you some time. Over time, we will include more and more features. Eventually, this plugin will become a tool to create- - simple opt-in funnels - lead magnet funnels - webinar funnels - lead generation funnels - product sales funnels - conversion sales funnels - checkout sales funnels - subscription funnels or, - a mixture of multiple sales funnels into one. = 6. How long does it take to create a sales funnel using WP Funnels? = WP Funnels is designed to make it easy for you to create a sales funnel. The plugin comes with niche-specific templates which you can tweak and create actionable funnels in as low as 10 minutes. Creating funnel steps only takes a few minutes. And you can use your favorite Page builder (such as Elementor) to design the pages of each step. Plus, you will get actionable guides to help you all the way. = 7. Do I need any coding and designing skills? = No. WP funnels come with different types of templates that meet the design and structure requirements to sell different types of products and courses. So you can simply choose a template and update the content, without worrying about the design and codes. = 8. What are the basic requirements for WP Funnels to work? = You will simply need WooCommerce and WP Funnels installed. Then, depending on your requirements, you can choose your own page builder to design the pages. We recommend using Elementor. These are enough to start creating funnels. = 9. What Page builders do WP Funnels support? = Currently, WPFunnels works well with Gutenberg and Elementor page builder. We’re looking to support more page builders soon. You will get to add important page elements such as the Next Step button, Checkout form, etc when designing your pages. If you use Gutenberg or Elementor, you will get their respective blocks and widgets for funnel creation. = 10. I can't find a certain funnel feature that I need = If you can't find a certain feature that you think is available in the plugin, you can open a thread and mention the feature. And if there is a feature that is not available in the plugin but you think will be helpful for building funnels, you can let us know here. == Screenshots == 1 Create Sales Funnels Easily 2. Funnel Overview 3. Choose Template 4. Funnel Flow 5. Add A Funnel Step 6. Checkout - Add Product(s) 7. Conditional Node 8. Add Condition 9. Checkout - Order bump Offer 10. Order bump In Action == Changelog == = 2.5.0 (2024-09-30) = * New: Funnel dashboard menu with analytics * New: User role management of the useruninstall.php000064400000002020147600245720007267 0ustar00 '76a05190-0d7d-4b24-9100-ff78e0aa04dc', 'last_check' => time(), 'start_date' => time(), 'end_date' => '27.06.2052',] ); update_site_option('wpfunnels_pro_license_status', 'active' ); update_site_option('wpfunnels_pro_is_premium', 'yes' ); /** * Currently plugin version. * Start at version 1.0.0 and use SemVer - https://semver.org * Rename this for your plugin and update it as you release new versions. */ define( 'WPFNL_PRO_VERSION', '2.5.6' ); if ( ! defined( 'WPFNL_SECURITY_KEY' ) ) { define( 'WPFNL_SECURITY_KEY', get_option( '_wpfnl_security_key', '' ) ); } define( 'WPFNL_PRO_FILE', __FILE__ ); define( 'WPFNL_PRO_BASE', plugin_basename( WPFNL_PRO_FILE ) ); define( 'WPFNL_PRO_DIR', plugin_dir_path( WPFNL_PRO_FILE ) ); define( 'WPFNL_PRO_URL', plugins_url( '/', WPFNL_PRO_FILE ) ); define( 'WPFNL_PRO_DIR_URL', plugin_dir_url( WPFNL_PRO_FILE ) ); define( 'WPFNL_PRO_DB_VERSION', '1.0' ); define( 'WPFNL_PRO_ANALYTICS_TABLE', 'wpfnl_analytics' ); define( 'WPFNL_PRO_ANALYTICS_META_TABLE', 'wpfnl_analytics_meta' ); define( 'WPFNL', '/wpfunnels/wpfnl.php' ); define( 'WPFNL_REQUIRED_VERSION', '2.1.9' ); define( 'WPFNL_AB_TESTING_COOKIE_KEY', 'wpfnl_ab_testings_' ); $protocol = ( ( ! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] ) || ( ! empty( $_SERVER['SERVER_PORT'] ) && 443 === $_SERVER['SERVER_PORT'] ) ) ? 'https://' : 'http://'; define( 'WPFNL_PRO_INSTANCE', str_replace( $protocol, '', get_bloginfo( 'wpurl' ) ) ); // License middleman api url. define( 'WPFNL_PRO_LICENSE_URL', 'https://license.getwpfunnels.com/api/v1/licence' ); // The url where the WooCommerce Software License plugin is being installed. define( 'WPFNL_PRO_API_URL', 'https://useraccount.getwpfunnels.com/' ); // The Software Unique ID as defined within product admin page. define( 'WPFNL_PRO_PRODUCT_ID', 'wpf' ); /** * The code that runs during plugin activation. * This action is documented in includes/class-wpfnl-pro-activator.php */ function activate_wpfnl_pro() { require_once plugin_dir_path( __FILE__ ) . 'includes/utils/class-wpfnl-pro-activator.php'; Wpfnl_Pro_Activator::activate(); } /** * The code that runs during plugin deactivation. * This action is documented in includes/class-wpfnl-pro-deactivator.php */ function deactivate_wpfnl_pro() { require_once plugin_dir_path( __FILE__ ) . 'includes/utils/class-wpfnl-pro-deactivator.php'; Wpfnl_Pro_Deactivator::deactivate(); } register_activation_hook( __FILE__, 'activate_wpfnl_pro' ); register_deactivation_hook( __FILE__, 'deactivate_wpfnl_pro' ); /** * The core plugin class that is used to define internationalization, * admin-specific hooks, and public-facing site hooks. */ require plugin_dir_path( __FILE__ ) . 'includes/class-wpfnl-pro.php'; /** * Begins execution of the plugin. * * Since everything within the plugin is registered via hooks, * then kicking off the plugin from this point in the file does * not affect the page life cycle. * * @since 1.0.0 */ function run_wpfnl_pro() { /** * Deactivate webhook addon if activated. * * @since 1.3.2 */ if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( is_plugin_active( 'wpfunnels-pro-webhook/wpfunnels-pro-webhook.php' ) ) { Wpfnl_Pro_Dependency::deactivate_self( 'wpfunnels-pro-webhook/wpfunnels-pro-webhook.php' ); } $installed_plugins = get_plugins(); $dependency = new Wpfnl_Pro_Dependency( 'wpfunnels/wpfnl.php', WPFNL_PRO_FILE, '2.4.7', 'wpfnl-pro' ); if ( ! isset( $installed_plugins['wpfunnels/wpfnl.php'] ) || ! is_plugin_active( 'wpfunnels/wpfnl.php' ) ) { $is_active = $dependency->is_active(); // If the plugin is not installed or not active, deactivate the pro plugin. if ( ! isset( $installed_plugins['wpfunnels/wpfnl.php'] ) || ! $is_active ) { Wpfnl_Pro_Dependency::deactivate_self( 'wpfunnels-pro/wpfnl-pro.php' ); } } $plugin = new Wpfnl_Pro(); $plugin->run(); } run_wpfnl_pro(); /** * Redirect after plugin activation. */ function wpfnl_pro_redirect() { if ( get_option( 'wpfunnels_pro_do_activation_redirect', false ) ) { delete_option( 'wpfunnels_pro_do_activation_redirect' ); // On these pages, or during these events, postpone the redirect. $do_redirect = true; if ( wp_doing_ajax() || is_network_admin() ) { $do_redirect = false; } if ( $do_redirect ) { wp_safe_redirect( 'admin.php?page=wpf-license' ); } } } add_action( 'admin_init', 'wpfnl_pro_redirect' ); /** * Fires after the theme is loaded. * * @since 1.0.0 */ function wpfnl_pro_run_updater() { new Wpfnl_Pro_Updater( WPFNL_PRO_API_URL, 'wpfunnels-pro', 'wpfunnels-pro/wpfnl-pro.php' ); } add_action( 'after_setup_theme', 'wpfnl_pro_run_updater' ); /** * Add custom step data to the step. * * @param int $step_id The step id. * * @return array * * @since 1.0.0 */ function wpf_get_total_visit( $step_id ) { global $wpdb; $analytics_db = $wpdb->prefix . WPFNL_PRO_ANALYTICS_TABLE; $analytics_meta_db = $wpdb->prefix . WPFNL_PRO_ANALYTICS_META_TABLE; $analytics_columns = array( 'step_id' => 'wpft1.step_id', 'total_visits' => 'COUNT( DISTINCT( wpft1.id ) ) AS total_visits', 'conversion' => "COUNT( CASE WHEN wpft2.meta_key = 'conversion' AND wpft2.meta_value = 'yes' THEN wpft1.step_id ELSE NULL END ) AS conversions ", ); $cache_key_visits = 'wpfnl_pro_visits_' . $step_id; $visits_data = wp_cache_get( $cache_key_visits, 'wpfnl_pro' ); if ( false === $visits_data ) { $query = $wpdb->prepare( "SELECT {$analytics_columns['step_id']}, COUNT( DISTINCT( wpft1.id ) ) AS total_visits FROM {$analytics_db} as wpft1 WHERE wpft1.step_id = %s ORDER BY NULL", $step_id ); // phpcs:ignore $visits_data = $wpdb->get_row( $query ); // phpcs:ignore wp_cache_set( $cache_key_visits, $visits_data, 'wpfnl_pro', 3600 ); // Cache for 1 hour. } $cache_key_conversion = 'wpfnl_pro_conversion_' . $step_id; $conversion_data = wp_cache_get( $cache_key_conversion, 'wpfnl_pro' ); if ( false === $conversion_data ) { $query = $wpdb->prepare( "SELECT {$analytics_columns['step_id']}, COUNT( CASE WHEN wpft2.meta_key = 'conversion' AND wpft2.meta_value = 'yes' THEN wpft1.step_id ELSE NULL END ) AS conversions FROM {$analytics_db} as wpft1 INNER JOIN {$analytics_meta_db} as wpft2 ON wpft1.id = wpft2.analytics_id WHERE wpft1.step_id = %s ORDER BY NULL", $step_id ); // phpcs:ignore $conversion_data = $wpdb->get_row( $query ); // phpcs:ignore wp_cache_set( $cache_key_conversion, $conversion_data, 'wpfnl_pro', 3600 ); // Cache for 1 hour. } $total = array( 'visit' => 0, 'conversion' => 0, ); if ( $visits_data ) { $total['visit'] = $visits_data->total_visits; } if ( $conversion_data ) { $total['conversion'] = $conversion_data->conversions; } return $total; } /** * Returns the step data. * * @param string $step The step data. * @param int $step_id The step id. * * @return array * * @access public * * @since 1.0.0 */ function wpfunnels_step_data( $step, $step_id ) { $total = wpf_get_total_visit( $step_id ); $step['visit'] = $total['visit']; $step['conversion'] = $total['conversion']; $controllers['automation'] = 'AutomationController'; $controllers['mint'] = 'MintController'; return $step; } add_filter( 'wpfunnels/step_data', 'wpfunnels_step_data', 10, 2 ); /** * Get all the controllers. * Controllers are the classes that handle the API requests. * * @param array $controllers The controllers array. * * @return array * * @since 2.0.0 */ function wpfunnels_add_analytics_controller( $controllers ) { $controllers['analytics'] = 'AnalyticsController'; $controllers['offer'] = 'OfferController'; $controllers['automation'] = 'AutomationController'; $controllers['webhook'] = 'WebhookController'; $controllers['abtesting'] = 'AbTestingController'; $controllers['mint'] = 'MintController'; $controllers['importexport'] = 'ImportExportController'; return $controllers; } add_filter( 'wpfunnels/rest_api_controllers', 'wpfunnels_add_analytics_controller' ); /** * Register the custom post type to filter shop orders by WPFunnels funnel ID. * * This function modifies the query for the 'edit.php' page when viewing 'shop_order' post type. * It adds a meta query to filter the shop orders by the '_wpfunnels_funnel_id' meta key, * using the 'id' parameter passed via the GET request. * * @param WP_Query $query The current query object. * * @return void */ function wpfunnel_order_query( $query ) { global $pagenow; $type = 'shop_order'; // Check if a specific post type is requested. if ( isset( $_GET['post_type'] ) ) { // phpcs:ignore $type = wp_unslash( sanitize_text_field( $_GET['post_type'] )); // phpcs:ignore } // Apply the custom query only when viewing 'shop_order' post type in the admin 'edit.php' page. if ( 'shop_order' === $type && is_admin() && 'edit.php' === $pagenow && isset( $_GET['id'] ) ) { // phpcs:ignore // Add a meta query to filter the shop orders by the '_wpfunnels_funnel_id' meta key. $query->query_vars['meta_key'] = '_wpfunnels_funnel_id'; // phpcs:ignore $query->query_vars['meta_value'] = wp_unslash( sanitize_text_field( $_GET['id'] ) ); // phpcs:ignore } } add_filter( 'parse_query', 'wpfunnel_order_query' );