get_contents_from_session(); $this->get_contents(); $this->get_contents_details(); $this->get_all_fees(); $this->get_discounts_from_session(); $this->get_quantity(); } /** * Retrieves the tax rate. * * This sets up the tax rate once so we don't have to recaculate it each time we need it. * * @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/8455 * * @since 3.0 * @return float */ private function get_tax_rate() { if ( null === $this->tax_rate ) { $this->tax_rate = edd_get_tax_rate(); } return $this->tax_rate; } /** * Sets the tax rate. * * @param float $tax_rate * * @since 3.0 */ public function set_tax_rate( $tax_rate ) { $this->tax_rate = $tax_rate; } /** * Populate the cart with the data stored in the session * * @since 2.7 * @return void */ public function get_contents_from_session() { $cart = EDD()->session->get( 'edd_cart' ); $this->contents = $cart; do_action( 'edd_cart_contents_loaded_from_session', $this ); } /** * Populate the discounts with the data stored in the session. * * @since 2.7 * @return void */ public function get_discounts_from_session() { $discounts = EDD()->session->get( 'cart_discounts' ); $this->discounts = $discounts; do_action( 'edd_cart_discounts_loaded_from_session', $this ); } /** * Get cart contents * * @since 2.7 * @return array List of cart contents. */ public function get_contents() { if ( ! did_action( 'edd_cart_contents_loaded_from_session' ) ) { $this->get_contents_from_session(); } $cart = is_array( $this->contents ) && ! empty( $this->contents ) ? array_values( $this->contents ) : array(); $cart_count = count( $cart ); foreach ( $cart as $key => $item ) { $download = new EDD_Download( $item['id'] ); // If the item is not a download or it's status has changed since it was added to the cart. if ( empty( $download->ID ) || ! $download->can_purchase() ) { unset( $cart[ $key ] ); } } // We've removed items, reset the cart session if ( count( $cart ) < $cart_count ) { $this->contents = $cart; $this->update_cart(); } $this->contents = apply_filters( 'edd_cart_contents', $cart ); do_action( 'edd_cart_contents_loaded' ); return (array) $this->contents; } /** * Get cart contents details * * @since 2.7 * @return array */ public function get_contents_details() { global $edd_is_last_cart_item, $edd_flat_discount_total; if ( empty( $this->contents ) ) { return array(); } $details = array(); $length = count( $this->contents ) - 1; foreach ( $this->contents as $key => $item ) { if( $key >= $length ) { $edd_is_last_cart_item = true; } $item['quantity'] = edd_item_quantities_enabled() ? absint( $item['quantity'] ) : 1; $item['quantity'] = max( 1, $item['quantity'] ); // Force quantity to 1 $options = isset( $item['options'] ) ? $item['options'] : array(); $price_id = isset( $options['price_id'] ) ? $options['price_id'] : null; $item_price = $this->get_item_price( $item['id'], $options ); $discount = $this->get_item_discount_amount( $item ); $discount = apply_filters( 'edd_get_cart_content_details_item_discount_amount', $discount, $item ); $quantity = $this->get_item_quantity( $item['id'], $options ); $fees = $this->get_fees( 'fee', $item['id'], $price_id ); $subtotal = floatval( $item_price ) * $quantity; // Subtotal for tax calculation must exclude fees that are greater than 0. See $this->get_tax_on_fees() $subtotal_for_tax = $subtotal; foreach ( $fees as $fee ) { $fee_amount = (float) $fee['amount']; $subtotal += $fee_amount; if( $fee_amount > 0 ) { continue; } $subtotal_for_tax += $fee_amount; } $tax = $this->get_item_tax( $item['id'], $options, $subtotal_for_tax - $discount ); if ( edd_prices_include_tax() ) { $subtotal -= round( $tax, edd_currency_decimal_filter() ); } $total = $subtotal - $discount + $tax; if ( $total < 0 ) { $total = 0; } $details[ $key ] = array( 'name' => get_the_title( $item['id'] ), 'id' => $item['id'], 'item_number' => $item, 'item_price' => round( $item_price, edd_currency_decimal_filter() ), 'quantity' => $quantity, 'discount' => round( $discount, edd_currency_decimal_filter() ), 'subtotal' => round( $subtotal, edd_currency_decimal_filter() ), 'tax' => round( $tax, edd_currency_decimal_filter() ), 'fees' => $fees, 'price' => round( $total, edd_currency_decimal_filter() ) ); if ( $edd_is_last_cart_item ) { $edd_is_last_cart_item = false; $edd_flat_discount_total = 0.00; } } $this->details = $details; return $this->details; } /** * Get Discounts. * * @since 2.7 * @return array $discounts The active discount codes */ public function get_discounts() { $this->get_discounts_from_session(); $this->discounts = ! empty( $this->discounts ) ? explode( '|', $this->discounts ) : array(); return $this->discounts; } /** * Update Cart * * @since 2.7 * @return void */ public function update_cart() { EDD()->session->set( 'edd_cart', $this->contents ); } /** * Checks if any discounts have been applied to the cart * * @since 2.7 * @return bool */ public function has_discounts() { if ( null !== $this->has_discounts ) { return $this->has_discounts; } $has_discounts = false; $discounts = $this->get_discounts(); if ( ! empty( $discounts ) ) { $has_discounts = true; } $this->has_discounts = apply_filters( 'edd_cart_has_discounts', $has_discounts ); return $this->has_discounts; } /** * Get quantity * * @since 2.7 * @return int */ public function get_quantity() { $total_quantity = 0; $contents = $this->get_contents(); if ( ! empty( $contents ) ) { $quantities = wp_list_pluck( $this->contents, 'quantity' ); $total_quantity = absint( array_sum( $quantities ) ); } $this->quantity = apply_filters( 'edd_get_cart_quantity', $total_quantity, $this->contents ); return $this->quantity; } /** * Checks if the cart is empty * * @since 2.7 * @return boolean */ public function is_empty() { return 0 === count( (array) $this->get_contents() ); } /** * Add to cart * * As of EDD 2.7, items can only be added to the cart when the object passed extends EDD_Cart_Item * * @since 2.7 * @return array $cart Updated cart object */ public function add( $download_id, $options = array() ) { $download = new EDD_Download( $download_id ); if ( empty( $download->ID ) ) { return; // Not a download product } if ( ! $download->can_purchase() ) { return; // Do not allow draft/pending to be purchased if can't edit. Fixes #1056 } do_action( 'edd_pre_add_to_cart', $download_id, $options ); /** * Pre-Add to Cart Contents. * * Prior to adding the new item to the cart, allow filtering of the current contents * * @since * @since 3.0 Added the additional $download_id and $options arguments. * * @param array The current cart contents. * @param int The download ID being added to the cart. * @param array The options for the item being added including but not limited to quantity. */ $this->contents = apply_filters( 'edd_pre_add_to_cart_contents', $this->contents, $download_id, $options ); $quantities_enabled = edd_item_quantities_enabled() && ! edd_download_quantities_disabled( $download_id ); if ( $download->has_variable_prices() && ! isset( $options['price_id'] ) ) { // Forces to the default price ID if none is specified and download has variable prices $options['price_id'] = get_post_meta( $download->ID, '_edd_default_price_id', true ); } if ( isset( $options['quantity'] ) ) { if ( is_array( $options['quantity'] ) ) { $quantity = array(); foreach ( $options['quantity'] as $q ) { $quantity[] = $quantities_enabled ? absint( preg_replace( '/[^0-9\.]/', '', $q ) ) : 1; } } else { $quantity = $quantities_enabled ? absint( preg_replace( '/[^0-9\.]/', '', $options['quantity'] ) ) : 1; } unset( $options['quantity'] ); } else { $quantity = 1; } // If the price IDs are a string and is a coma separated list, make it an array (allows custom add to cart URLs) if ( isset( $options['price_id'] ) && ! is_array( $options['price_id'] ) && false !== strpos( $options['price_id'], ',' ) ) { $options['price_id'] = explode( ',', $options['price_id'] ); } $items = array(); if ( isset( $options['price_id'] ) && is_array( $options['price_id'] ) ) { // Process multiple price options at once foreach ( $options['price_id'] as $key => $price ) { $items[] = array( 'id' => $download_id, 'options' => array( 'price_id' => preg_replace( '/[^0-9\.-]/', '', $price ) ), 'quantity' => is_array( $quantity ) && isset( $quantity[ $key ] ) ? $quantity[ $key ] : $quantity, ); } } else { // Sanitize price IDs foreach( $options as $key => $option ) { if ( 'price_id' == $key ) { $options[ $key ] = preg_replace( '/[^0-9\.-]/', '', $option ); } } // Add a single item $items[] = array( 'id' => $download_id, 'options' => $options, 'quantity' => $quantity ); } foreach ( $items as &$item ) { $item = apply_filters( 'edd_add_to_cart_item', $item ); $to_add = $item; if ( ! is_array( $to_add ) ) { return; } if ( ! isset( $to_add['id'] ) || empty( $to_add['id'] ) ) { return; } if ( edd_item_in_cart( $to_add['id'], $to_add['options'] ) && edd_item_quantities_enabled() ) { $key = edd_get_item_position_in_cart( $to_add['id'], $to_add['options'] ); if ( is_array( $quantity ) ) { $this->contents[ $key ]['quantity'] += $quantity[ $key ]; } else { $this->contents[ $key ]['quantity'] += $quantity; } } else { $this->contents[] = $to_add; } } unset( $item ); $this->update_cart(); do_action( 'edd_post_add_to_cart', $download_id, $options, $items ); // Clear all the checkout errors, if any edd_clear_errors(); return count( $this->contents ) - 1; } /** * Remove from cart * * @since 2.7 * * @param int $key Cart key to remove. This key is the numerical index of the item contained within the cart array. * @return array Updated cart contents */ public function remove( $key ) { $cart = $this->get_contents(); do_action( 'edd_pre_remove_from_cart', $key ); if ( ! is_array( $cart ) ) { return true; // Empty cart } else { $item_id = isset( $cart[ $key ]['id'] ) ? $cart[ $key ]['id'] : null; unset( $cart[ $key ] ); } $this->contents = $cart; $this->update_cart(); do_action( 'edd_post_remove_from_cart', $key, $item_id ); edd_clear_errors(); return $this->contents; } /** * Generate the URL to remove an item from the cart. * * @since 2.7 * * @param int $cart_key Cart item key * @return string $remove_url URL to remove the cart item */ public function remove_item_url( $cart_key ) { $current_page = edd_doing_ajax() ? edd_get_checkout_uri() : edd_get_current_page_url(); $remove_url = edd_add_cache_busting( add_query_arg( array( 'cart_item' => urlencode( $cart_key ), 'edd_action' => 'remove', ), $current_page ) ); return apply_filters( 'edd_remove_item_url', $remove_url ); } /** * Generate the URL to remove a fee from the cart. * * @since 2.7 * * @param int $fee_id Fee ID. * @return string $remove_url URL to remove the cart item */ public function remove_fee_url( $fee_id = '' ) { $current_page = edd_doing_ajax() ? edd_get_checkout_uri() : edd_get_current_page_url(); $remove_url = add_query_arg( array( 'fee' => urlencode( $fee_id ), 'edd_action' => 'remove_fee', 'nocache' => 'true' ), $current_page ); return apply_filters( 'edd_remove_fee_url', $remove_url ); } /** * Empty the cart * * @since 2.7 * @return void */ public function empty_cart() { // Remove cart contents. EDD()->session->set( 'edd_cart', NULL ); // Remove all cart fees. EDD()->session->set( 'edd_cart_fees', NULL ); // Remove any resuming payments. EDD()->session->set( 'edd_resume_payment', NULL ); // Remove any active discounts $this->remove_all_discounts(); $this->contents = array(); do_action( 'edd_empty_cart' ); } /** * Remove discount from the cart * * @since 2.7 * @return array Discount codes */ public function remove_discount( $code = '' ) { if ( empty( $code ) ) { return; } if ( $this->discounts ) { $key = array_search( $code, $this->discounts ); if ( false !== $key ) { unset( $this->discounts[ $key ] ); } $this->discounts = implode( '|', array_values( $this->discounts ) ); // update the active discounts EDD()->session->set( 'cart_discounts', $this->discounts ); } do_action( 'edd_cart_discount_removed', $code, $this->discounts ); do_action( 'edd_cart_discounts_updated', $this->discounts ); return $this->discounts; } /** * Remove all discount codes * * @since 2.7 * @return void */ public function remove_all_discounts() { EDD()->session->set( 'cart_discounts', null ); do_action( 'edd_cart_discounts_removed' ); } /** * Get the discounted amount on a price * * @since 2.7 * @since 3.0 Use `edd_get_item_discount_amount()` for calculations. * * @param array $item Cart item. * @param bool|string $discount False to use the cart discounts or a string to check with a discount code. * @return float The discounted amount */ public function get_item_discount_amount( $item = array(), $discount = false ) { // Validate item. if ( empty( $item ) || empty( $item['id'] ) ) { return 0; } if ( ! isset( $item['quantity'] ) ) { return 0; } if ( ! isset( $item['options'] ) ) { $item['options'] = array(); /* * Support for variable pricing when calling `edd_get_cart_item_discount_amount()` * @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/8246 */ if ( isset( $item['item_number']['options'] ) ) { $item['options'] = $item['item_number']['options']; } } $discounts = false === $discount ? $this->get_discounts() : array( $discount ); $item_price = $this->get_item_price( $item['id'], $item['options'] ); $discount_amount = edd_get_item_discount_amount( $item, $this->get_contents(), $discounts, $item_price ); $discounted_amount = ( $item_price - $discount_amount ); /** * Filters the amount to be discounted from the original cart item amount. * * @since unknown * * @param float $discounted_amount Amount to be discounted from the cart item amount. * @param string[] $discounts Discount codes applied to the Cart. * @param array $item Cart item. * @param float $item_price Cart item price. */ $discounted_amount = apply_filters( 'edd_get_cart_item_discounted_amount', $discounted_amount, $discounts, $item, $item_price ); // Recalculate using the legacy filter discounted amount. $discount_amount = round( ( $item_price - $discounted_amount ), edd_currency_decimal_filter() ); return $discount_amount; } /** * Shows the fully formatted cart discount * * @since 2.7 * * @param bool $echo Echo? * @return string $amount Fully formatted cart discount */ public function display_cart_discount( $echo = false ) { $discounts = $this->get_discounts(); if ( empty( $discounts ) ) { return false; } $discount_id = edd_get_discount_id_by_code( $discounts[0] ); $amount = edd_format_discount_rate( edd_get_discount_type( $discount_id ), edd_get_discount_amount( $discount_id ) ); if ( $echo ) { echo esc_html( $amount ); } return $amount; } /** * Checks to see if an item is in the cart. * * @since 2.7 * * @param int $download_id Download ID of the item to check. * @param array $options * @return bool */ public function is_item_in_cart( $download_id = 0, $options = array() ) { $cart = $this->get_contents(); $ret = false; if ( is_array( $cart ) ) { foreach ( $cart as $item ) { if ( $item['id'] == $download_id ) { if ( isset( $options['price_id'] ) && isset( $item['options']['price_id'] ) ) { if ( $options['price_id'] == $item['options']['price_id'] ) { $ret = true; break; } } else { $ret = true; break; } } } } return (bool) apply_filters( 'edd_item_in_cart', $ret, $download_id, $options ); } /** * Get the position of an item in the cart * * @since 2.7 * * @param int $download_id Download ID of the item to check. * @param array $options * @return mixed int|false */ public function get_item_position( $download_id = 0, $options = array() ) { $cart = $this->get_contents(); if ( ! is_array( $cart ) ) { return false; } else { foreach ( $cart as $position => $item ) { if ( $item['id'] == $download_id ) { if ( isset( $options['price_id'] ) && isset( $item['options']['price_id'] ) ) { if ( (int) $options['price_id'] == (int) $item['options']['price_id'] ) { return $position; } } else { return $position; } } } } return false; } /** * Get the quantity of an item in the cart. * * @since 2.7 * * @param int $download_id Download ID of the item * @param array $options * @return int Numerical index of the position of the item in the cart */ public function get_item_quantity( $download_id = 0, $options = array() ) { $key = $this->get_item_position( $download_id, $options ); $quantity = isset( $this->contents[ $key ]['quantity'] ) && edd_item_quantities_enabled() ? $this->contents[ $key ]['quantity'] : 1; if ( $quantity < 1 ) { $quantity = 1; } return absint( apply_filters( 'edd_get_cart_item_quantity', $quantity, $download_id, $options ) ); } /** * Set the quantity of an item in the cart. * * @since 2.7 * * @param int $download_id Download ID of the item * @param int $quantity Updated quantity of the item * @param array $options * @return array $contents Updated cart object. */ public function set_item_quantity( $download_id = 0, $quantity = 1, $options = array() ) { $key = $this->get_item_position( $download_id, $options ); if ( false === $key ) { return $this->contents; } if ( $quantity < 1 ) { $quantity = 1; } $this->contents[ $key ]['quantity'] = $quantity; $this->update_cart(); do_action( 'edd_after_set_cart_item_quantity', $download_id, $quantity, $options, $this->contents ); return $this->contents; } /** * Cart Item Price. * * @since 2.7 * * @param int $item_id Download (cart item) ID number * @param array $options Optional parameters, used for defining variable prices * @return string Fully formatted price */ public function item_price( $item_id = 0, $options = array() ) { $price = $this->get_item_price( $item_id, $options ); $label = ''; $price_id = isset( $options['price_id'] ) ? $options['price_id'] : false; if ( ! edd_is_free_download( $item_id, $price_id ) && ! edd_download_is_tax_exclusive( $item_id ) ) { if ( edd_prices_show_tax_on_checkout() && ! edd_prices_include_tax() ) { $price += edd_get_cart_item_tax( $item_id, $options, $price ); } if ( ! edd_prices_show_tax_on_checkout() && edd_prices_include_tax() ) { $price -= edd_get_cart_item_tax( $item_id, $options, $price ); } if ( edd_display_tax_rate() ) { $label = ' – '; if ( edd_prices_show_tax_on_checkout() ) { $label .= sprintf( __( 'includes %s tax', 'easy-digital-downloads' ), edd_get_formatted_tax_rate() ); } else { $label .= sprintf( __( 'excludes %s tax', 'easy-digital-downloads' ), edd_get_formatted_tax_rate() ); } $label = apply_filters( 'edd_cart_item_tax_description', $label, $item_id, $options ); } } $price = edd_currency_filter( edd_format_amount( $price ) ); return apply_filters( 'edd_cart_item_price_label', $price . $label, $item_id, $options ); } /** * Gets the price of the cart item. Always exclusive of taxes. * * Do not use this for getting the final price (with taxes and discounts) of an item. * Use edd_get_cart_item_final_price() * * @since 2.7 * * @param int $download_id Download ID for the cart item * @param array $options Optional parameters, used for defining variable prices * @param bool $remove_tax_from_inclusive Remove the tax amount from tax inclusive priced products. * @return float|bool Price for this item */ public function get_item_price( $download_id = 0, $options = array(), $remove_tax_from_inclusive = false ) { $price = 0; $variable_prices = edd_has_variable_prices( $download_id ); if ( $variable_prices ) { $prices = edd_get_variable_prices( $download_id ); if ( $prices ) { if ( ! empty( $options ) ) { $price = isset( $prices[ $options['price_id'] ] ) ? $prices[ $options['price_id'] ]['amount'] : false; } else { $price = false; } } } if ( ! $variable_prices || false === $price ) { // Get the standard Download price if not using variable prices $price = edd_get_download_price( $download_id ); } if ( $remove_tax_from_inclusive && edd_prices_include_tax() ) { $price -= $this->get_item_tax( $download_id, $options, $price ); } return apply_filters( 'edd_cart_item_price', $price, $download_id, $options ); } /** * Final Price of Item in Cart (incl. discounts and taxes) * * @since 2.7 * * @param int $item_key Cart item key * @return float Final price for the item */ public function get_item_final_price( $item_key = 0 ) { $final_price = $this->details[ $item_key ]['price']; return apply_filters( 'edd_cart_item_final_price', $final_price, $item_key ); } /** * Calculate the tax for an item in the cart. * * @since 2.7 * * @param array $download_id Download ID * @param array $options Cart item options * @param float $subtotal Cart item subtotal * @return float Tax amount */ public function get_item_tax( $download_id = 0, $options = array(), $subtotal = '' ) { $tax = 0; if ( ! edd_download_is_tax_exclusive( $download_id ) ) { $country = ! empty( $_POST['billing_country'] ) ? $_POST['billing_country'] : false; $state = ! empty( $_POST['card_state'] ) ? $_POST['card_state'] : false; $tax = edd_calculate_tax( $subtotal, $country, $state, true, $this->get_tax_rate() ); } $tax = max( $tax, 0 ); return apply_filters( 'edd_get_cart_item_tax', $tax, $download_id, $options, $subtotal ); } /** * Get Cart Fees * * @since 2.7 * @return array Cart fees */ public function get_fees( $type = 'all', $download_id = 0, $price_id = null ) { return EDD()->fees->get_fees( $type, $download_id, $price_id ); } /** * Get All Cart Fees. * * @since 2.7 * @return array */ public function get_all_fees() { $this->fees = EDD()->fees->get_fees( 'all' ); return $this->fees; } /** * Get Cart Items Subtotal. * * @since 2.7 * * @param array $items Cart items array * @return float items subtotal */ public function get_items_subtotal( $items ) { $subtotal = 0.00; if ( is_array( $items ) && ! empty( $items ) ) { $prices = wp_list_pluck( $items, 'subtotal' ); if ( is_array( $prices ) ) { $subtotal = array_sum( $prices ); } else { $subtotal = 0.00; } if ( $subtotal < 0 ) { $subtotal = 0.00; } } $this->subtotal = apply_filters( 'edd_get_cart_items_subtotal', $subtotal ); return $this->subtotal; } /** * Get Discountable Subtotal. * * @since 2.7 * @return float Total discountable amount before taxes */ public function get_discountable_subtotal( $code_id ) { $cart_items = $this->get_contents_details(); $items = array(); $excluded_products = edd_get_discount_excluded_products( $code_id ); if ( $cart_items ) { foreach( $cart_items as $item ) { if ( ! in_array( $item['id'], $excluded_products ) ) { $items[] = $item; } } } $subtotal = $this->get_items_subtotal( $items ); return apply_filters( 'edd_get_cart_discountable_subtotal', $subtotal ); } /** * Get Discounted Amount. * * @since 2.7 * * @param bool $discounts Discount codes * @return float|mixed|void Total discounted amount */ public function get_discounted_amount( $discounts = false ) { $amount = 0.00; $items = $this->get_contents_details(); if ( $items ) { $discounts = wp_list_pluck( $items, 'discount' ); if ( is_array( $discounts ) ) { $discounts = array_map( 'floatval', $discounts ); $amount = array_sum( $discounts ); } } return apply_filters( 'edd_get_cart_discounted_amount', $amount ); } /** * Get Cart Subtotal. * * Gets the total price amount in the cart before taxes and before any discounts. * * @since 2.7 * * @return float Total amount before taxes */ public function get_subtotal() { $items = $this->get_contents_details(); $subtotal = $this->get_items_subtotal( $items ); return apply_filters( 'edd_get_cart_subtotal', $subtotal ); } /** * Subtotal (before taxes). * * @since 2.7 * @return float Total amount before taxes fully formatted */ public function subtotal() { return esc_html( edd_currency_filter( edd_format_amount( edd_get_cart_subtotal() ) ) ); } /** * Get Total Cart Amount. * * @since 2.7 * * @param bool $discounts Array of discounts to apply (needed during AJAX calls) * @return float Cart amount */ public function get_total( $discounts = false ) { $subtotal = (float) $this->get_subtotal(); $discounts = (float) $this->get_discounted_amount(); $fees = (float) $this->get_total_fees(); $cart_tax = (float) $this->get_tax(); $total_wo_tax = $subtotal - $discounts + $fees; $total = $subtotal - $discounts + $cart_tax + $fees; if ( $total < 0 || ! $total_wo_tax > 0 ) { $total = 0.00; } $this->total = (float) apply_filters( 'edd_get_cart_total', $total ); return $this->total; } /** * Fully Formatted Total Cart Amount. * * @since 2.7 * * @param bool $echo * @return mixed|string|void */ public function total( $echo = false ) { $total = apply_filters( 'edd_cart_total', edd_currency_filter( edd_format_amount( $this->get_total() ) ) ); if ( $echo ) { echo esc_html( $total ); } return $total; } /** * Get Cart Fee Total * * @since 2.7 * @return double */ public function get_total_fees() { $fee_total = 0.00; foreach ( $this->get_fees() as $fee ) { // Since fees affect cart item totals, we need to not count them towards the cart total if there is an association. if ( ! empty( $fee['download_id'] ) ) { continue; } $fee_total += $fee['amount']; } return apply_filters( 'edd_get_fee_total', $fee_total, $this->fees ); } /** * Get the price ID for an item in the cart. * * @since 2.7 * * @param array $item Item details * @return string $price_id Price ID */ public function get_item_price_id( $item = array() ) { if ( isset( $item['item_number'] ) ) { $price_id = isset( $item['item_number']['options']['price_id'] ) ? $item['item_number']['options']['price_id'] : null; } else { $price_id = isset( $item['options']['price_id'] ) ? $item['options']['price_id'] : null; } return $price_id; } /** * Get the price name for an item in the cart. * * @since 2.7 * * @param array $item Item details * @return string $name Price name */ public function get_item_price_name( $item = array() ) { $price_id = (int) $this->get_item_price_id( $item ); $prices = edd_get_variable_prices( $item['id'] ); $name = ! empty( $prices[ $price_id ] ) ? $prices[ $price_id ]['name'] : ''; return apply_filters( 'edd_get_cart_item_price_name', $name, $item['id'], $price_id, $item ); } /** * Get the name of an item in the cart. * * @since 2.7 * * @param array $item Item details * @return string $name Item name */ public function get_item_name( $item = array() ) { $item_title = get_the_title( $item['id'] ); if ( empty( $item_title ) ) { $item_title = $item['id']; } if ( edd_has_variable_prices( $item['id'] ) && false !== edd_get_cart_item_price_id( $item ) ) { $item_title .= ' - ' . edd_get_cart_item_price_name( $item ); } return apply_filters( 'edd_get_cart_item_name', $item_title, $item['id'], $item ); } /** * Get all applicable tax for the items in the cart * * @since 2.7 * @return float Total tax amount */ public function get_tax() { $cart_tax = 0; $items = $this->get_contents_details(); if ( $items ) { $taxes = wp_list_pluck( $items, 'tax' ); if ( is_array( $taxes ) ) { $cart_tax = array_sum( $taxes ); } } $cart_tax += $this->get_tax_on_fees(); $subtotal = $this->get_subtotal(); if ( empty( $subtotal ) ) { $cart_tax = 0; } $cart_tax = apply_filters( 'edd_get_cart_tax', edd_sanitize_amount( $cart_tax ) ); return $cart_tax; } /** * Gets the total tax amount for the cart contents in a fully formatted way * * @since 2.7 * * @param boolean $echo Decides if the result should be returned or not * @return string Total tax amount */ public function tax( $echo = false ) { $cart_tax = $this->get_tax(); $cart_tax = edd_currency_filter( edd_format_amount( $cart_tax ) ); $tax = max( $cart_tax, 0 ); $tax = apply_filters( 'edd_cart_tax', $cart_tax ); if ( $echo ) { echo esc_html( $tax ); } return $tax; } /** * Get tax applicable for fees. * * @since 2.7 * @return float Total taxable amount for fees */ public function get_tax_on_fees() { $tax = 0; $fees = edd_get_cart_fees(); if ( $fees ) { foreach ( $fees as $fee_id => $fee ) { if ( ! empty( $fee['no_tax'] ) || $fee['amount'] < 0 ) { continue; } /** * Fees (at this time) must be exclusive of tax */ add_filter( 'edd_prices_include_tax', '__return_false' ); $tax += edd_calculate_tax( $fee['amount'], '', '', true, $this->get_tax_rate() ); remove_filter( 'edd_prices_include_tax', '__return_false' ); } } return apply_filters( 'edd_get_cart_fee_tax', $tax ); } /** * Is Cart Saving Enabled? * * @since 2.7 * @return bool */ public function is_saving_enabled() { return edd_get_option( 'enable_cart_saving', false ); } /** * Checks if the cart has been saved * * @since 2.7 * @return bool */ public function is_saved() { if ( ! $this->is_saving_enabled() ) { return false; } $saved_cart = get_user_meta( get_current_user_id(), 'edd_saved_cart', true ); if ( is_user_logged_in() ) { if ( ! $saved_cart ) { return false; } if ( $saved_cart === EDD()->session->get( 'edd_cart' ) ) { return false; } return true; } else { if ( ! isset( $_COOKIE['edd_saved_cart'] ) ) { return false; } if ( json_decode( stripslashes( $_COOKIE['edd_saved_cart'] ), true ) === EDD()->session->get( 'edd_cart' ) ) { return false; } return true; } } /** * Save Cart * * @since 2.7 * @return bool */ public function save() { // Bail if carts cannot be saved if ( ! $this->is_saving_enabled() ) { return false; } // Get cart & cart token $cart = EDD()->session->get( 'edd_cart' ); $token = edd_generate_cart_token(); if ( is_user_logged_in() ) { $user_id = get_current_user_id(); update_user_meta( $user_id, 'edd_saved_cart', $cart, false ); update_user_meta( $user_id, 'edd_cart_token', $token, false ); } else { $cart = json_encode( $cart ); $expires = time() + WEEK_IN_SECONDS; @setcookie( 'edd_saved_cart', $cart, $expires, COOKIEPATH, COOKIE_DOMAIN ); @setcookie( 'edd_cart_token', $token, $expires, COOKIEPATH, COOKIE_DOMAIN ); } // Get all cart messages $messages = EDD()->session->get( 'edd_cart_messages' ); // Make sure it's an array, if empty if ( empty( $messages ) ) { $messages = array(); } $checkout_url = add_query_arg( array( 'edd_action' => 'restore_cart', 'edd_cart_token' => sanitize_key( $token ), ), edd_get_checkout_uri() ); // Add the success message $messages['edd_cart_save_successful'] = sprintf( '%1$s: %2$s %3$s', __( 'Success', 'easy-digital-downloads' ), __( 'Cart saved successfully. You can restore your cart using this URL:', 'easy-digital-downloads' ), esc_url( edd_get_checkout_uri() . '?edd_action=restore_cart&edd_cart_token=' . urlencode( $token ) ) ); // Set these messages in the session EDD()->session->set( 'edd_cart_messages', $messages ); // Return if cart saved return ! empty( $cart ); } /** * Restore Cart * * @since 2.7 * @return bool */ public function restore() { if ( ! $this->is_saving_enabled() ) { return false; } $user_id = get_current_user_id(); $saved_cart = get_user_meta( $user_id, 'edd_saved_cart', true ); $token = $this->get_token(); if ( is_user_logged_in() && $saved_cart ) { $messages = EDD()->session->get( 'edd_cart_messages' ); if ( ! $messages ) { $messages = array(); } if ( isset( $_GET['edd_cart_token'] ) && ! hash_equals( $_GET['edd_cart_token'], $token ) ) { $messages['edd_cart_restoration_failed'] = sprintf( '%1$s: %2$s', __( 'Error', 'easy-digital-downloads' ), __( 'Cart restoration failed. Invalid token.', 'easy-digital-downloads' ) ); EDD()->session->set( 'edd_cart_messages', $messages ); } delete_user_meta( $user_id, 'edd_saved_cart' ); delete_user_meta( $user_id, 'edd_cart_token' ); if ( isset( $_GET['edd_cart_token'] ) && $_GET['edd_cart_token'] != $token ) { return new WP_Error( 'invalid_cart_token', __( 'The cart cannot be restored. Invalid token.', 'easy-digital-downloads' ) ); } } elseif ( ! is_user_logged_in() && isset( $_COOKIE['edd_saved_cart'] ) && $token ) { $saved_cart = $_COOKIE['edd_saved_cart']; if ( ! hash_equals( $_GET['edd_cart_token'], $token ) ) { $messages['edd_cart_restoration_failed'] = sprintf( '%1$s: %2$s', __( 'Error', 'easy-digital-downloads' ), __( 'Cart restoration failed. Invalid token.', 'easy-digital-downloads' ) ); EDD()->session->set( 'edd_cart_messages', $messages ); return new WP_Error( 'invalid_cart_token', __( 'The cart cannot be restored. Invalid token.', 'easy-digital-downloads' ) ); } $saved_cart = json_decode( stripslashes( $saved_cart ), true ); setcookie( 'edd_saved_cart', '', time()-3600, COOKIEPATH, COOKIE_DOMAIN ); setcookie( 'edd_cart_token', '', time()-3600, COOKIEPATH, COOKIE_DOMAIN ); } $messages['edd_cart_restoration_successful'] = sprintf( '%1$s: %2$s', __( 'Success', 'easy-digital-downloads' ), __( 'Cart restored successfully.', 'easy-digital-downloads' ) ); EDD()->session->set( 'edd_cart', $saved_cart ); EDD()->session->set( 'edd_cart_messages', $messages ); // @e also have to set this instance to what the session is. $this->contents = $saved_cart; return true; } /** * Retrieve a saved cart token. Used in validating saved carts * * @since 2.7 * @return int */ public function get_token() { $user_id = get_current_user_id(); if ( is_user_logged_in() ) { $token = get_user_meta( $user_id, 'edd_cart_token', true ); } else { $token = isset( $_COOKIE['edd_cart_token'] ) ? $_COOKIE['edd_cart_token'] : false; } return apply_filters( 'edd_get_cart_token', $token, $user_id ); } /** * Generate URL token to restore the cart via a URL * * @since 2.7 * @return int */ public function generate_token() { return apply_filters( 'edd_generate_cart_token', md5( mt_rand() . time() ) ); } }