rule_data = $rule_data; if ( ! empty( $rule_data['cart_adjustments'] ) ) { foreach ( $rule_data['cart_adjustments'] as $cart_adj_data ) { try { $cart_adj = WDP_Cart_Adj_Registry::get_instance()->create_adj( $cart_adj_data ); $this->cart_adjustments[] = $cart_adj; } catch ( Exception $exception ) { } } } if ( ! empty( $rule_data['conditions'] ) ) { foreach ( $rule_data['conditions'] as $condition_data ) { try { $condition = WDP_Condition_Registry::get_instance()->create_condition( $condition_data ); $this->conditions[] = $condition; } catch ( Exception $exception ) { } } } if ( ! empty( $rule_data['limits'] ) ) { foreach ( $rule_data['limits'] as $limit_data ) { try { $limit = WDP_Limit_Registry::get_instance()->create_limit( $limit_data ); $this->limits[] = $limit; } catch ( Exception $exception ) { } } } if ( $this->has_bulk() ) { $this->rule_range_adjustments = new WDP_Rule_Range_Adjustments( $this->rule_data ); } } public function has_bulk() { return ! empty( $this->rule_data['bulk_adjustments']['ranges'] ); } public function get_needle_cary_sort() { return isset( $this->rule_data['options']['apply_to'] ) ? $this->rule_data['options']['apply_to'] : 'expensive'; } public function has_roles_discount() { return ! empty( $this->rule_data['role_discounts']['rows'] ); } public function get_id() { return (int) $this->rule_data['id']; } /** * @param $cart WDP_Cart * * @return boolean */ public function apply_to_cart( $cart ) { global $wp_filter; $current_wp_filter = $wp_filter; $result = false; try { $result = $this->process_apply_to_cart( $cart ); } catch ( WDP_Rule_Execution_Timeout $e ) { $this->leave_disable_notice(); WDP_Database::mark_as_disabled_by_plugin( $this->get_id() ); } $wp_filter = $current_wp_filter; return $result; } /** * @param $cart WDP_Cart * @throws WDP_Rule_Execution_Timeout * * @return boolean */ private function process_apply_to_cart( $cart ) { $this->exec_rule_start = microtime( true ); $this->rule_data = apply_filters( 'wdp_before_apply_rule', $this->rule_data, $cart ); $is_apply = apply_filters( 'wdp_is_apply_rule', true, $this->rule_data, $cart ); if ( ! $is_apply ) { return false; } if ( ! $this->check_limits( $cart ) ) { return false; } $this->check_execution_time( $cart->get_context() ); if ( ! $this->check_conditions( $cart ) ) { return false; } $this->check_execution_time( $cart->get_context() ); $set_collection = $this->create_sets($cart); if ( ! $set_collection ) { return false; } foreach ( $set_collection->get_sets() as $index => &$set ) { $this->apply_product_adjustment( $set ); } $this->check_execution_time( $cart->get_context() ); $this->add_free_products( $cart, $set_collection ); $this->check_execution_time( $cart->get_context() ); if ( $set_collection->is_empty() ) { return false; } if ( ! empty( $this->rule_data['sortable_blocks_priority'] ) && is_array( $this->rule_data['sortable_blocks_priority'] ) ) { foreach ( $this->rule_data['sortable_blocks_priority'] as $block_name ) { $initial_collection = clone $set_collection; if ( 'roles' == $block_name ) { if ( $set_collection instanceof WDP_Cart_Items_Collection ) { $set_collection = $this->collect_sets( $set_collection->get_items(), $cart ); } $this->apply_roles_discount( $cart, $set_collection ); } elseif ( 'bulk-adjustments' == $block_name && ( empty( $this->rule_data['role_discounts']['dont_apply_bulk_if_roles_matched'] ) || ! $this->is_set_collections_equal( $initial_collection, $set_collection ) ) ) { $this->apply_bulk_adjustment( $cart, $set_collection ); } } } else { $this->apply_roles_discount( $cart, $set_collection ); $this->apply_bulk_adjustment( $cart, $set_collection ); } $this->check_execution_time( $cart->get_context() ); $this->apply_cart_adjustment( $cart ); $this->check_execution_time( $cart->get_context() ); if ( $set_collection instanceof WDP_Cart_Set_Collection ) { $collection = $this->decompose_sets( $set_collection ); } else { $collection = $set_collection; } $this->check_execution_time( $cart->get_context() ); $this->apply_changes_to_cart( $cart, $collection ); return true; } /** * @param $collection_a WDP_Cart_Set_Collection * @param $collection_b WDP_Cart_Set_Collection * * @return boolean */ private function is_set_collections_equal( $collection_a, $collection_b ) { return $collection_a->get_hash() === $collection_b->get_hash(); } protected function check_limits( $cart ) { foreach ( $this->limits as $limit ) { if ( ! $limit->check( $cart, $this->get_id() ) ) { return false; } } return true; } /** * @param $cart WDP_Cart * * @return bool */ protected function check_conditions( $cart ) { if ( empty( $this->conditions ) ) { return true; } $relationship = $this->get_conditions_relationship( $this->rule_data ); $result = false; foreach ( $this->conditions as $condition ) { if ( ! $condition->check( $cart ) ) { if ( 'and' == $relationship ) { return false; } continue; } // check_conditions always true if relationship not 'and' and at least one condition checked $result = true; } return $result; } protected function check_conditions_for_a_match( $cart ) { if ( empty( $this->conditions ) ) { return true; } $relationship = $this->get_conditions_relationship( $this->rule_data ); $result = false; foreach ( $this->conditions as $condition ) { if ( ! $condition->match( $cart ) ) { if ( 'and' == $relationship ) { return false; } } else { if ( 'or' == $relationship ) { return true; } else { $result = true; } } } return $result; } protected function get_conditions_relationship( $rule_data ) { return ! empty( $rule_data['additional']['conditions_relationship'] ) ? $rule_data['additional']['conditions_relationship'] : 'and'; } protected function get_roles_rows( $rule_data ) { if ( empty( $rule_data['role_discounts']['rows'] ) || ! is_array( $rule_data['role_discounts']['rows'] ) ) { return array(); } $rows = $rule_data['role_discounts']['rows']; foreach ( $rows as &$row ) { if ( ! isset( $row['roles'], $row['discount_type'], $row['discount_value'] ) && ! is_array( $row['roles'] ) ) { unset( $row ); } } return $rows; } protected function check_attempt( $current_attempt ) { $attempts_limit = $this->get_attempt_limit(); return $attempts_limit < 0 || $current_attempt < $attempts_limit; } protected function get_attempt_limit() { return isset( $this->rule_data['options']['repeat'] ) ? (int) $this->rule_data['options']['repeat'] : - 1; } protected function get_filters() { $filters = isset( $this->rule_data['filters'] ) ? $this->rule_data['filters'] : array(); if ( empty( $filters ) ) { $filters[] = array( 'qty' => 1, 'qty_end' => 1, 'type' => 'products', 'method' => 'any', 'value' => array(), ); } foreach ( $filters as &$filter ) { if ( ! isset( $filter['qty_end'] ) ) { $filter['qty_end'] = $filter['qty']; } if ( empty( $filter['value'] ) ) { $filter['value'] = array(); } } return $filters; } /** * @param $cart WDP_Cart * @throws WDP_Rule_Execution_Timeout * * @return WDP_Cart_Set_Collection|false */ protected function create_sets( &$cart ) { if ( ! $cart_mutable_items = $cart->get_mutable_items() ) { return false; } uasort( $cart_mutable_items, array( $this, 'sort_items' ) ); $cart_mutable_items = array_values( $cart_mutable_items); $cart->purge_mutable_items(); return $this->collect_sets( $cart_mutable_items, $cart ); } /** * @param WDP_Cart_Item[] $cart_items * @param WDP_Cart $cart * * @return WDP_Cart_Set_Collection */ protected function collect_sets( $cart_items, $cart ) { $set_collector = new WDP_Rule_Set_Collector( $this->get_id() ); $set_collector->register_check_execution_time_function( array( $this, 'check_execution_time' ), $cart->get_context() ); $set_collector->set_limit( $this->get_attempt_limit() ); $set_collector->add_items( $cart_items ); $set_collector->apply_filters( $cart, $this->get_filters() ); return $set_collector->collect_sets( $cart, 'legacy' ); } protected function sort_items( $item1, $item2 ) { /** * @var $item1 WDP_Cart_Item * @var $item2 WDP_Cart_Item */ $price1 = $item1->get_price(); $price2 = $item2->get_price(); if ( 'cheap' === $this->get_needle_cary_sort() ) { return $price1 - $price2; } elseif ( 'expensive' === $this->get_needle_cary_sort() ) { return $price2 - $price1; } return 0; } public function has_product_adjustment() { return isset( $this->rule_data['product_adjustments']['type'] ) ? $this->rule_data['product_adjustments']['type'] : false; } /** * @param $set WDP_Cart_Set */ protected function apply_product_adjustment( &$set ) { if ( ! $this->has_product_adjustment() ) { return; } $adj_type = isset( $this->rule_data['product_adjustments']['type'] ) ? $this->rule_data['product_adjustments']['type'] : false; if ( ! $adj_type ) { return; } $adjustment_data = isset( $this->rule_data['product_adjustments'][ $adj_type ] ) ? $this->rule_data['product_adjustments'][ $adj_type ] : false; if ( ! $adjustment_data ) { return; } $limit = isset( $this->rule_data['product_adjustments']['max_discount_sum'] ) ? $this->rule_data['product_adjustments']['max_discount_sum'] : null; $price_calculator = new WDP_Price_Calculator(); if ( $limit !== null ) { $price_calculator->set_discount_total_limit( $limit ); } if ( 'total' === $adj_type ) { $adjustment = $adjustment_data; if ( empty( $adjustment ) || empty( $adjustment['type'] ) ) { return; } $set = $price_calculator->set_type( $adjustment['type'] )->set_value( $adjustment['value'] )->apply_to_set()->calculate_price_for_set( $set ); } elseif ( 'split' === $adj_type ) { $adjustments = $adjustment_data; $iterator = new MultipleIterator; $iterator->attachIterator( new ArrayIterator( $adjustments ) ); $iterator->attachIterator( new ArrayIterator( range( 0, count( $set->get_items() ) ) ) ); foreach ( $iterator as $list ) { $adjustment = $list[0]; $index = $list[1]; $items = $set->get_items_by_position( $index ); /** * @var $item WDP_Cart_Item */ if ( empty( $adjustment['type'] ) ) { continue; } $items = $price_calculator->set_type( $adjustment['type'] )->set_value( $adjustment['value'] )->calculate_price_for_items( $this->get_id(), $items ); if ( ! $items ) { continue; } $prices = array(); foreach ( $items as $item ) { /** * @var $item WDP_Cart_Item */ $prices[] = $item->get_price(); } $set->set_price_for_items_by_position( $index, $prices ); } } return; } /** * @var $attempt integer * * @return bool */ protected function check_deal_attempt( $attempt ) { $attempts_limit = (int) $this->rule_data['get_products']['repeat']; return $attempts_limit < 0 || $attempt < $attempts_limit; } public function has_free_products() { return isset( $this->rule_data['get_products']['value'] ) ? $this->rule_data['get_products']['value'] : false; } /** * @param $cart WDP_Cart * @param $set_collection WDP_Cart_Set_Collection * */ protected function add_free_products( &$cart, $set_collection ) { if ( ! $this->has_free_products() ) { return; } $adjustments = isset( $this->rule_data['get_products']['value'] ) ? $this->rule_data['get_products']['value'] : false; if ( empty( $adjustments ) ) { return; } $temporary_qties = $this->calculate_temporary_qties( $cart, $set_collection ); $attempt = 1; while ( $attempt <= $set_collection->get_count() ) { foreach ( $adjustments as $deal_adjustment ) { if ( empty( $deal_adjustment['qty'] ) || empty( $deal_adjustment['value'] ) ) { continue; } $qty = (int) $deal_adjustment['qty']; foreach ( $deal_adjustment['value'] as $deal_adjustment_product_id ) { if ( $qty < 1 ) { break; } $product = $this->get_product( $deal_adjustment_product_id ); if ( ! $product ) { continue; } $qty_used = $cart->get_qty_used( $deal_adjustment_product_id ); if ( isset( $temporary_qties[ $deal_adjustment_product_id ] ) ) { $qty_used += $temporary_qties[ $deal_adjustment_product_id ]; } $qty_to_add = $this->get_product_qty_available_for_sale( $product, $qty_used, $qty ); if ( $qty_to_add > 0 ) { $cart->gift_product( $this->get_id(), $product, $qty_to_add ); $qty -= $qty_to_add; } } } if ( ! $this->check_deal_attempt( $attempt ) ) { break; }; $attempt ++; } } /** * @param $product WC_Product * @param $qty_used integer * @param $qty_required integer * * @return int */ protected function get_product_qty_available_for_sale( $product, $qty_used, $qty_required ) { $available_qty = 0; if ( $product->managing_stock() ) { if ( $product->backorders_allowed() ) { $available_qty = $qty_required; } else { $available_for_now = $product->get_stock_quantity() - $qty_used; $available_qty = $available_for_now >= $qty_required ? $qty_required : $available_for_now; } } else { $available_qty = $qty_required; } return $available_qty; } /** * @param $cart WDP_Cart * @param $set_collection WDP_Cart_Set_Collection * * @return array */ protected function calculate_temporary_qties( $cart, $set_collection ) { $temporary_qties = array(); $collection = $this->decompose_sets( $set_collection ); foreach ( $collection->get_items() as $item ) { /** * @var $item WDP_Cart_Item */ if ( $item->is_temporary() ) { $original_item = $cart->get_item_data_by_hash( $item->get_hash() ); $product_id = $original_item['product_id']; if ( ! isset( $temporary_qties[ $product_id ] ) ) { $temporary_qties[ $product_id ] = 0; } $temporary_qties[ $product_id ] += $item->get_qty(); } } return $temporary_qties; } /** * @param $cart WDP_Cart * @param $set_collection WDP_Cart_Set_Collection * * @return WDP_Cart_Set_Collection */ protected function apply_roles_discount( $cart, &$set_collection ) { if ( ! $this->has_roles_discount() ) { return $set_collection; } if ( ! ( $current_user_roles = $cart->get_context()->get_customer_roles() ) ) { return $set_collection; } foreach ( $this->get_roles_rows( $this->rule_data ) as $roles_rule ) { if ( ! count( array_intersect( $roles_rule['roles'], $current_user_roles ) ) ) { continue; } $price_calculator = new WDP_Price_Calculator(); $price_calculator->set_type( $roles_rule['discount_type'] )->set_value( $roles_rule['discount_value'] ); $price_calculator->apply_to_set(); $sets = $set_collection->get_sets(); $set_collection->purge(); foreach ( $sets as $set ) { $set_collection->add( $price_calculator->calculate_price_for_set( $set ) ); } } return $set_collection; } /** * @param $cart WDP_Cart * @param $set_collection WDP_Cart_Set_Collection */ protected function apply_bulk_adjustment( $cart, &$set_collection ) { if ( ! $this->has_bulk() || is_null( $this->rule_range_adjustments ) ) { return; } $set_collection = $this->rule_range_adjustments->apply_adjustments( $cart, $set_collection ); } /** * @param $product_id int * * @return WC_Product */ protected function get_product( $product_id ) { if ( isset( $this->loaded_products[ $product_id ] ) ) { return $this->loaded_products[ $product_id ]; } return $this->loaded_products[ $product_id ] = wc_get_product( $product_id ); } /** * @param $cart WDP_Cart * */ protected function apply_cart_adjustment( &$cart ) { foreach ( $this->cart_adjustments as $cart_adjustment ) { /** * @var $cart_adjustment WDP_Cart_Adjustment */ $cart_adjustment->apply_to_cart( $cart, $this->get_id() ); } } /** * @param $set_collection WDP_Cart_Set_Collection * * @return WDP_Cart_Items_Collection */ protected function decompose_sets( $set_collection ) { $items_collection = new WDP_Cart_Items_Collection( $this->get_id() ); foreach ( $set_collection->get_sets() as $set ) { /** * @var $set WDP_Cart_Set */ foreach ( $set->get_items() as $hash => $item ) { /** * @var $item WDP_Cart_Item */ $new_item = clone $item; $new_item->set_qty( $item->get_qty() * $set->get_qty() ); $items_collection->add( $new_item ); } } return $items_collection; } /** * @param $cart WDP_Cart * @param $collection WDP_Cart_Items_Collection */ protected function apply_changes_to_cart( &$cart, $collection ) { if ( $this->is_exclusive() ) { $collection->make_items_immutable(); } foreach ( $collection->get_items() as $item ) { $cart->add_to_cart( $item ); } $cart->destroy_empty_items(); } public function is_exclusive() { return (bool) $this->rule_data['exclusive']; } public function get_priority() { return (int) $this->rule_data['priority']; } public function get_product_dependencies() { $dependencies = array(); foreach ( $this->get_filters() as $filter ) { $dependencies[] = array( 'qty' => $filter['qty'], 'qty_end' => $filter['qty_end'], 'type' => $filter['type'], 'method' => $filter['method'], 'values' => $filter['value'], ); } return $dependencies; } /** * @param $context WDP_Cart_Context * * @return array|bool */ public function get_bulk_details( $context ) { if ( ! $this->has_bulk() ) { return array(); } $bulk = $this->maybe_update_bulk_prices( $context ); $ranges = $bulk['ranges']; $ret = array( 'type' => $bulk['type'], 'discount' => $bulk['discount_type'], 'ranges' => array(), 'table_message' => $bulk['table_message'], ); foreach ( $ranges as $range ) { $ret['ranges'][] = array( 'from' => $range['from'], 'to' => $range['to'], 'value' => $range['value'], ); } return $ret; } /** * Change bulk prices according ONLY roles rules. * Apply only if roles has larger priority and bulk adjustments type is fixed price. * Affected only in bulk table. * We need not to apply other rule sections because fixed price from bulk overrides them. * Be advised, you must rework this method if order of submethods in "apply_to_cart" has been changed. * * @return array * @var $context WDP_Cart_Context * */ private function maybe_update_bulk_prices( $context ) { $bulk = $this->rule_data['bulk_adjustments']; if ( empty( $bulk['discount_type'] ) || "price__fixed" !== $bulk['discount_type'] ) { return $bulk; } $update = false; if ( ! empty( $this->rule_data['sortable_blocks_priority'] ) && is_array( $this->rule_data['sortable_blocks_priority'] ) ) { $role_priority = - 1; $bulk_priority = - 1; foreach ( $this->rule_data['sortable_blocks_priority'] as $key => $priority_label ) { if ( "roles" == $priority_label ) { $role_priority = $key; } elseif ( "bulk-adjustments" == $priority_label ) { $bulk_priority = $key; } } if ( $role_priority !== - 1 && $bulk_priority !== - 1 && ( $role_priority > $bulk_priority ) ) { $update = true; } } if ( $update && ! empty( $bulk['ranges'] ) ) { $price_calculator = new WDP_Price_Calculator(); $roles_rule = array(); $roles_rules = $this->get_roles_rows( $this->rule_data ); if ( ! $roles_rules ) { return $bulk; } if ( ! ( $current_user_roles = $context->get_customer_roles() ) ) { return $bulk; } foreach ( $roles_rules as $tmp_roles_rule ) { if ( empty( $tmp_roles_rule['roles'] ) ) { continue; } $roles = $tmp_roles_rule['roles']; if ( ! count( array_intersect( $roles, $current_user_roles ) ) ) { continue; } $roles_rule = $tmp_roles_rule; } foreach ( $bulk['ranges'] as &$range ) { if ( empty( $range['value'] ) || ! isset( $roles_rule['discount_type'], $roles_rule['discount_value'] ) ) { continue; } $price_calculator->set_type( $roles_rule['discount_type'] )->set_value( $roles_rule['discount_value'] ); $range['value'] = $price_calculator->calculate_single_price( $range['value'] ); } } return $bulk; } /** * @param $cart WDP_Cart * @param WC_Product|int $the_product * * @throws Exception * * @return bool */ public function is_product_matched( $cart, $the_product ) { $limits_verified = $this->check_limits( $cart ); if ( ! $limits_verified ) { return false; } $conditions_verified = $this->check_conditions_for_a_match( $cart ); if ( ! $conditions_verified ) { return false; } $matched = $this->match_product_with_filters( $the_product ); return $matched; } /** * @param WC_Product|int $the_product * * @throws Exception * * @return bool */ protected function match_product_with_filters( $the_product ) { $product = null; if ( $the_product instanceof WC_Product ) { $product = $the_product; } elseif ( is_numeric( $the_product ) ) { $product = WC()->product_factory->get_product( $the_product ); } if ( ! $product ) { throw new Exception( __( 'Product does not exists', 'advanced-dynamic-pricing-for-woocommerce' ) ); } $matched = true; foreach ( $this->get_filters() as $filter_key => $filter ) { $product_filtering = new WDP_Product_Filtering_new(); $product_filtering->prepare( $filter['type'], $filter['value'], $filter['method'] ); $matched = $product_filtering->check_product_suitability( $product ); if ( $matched ) { break; } } return $matched; } /** * @param $context WDP_Cart_Context * * @throws WDP_Rule_Execution_Timeout */ public function check_execution_time( $context ) { if ( ( microtime( true ) - $this->exec_rule_start ) > (float)$context->get_option( 'rule_max_exec_time' ) ) { throw new WDP_Rule_Execution_Timeout(); } } public function leave_disable_notice() { $value = get_option( WDP_Settings::$disabled_rules_option_name, array() ); $value[] = array( 'id' => $this->get_id(), 'is_exclusive' => $this->is_exclusive(), ); update_option( WDP_Settings::$disabled_rules_option_name, $value ); } public function has_conditions() { return ! empty( $this->conditions ); } public function has_cart_adjustments() { return ! empty( $this->cart_adjustments ); } public function has_limits() { return ! empty( $this->limits ); } /** * @param $cart WDP_Cart * * @return boolean */ public function is_rule_matched_cart( $cart ) { $limits_verified = $this->check_limits( $cart ); if ( ! $limits_verified ) { return false; } $conditions_verified = $this->check_conditions_for_a_match( $cart ); if ( ! $conditions_verified ) { return false; } return true; } public function is_simple_on_sale_rule() { $filters = $this->get_filters(); if ( ! $filters ) { return false; } $filter = reset( $filters ); $is_simple_filter = 1 === (int) $filter['qty'] && 1 === (int) $filter['qty_end'] && 'in_list' === $filter['method'] && ! empty( $filter['value'] ) && empty( $filter['product_exclude']['values'] ); return $is_simple_filter && $this->has_product_adjustment() && ! $this->has_free_products() && ! $this->has_bulk() && ! $this->has_roles_discount() && ! $this->has_conditions() && ! $this->has_cart_adjustments() && ! $this->has_limits(); } }