PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.3
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.3
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
← All changes | includes/core/offer/class-wpfnl-offer.php +114 -124 3.12.13 → 3.13.3 View file →
@@ -163,23 +163,53 @@
163 163 * @return void
164 164 * @since 2.0.5
165 165 */
166 166 public function add_offer_product_to_cart(){
167 - $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0;
168 - $quantity = isset( $_POST['quantity'] ) ? intval( $_POST['quantity'] ) : 0;
169 - $variation_id = isset( $_POST['variation_id'] ) ? intval( $_POST['variation_id'] ) : 0;
170 - $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
171 - if( isset( $_POST['is_variable'], $_POST['attr'] ) && $_POST['is_variable'] == 'true' ){
172 - $payload = [
173 - 'product_id' => $product_id,
174 - 'data' => $_POST['attr'],
175 - ];
176 - $variation_id = $this->wpfnl_get_variation_id($payload);
167 + $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
168 +
169 + if ( ! wp_verify_nonce( $nonce, 'wpfnl_offer_nonce' ) ) {
170 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
177 171 }
178 172
173 + $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
179 174
180 - $total = $this->get_discounted_price( $step_id, $product_id, $quantity );
181 - $total = floatval($total)/intval($quantity);
175 + // Which product is sold, and how many, come from the step configuration.
176 + // Bail before touching the cart; the old code emptied it first and
177 + // validated nothing.
178 + $offer_item = Wpfnl_functions::resolve_offer_cart_item( $step_id );
179 +
180 + if ( is_wp_error( $offer_item ) ) {
181 + wp_send_json_error( array( 'message' => $offer_item->get_error_message() ), 400 );
182 + }
183 +
184 + // The request's only choice is which variation of that product to buy.
185 + // Match against the configured parent, never a requested one: WC_Product
186 + // throws on an id that is not a product.
187 + if ( isset( $_POST['is_variable'], $_POST['attr'] ) && 'true' == $_POST['is_variable'] && is_array( $_POST['attr'] ) ) {
188 + $variation_id = $this->wpfnl_get_variation_id(
189 + [
190 + 'product_id' => $offer_item['product_id'],
191 + 'data' => wc_clean( wp_unslash( $_POST['attr'] ) ),
192 + ]
193 + );
194 +
195 + $offer_item = Wpfnl_functions::resolve_offer_cart_item( $step_id, $variation_id );
196 +
197 + if ( is_wp_error( $offer_item ) ) {
198 + wp_send_json_error( array( 'message' => $offer_item->get_error_message() ), 400 );
199 + }
200 + }
201 +
202 + $product_id = $offer_item['product_id'];
203 + $variation_id = $offer_item['variation_id'];
204 + $quantity = $offer_item['quantity'];
205 +
206 + // Price the item that is actually added to the cart. Pricing the parent
207 + // of a variable product yields its cheapest variation's price.
208 + $priced_id = $variation_id ? $variation_id : $product_id;
209 +
210 + $total = $this->get_discounted_price( $step_id, $priced_id, $quantity );
211 + $total = floatval( $total ) / $quantity;
182 212 $custom_data = [
183 213 'custom_price' => $total,
184 214 ];
185 215
@@ -216,8 +246,13 @@
216 246
217 247 $type = get_post_meta($step_id, '_step_type', true);
218 248 $discount_data = get_post_meta($step_id, '_wpfnl_'.$type.'_discount', true);
219 249 $product = wc_get_product( $product_id );
250 +
251 + if ( ! $product ) {
252 + return 0;
253 + }
254 +
220 255 $total = isset($discount_data['discountApplyTo']) && 'regular' === $discount_data['discountApplyTo'] ? $product->get_regular_price() : $product->get_sale_price();
221 256 $total = !$total ? $product->get_price() : $total;
222 257 $total = floatval($total) * intval($quantity);
223 258 if( $discount_instance->maybe_time_bound_discount( $step_id ) && !$discount_instance->maybe_validate_discount_time( $step_id ) ) {
@@ -772,13 +807,10 @@
772 807 public function process_upsell_accepted_action() {
773 808
774 809 $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
775 810
776 - if ( $nonce && ! wp_verify_nonce( $nonce, 'wpfnl_upsell_accepted_nonce' ) ) {
777 - return array(
778 - 'status' => 'success',
779 - 'message' => __( 'Unauthorized request', 'wpfnl-pro' )
780 - );
811 + if ( ! wp_verify_nonce( $nonce, 'wpfnl_upsell_accepted_nonce' ) ) {
812 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
781 813 }
782 814
783 815
784 816
@@ -807,16 +839,10 @@
807 839 }
808 840
809 841 $order = wc_get_order($order_id);
810 842
811 - $wpfnl_functions_instance = new Wpfnl_functions();
812 - if( method_exists($wpfnl_functions_instance,'is_valid_order_owner')){
813 - if (!Wpfnl_functions::is_valid_order_owner($order, $order_key)) {
814 - wp_send_json([
815 - 'status' => 'error',
816 - 'message' => __( 'Unauthorized request', 'wpfnl-pro' ),
817 - ]);
818 - }
843 + if ( ! Wpfnl_functions::is_valid_order_owner( $order, $order_key ) ) {
844 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
819 845 }
820 846
821 847 if ( $order_id && $product_id ) {
822 848 $data = array(
@@ -848,13 +874,10 @@
848 874 */
849 875 public function process_downsell_accepted_action() {
850 876
851 877 $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
852 - if ( $nonce && ! wp_verify_nonce( $nonce, 'wpfnl_downsell_accepted_nonce' ) ) {
853 - return array(
854 - 'status' => 'success',
855 - 'message' => __( 'Unauthorized request', 'wpfnl-pro' )
856 - );
878 + if ( ! wp_verify_nonce( $nonce, 'wpfnl_downsell_accepted_nonce' ) ) {
879 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
857 880 }
858 881
859 882 $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
860 883 $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0;
@@ -882,16 +905,10 @@
882 905 }
883 906
884 907 $order = wc_get_order($order_id);
885 908
886 - $wpfnl_functions_instance = new Wpfnl_functions();
887 - if( method_exists($wpfnl_functions_instance,'is_valid_order_owner')){
888 - if (!Wpfnl_functions::is_valid_order_owner($order, $order_key)) {
889 - wp_send_json([
890 - 'status' => 'error',
891 - 'message' => __( 'Unauthorized request', 'wpfnl-pro' ),
892 - ]);
893 - }
909 + if ( ! Wpfnl_functions::is_valid_order_owner( $order, $order_key ) ) {
910 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
894 911 }
895 912
896 913 $all_products = [];
897 914 $product_data = array();
@@ -927,12 +944,9 @@
927 944 */
928 945 public function process_upsell_rejected_action() {
929 946 $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
930 947 if ( ! wp_verify_nonce( $nonce, 'wpfnl_upsell_rejected_nonce' ) ) {
931 - return array(
932 - 'status' => 'success',
933 - 'message' => __( 'Unauthorized request', 'wpfnl-pro' )
934 - );
948 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
935 949 }
936 950
937 951 $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
938 952 $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0;
@@ -965,8 +979,14 @@
965 979 'message' => __( 'Order does not exist', 'wpfnl-pro' ),
966 980 );
967 981
968 982 if ( $order_id ) {
983 + $order = wc_get_order( $order_id );
984 +
985 + if ( ! Wpfnl_functions::is_valid_order_owner( $order, $order_key ) ) {
986 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
987 + }
988 +
969 989 $data = array(
970 990 'order_id' => $order_id,
971 991 'product_id' => $product_id,
972 992 'quantity' => $quantity,
@@ -995,12 +1015,9 @@
995 1015 */
996 1016 public function process_downsell_rejected_action() {
997 1017 $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
998 1018 if ( ! wp_verify_nonce( $nonce, 'wpfnl_downsell_rejected_nonce' ) ) {
999 - return array(
1000 - 'status' => 'success',
1001 - 'message' => __( 'Unauthorized request', 'wpfnl-pro' )
1002 - );
1019 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
1003 1020 }
1004 1021 $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
1005 1022 $funnel_id = isset( $_POST['funnel_id'] ) ? intval( $_POST['funnel_id'] ) : 0;
1006 1023 $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0;
@@ -1032,8 +1049,14 @@
1032 1049 'message' => __( 'Order does not exist', 'wpfnl-pro' ),
1033 1050 );
1034 1051
1035 1052 if ( $order_id ) {
1053 + $order = wc_get_order( $order_id );
1054 +
1055 + if ( ! Wpfnl_functions::is_valid_order_owner( $order, $order_key ) ) {
1056 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
1057 + }
1058 +
1036 1059 $data = array(
1037 1060 'order_id' => $order_id,
1038 1061 'product_id' => $product_id,
1039 1062 'quantity' => $quantity,
@@ -1060,16 +1083,27 @@
1060 1083 * @since 1.0.0
1061 1084 */
1062 1085 public function wpfnl_after_process_with_auth_ajax()
1063 1086 {
1064 - $step_id = $_POST['step_id'];
1065 - $order_id = $_POST['order_id'];
1066 - $order_key = $_POST['order_key'];
1087 + $nonce = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
1088 +
1089 + if ( ! wp_verify_nonce( $nonce, 'wpfnl_offer_nonce' ) ) {
1090 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
1091 + }
1092 +
1093 + $step_id = isset( $_POST['step_id'] ) ? intval( $_POST['step_id'] ) : 0;
1094 + $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0;
1095 + $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash( $_POST['order_key'] ) ) : '';
1096 +
1097 + if ( ! Wpfnl_functions::is_valid_order_owner( wc_get_order( $order_id ), $order_key ) ) {
1098 + wp_send_json_error( array( 'message' => __( 'Unauthorized request', 'wpfnl' ) ), 403 );
1099 + }
1100 +
1067 1101 $step_type = get_post_meta($step_id, '_step_type', true);
1068 1102 $intent_id = '';
1069 1103
1070 1104 if (isset($_POST['intent_id'])) {
1071 - $intent_id = $_POST['intent_id'];
1105 + $intent_id = sanitize_text_field( wp_unslash( $_POST['intent_id'] ) );
1072 1106 }
1073 1107
1074 1108 $extra_data = [];
1075 1109 $extra_data['step_id'] = $step_id;
@@ -1079,9 +1113,9 @@
1079 1113 $extra_data['intent_id'] = $intent_id;
1080 1114
1081 1115 $step_meta = get_post_meta($step_id, '_wpfnl_'.$step_type.'_products', true);
1082 1116
1083 - if ($step_meta[0]) {
1117 + if ( ! empty( $step_meta[0] ) ) {
1084 1118 $step_meta = $step_meta[0];
1085 1119 } else {
1086 1120 wp_send_json([
1087 1121 'status' => 'error',
@@ -1183,19 +1217,23 @@
1183 1217 }
1184 1218
1185 1219 $offer_product_id = isset( $offer_product[ 'id' ] ) ? $offer_product[ 'id' ] : 0;
1186 1220 $offer_total_price = isset( $offer_product[ 'total' ] ) ? $offer_product[ 'total' ] : 0;
1187 - $shipping = $this->calculate_offer_product_shipping_price( $order, $offer_product_id, $offer_total_price );
1221 + $offer_qty = isset( $offer_product[ 'qty' ] ) ? $offer_product[ 'qty' ] : 1;
1222 + $shipping = $this->calculate_offer_product_shipping_price( $order, $offer_product_id, $offer_total_price, $offer_qty );
1188 1223 $shipping_price = isset( $shipping[ 'total' ] ) ? $shipping[ 'total' ] : 0;
1189 1224 $shipping_tax = isset( $shipping[ 'total_tax' ] ) ? $shipping[ 'total_tax' ] : 0;
1190 1225 $shipping_price = $shipping_price + $shipping_tax;
1191 1226
1227 + /**
1228 + * Charge the derived shipping alongside the offer product. The same
1229 + * $shipping array is handed to after_offer_charged() below, which
1230 + * records it as a shipping line, so the amount charged and the
1231 + * amount recorded on the order stay in lockstep.
1232 + */
1192 1233 $offer_product[ 'price' ] = $offer_total_price + $shipping_price;
1193 1234 $offer_product[ 'total' ] = $offer_total_price + $shipping_price;
1194 1235
1195 - $offer_product[ 'price' ] = $offer_total_price;
1196 - $offer_product[ 'total' ] = $offer_total_price;
1197 -
1198 1236 // Handle chained products if pro plugin is active
1199 1237 $total_chained_product_price = 0;
1200 1238 if( class_exists( '\WPFunnelsPro\Compatibility\ChainedProduct' ) && Wpfnl_functions::is_wc_active() ){
1201 1239 $chained_product_class_instance = new \WPFunnelsPro\Compatibility\ChainedProduct();
@@ -1385,9 +1423,13 @@
1385 1423 * @param $step_id
1386 1424 * @param $funnel_id
1387 1425 * @return string
1388 1426 */
1389 - public function get_next_step_url( \WC_Order $order , $step_id, $funnel_id ) {
1427 + public function get_next_step_url( $order , $step_id, $funnel_id ) {
1428 + if ( ! is_a( $order, 'WC_Order' ) ) {
1429 + return false;
1430 + }
1431 +
1390 1432 $next_step = Wpfnl_functions::get_next_conditional_step( $funnel_id, $step_id, $order, $this->offerStatus );
1391 1433 $next_step = apply_filters( 'wpfunnels/next_step_data', $next_step );
1392 1434 $next_step_id = isset($next_step['step_id']) ? $next_step['step_id'] : '';
1393 1435
@@ -1574,80 +1616,19 @@
1574 1616
1575 1617 /**
1576 1618 * @desc calculate shipping cost for offer steps (upsell/downsell)
1577 1619 *
1620 + * Delegates to the shared helper so the free and pro copies of this handler
1621 + * cannot drift apart.
1622 + *
1578 1623 * @param \WC_Order $order
1579 1624 * @param $product_id
1580 1625 * @param $total_price
1581 - * @return int|mixed|void
1626 + * @param int $qty
1627 + * @return array
1582 1628 */
1583 - private function calculate_offer_product_shipping_price( \WC_Order $order, $product_id, $total_price ) {
1584 - if( !$product_id ) {
1585 - return;
1586 - }
1587 - if( !WC()->shipping()->get_shipping_classes() ) {
1588 - return;
1589 - }
1590 -
1591 - $product = wc_get_product( $product_id );
1592 - if( $product && $product->is_virtual() ) {
1593 - return;
1594 - }
1595 -
1596 - $shipping = [];
1597 - $shipping_methods = $order->get_shipping_methods();
1598 - $offer_settings = Wpfnl_functions::get_offer_settings();
1599 - $offer_orders_option = isset( $offer_settings[ 'offer_orders' ] ) ? $offer_settings[ 'offer_orders' ] : '';
1600 -
1601 - if( !empty( $shipping_methods ) ) {
1602 - foreach( $shipping_methods as $key => $method ) {
1603 - if( $method ){
1604 - $shipping = [
1605 - 'name' => $method->get_name(),
1606 - 'method_title' => $method->get_method_title(),
1607 - 'method_id' => $method->get_method_id(),
1608 - 'instance_id' => $method->get_instance_id(),
1609 - 'total' => $method->get_total(),
1610 - 'total_tax' => $method->get_total_tax(),
1611 - 'taxes' => $method->get_taxes(),
1612 - 'tax_percentage' => ( $method->get_total() != 0 ) || ( $method->get_total() != 0.00 ) ? ( $method->get_total_tax() * 100 ) / $method->get_total() : 0,
1613 - ];
1614 - }
1615 -
1616 - }
1617 - }
1618 -
1619 - if( 'main-order' === $offer_orders_option ) {
1620 - $wc_shipping_zones = \WC_Shipping_Zones::get_zones();
1621 -
1622 - if( !empty( $wc_shipping_zones ) && isset( $shipping[ 'method_id' ] ) && 'flat_rate' === $shipping[ 'method_id' ] ) {
1623 - foreach( $wc_shipping_zones as $zone ) {
1624 - if( isset( $zone[ 'shipping_methods' ] ) && !empty( $zone[ 'shipping_methods' ] ) ) {
1625 - foreach( $zone[ 'shipping_methods' ] as $method ) {
1626 - if( 'WC_Shipping_Flat_Rate' === get_class( $method ) ) {
1627 - if( isset( $method->instance_id ) && isset( $shipping[ 'instance_id' ] ) ) {
1628 - if( $method->instance_id == $shipping[ 'instance_id' ] ) {
1629 - $product_shipping_class_id = $product->get_shipping_class_id();
1630 - $instance_settings = isset( $method->instance_settings ) ? $method->instance_settings : [];
1631 - $shipping_cost = isset( $instance_settings[ 'class_cost_' . $product_shipping_class_id ] ) ? $instance_settings[ 'class_cost_' . $product_shipping_class_id ] : $instance_settings[ 'no_class_cost' ];
1632 - $shipping_cost = $shipping_cost ? $shipping_cost : 0;
1633 - $tax_percentage = isset( $shipping[ 'tax_percentage' ] ) ? $shipping[ 'tax_percentage' ] : 0;
1634 - $total_tax = ( $shipping_cost * $tax_percentage ) / 100;
1635 - $shipping[ 'total' ] = $shipping_cost;
1636 - $shipping[ 'total_tax' ] = $total_tax;
1637 - $shipping[ 'taxes' ] = [
1638 - 'total' => [ $total_tax ]
1639 - ];
1640 - }
1641 - }
1642 - }
1643 - }
1644 - }
1645 - }
1646 - }
1647 - }
1648 - unset( $shipping[ 'tax_percentage' ] );
1649 - return $shipping;
1629 + private function calculate_offer_product_shipping_price( \WC_Order $order, $product_id, $total_price, $qty = 1 ) {
1630 + return Wpfnl_functions::get_offer_shipping( $order, $product_id, $total_price, $qty );
1650 1631 }
1651 1632
1652 1633 /**
1653 1634 * Check if current step is upsell or downsell
@@ -1843,8 +1824,17 @@
1843 1824 $step_type = get_post_meta( $step_id, '_step_type', true );
1844 1825 wc_add_order_item_meta( $item_id, "_wpfunnels_{$step_type}", 'yes' );
1845 1826 wc_add_order_item_meta( $item_id, '_wpfunnels_step_id', $offer_product['step_id'] );
1846 1827 $order->update_meta_data( '_wpfunnels_offer_' . $step_id, $step_id );
1828 +
1829 + // Record any shipping derived for this offer so the amount charged
1830 + // and the amount recorded on the order stay in lockstep.
1831 + if ( ! empty( $shipping_data ) ) {
1832 + $offer_name = isset( $offer_product['name'] ) ? $offer_product['name'] : '';
1833 + $offer_qty = isset( $offer_product['qty'] ) ? $offer_product['qty'] : 1;
1834 + $order = Wpfnl_functions::add_offer_shipping_to_order( $order, $shipping_data, $offer_name, $offer_qty );
1835 + }
1836 +
1847 1837 $order->calculate_totals();
1848 1838 $order->save();
1849 1839
1850 1840 $result = array(