dvadf
File manager - Edit - /home/theblueo/tv/wp-includes/pomo/lib/abstracts.tar
Back
abstract-wc-product.php 0000666 00000134003 15214130426 0011150 0 ustar 00 <?php /** * Abstract Product Class * * The WooCommerce product class handles individual product data. * * @class WC_Product * @var WP_Post * @version 2.1.0 * @package WooCommerce/Abstracts * @category Abstract Class * @author WooThemes * * @property string $width Product width * @property string $length Product length * @property string $height Product height * @property string $weight Product weight * @property string $price Product price * @property string $regular_price Product regular price * @property string $sale_price Product sale price * @property string $product_image_gallery String of image IDs in the gallery * @property string $sku Product SKU * @property string $stock Stock amount * @property string $downloadable Shows/define if the product is downloadable * @property string $virtual Shows/define if the product is virtual * @property string $sold_individually Allow one item to be bought in a single order * @property string $tax_status Tax status * @property string $tax_class Tax class * @property string $manage_stock Shows/define if can manage the product stock * @property string $stock_status Stock status * @property string $backorders Whether or not backorders are allowed * @property string $featured Featured product * @property string $visibility Product visibility * @property string $variation_id Variation ID when dealing with variations */ class WC_Product { /** * The product (post) ID. * * @var int */ public $id = 0; /** * $post Stores post data. * * @var $post WP_Post */ public $post = null; /** * The product's type (simple, variable etc). * * @var string */ public $product_type = null; /** * Product shipping class. * * @var string */ protected $shipping_class = ''; /** * ID of the shipping class this product has. * * @var int */ protected $shipping_class_id = 0; /** @public string The product's total stock, including that of its children. */ public $total_stock; /** * Supported features such as 'ajax_add_to_cart'. * @var array */ protected $supports = array(); /** * Constructor gets the post object and sets the ID for the loaded product. * * @param int|WC_Product|object $product Product ID, post object, or product object */ public function __construct( $product ) { if ( is_numeric( $product ) ) { $this->id = absint( $product ); $this->post = get_post( $this->id ); } elseif ( $product instanceof WC_Product ) { $this->id = absint( $product->id ); $this->post = $product->post; } elseif ( isset( $product->ID ) ) { $this->id = absint( $product->ID ); $this->post = $product; } } /** * __isset function. * * @param mixed $key * @return bool */ public function __isset( $key ) { return metadata_exists( 'post', $this->id, '_' . $key ); } /** * __get function. * * @param string $key * @return mixed */ public function __get( $key ) { $value = get_post_meta( $this->id, '_' . $key, true ); // Get values or default if not set if ( in_array( $key, array( 'downloadable', 'virtual', 'backorders', 'manage_stock', 'featured', 'sold_individually' ) ) ) { $value = $value ? $value : 'no'; } elseif ( in_array( $key, array( 'product_attributes', 'crosssell_ids', 'upsell_ids' ) ) ) { $value = $value ? $value : array(); } elseif ( 'visibility' === $key ) { $value = $value ? $value : 'hidden'; } elseif ( 'stock' === $key ) { $value = $value ? $value : 0; } elseif ( 'stock_status' === $key ) { $value = $value ? $value : 'instock'; } elseif ( 'tax_status' === $key ) { $value = $value ? $value : 'taxable'; } if ( false !== $value ) { $this->$key = $value; } return $value; } /** * Get the product's post data. * * @return object */ public function get_post_data() { return $this->post; } /** * Check if a product supports a given feature. * * Product classes should override this to declare support (or lack of support) for a feature. * * @param string $feature string The name of a feature to test support for. * @return bool True if the product supports the feature, false otherwise. * @since 2.5.0 */ public function supports( $feature ) { return apply_filters( 'woocommerce_product_supports', in_array( $feature, $this->supports ) ? true : false, $feature, $this ); } /** * Return the product ID * * @since 2.5.0 * @return int product (post) ID */ public function get_id() { return $this->id; } /** * Returns the gallery attachment ids. * * @return array */ public function get_gallery_attachment_ids() { return apply_filters( 'woocommerce_product_gallery_attachment_ids', array_filter( array_filter( (array) explode( ',', $this->product_image_gallery ) ), 'wp_attachment_is_image' ), $this ); } /** * Wrapper for get_permalink. * * @return string */ public function get_permalink() { return get_permalink( $this->id ); } /** * Get SKU (Stock-keeping unit) - product unique ID. * * @return string */ public function get_sku() { return apply_filters( 'woocommerce_get_sku', $this->sku, $this ); } /** * Returns number of items available for sale. * * @return int */ public function get_stock_quantity() { return apply_filters( 'woocommerce_get_stock_quantity', $this->managing_stock() ? wc_stock_amount( $this->stock ) : null, $this ); } /** * Get total stock - This is the stock of parent and children combined. * * @return int */ public function get_total_stock() { if ( empty( $this->total_stock ) ) { if ( sizeof( $this->get_children() ) > 0 ) { $this->total_stock = max( 0, $this->get_stock_quantity() ); foreach ( $this->get_children() as $child_id ) { if ( 'yes' === get_post_meta( $child_id, '_manage_stock', true ) ) { $stock = get_post_meta( $child_id, '_stock', true ); $this->total_stock += max( 0, wc_stock_amount( $stock ) ); } } } else { $this->total_stock = $this->get_stock_quantity(); } } return wc_stock_amount( $this->total_stock ); } /** * Check if the stock status needs changing. */ public function check_stock_status() { if ( ! $this->backorders_allowed() && $this->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) { if ( $this->stock_status !== 'outofstock' ) { $this->set_stock_status( 'outofstock' ); } } elseif ( $this->backorders_allowed() || $this->get_total_stock() > get_option( 'woocommerce_notify_no_stock_amount' ) ) { if ( $this->stock_status !== 'instock' ) { $this->set_stock_status( 'instock' ); } } } /** * Set stock level of the product. * * Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues). * We cannot rely on the original loaded value in case another order was made since then. * * @param int $amount (default: null) * @param string $mode can be set, add, or subtract * @return int new stock level */ public function set_stock( $amount = null, $mode = 'set' ) { global $wpdb; if ( ! is_null( $amount ) && $this->managing_stock() ) { // Ensure key exists add_post_meta( $this->id, '_stock', 0, true ); // Update stock in DB directly switch ( $mode ) { case 'add' : $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value + %f WHERE post_id = %d AND meta_key='_stock'", $amount, $this->id ) ); break; case 'subtract' : $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value - %f WHERE post_id = %d AND meta_key='_stock'", $amount, $this->id ) ); break; default : $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='_stock'", $amount, $this->id ) ); break; } // Clear caches wp_cache_delete( $this->id, 'post_meta' ); delete_transient( 'wc_low_stock_count' ); delete_transient( 'wc_outofstock_count' ); unset( $this->stock ); // Stock status $this->check_stock_status(); // Trigger action do_action( 'woocommerce_product_set_stock', $this ); } return $this->get_stock_quantity(); } /** * Reduce stock level of the product. * * @param int $amount Amount to reduce by. Default: 1 * @return int new stock level */ public function reduce_stock( $amount = 1 ) { return $this->set_stock( $amount, 'subtract' ); } /** * Increase stock level of the product. * * @param int $amount Amount to increase by. Default 1. * @return int new stock level */ public function increase_stock( $amount = 1 ) { return $this->set_stock( $amount, 'add' ); } /** * Set stock status of the product. * * @param string $status */ public function set_stock_status( $status ) { $status = ( 'outofstock' === $status ) ? 'outofstock' : 'instock'; // Sanity check if ( $this->managing_stock() ) { if ( ! $this->backorders_allowed() && $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) { $status = 'outofstock'; } } if ( update_post_meta( $this->id, '_stock_status', $status ) ) { $this->stock_status = $status; do_action( 'woocommerce_product_set_stock_status', $this->id, $status ); } } /** * Return the product type. * * @return string */ public function get_type() { return is_null( $this->product_type ) ? '' : $this->product_type; } /** * Checks the product type. * * Backwards compat with downloadable/virtual. * * @param string $type Array or string of types * @return bool */ public function is_type( $type ) { return ( $this->product_type == $type || ( is_array( $type ) && in_array( $this->product_type, $type ) ) ) ? true : false; } /** * Checks if a product is downloadable. * * @return bool */ public function is_downloadable() { return $this->downloadable == 'yes' ? true : false; } /** * Check if downloadable product has a file attached. * * @since 1.6.2 * * @param string $download_id file identifier * @return bool Whether downloadable product has a file attached. */ public function has_file( $download_id = '' ) { return ( $this->is_downloadable() && $this->get_file( $download_id ) ) ? true : false; } /** * Gets an array of downloadable files for this product. * * @since 2.1.0 * * @return array */ public function get_files() { $downloadable_files = array_filter( isset( $this->downloadable_files ) ? (array) maybe_unserialize( $this->downloadable_files ) : array() ); if ( ! empty( $downloadable_files ) ) { foreach ( $downloadable_files as $key => $file ) { if ( ! is_array( $file ) ) { $downloadable_files[ $key ] = array( 'file' => $file, 'name' => '' ); } // Set default name if ( empty( $file['name'] ) ) { $downloadable_files[ $key ]['name'] = wc_get_filename_from_url( $file['file'] ); } // Filter URL $downloadable_files[ $key ]['file'] = apply_filters( 'woocommerce_file_download_path', $downloadable_files[ $key ]['file'], $this, $key ); } } return apply_filters( 'woocommerce_product_files', $downloadable_files, $this ); } /** * Get a file by $download_id. * * @param string $download_id file identifier * @return array|false if not found */ public function get_file( $download_id = '' ) { $files = $this->get_files(); if ( '' === $download_id ) { $file = sizeof( $files ) ? current( $files ) : false; } elseif ( isset( $files[ $download_id ] ) ) { $file = $files[ $download_id ]; } else { $file = false; } // allow overriding based on the particular file being requested return apply_filters( 'woocommerce_product_file', $file, $this, $download_id ); } /** * Get file download path identified by $download_id. * * @param string $download_id file identifier * @return string */ public function get_file_download_path( $download_id ) { $files = $this->get_files(); if ( isset( $files[ $download_id ] ) ) { $file_path = $files[ $download_id ]['file']; } else { $file_path = ''; } // allow overriding based on the particular file being requested return apply_filters( 'woocommerce_product_file_download_path', $file_path, $this, $download_id ); } /** * Checks if a product is virtual (has no shipping). * * @return bool */ public function is_virtual() { return apply_filters( 'woocommerce_is_virtual', $this->virtual == 'yes' ? true : false, $this ); } /** * Checks if a product needs shipping. * * @return bool */ public function needs_shipping() { return apply_filters( 'woocommerce_product_needs_shipping', $this->is_virtual() ? false : true, $this ); } /** * Check if a product is sold individually (no quantities). * * @return bool */ public function is_sold_individually() { $return = false; if ( 'yes' == $this->sold_individually ) { $return = true; } return apply_filters( 'woocommerce_is_sold_individually', $return, $this ); } /** * Returns the child product. * * @param mixed $child_id * @return WC_Product|WC_Product|WC_Product_variation */ public function get_child( $child_id ) { return wc_get_product( $child_id ); } /** * Returns the children. * * @return array */ public function get_children() { return array(); } /** * Returns whether or not the product has any child product. * * @return bool */ public function has_child() { return false; } /** * Returns whether or not the product post exists. * * @return bool */ public function exists() { return empty( $this->post ) ? false : true; } /** * Returns whether or not the product is taxable. * * @return bool */ public function is_taxable() { $taxable = $this->get_tax_status() === 'taxable' && wc_tax_enabled() ? true : false; return apply_filters( 'woocommerce_product_is_taxable', $taxable, $this ); } /** * Returns whether or not the product shipping is taxable. * * @return bool */ public function is_shipping_taxable() { return $this->get_tax_status() === 'taxable' || $this->get_tax_status() === 'shipping' ? true : false; } /** * Get the title of the post. * * @return string */ public function get_title() { return apply_filters( 'woocommerce_product_title', $this->post ? $this->post->post_title : '', $this ); } /** * Get the parent of the post. * * @return int */ public function get_parent() { return apply_filters( 'woocommerce_product_parent', absint( $this->post->post_parent ), $this ); } /** * Get the add to url used mainly in loops. * * @return string */ public function add_to_cart_url() { return apply_filters( 'woocommerce_product_add_to_cart_url', get_permalink( $this->id ), $this ); } /** * Get the add to cart button text for the single page. * * @return string */ public function single_add_to_cart_text() { return apply_filters( 'woocommerce_product_single_add_to_cart_text', __( 'Add to cart', 'woocommerce' ), $this ); } /** * Get the add to cart button text. * * @return string */ public function add_to_cart_text() { return apply_filters( 'woocommerce_product_add_to_cart_text', __( 'Read more', 'woocommerce' ), $this ); } /** * Returns whether or not the product is stock managed. * * @return bool */ public function managing_stock() { return ( ! isset( $this->manage_stock ) || $this->manage_stock == 'no' || get_option( 'woocommerce_manage_stock' ) !== 'yes' ) ? false : true; } /** * Returns whether or not the product is in stock. * * @return bool */ public function is_in_stock() { $status = $this->stock_status === 'instock'; /** * Sanity check to ensure stock qty is not lower than 0 but still listed * instock. * * Check is not required for products on backorder since they can be * instock regardless of actual stock quantity. */ if ( $this->managing_stock() && ! $this->backorders_allowed() && $this->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) { $status = false; } return apply_filters( 'woocommerce_product_is_in_stock', $status ); } /** * Returns whether or not the product can be backordered. * * @return bool */ public function backorders_allowed() { return apply_filters( 'woocommerce_product_backorders_allowed', $this->backorders === 'yes' || $this->backorders === 'notify' ? true : false, $this->id, $this ); } /** * Returns whether or not the product needs to notify the customer on backorder. * * @return bool */ public function backorders_require_notification() { return apply_filters( 'woocommerce_product_backorders_require_notification', $this->managing_stock() && $this->backorders === 'notify' ? true : false, $this ); } /** * Check if a product is on backorder. * * @param int $qty_in_cart (default: 0) * @return bool */ public function is_on_backorder( $qty_in_cart = 0 ) { return $this->managing_stock() && $this->backorders_allowed() && ( $this->get_total_stock() - $qty_in_cart ) < 0 ? true : false; } /** * Returns whether or not the product has enough stock for the order. * * @param mixed $quantity * @return bool */ public function has_enough_stock( $quantity ) { return ! $this->managing_stock() || $this->backorders_allowed() || $this->get_stock_quantity() >= $quantity ? true : false; } /** * Returns the availability of the product. * * If stock management is enabled at global and product level, a stock message * will be shown. e.g. In stock, In stock x10, Out of stock. * * If stock management is disabled at global or product level, out of stock * will be shown when needed, but in stock will be hidden from view. * * This can all be changed through use of the woocommerce_get_availability filter. * * @return string */ public function get_availability() { return apply_filters( 'woocommerce_get_availability', array( 'availability' => $this->get_availability_text(), 'class' => $this->get_availability_class(), ), $this ); } /** * Get availability text based on stock status. * * @return string */ protected function get_availability_text() { if ( ! $this->is_in_stock() ) { $availability = __( 'Out of stock', 'woocommerce' ); } elseif ( $this->managing_stock() && $this->is_on_backorder( 1 ) ) { $availability = $this->backorders_require_notification() ? __( 'Available on backorder', 'woocommerce' ) : __( 'In stock', 'woocommerce' ); } elseif ( $this->managing_stock() ) { switch ( get_option( 'woocommerce_stock_format' ) ) { case 'no_amount' : $availability = __( 'In stock', 'woocommerce' ); break; case 'low_amount' : if ( $this->get_total_stock() <= get_option( 'woocommerce_notify_low_stock_amount' ) ) { $availability = sprintf( __( 'Only %s left in stock', 'woocommerce' ), $this->get_total_stock() ); if ( $this->backorders_allowed() && $this->backorders_require_notification() ) { $availability .= ' ' . __( '(also available on backorder)', 'woocommerce' ); } } else { $availability = __( 'In stock', 'woocommerce' ); } break; default : $availability = sprintf( __( '%s in stock', 'woocommerce' ), $this->get_total_stock() ); if ( $this->backorders_allowed() && $this->backorders_require_notification() ) { $availability .= ' ' . __( '(also available on backorder)', 'woocommerce' ); } break; } } else { $availability = ''; } return apply_filters( 'woocommerce_get_availability_text', $availability, $this ); } /** * Get availability classname based on stock status. * * @return string */ protected function get_availability_class() { if ( ! $this->is_in_stock() ) { $class = 'out-of-stock'; } elseif ( $this->managing_stock() && $this->is_on_backorder( 1 ) && $this->backorders_require_notification() ) { $class = 'available-on-backorder'; } else { $class = 'in-stock'; } return apply_filters( 'woocommerce_get_availability_class', $class, $this ); } /** * Returns whether or not the product is featured. * * @return bool */ public function is_featured() { return $this->featured === 'yes' ? true : false; } /** * Returns whether or not the product is visible in the catalog. * * @return bool */ public function is_visible() { if ( ! $this->post ) { $visible = false; // Published/private } elseif ( $this->post->post_status !== 'publish' && ! current_user_can( 'edit_post', $this->id ) ) { $visible = false; // Out of stock visibility } elseif ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $this->is_in_stock() ) { $visible = false; // visibility setting } elseif ( 'hidden' === $this->visibility ) { $visible = false; } elseif ( 'visible' === $this->visibility ) { $visible = true; // Visibility in loop } elseif ( is_search() ) { $visible = 'search' === $this->visibility; } else { $visible = 'catalog' === $this->visibility; } return apply_filters( 'woocommerce_product_is_visible', $visible, $this->id ); } /** * Returns whether or not the product is on sale. * * @return bool */ public function is_on_sale() { return apply_filters( 'woocommerce_product_is_on_sale', ( $this->get_sale_price() !== $this->get_regular_price() && $this->get_sale_price() === $this->get_price() ), $this ); } /** * Returns false if the product cannot be bought. * * @return bool */ public function is_purchasable() { $purchasable = true; // Products must exist of course if ( ! $this->exists() ) { $purchasable = false; // Other products types need a price to be set } elseif ( $this->get_price() === '' ) { $purchasable = false; // Check the product is published } elseif ( $this->post->post_status !== 'publish' && ! current_user_can( 'edit_post', $this->id ) ) { $purchasable = false; } return apply_filters( 'woocommerce_is_purchasable', $purchasable, $this ); } /** * Set a products price dynamically. * * @param float $price Price to set. */ public function set_price( $price ) { $this->price = $price; } /** * Adjust a products price dynamically. * * @param mixed $price */ public function adjust_price( $price ) { $this->price = $this->price + $price; } /** * Returns the product's sale price. * * @return string price */ public function get_sale_price() { return apply_filters( 'woocommerce_get_sale_price', $this->sale_price, $this ); } /** * Returns the product's regular price. * * @return string price */ public function get_regular_price() { return apply_filters( 'woocommerce_get_regular_price', $this->regular_price, $this ); } /** * Returns the product's active price. * * @return string price */ public function get_price() { return apply_filters( 'woocommerce_get_price', $this->price, $this ); } /** * Returns the price (including tax). Uses customer tax rates. Can work for a specific $qty for more accurate taxes. * * @param int $qty * @param string $price to calculate, left blank to just use get_price() * @return string */ public function get_price_including_tax( $qty = 1, $price = '' ) { if ( $price === '' ) { $price = $this->get_price(); } $price = (float) $price; if ( $this->is_taxable() ) { if ( get_option( 'woocommerce_prices_include_tax' ) === 'no' ) { $tax_rates = WC_Tax::get_rates( $this->get_tax_class() ); $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, false ); $tax_amount = WC_Tax::get_tax_total( $taxes ); $price = round( $price * $qty + $tax_amount, wc_get_price_decimals() ); } else { $tax_rates = WC_Tax::get_rates( $this->get_tax_class() ); $base_tax_rates = WC_Tax::get_base_tax_rates( $this->tax_class ); if ( ! empty( WC()->customer ) && WC()->customer->is_vat_exempt() ) { $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true ); $base_tax_amount = array_sum( $base_taxes ); $price = round( $price * $qty - $base_tax_amount, wc_get_price_decimals() ); /** * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. */ } elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true ); $modded_taxes = WC_Tax::calc_tax( ( $price * $qty ) - array_sum( $base_taxes ), $tax_rates, false ); $price = round( ( $price * $qty ) - array_sum( $base_taxes ) + array_sum( $modded_taxes ), wc_get_price_decimals() ); } else { $price = $price * $qty; } } } else { $price = $price * $qty; } return apply_filters( 'woocommerce_get_price_including_tax', $price, $qty, $this ); } /** * Returns the price (excluding tax) - ignores tax_class filters since the price may *include* tax and thus needs subtracting. * Uses store base tax rates. Can work for a specific $qty for more accurate taxes. * * @param int $qty * @param string $price to calculate, left blank to just use get_price() * @return string */ public function get_price_excluding_tax( $qty = 1, $price = '' ) { if ( $price === '' ) { $price = $this->get_price(); } $price = (float) $price; if ( $this->is_taxable() && 'yes' === get_option( 'woocommerce_prices_include_tax' ) ) { $tax_rates = WC_Tax::get_base_tax_rates( $this->tax_class ); $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, true ); $price = WC_Tax::round( $price * $qty - array_sum( $taxes ) ); } else { $price = $price * $qty; } return apply_filters( 'woocommerce_get_price_excluding_tax', $price, $qty, $this ); } /** * Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting. * * @param string $price to calculate, left blank to just use get_price() * @param integer $qty passed on to get_price_including_tax() or get_price_excluding_tax() * @return string */ public function get_display_price( $price = '', $qty = 1 ) { if ( $price === '' ) { $price = $this->get_price(); } $tax_display_mode = get_option( 'woocommerce_tax_display_shop' ); $display_price = $tax_display_mode == 'incl' ? $this->get_price_including_tax( $qty, $price ) : $this->get_price_excluding_tax( $qty, $price ); return $display_price; } /** * Get the suffix to display after prices > 0. * * @param string $price to calculate, left blank to just use get_price() * @param integer $qty passed on to get_price_including_tax() or get_price_excluding_tax() * @return string */ public function get_price_suffix( $price = '', $qty = 1 ) { if ( $price === '' ) { $price = $this->get_price(); } $price_display_suffix = get_option( 'woocommerce_price_display_suffix' ); $woocommerce_calc_taxes = get_option( 'woocommerce_calc_taxes', 'no' ); if ( $price_display_suffix && 'yes' === $woocommerce_calc_taxes ) { $price_display_suffix = ' <small class="woocommerce-price-suffix">' . $price_display_suffix . '</small>'; $find = array( '{price_including_tax}', '{price_excluding_tax}' ); $replace = array( wc_price( $this->get_price_including_tax( $qty, $price ) ), wc_price( $this->get_price_excluding_tax( $qty, $price ) ) ); $price_display_suffix = str_replace( $find, $replace, $price_display_suffix ); } else { $price_display_suffix = ''; } return apply_filters( 'woocommerce_get_price_suffix', $price_display_suffix, $this ); } /** * Returns the price in html format. * * @param string $price (default: '') * @return string */ public function get_price_html( $price = '' ) { $display_price = $this->get_display_price(); $display_regular_price = $this->get_display_price( $this->get_regular_price() ); if ( $this->get_price() > 0 ) { if ( $this->is_on_sale() && $this->get_regular_price() ) { $price .= $this->get_price_html_from_to( $display_regular_price, $display_price ) . $this->get_price_suffix(); $price = apply_filters( 'woocommerce_sale_price_html', $price, $this ); } else { $price .= wc_price( $display_price ) . $this->get_price_suffix(); $price = apply_filters( 'woocommerce_price_html', $price, $this ); } } elseif ( $this->get_price() === '' ) { $price = apply_filters( 'woocommerce_empty_price_html', '', $this ); } elseif ( $this->get_price() == 0 ) { if ( $this->is_on_sale() && $this->get_regular_price() ) { $price .= $this->get_price_html_from_to( $display_regular_price, __( 'Free!', 'woocommerce' ) ); $price = apply_filters( 'woocommerce_free_sale_price_html', $price, $this ); } else { $price = '<span class="amount">' . __( 'Free!', 'woocommerce' ) . '</span>'; $price = apply_filters( 'woocommerce_free_price_html', $price, $this ); } } return apply_filters( 'woocommerce_get_price_html', $price, $this ); } /** * Functions for getting parts of a price, in html, used by get_price_html. * * @return string */ public function get_price_html_from_text() { $from = '<span class="from">' . _x( 'From:', 'min_price', 'woocommerce' ) . ' </span>'; return apply_filters( 'woocommerce_get_price_html_from_text', $from, $this ); } /** * Functions for getting parts of a price, in html, used by get_price_html. * * @param string $from String or float to wrap with 'from' text * @param mixed $to String or float to wrap with 'to' text * @return string */ public function get_price_html_from_to( $from, $to ) { $price = '<del>' . ( ( is_numeric( $from ) ) ? wc_price( $from ) : $from ) . '</del> <ins>' . ( ( is_numeric( $to ) ) ? wc_price( $to ) : $to ) . '</ins>'; return apply_filters( 'woocommerce_get_price_html_from_to', $price, $from, $to, $this ); } /** * Returns the tax class. * * @return string */ public function get_tax_class() { return apply_filters( 'woocommerce_product_tax_class', $this->tax_class, $this ); } /** * Returns the tax status. * * @return string */ public function get_tax_status() { return $this->tax_status; } /** * Get the average rating of product. This is calculated once and stored in postmeta. * @return string */ public function get_average_rating() { // No meta data? Do the calculation if ( ! metadata_exists( 'post', $this->id, '_wc_average_rating' ) ) { $this->sync_average_rating( $this->id ); } return (string) floatval( get_post_meta( $this->id, '_wc_average_rating', true ) ); } /** * Get the total amount (COUNT) of ratings. * @param int $value Optional. Rating value to get the count for. By default returns the count of all rating values. * @return int */ public function get_rating_count( $value = null ) { // No meta data? Do the calculation if ( ! metadata_exists( 'post', $this->id, '_wc_rating_count' ) ) { $this->sync_rating_count( $this->id ); } $counts = array_filter( (array) get_post_meta( $this->id, '_wc_rating_count', true ) ); if ( is_null( $value ) ) { return array_sum( $counts ); } else { return isset( $counts[ $value ] ) ? $counts[ $value ] : 0; } } /** * Sync product rating. Can be called statically. * @param int $post_id */ public static function sync_average_rating( $post_id ) { if ( ! metadata_exists( 'post', $post_id, '_wc_rating_count' ) ) { self::sync_rating_count( $post_id ); } $count = array_sum( (array) get_post_meta( $post_id, '_wc_rating_count', true ) ); if ( $count ) { global $wpdb; $ratings = $wpdb->get_var( $wpdb->prepare(" SELECT SUM(meta_value) FROM $wpdb->commentmeta LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID WHERE meta_key = 'rating' AND comment_post_ID = %d AND comment_approved = '1' AND meta_value > 0 ", $post_id ) ); $average = number_format( $ratings / $count, 2, '.', '' ); } else { $average = 0; } update_post_meta( $post_id, '_wc_average_rating', $average ); } /** * Sync product rating count. Can be called statically. * @param int $post_id */ public static function sync_rating_count( $post_id ) { global $wpdb; $counts = array(); $raw_counts = $wpdb->get_results( $wpdb->prepare( " SELECT meta_value, COUNT( * ) as meta_value_count FROM $wpdb->commentmeta LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID WHERE meta_key = 'rating' AND comment_post_ID = %d AND comment_approved = '1' AND meta_value > 0 GROUP BY meta_value ", $post_id ) ); foreach ( $raw_counts as $count ) { $counts[ $count->meta_value ] = $count->meta_value_count; } update_post_meta( $post_id, '_wc_rating_count', $counts ); } /** * Returns the product rating in html format. * * @param string $rating (default: '') * * @return string */ public function get_rating_html( $rating = null ) { $rating_html = ''; if ( ! is_numeric( $rating ) ) { $rating = $this->get_average_rating(); } if ( $rating > 0 ) { $rating_html = '<div class="star-rating" title="' . sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $rating ) . '">'; $rating_html .= '<span style="width:' . ( ( $rating / 5 ) * 100 ) . '%"><strong class="rating">' . $rating . '</strong> ' . __( 'out of 5', 'woocommerce' ) . '</span>'; $rating_html .= '</div>'; } return apply_filters( 'woocommerce_product_get_rating_html', $rating_html, $rating ); } /** * Get the total amount (COUNT) of reviews. * * @since 2.3.2 * @return int The total numver of product reviews */ public function get_review_count() { global $wpdb; // No meta date? Do the calculation if ( ! metadata_exists( 'post', $this->id, '_wc_review_count' ) ) { $count = $wpdb->get_var( $wpdb->prepare(" SELECT COUNT(*) FROM $wpdb->comments WHERE comment_parent = 0 AND comment_post_ID = %d AND comment_approved = '1' ", $this->id ) ); update_post_meta( $this->id, '_wc_review_count', $count ); } else { $count = get_post_meta( $this->id, '_wc_review_count', true ); } return apply_filters( 'woocommerce_product_review_count', $count, $this ); } /** * Returns the upsell product ids. * * @return array */ public function get_upsells() { return apply_filters( 'woocommerce_product_upsell_ids', (array) maybe_unserialize( $this->upsell_ids ), $this ); } /** * Returns the cross sell product ids. * * @return array */ public function get_cross_sells() { return apply_filters( 'woocommerce_product_crosssell_ids', (array) maybe_unserialize( $this->crosssell_ids ), $this ); } /** * Returns the product categories. * * @param string $sep (default: ', ') * @param string $before (default: '') * @param string $after (default: '') * @return string */ public function get_categories( $sep = ', ', $before = '', $after = '' ) { return get_the_term_list( $this->id, 'product_cat', $before, $sep, $after ); } /** * Returns the product tags. * * @param string $sep (default: ', ') * @param string $before (default: '') * @param string $after (default: '') * @return array */ public function get_tags( $sep = ', ', $before = '', $after = '' ) { return get_the_term_list( $this->id, 'product_tag', $before, $sep, $after ); } /** * Returns the product shipping class. * * @return string */ public function get_shipping_class() { if ( ! $this->shipping_class ) { $classes = get_the_terms( $this->id, 'product_shipping_class' ); if ( $classes && ! is_wp_error( $classes ) ) { $this->shipping_class = current( $classes )->slug; } else { $this->shipping_class = ''; } } return $this->shipping_class; } /** * Returns the product shipping class ID. * * @return int */ public function get_shipping_class_id() { if ( ! $this->shipping_class_id ) { $classes = get_the_terms( $this->id, 'product_shipping_class' ); if ( $classes && ! is_wp_error( $classes ) ) { $this->shipping_class_id = current( $classes )->term_id; } else { $this->shipping_class_id = 0; } } return absint( $this->shipping_class_id ); } /** * Get and return related products. * * Notes: * - Results are cached in a transient for faster queries. * - To make results appear random, we query and extra 10 products and shuffle them. * - To ensure we always have enough results, it will check $limit before returning the cached result, if not recalc. * - This used to rely on transient version to invalidate cache, but to avoid multiple transients we now just expire daily. * This means if a related product is edited and no longer related, it won't be removed for 24 hours. Acceptable trade-off for performance. * - Saving a product will flush caches for that product. * * @param int $limit (default: 5) Should be an integer greater than 0. * @return array Array of post IDs */ public function get_related( $limit = 5 ) { global $wpdb; $transient_name = 'wc_related_' . $this->id; $related_posts = get_transient( $transient_name ); $limit = $limit > 0 ? $limit : 5; // We want to query related posts if they are not cached, or we don't have enough if ( false === $related_posts || sizeof( $related_posts ) < $limit ) { // Related products are found from category and tag $tags_array = $this->get_related_terms( 'product_tag' ); $cats_array = $this->get_related_terms( 'product_cat' ); // Don't bother if none are set if ( 1 === sizeof( $cats_array ) && 1 === sizeof( $tags_array )) { $related_posts = array(); } else { // Sanitize $exclude_ids = array_map( 'absint', array_merge( array( 0, $this->id ), $this->get_upsells() ) ); // Generate query - but query an extra 10 results to give the appearance of random results $query = $this->build_related_query( $cats_array, $tags_array, $exclude_ids, $limit + 10 ); // Get the posts $related_posts = $wpdb->get_col( implode( ' ', $query ) ); } set_transient( $transient_name, $related_posts, DAY_IN_SECONDS ); } // Randomise the results shuffle( $related_posts ); // Limit the returned results return array_slice( $related_posts, 0, $limit ); } /** * Returns a single product attribute. * * @param mixed $attr * @return string */ public function get_attribute( $attr ) { $attributes = $this->get_attributes(); $attr = sanitize_title( $attr ); if ( isset( $attributes[ $attr ] ) || isset( $attributes[ 'pa_' . $attr ] ) ) { $attribute = isset( $attributes[ $attr ] ) ? $attributes[ $attr ] : $attributes[ 'pa_' . $attr ]; if ( isset( $attribute['is_taxonomy'] ) && $attribute['is_taxonomy'] ) { return implode( ', ', wc_get_product_terms( $this->id, $attribute['name'], array( 'fields' => 'names' ) ) ); } else { return $attribute['value']; } } return ''; } /** * Returns product attributes. * * @return array */ public function get_attributes() { $attributes = array_filter( (array) maybe_unserialize( $this->product_attributes ) ); $taxonomies = wp_list_pluck( wc_get_attribute_taxonomies(), 'attribute_name' ); // Check for any attributes which have been removed globally foreach ( $attributes as $key => $attribute ) { if ( $attribute['is_taxonomy'] ) { if ( ! in_array( substr( $attribute['name'], 3 ), $taxonomies ) ) { unset( $attributes[ $key ] ); } } } return apply_filters( 'woocommerce_get_product_attributes', $attributes ); } /** * Returns whether or not the product has any attributes set. * * @return boolean */ public function has_attributes() { if ( sizeof( $this->get_attributes() ) > 0 ) { foreach ( $this->get_attributes() as $attribute ) { if ( isset( $attribute['is_visible'] ) && $attribute['is_visible'] ) { return true; } } } return false; } /** * Returns whether or not we are showing dimensions on the product page. * * @return bool */ public function enable_dimensions_display() { return apply_filters( 'wc_product_enable_dimensions_display', true ); } /** * Returns whether or not the product has dimensions set. * * @return bool */ public function has_dimensions() { return $this->get_dimensions() ? true : false; } /** * Returns the product length. * @return string */ public function get_length() { return apply_filters( 'woocommerce_product_length', '' === $this->length ? '' : wc_format_decimal( $this->length ), $this ); } /** * Returns the product width. * @return string */ public function get_width() { return apply_filters( 'woocommerce_product_width', '' === $this->width ? '' : wc_format_decimal( $this->width ), $this ); } /** * Returns the product height. * @return string */ public function get_height() { return apply_filters( 'woocommerce_product_height', '' === $this->height ? '' : wc_format_decimal( $this->height ), $this ); } /** * Returns the product's weight. * @todo refactor filters in this class to naming woocommerce_product_METHOD * @return string */ public function get_weight() { return apply_filters( 'woocommerce_product_weight', apply_filters( 'woocommerce_product_get_weight', '' === $this->weight ? '' : wc_format_decimal( $this->weight ) ), $this ); } /** * Returns whether or not the product has weight set. * * @return bool */ public function has_weight() { return $this->get_weight() ? true : false; } /** * Returns formatted dimensions. * @return string */ public function get_dimensions() { $dimensions = implode( ' x ', array_filter( array( wc_format_localized_decimal( $this->get_length() ), wc_format_localized_decimal( $this->get_width() ), wc_format_localized_decimal( $this->get_height() ), ) ) ); if ( ! empty( $dimensions ) ) { $dimensions .= ' ' . get_option( 'woocommerce_dimension_unit' ); } return apply_filters( 'woocommerce_product_dimensions', $dimensions, $this ); } /** * Lists a table of attributes for the product page. */ public function list_attributes() { wc_get_template( 'single-product/product-attributes.php', array( 'product' => $this ) ); } /** * Gets the main product image ID. * * @return int */ public function get_image_id() { if ( has_post_thumbnail( $this->id ) ) { $image_id = get_post_thumbnail_id( $this->id ); } elseif ( ( $parent_id = wp_get_post_parent_id( $this->id ) ) && has_post_thumbnail( $parent_id ) ) { $image_id = get_post_thumbnail_id( $parent_id ); } else { $image_id = 0; } return $image_id; } /** * Returns the main product image. * * @param string $size (default: 'shop_thumbnail') * @param array $attr * @param bool True to return $placeholder if no image is found, or false to return an empty string. * @return string */ public function get_image( $size = 'shop_thumbnail', $attr = array(), $placeholder = true ) { if ( has_post_thumbnail( $this->id ) ) { $image = get_the_post_thumbnail( $this->id, $size, $attr ); } elseif ( ( $parent_id = wp_get_post_parent_id( $this->id ) ) && has_post_thumbnail( $parent_id ) ) { $image = get_the_post_thumbnail( $parent_id, $size, $attr ); } elseif ( $placeholder ) { $image = wc_placeholder_img( $size ); } else { $image = ''; } return $image; } /** * Get product name with SKU or ID. Used within admin. * * @return string Formatted product name */ public function get_formatted_name() { if ( $this->get_sku() ) { $identifier = $this->get_sku(); } else { $identifier = '#' . $this->id; } return sprintf( '%s – %s', $identifier, $this->get_title() ); } /** * Retrieves related product terms. * * @param string $term * @return array */ protected function get_related_terms( $term ) { $terms_array = array(0); $terms = apply_filters( 'woocommerce_get_related_' . $term . '_terms', wp_get_post_terms( $this->id, $term ), $this->id ); foreach ( $terms as $term ) { $terms_array[] = $term->term_id; } return array_map( 'absint', $terms_array ); } /** * Builds the related posts query. * * @param array $cats_array * @param array $tags_array * @param array $exclude_ids * @param int $limit * @return string */ protected function build_related_query( $cats_array, $tags_array, $exclude_ids, $limit ) { global $wpdb; $limit = absint( $limit ); $query = array(); $query['fields'] = "SELECT DISTINCT ID FROM {$wpdb->posts} p"; $query['join'] = " INNER JOIN {$wpdb->postmeta} pm ON ( pm.post_id = p.ID AND pm.meta_key='_visibility' )"; $query['join'] .= " INNER JOIN {$wpdb->term_relationships} tr ON (p.ID = tr.object_id)"; $query['join'] .= " INNER JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)"; $query['join'] .= " INNER JOIN {$wpdb->terms} t ON (t.term_id = tt.term_id)"; if ( get_option( 'woocommerce_hide_out_of_stock_items' ) === 'yes' ) { $query['join'] .= " INNER JOIN {$wpdb->postmeta} pm2 ON ( pm2.post_id = p.ID AND pm2.meta_key='_stock_status' )"; } $query['where'] = " WHERE 1=1"; $query['where'] .= " AND p.post_status = 'publish'"; $query['where'] .= " AND p.post_type = 'product'"; $query['where'] .= " AND p.ID NOT IN ( " . implode( ',', $exclude_ids ) . " )"; $query['where'] .= " AND pm.meta_value IN ( 'visible', 'catalog' )"; if ( get_option( 'woocommerce_hide_out_of_stock_items' ) === 'yes' ) { $query['where'] .= " AND pm2.meta_value = 'instock'"; } $relate_by_category = apply_filters( 'woocommerce_product_related_posts_relate_by_category', true, $this->id ); $relate_by_tag = apply_filters( 'woocommerce_product_related_posts_relate_by_tag', true, $this->id ); if ( $relate_by_category || $relate_by_tag ) { $query['where'] .= ' AND ('; if ( $relate_by_category ) { $query['where'] .= " ( tt.taxonomy = 'product_cat' AND t.term_id IN ( " . implode( ',', $cats_array ) . " ) ) "; if ( $relate_by_tag ) { $query['where'] .= ' OR '; } } if ( $relate_by_tag ) { $query['where'] .= " ( tt.taxonomy = 'product_tag' AND t.term_id IN ( " . implode( ',', $tags_array ) . " ) ) "; } $query['where'] .= ')'; } $query['limits'] = " LIMIT {$limit} "; $query = apply_filters( 'woocommerce_product_related_posts_query', $query, $this->id ); return $query; } } abstract-wc-rest-controller.php 0000666 00000013611 15214130426 0012627 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Abstract Rest Controler Class * * @author WooThemes * @category API * @package WooCommerce/Abstracts * @extends WP_REST_Controller * @version 2.6.0 */ abstract class WC_REST_Controller extends WP_REST_Controller { /** * Endpoint namespace. * * @var string */ protected $namespace = 'wc/v1'; /** * Route base. * * @var string */ protected $rest_base = ''; /** * Add the schema from additional fields to an schema array. * * The type of object is inferred from the passed schema. * * @param array $schema Schema array. */ protected function add_additional_fields_schema( $schema ) { if ( empty( $schema['title'] ) ) { return $schema; } /** * Can't use $this->get_object_type otherwise we cause an inf loop. */ $object_type = $schema['title']; $additional_fields = $this->get_additional_fields( $object_type ); foreach ( $additional_fields as $field_name => $field_options ) { if ( ! $field_options['schema'] ) { continue; } $schema['properties'][ $field_name ] = $field_options['schema']; } $schema['properties'] = apply_filters( 'woocommerce_rest_' . $object_type . '_schema', $schema['properties'] ); return $schema; } /** * Get normalized rest base. * * @return string */ protected function get_normalized_rest_base() { return preg_replace( '/\(.*\)\//i', '', $this->rest_base ); } /** * Check batch limit. * * @param array $items Request items. * @return bool|WP_Error */ protected function check_batch_limit( $items ) { $limit = apply_filters( 'woocommerce_rest_batch_items_limit', 100, $this->get_normalized_rest_base() ); $total = 0; if ( ! empty( $items['create'] ) ) { $total += count( $items['create'] ); } if ( ! empty( $items['update'] ) ) { $total += count( $items['update'] ); } if ( ! empty( $items['delete'] ) ) { $total += count( $items['delete'] ); } if ( $total > $limit ) { return new WP_Error( 'woocommerce_rest_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'woocommerce' ), $limit ), array( 'status' => 413 ) ); } return true; } /** * Bulk create, update and delete items. * * @param WP_REST_Request $request Full details about the request. * @return array Of WP_Error or WP_REST_Response. */ public function batch_items( $request ) { /** @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; // Get the request params. $items = array_filter( $request->get_params() ); $response = array(); // Check batch limit. $limit = $this->check_batch_limit( $items ); if ( is_wp_error( $limit ) ) { return $limit; } if ( ! empty( $items['create'] ) ) { foreach ( $items['create'] as $item ) { $_item = new WP_REST_Request( 'POST' ); // Default parameters. $defaults = array(); $schema = $this->get_public_item_schema(); foreach ( $schema['properties'] as $arg => $options ) { if ( isset( $options['default'] ) ) { $defaults[ $arg ] = $options['default']; } } $_item->set_default_params( $defaults ); // Set request parameters. $_item->set_body_params( $item ); $_response = $this->create_item( $_item ); if ( is_wp_error( $_response ) ) { $response['create'][] = array( 'id' => 0, 'error' => array( 'code' => $_response->get_error_code(), 'message' => $_response->get_error_message(), 'data' => $_response->get_error_data() ), ); } else { $response['create'][] = $wp_rest_server->response_to_data( $_response, '' ); } } } if ( ! empty( $items['update'] ) ) { foreach ( $items['update'] as $item ) { $_item = new WP_REST_Request( 'PUT' ); $_item->set_body_params( $item ); $_response = $this->update_item( $_item ); if ( is_wp_error( $_response ) ) { $response['update'][] = array( 'id' => $item['id'], 'error' => array( 'code' => $_response->get_error_code(), 'message' => $_response->get_error_message(), 'data' => $_response->get_error_data() ), ); } else { $response['update'][] = $wp_rest_server->response_to_data( $_response, '' ); } } } if ( ! empty( $items['delete'] ) ) { foreach ( $items['delete'] as $id ) { $id = (int) $id; if ( 0 === $id ) { continue; } $_item = new WP_REST_Request( 'DELETE' ); $_item->set_query_params( array( 'id' => $id, 'force' => true ) ); $_response = $this->delete_item( $_item ); if ( is_wp_error( $_response ) ) { $response['delete'][] = array( 'id' => $id, 'error' => array( 'code' => $_response->get_error_code(), 'message' => $_response->get_error_message(), 'data' => $_response->get_error_data() ), ); } else { $response['delete'][] = $wp_rest_server->response_to_data( $_response, '' ); } } } return $response; } /** * Get the batch schema, conforming to JSON Schema. * * @return array */ public function get_public_batch_schema() { $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'batch', 'type' => 'object', 'properties' => array( 'create' => array( 'description' => __( 'List of created resources.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', ), ), 'update' => array( 'description' => __( 'List of updated resources.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', ), ), 'delete' => array( 'description' => __( 'List of delete resources.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'integer', ), ), ), ); return $schema; } } abstract-wc-widget.php 0000666 00000016224 15214130426 0010757 0 ustar 00 <?php /** * Abstract Widget Class * * @author WooThemes * @category Widgets * @package WooCommerce/Abstracts * @version 2.5.0 * @extends WP_Widget */ abstract class WC_Widget extends WP_Widget { /** * CSS class. * * @var string */ public $widget_cssclass; /** * Widget description. * * @var string */ public $widget_description; /** * Widget ID. * * @var string */ public $widget_id; /** * Widget name. * * @var string */ public $widget_name; /** * Settings. * * @var array */ public $settings; /** * Constructor. */ public function __construct() { $widget_ops = array( 'classname' => $this->widget_cssclass, 'description' => $this->widget_description, 'customize_selective_refresh' => true ); parent::__construct( $this->widget_id, $this->widget_name, $widget_ops ); add_action( 'save_post', array( $this, 'flush_widget_cache' ) ); add_action( 'deleted_post', array( $this, 'flush_widget_cache' ) ); add_action( 'switch_theme', array( $this, 'flush_widget_cache' ) ); } /** * Get cached widget. * * @param array $args * @return bool true if the widget is cached otherwise false */ public function get_cached_widget( $args ) { $cache = wp_cache_get( apply_filters( 'woocommerce_cached_widget_id', $this->widget_id ), 'widget' ); if ( ! is_array( $cache ) ) { $cache = array(); } if ( isset( $cache[ $args['widget_id'] ] ) ) { echo $cache[ $args['widget_id'] ]; return true; } return false; } /** * Cache the widget. * * @param array $args * @param string $content * @return string the content that was cached */ public function cache_widget( $args, $content ) { wp_cache_set( apply_filters( 'woocommerce_cached_widget_id', $this->widget_id ), array( $args['widget_id'] => $content ), 'widget' ); return $content; } /** * Flush the cache. */ public function flush_widget_cache() { wp_cache_delete( apply_filters( 'woocommerce_cached_widget_id', $this->widget_id ), 'widget' ); } /** * Output the html at the start of a widget. * * @param array $args * @return string */ public function widget_start( $args, $instance ) { echo $args['before_widget']; if ( $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base ) ) { echo $args['before_title'] . $title . $args['after_title']; } } /** * Output the html at the end of a widget. * * @param array $args * @return string */ public function widget_end( $args ) { echo $args['after_widget']; } /** * Updates a particular instance of a widget. * * @see WP_Widget->update * @param array $new_instance * @param array $old_instance * @return array */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; if ( empty( $this->settings ) ) { return $instance; } // Loop settings and get values to save. foreach ( $this->settings as $key => $setting ) { if ( ! isset( $setting['type'] ) ) { continue; } // Format the value based on settings type. switch ( $setting['type'] ) { case 'number' : $instance[ $key ] = absint( $new_instance[ $key ] ); if ( isset( $setting['min'] ) && '' !== $setting['min'] ) { $instance[ $key ] = max( $instance[ $key ], $setting['min'] ); } if ( isset( $setting['max'] ) && '' !== $setting['max'] ) { $instance[ $key ] = min( $instance[ $key ], $setting['max'] ); } break; case 'textarea' : $instance[ $key ] = wp_kses( trim( wp_unslash( $new_instance[ $key ] ) ), wp_kses_allowed_html( 'post' ) ); break; case 'checkbox' : $instance[ $key ] = empty( $new_instance[ $key ] ) ? 0 : 1; break; default: $instance[ $key ] = sanitize_text_field( $new_instance[ $key ] ); break; } /** * Sanitize the value of a setting. */ $instance[ $key ] = apply_filters( 'woocommerce_widget_settings_sanitize_option', $instance[ $key ], $new_instance, $key, $setting ); } $this->flush_widget_cache(); return $instance; } /** * Outputs the settings update form. * * @see WP_Widget->form * @param array $instance */ public function form( $instance ) { if ( empty( $this->settings ) ) { return; } foreach ( $this->settings as $key => $setting ) { $class = isset( $setting['class'] ) ? $setting['class'] : ''; $value = isset( $instance[ $key ] ) ? $instance[ $key ] : $setting['std']; switch ( $setting['type'] ) { case 'text' : ?> <p> <label for="<?php echo $this->get_field_id( $key ); ?>"><?php echo $setting['label']; ?></label> <input class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo $this->get_field_name( $key ); ?>" type="text" value="<?php echo esc_attr( $value ); ?>" /> </p> <?php break; case 'number' : ?> <p> <label for="<?php echo $this->get_field_id( $key ); ?>"><?php echo $setting['label']; ?></label> <input class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo $this->get_field_name( $key ); ?>" type="number" step="<?php echo esc_attr( $setting['step'] ); ?>" min="<?php echo esc_attr( $setting['min'] ); ?>" max="<?php echo esc_attr( $setting['max'] ); ?>" value="<?php echo esc_attr( $value ); ?>" /> </p> <?php break; case 'select' : ?> <p> <label for="<?php echo $this->get_field_id( $key ); ?>"><?php echo $setting['label']; ?></label> <select class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo $this->get_field_name( $key ); ?>"> <?php foreach ( $setting['options'] as $option_key => $option_value ) : ?> <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $option_key, $value ); ?>><?php echo esc_html( $option_value ); ?></option> <?php endforeach; ?> </select> </p> <?php break; case 'textarea' : ?> <p> <label for="<?php echo $this->get_field_id( $key ); ?>"><?php echo $setting['label']; ?></label> <textarea class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo $this->get_field_name( $key ); ?>" cols="20" rows="3"><?php echo esc_textarea( $value ); ?></textarea> <?php if ( isset( $setting['desc'] ) ) : ?> <small><?php echo esc_html( $setting['desc'] ); ?></small> <?php endif; ?> </p> <?php break; case 'checkbox' : ?> <p> <input class="checkbox <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="checkbox" value="1" <?php checked( $value, 1 ); ?> /> <label for="<?php echo $this->get_field_id( $key ); ?>"><?php echo $setting['label']; ?></label> </p> <?php break; // Default: run an action default : do_action( 'woocommerce_widget_field_' . $setting['type'], $key, $value, $setting, $instance ); break; } } } } abstract-wc-integration.php 0000666 00000003167 15214130426 0012021 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Abstract Integration Class * * Extended by individual integrations to offer additional functionality. * * @class WC_Integration * @extends WC_Settings_API * @version 2.6.0 * @package WooCommerce/Abstracts * @category Abstract Class * @author WooThemes */ abstract class WC_Integration extends WC_Settings_API { /** * yes or no based on whether the integration is enabled. * @var string */ public $enabled = 'yes'; /** * Integration title. * @var string */ public $method_title = ''; /** * Integration description. * @var string */ public $method_description = ''; /** * Return the title for admin screens. * @return string */ public function get_method_title() { return apply_filters( 'woocommerce_integration_title', $this->method_title, $this ); } /** * Return the description for admin screens. * @return string */ public function get_method_description() { return apply_filters( 'woocommerce_integration_description', $this->method_description, $this ); } /** * Output the gateway settings screen. */ public function admin_options() { echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>'; echo wp_kses_post( wpautop( $this->get_method_description() ) ); echo '<div><input type="hidden" name="section" value="' . esc_attr( $this->id ) . '" /></div>'; parent::admin_options(); } /** * Init settings for gateways. */ public function init_settings() { parent::init_settings(); $this->enabled = ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled'] ? 'yes' : 'no'; } } abstract-wc-data.php 0000666 00000022110 15214130426 0010374 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Abstract WC Data Class * * Implemented by classes using the same CRUD(s) pattern. * * @version 2.6.0 * @package WooCommerce/Abstracts * @category Abstract Class * @author WooThemes */ abstract class WC_Data { /** * Core data for this object, name value pairs (name + default value). * @var array */ protected $_data = array(); /** * Stores meta in cache for future reads. * A group must be set to to enable caching. * @var string */ protected $_cache_group = ''; /** * Meta type. This should match up with * the types avaiable at https://codex.wordpress.org/Function_Reference/add_metadata. * WP defines 'post', 'user', 'comment', and 'term'. */ protected $_meta_type = 'post'; /** * This only needs set if you are using a custom metadata type (for example payment tokens. * This should be the name of the field your table uses for associating meta with objects. * For example, in payment_tokenmeta, this would be payment_token_id. * @var string */ protected $object_id_field_for_meta = ''; /** * Stores additonal meta data. * @var array */ protected $_meta_data = array(); /** * Internal meta keys we don't want exposed for the object. * @var array */ protected $_internal_meta_keys = array(); /** * Returns the unique ID for this object. * @return int */ abstract public function get_id(); /** * Creates new object in the database. */ abstract public function create(); /** * Read object from the database. * @param int ID of the object to load. */ abstract public function read( $id ); /** * Updates object data in the database. */ abstract public function update(); /** * Updates object data in the database. */ abstract public function delete(); /** * Save should create or update based on object existance. */ abstract public function save(); /** * Change data to JSON format. * @return string Data in JSON format. */ public function __toString() { return json_encode( $this->get_data() ); } /** * Returns all data for this object. * @return array */ public function get_data() { return array_merge( $this->_data, array( 'meta_data' => $this->get_meta_data() ) ); } /** * Get All Meta Data * @since 2.6.0 * @return array */ public function get_meta_data() { return $this->_meta_data; } /** * Internal meta keys we don't want exposed as part of meta_data. This is in * addition to all data props with _ prefix. * @since 2.6.0 * @return array */ protected function prefix_key( $key ) { return '_' === substr( $key, 0, 1 ) ? $key : '_' . $key; } /** * Internal meta keys we don't want exposed as part of meta_data. This is in * addition to all data props with _ prefix. * @since 2.6.0 * @return array */ protected function get_internal_meta_keys() { return array_merge( array_map( array( $this, 'prefix_key' ), array_keys( $this->_data ) ), $this->_internal_meta_keys ); } /** * Get Meta Data by Key. * @since 2.6.0 * @param string $key * @param bool $single return first found meta with key, or all with $key * @return mixed */ public function get_meta( $key = '', $single = true ) { $array_keys = array_keys( wp_list_pluck( $this->_meta_data, 'key' ), $key ); $value = ''; if ( ! empty( $array_keys ) ) { if ( $single ) { $value = $this->_meta_data[ current( $array_keys ) ]->value; } else { $value = array_intersect_key( $this->_meta_data, array_flip( $array_keys ) ); } } return $value; } /** * Set all meta data from array. * @since 2.6.0 * @param array $data Key/Value pairs */ public function set_meta_data( $data ) { if ( ! empty( $data ) && is_array( $data ) ) { foreach ( $data as $meta ) { $meta = (array) $meta; if ( isset( $meta['key'], $meta['value'], $meta['meta_id'] ) ) { $this->_meta_data[] = (object) array( 'key' => $meta['key'], 'value' => $meta['value'], 'meta_id' => $meta['meta_id'], ); } } } } /** * Add meta data. * @since 2.6.0 * @param string $key Meta key * @param string $value Meta value * @param bool $unique Should this be a unique key? */ public function add_meta_data( $key, $value, $unique = false ) { if ( $unique ) { $array_keys = array_keys( wp_list_pluck( $this->_meta_data, 'key' ), $key ); $this->_meta_data = array_diff_key( $this->_meta_data, array_fill_keys( $array_keys, '' ) ); } $this->_meta_data[] = (object) array( 'key' => $key, 'value' => $value, ); } /** * Update meta data by key or ID, if provided. * @since 2.6.0 * @param string $key * @param string $value * @param int $meta_id */ public function update_meta_data( $key, $value, $meta_id = '' ) { $array_key = ''; if ( $meta_id ) { $array_key = array_keys( wp_list_pluck( $this->_meta_data, 'meta_id' ), $meta_id ); } if ( $array_key ) { $this->_meta_data[ current( $array_key ) ] = (object) array( 'key' => $key, 'value' => $value, 'meta_id' => $meta_id, ); } else { $this->add_meta_data( $key, $value, true ); } } /** * Delete meta data. * @since 2.6.0 * @param array $key Meta key */ public function delete_meta_data( $key ) { $array_keys = array_keys( wp_list_pluck( $this->_meta_data, 'key' ), $key ); $this->_meta_data = array_diff_key( $this->_meta_data, array_fill_keys( $array_keys, '' ) ); } /** * Read Meta Data from the database. Ignore any internal properties. * @since 2.6.0 */ protected function read_meta_data() { $this->_meta_data = array(); $cache_loaded = false; if ( ! $this->get_id() ) { return; } if ( ! empty ( $this->_cache_group ) ) { $cache_key = WC_Cache_Helper::get_cache_prefix( $this->_cache_group ) . $this->get_id(); $cached_meta = wp_cache_get( $cache_key, $this->_cache_group ); if ( false !== $cached_meta ) { $this->_meta_data = $cached_meta; $cache_loaded = true; } } if ( ! $cache_loaded ) { global $wpdb; $db_info = $this->_get_db_info(); $raw_meta_data = $wpdb->get_results( $wpdb->prepare( " SELECT " . $db_info['meta_id_field'] . ", meta_key, meta_value FROM " . $db_info['table'] . " WHERE " . $db_info['object_id_field'] . " = %d ORDER BY " . $db_info['meta_id_field'] . " ", $this->get_id() ) ); foreach ( $raw_meta_data as $meta ) { if ( in_array( $meta->meta_key, $this->get_internal_meta_keys() ) ) { continue; } $this->_meta_data[] = (object) array( 'key' => $meta->meta_key, 'value' => $meta->meta_value, 'meta_id' => $meta->{ $db_info['meta_id_field'] }, ); } if ( ! empty ( $this->_cache_group ) ) { wp_cache_set( $cache_key, $this->_meta_data, $this->_cache_group ); } } } /** * Update Meta Data in the database. * @since 2.6.0 */ protected function save_meta_data() { global $wpdb; $db_info = $this->_get_db_info(); $all_meta_ids = array_map( 'absint', $wpdb->get_col( $wpdb->prepare( " SELECT " . $db_info['meta_id_field'] . " FROM " . $db_info['table'] . " WHERE " . $db_info['object_id_field'] . " = %d", $this->get_id() ) . " AND meta_key NOT IN ('" . implode( "','", array_map( 'esc_sql', $this->get_internal_meta_keys() ) ) . "'); " ) ); $set_meta_ids = array(); foreach ( $this->_meta_data as $array_key => $meta ) { if ( empty( $meta->meta_id ) ) { $new_meta_id = add_metadata( $this->_meta_type, $this->get_id(), $meta->key, $meta->value, false ); $set_meta_ids[] = $new_meta_id; $this->_meta_data[ $array_key ]->meta_id = $new_meta_id; } else { update_metadata_by_mid( $this->_meta_type, $meta->meta_id, $meta->value, $meta->key ); $set_meta_ids[] = absint( $meta->meta_id ); } } // Delete no longer set meta data $delete_meta_ids = array_diff( $all_meta_ids, $set_meta_ids ); foreach ( $delete_meta_ids as $meta_id ) { delete_metadata_by_mid( $this->_meta_type, $meta_id ); } if ( ! empty ( $this->_cache_group ) ) { WC_Cache_Helper::incr_cache_prefix( $this->_cache_group ); } $this->read_meta_data(); } /** * Table structure is slightly different between meta types, this function will return what we need to know. * @since 2.6.0 * @return array Array elements: table, object_id_field, meta_id_field */ protected function _get_db_info() { global $wpdb; $meta_id_field = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well. $table = $wpdb->prefix; // If we are dealing with a type of metadata that is not a core type, the table should be prefixed. if ( ! in_array( $this->_meta_type, array( 'post', 'user', 'comment', 'term' ) ) ) { $table .= 'woocommerce_'; } $table .= $this->_meta_type . 'meta'; $object_id_field = $this->_meta_type . '_id'; // Figure out our field names. if ( 'user' === $this->_meta_type ) { $meta_id_field = 'umeta_id'; } if ( ! empty( $this->object_id_field_for_meta ) ) { $object_id_field = $this->object_id_field_for_meta; } return array( 'table' => $table, 'object_id_field' => $object_id_field, 'meta_id_field' => $meta_id_field, ); } } abstract-wc-settings-api.php 0000666 00000064656 15214130426 0012117 0 ustar 00 <?php /** * Abstract Settings API Class * * Admin Settings API used by Integrations, Shipping Methods, and Payment Gateways. * * @class WC_Settings_API * @version 2.6.0 * @package WooCommerce/Abstracts * @category Abstract Class * @author WooThemes */ abstract class WC_Settings_API { /** * The plugin ID. Used for option names. * @var string */ public $plugin_id = 'woocommerce_'; /** * ID of the class extending the settings API. Used in option names. * @var string */ public $id = ''; /** * Validation errors. * @var array of strings */ public $errors = array(); /** * Setting values. * @var array */ public $settings = array(); /** * Form option fields. * @var array */ public $form_fields = array(); /** * The posted settings data. When empty, $_POST data will be used. * @var array */ protected $data = array(); /** * Get the form fields after they are initialized. * @return array of options */ public function get_form_fields() { return apply_filters( 'woocommerce_settings_api_form_fields_' . $this->id, array_map( array( $this, 'set_defaults' ), $this->form_fields ) ); } /** * Set default required properties for each field. * @param array */ protected function set_defaults( $field ) { if ( ! isset( $field['default'] ) ) { $field['default'] = ''; } return $field; } /** * Output the admin options table. */ public function admin_options() { echo '<table class="form-table">' . $this->generate_settings_html( $this->get_form_fields(), false ) . '</table>'; } /** * Initialise settings form fields. * * Add an array of fields to be displayed * on the gateway's settings screen. * * @since 1.0.0 * @return string */ public function init_form_fields() {} /** * Return the name of the option in the WP DB. * @since 2.6.0 * @return string */ public function get_option_key() { return $this->plugin_id . $this->id . '_settings'; } /** * Get a fields type. Defaults to "text" if not set. * @param array $field * @return string */ public function get_field_type( $field ) { return empty( $field['type'] ) ? 'text' : $field['type']; } /** * Get a fields default value. Defaults to "" if not set. * @param array $field * @return string */ public function get_field_default( $field ) { return empty( $field['default'] ) ? '' : $field['default']; } /** * Get a field's posted and validated value. * @param string $key * @param array $field * @param array $post_data * @return string */ public function get_field_value( $key, $field, $post_data = array() ) { $type = $this->get_field_type( $field ); $field_key = $this->get_field_key( $key ); $post_data = empty( $post_data ) ? $_POST : $post_data; $value = isset( $post_data[ $field_key ] ) ? $post_data[ $field_key ] : null; // Look for a validate_FIELDID_field method for special handling if ( is_callable( array( $this, 'validate_' . $key . '_field' ) ) ) { return $this->{'validate_' . $key . '_field'}( $key, $value ); } // Look for a validate_FIELDTYPE_field method if ( is_callable( array( $this, 'validate_' . $type . '_field' ) ) ) { return $this->{'validate_' . $type . '_field'}( $key, $value ); } // Fallback to text return $this->validate_text_field( $key, $value ); } /** * Sets the POSTed data. This method can be used to set specific data, instead * of taking it from the $_POST array. * @param array data */ public function set_post_data( $data = array() ) { $this->data = $data; } /** * Returns the POSTed data, to be used to save the settings. * @return array */ public function get_post_data() { if ( ! empty( $this->data ) && is_array( $this->data ) ) { return $this->data; } return $_POST; } /** * Processes and saves options. * If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out. * @return bool was anything saved? */ public function process_admin_options() { $this->init_settings(); $post_data = $this->get_post_data(); foreach ( $this->get_form_fields() as $key => $field ) { if ( 'title' !== $this->get_field_type( $field ) ) { try { $this->settings[ $key ] = $this->get_field_value( $key, $field, $post_data ); } catch ( Exception $e ) { $this->add_error( $e->getMessage() ); } } } return update_option( $this->get_option_key(), apply_filters( 'woocommerce_settings_api_sanitized_fields_' . $this->id, $this->settings ) ); } /** * Add an error message for display in admin on save. * @param string $error */ public function add_error( $error ) { $this->errors[] = $error; } /** * Get admin error messages. */ public function get_errors() { return $this->errors; } /** * Display admin error messages. */ public function display_errors() { if ( $this->get_errors() ) { echo '<div id="woocommerce_errors" class="error notice is-dismissible">'; foreach ( $this->get_errors() as $error ) { echo '<p>' . wp_kses_post( $error ) . '</p>'; } echo '</div>'; } } /** * Initialise Settings. * * Store all settings in a single database entry * and make sure the $settings array is either the default * or the settings stored in the database. * * @since 1.0.0 * @uses get_option(), add_option() */ public function init_settings() { $this->settings = get_option( $this->get_option_key(), null ); // If there are no settings defined, use defaults. if ( ! is_array( $this->settings ) ) { $form_fields = $this->get_form_fields(); $this->settings = array_merge( array_fill_keys( array_keys( $form_fields ), '' ), wp_list_pluck( $form_fields, 'default' ) ); } } /** * get_option function. * * Gets an option from the settings API, using defaults if necessary to prevent undefined notices. * * @param string $key * @param mixed $empty_value * @return string The value specified for the option or a default value for the option. */ public function get_option( $key, $empty_value = null ) { if ( empty( $this->settings ) ) { $this->init_settings(); } // Get option default if unset. if ( ! isset( $this->settings[ $key ] ) ) { $form_fields = $this->get_form_fields(); $this->settings[ $key ] = isset( $form_fields[ $key ] ) ? $this->get_field_default( $form_fields[ $key ] ) : ''; } if ( ! is_null( $empty_value ) && '' === $this->settings[ $key ] ) { $this->settings[ $key ] = $empty_value; } return $this->settings[ $key ]; } /** * Prefix key for settings. * * @param mixed $key * @return string */ public function get_field_key( $key ) { return $this->plugin_id . $this->id . '_' . $key; } /** * Generate Settings HTML. * * Generate the HTML for the fields on the "settings" screen. * * @param array $form_fields (default: array()) * @since 1.0.0 * @uses method_exists() * @return string the html for the settings */ public function generate_settings_html( $form_fields = array(), $echo = true ) { if ( empty( $form_fields ) ) { $form_fields = $this->get_form_fields(); } $html = ''; foreach ( $form_fields as $k => $v ) { $type = $this->get_field_type( $v ); if ( method_exists( $this, 'generate_' . $type . '_html' ) ) { $html .= $this->{'generate_' . $type . '_html'}( $k, $v ); } else { $html .= $this->generate_text_html( $k, $v ); } } if ( $echo ) { echo $html; } else { return $html; } } /** * Get HTML for tooltips. * * @param array $data * @return string */ public function get_tooltip_html( $data ) { if ( $data['desc_tip'] === true ) { $tip = $data['description']; } elseif ( ! empty( $data['desc_tip'] ) ) { $tip = $data['desc_tip']; } else { $tip = ''; } return $tip ? wc_help_tip( $tip, true ) : ''; } /** * Get HTML for descriptions. * * @param array $data * @return string */ public function get_description_html( $data ) { if ( $data['desc_tip'] === true ) { $description = ''; } elseif ( ! empty( $data['desc_tip'] ) ) { $description = $data['description']; } elseif ( ! empty( $data['description'] ) ) { $description = $data['description']; } else { $description = ''; } return $description ? '<p class="description">' . wp_kses_post( $description ) . '</p>' . "\n" : ''; } /** * Get custom attributes. * * @param array $data * @return string */ public function get_custom_attribute_html( $data ) { $custom_attributes = array(); if ( ! empty( $data['custom_attributes'] ) && is_array( $data['custom_attributes'] ) ) { foreach ( $data['custom_attributes'] as $attribute => $attribute_value ) { $custom_attributes[] = esc_attr( $attribute ) . '="' . esc_attr( $attribute_value ) . '"'; } } return implode( ' ', $custom_attributes ); } /** * Generate Text Input HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_text_html( $key, $data ) { $field_key = $this->get_field_key( $key ); $defaults = array( 'title' => '', 'disabled' => false, 'class' => '', 'css' => '', 'placeholder' => '', 'type' => 'text', 'desc_tip' => false, 'description' => '', 'custom_attributes' => array(), ); $data = wp_parse_args( $data, $defaults ); ob_start(); ?> <tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> <?php echo $this->get_tooltip_html( $data ); ?> </th> <td class="forminp"> <fieldset> <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> <input class="input-text regular-input <?php echo esc_attr( $data['class'] ); ?>" type="<?php echo esc_attr( $data['type'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( $this->get_option( $key ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?> /> <?php echo $this->get_description_html( $data ); ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Price Input HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_price_html( $key, $data ) { $field_key = $this->get_field_key( $key ); $defaults = array( 'title' => '', 'disabled' => false, 'class' => '', 'css' => '', 'placeholder' => '', 'type' => 'text', 'desc_tip' => false, 'description' => '', 'custom_attributes' => array(), ); $data = wp_parse_args( $data, $defaults ); ob_start(); ?> <tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> <?php echo $this->get_tooltip_html( $data ); ?> </th> <td class="forminp"> <fieldset> <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> <input class="wc_input_price input-text regular-input <?php echo esc_attr( $data['class'] ); ?>" type="text" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( wc_format_localized_price( $this->get_option( $key ) ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?> /> <?php echo $this->get_description_html( $data ); ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Decimal Input HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_decimal_html( $key, $data ) { $field_key = $this->get_field_key( $key ); $defaults = array( 'title' => '', 'disabled' => false, 'class' => '', 'css' => '', 'placeholder' => '', 'type' => 'text', 'desc_tip' => false, 'description' => '', 'custom_attributes' => array(), ); $data = wp_parse_args( $data, $defaults ); ob_start(); ?> <tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> <?php echo $this->get_tooltip_html( $data ); ?> </th> <td class="forminp"> <fieldset> <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> <input class="wc_input_decimal input-text regular-input <?php echo esc_attr( $data['class'] ); ?>" type="text" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( wc_format_localized_decimal( $this->get_option( $key ) ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?> /> <?php echo $this->get_description_html( $data ); ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Password Input HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_password_html( $key, $data ) { $data['type'] = 'password'; return $this->generate_text_html( $key, $data ); } /** * Generate Color Picker Input HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_color_html( $key, $data ) { $field_key = $this->get_field_key( $key ); $defaults = array( 'title' => '', 'disabled' => false, 'class' => '', 'css' => '', 'placeholder' => '', 'desc_tip' => false, 'description' => '', 'custom_attributes' => array(), ); $data = wp_parse_args( $data, $defaults ); ob_start(); ?> <tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> <?php echo $this->get_tooltip_html( $data ); ?> </th> <td class="forminp"> <fieldset> <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> <span class="colorpickpreview" style="background:<?php echo esc_attr( $this->get_option( $key ) ); ?>;"></span> <input class="colorpick <?php echo esc_attr( $data['class'] ); ?>" type="text" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( $this->get_option( $key ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?> /> <div id="colorPickerDiv_<?php echo esc_attr( $field_key ); ?>" class="colorpickdiv" style="z-index: 100; background: #eee; border: 1px solid #ccc; position: absolute; display: none;"></div> <?php echo $this->get_description_html( $data ); ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Textarea HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_textarea_html( $key, $data ) { $field_key = $this->get_field_key( $key ); $defaults = array( 'title' => '', 'disabled' => false, 'class' => '', 'css' => '', 'placeholder' => '', 'type' => 'text', 'desc_tip' => false, 'description' => '', 'custom_attributes' => array(), ); $data = wp_parse_args( $data, $defaults ); ob_start(); ?> <tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> <?php echo $this->get_tooltip_html( $data ); ?> </th> <td class="forminp"> <fieldset> <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> <textarea rows="3" cols="20" class="input-text wide-input <?php echo esc_attr( $data['class'] ); ?>" type="<?php echo esc_attr( $data['type'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?>><?php echo esc_textarea( $this->get_option( $key ) ); ?></textarea> <?php echo $this->get_description_html( $data ); ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Checkbox HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_checkbox_html( $key, $data ) { $field_key = $this->get_field_key( $key ); $defaults = array( 'title' => '', 'label' => '', 'disabled' => false, 'class' => '', 'css' => '', 'type' => 'text', 'desc_tip' => false, 'description' => '', 'custom_attributes' => array(), ); $data = wp_parse_args( $data, $defaults ); if ( ! $data['label'] ) { $data['label'] = $data['title']; } ob_start(); ?> <tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> <?php echo $this->get_tooltip_html( $data ); ?> </th> <td class="forminp"> <fieldset> <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> <label for="<?php echo esc_attr( $field_key ); ?>"> <input <?php disabled( $data['disabled'], true ); ?> class="<?php echo esc_attr( $data['class'] ); ?>" type="checkbox" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="1" <?php checked( $this->get_option( $key ), 'yes' ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?> /> <?php echo wp_kses_post( $data['label'] ); ?></label><br/> <?php echo $this->get_description_html( $data ); ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Select HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_select_html( $key, $data ) { $field_key = $this->get_field_key( $key ); $defaults = array( 'title' => '', 'disabled' => false, 'class' => '', 'css' => '', 'placeholder' => '', 'type' => 'text', 'desc_tip' => false, 'description' => '', 'custom_attributes' => array(), 'options' => array(), ); $data = wp_parse_args( $data, $defaults ); ob_start(); ?> <tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> <?php echo $this->get_tooltip_html( $data ); ?> </th> <td class="forminp"> <fieldset> <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> <select class="select <?php echo esc_attr( $data['class'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?>> <?php foreach ( (array) $data['options'] as $option_key => $option_value ) : ?> <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $option_key, esc_attr( $this->get_option( $key ) ) ); ?>><?php echo esc_attr( $option_value ); ?></option> <?php endforeach; ?> </select> <?php echo $this->get_description_html( $data ); ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Multiselect HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_multiselect_html( $key, $data ) { $field_key = $this->get_field_key( $key ); $defaults = array( 'title' => '', 'disabled' => false, 'class' => '', 'css' => '', 'placeholder' => '', 'type' => 'text', 'desc_tip' => false, 'description' => '', 'custom_attributes' => array(), 'select_buttons' => false, 'options' => array(), ); $data = wp_parse_args( $data, $defaults ); $value = (array) $this->get_option( $key, array() ); ob_start(); ?> <tr valign="top"> <th scope="row" class="titledesc"> <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> <?php echo $this->get_tooltip_html( $data ); ?> </th> <td class="forminp"> <fieldset> <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> <select multiple="multiple" class="multiselect <?php echo esc_attr( $data['class'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>[]" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?>> <?php foreach ( (array) $data['options'] as $option_key => $option_value ) : ?> <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( in_array( $option_key, $value ), true ); ?>><?php echo esc_attr( $option_value ); ?></option> <?php endforeach; ?> </select> <?php echo $this->get_description_html( $data ); ?> <?php if ( $data['select_buttons'] ) : ?> <br/><a class="select_all button" href="#"><?php _e( 'Select all', 'woocommerce' ); ?></a> <a class="select_none button" href="#"><?php _e( 'Select none', 'woocommerce' ); ?></a> <?php endif; ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Title HTML. * * @param mixed $key * @param mixed $data * @since 1.0.0 * @return string */ public function generate_title_html( $key, $data ) { $field_key = $this->get_field_key( $key ); $defaults = array( 'title' => '', 'class' => '', ); $data = wp_parse_args( $data, $defaults ); ob_start(); ?> </table> <h3 class="wc-settings-sub-title <?php echo esc_attr( $data['class'] ); ?>" id="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></h3> <?php if ( ! empty( $data['description'] ) ) : ?> <p><?php echo wp_kses_post( $data['description'] ); ?></p> <?php endif; ?> <table class="form-table"> <?php return ob_get_clean(); } /** * Validate Text Field. * * Make sure the data is escaped correctly, etc. * * @param string $key Field key * @param string|null $value Posted Value * @return string */ public function validate_text_field( $key, $value ) { $value = is_null( $value ) ? '' : $value; return wp_kses_post( trim( stripslashes( $value ) ) ); } /** * Validate Price Field. * * Make sure the data is escaped correctly, etc. * * @param string $key * @param string|null $value Posted Value * @return string */ public function validate_price_field( $key, $value ) { $value = is_null( $value ) ? '' : $value; return $value === '' ? '' : wc_format_decimal( trim( stripslashes( $value ) ) ); } /** * Validate Decimal Field. * * Make sure the data is escaped correctly, etc. * * @param string $key * @param string|null $value Posted Value * @return string */ public function validate_decimal_field( $key, $value ) { $value = is_null( $value ) ? '' : $value; return $value === '' ? '' : wc_format_decimal( trim( stripslashes( $value ) ) ); } /** * Validate Password Field. No input sanitization is used to avoid corrupting passwords. * * @param string $key * @param string|null $value Posted Value * @return string */ public function validate_password_field( $key, $value ) { $value = is_null( $value ) ? '' : $value; return trim( stripslashes( $value ) ); } /** * Validate Textarea Field. * * @param string $key * @param string|null $value Posted Value * @return string */ public function validate_textarea_field( $key, $value ) { $value = is_null( $value ) ? '' : $value; return wp_kses( trim( stripslashes( $value ) ), array_merge( array( 'iframe' => array( 'src' => true, 'style' => true, 'id' => true, 'class' => true ) ), wp_kses_allowed_html( 'post' ) ) ); } /** * Validate Checkbox Field. * * If not set, return "no", otherwise return "yes". * * @param string $key * @param string|null $value Posted Value * @return string */ public function validate_checkbox_field( $key, $value ) { return ! is_null( $value ) ? 'yes' : 'no'; } /** * Validate Select Field. * * @param string $key * @param string $value Posted Value * @return string */ public function validate_select_field( $key, $value ) { $value = is_null( $value ) ? '' : $value; return wc_clean( stripslashes( $value ) ); } /** * Validate Multiselect Field. * * @param string $key * @param string $value Posted Value * @return string */ public function validate_multiselect_field( $key, $value ) { return is_array( $value ) ? array_map( 'wc_clean', array_map( 'stripslashes', $value ) ) : ''; } /** * Validate the data on the "Settings" form. * @deprecated 2.6.0 No longer used */ public function validate_settings_fields( $form_fields = array() ) { _deprecated_function( 'validate_settings_fields', '2.6' ); } /** * Format settings if needed. * @deprecated 2.6.0 Unused * @param array $value * @return array */ public function format_settings( $value ) { _deprecated_function( 'format_settings', '2.6' ); return $value; } } abstract-wc-payment-token.php 0000666 00000017144 15214130426 0012271 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * WooCommerce Payment Token. * * Representation of a general payment token to be extended by individuals types of tokens * examples: Credit Card, eCheck. * * @class WC_Payment_Token * @since 2.6.0 * @package WooCommerce/Abstracts * @category Abstract Class * @author WooThemes */ abstract class WC_Payment_Token extends WC_Data { /** * Token Data (stored in the payment_tokens table). * @var array */ protected $_data = array( 'id' => 0, 'gateway_id' => '', 'token' => '', 'is_default' => 0, 'user_id' => 0, ); /** * Meta type. Payment tokens are a new object type. * @var string */ protected $_meta_type = 'payment_token'; /** * Initialize a payment token. * * These fields are accepted by all payment tokens: * is_default - boolean Optional - Indicates this is the default payment token for a user * token - string Required - The actual token to store * gateway_id - string Required - Identifier for the gateway this token is associated with * user_id - int Optional - ID for the user this token is associated with. 0 if this token is not associated with a user * * @since 2.6.0 * @param mixed $token */ public function __construct( $token = '' ) { if ( is_numeric( $token ) ) { $this->read( $token ); } else if ( is_object( $token ) ) { $token_id = $token->get_id(); if ( ! empty( $token_id ) ) { $this->read( $token->get_id() ); } } // Set token type (cc, echeck) if ( ! empty( $this->type ) ) { $this->_data['type'] = $this->type; } } /** * Returns the payment token ID. * @since 2.6.0 * @return integer Token ID */ public function get_id() { return absint( $this->_data['id'] ); } /** * Returns the raw payment token. * @since 2.6.0 * @return string Raw token */ public function get_token() { return $this->_data['token']; } /** * Set the raw payment token. * @since 2.6.0 * @param string $token */ public function set_token( $token ) { $this->_data['token'] = $token; } /** * Returns the type of this payment token (CC, eCheck, or something else). * @since 2.6.0 * @return string Payment Token Type (CC, eCheck) */ public function get_type() { return isset( $this->_data['type'] ) ? $this->_data['type'] : ''; } /** * Get type to display to user. * @return string */ public function get_display_name() { return $this->get_type(); } /** * Returns the user ID associated with the token or false if this token is not associated. * @since 2.6.0 * @return int User ID if this token is associated with a user or 0 if no user is associated */ public function get_user_id() { return ( isset( $this->_data['user_id'] ) && $this->_data['user_id'] > 0 ) ? absint( $this->_data['user_id'] ) : 0; } /** * Set the user ID for the user associated with this order. * @since 2.6.0 * @param int $user_id */ public function set_user_id( $user_id ) { $this->_data['user_id'] = absint( $user_id ); } /** * Returns the ID of the gateway associated with this payment token. * @since 2.6.0 * @return string Gateway ID */ public function get_gateway_id() { return $this->_data['gateway_id']; } /** * Set the gateway ID. * @since 2.6.0 * @param string $gateway_id */ public function set_gateway_id( $gateway_id ) { $this->_data['gateway_id'] = $gateway_id; } /** * Returns if the token is marked as default. * @since 2.6.0 * @return boolean True if the token is default */ public function is_default() { return ! empty( $this->_data['is_default'] ); } /** * Marks the payment as default or non-default. * @since 2.6.0 * @param boolean $is_default True or false */ public function set_default( $is_default ) { $this->_data['is_default'] = (bool) $is_default; } /** * Validate basic token info (token and type are required). * @since 2.6.0 * @return boolean True if the passed data is valid */ public function validate() { if ( empty( $this->_data['token'] ) ) { return false; } if ( empty( $this->_data['type'] ) ) { return false; } return true; } /** * Get a token from the database. * @since 2.6.0 * @param int $token_id Token ID */ public function read( $token_id ) { global $wpdb; if ( $token = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d LIMIT 1;", $token_id ) ) ) { $token_id = $token->token_id; $token = (array) $token; unset( $token['token_id'] ); $this->_data = $token; $this->_data['id'] = $token_id; $this->read_meta_data(); } } /** * Update a payment token. * @since 2.6.0 * @return boolean on success, false if validation failed and a payment token could not be updated */ public function update() { if ( false === $this->validate() ) { return false; } global $wpdb; $payment_token_data = array( 'gateway_id' => $this->get_gateway_id(), 'token' => $this->get_token(), 'user_id' => $this->get_user_id(), 'type' => $this->get_type(), ); $wpdb->update( $wpdb->prefix . 'woocommerce_payment_tokens', $payment_token_data, array( 'token_id' => $this->get_id() ) ); $this->save_meta_data(); // Make sure all other tokens are not set to default if ( $this->is_default() && $this->get_user_id() > 0 ) { WC_Payment_Tokens::set_users_default( $this->get_user_id(), $this->get_id() ); } do_action( 'woocommerce_payment_token_updated', $this->get_id() ); return true; } /** * Create a new payment token in the database. * @since 2.6.0 * @return boolean on success, false if validation failed and a payment token could not be created */ public function create() { if ( false === $this->validate() ) { return false; } global $wpdb; // Are there any other tokens? If not, set this token as default if ( ! $this->is_default() && $this->get_user_id() > 0 ) { $default_token = WC_Payment_Tokens::get_customer_default_token( $this->get_user_id() ); if ( is_null( $default_token ) ) { $this->set_default( true ); } } $payment_token_data = array( 'gateway_id' => $this->get_gateway_id(), 'token' => $this->get_token(), 'user_id' => $this->get_user_id(), 'type' => $this->get_type(), ); $wpdb->insert( $wpdb->prefix . 'woocommerce_payment_tokens', $payment_token_data ); $this->_data['id'] = $token_id = $wpdb->insert_id; $this->save_meta_data(); // Make sure all other tokens are not set to default if ( $this->is_default() && $this->get_user_id() > 0 ) { WC_Payment_Tokens::set_users_default( $this->get_user_id(), $token_id ); } do_action( 'woocommerce_payment_token_created', $token_id ); return true; } /** * Saves a payment token to the database - does not require you to know if this is a new token or an update token. * @since 2.6.0 * @return boolean on success, false if validation failed and a payment token could not be saved */ public function save() { if ( $this->get_id() > 0 ) { return $this->update(); } else { return $this->create(); } } /** * Remove a payment token from the database. * @since 2.6.0 */ public function delete() { global $wpdb; $this->read( $this->get_id() ); // Make sure we have a token to return after deletion $wpdb->delete( $wpdb->prefix . 'woocommerce_payment_tokens', array( 'token_id' => $this->get_id() ), array( '%d' ) ); $wpdb->delete( $wpdb->prefix . 'woocommerce_payment_tokenmeta', array( 'payment_token_id' => $this->get_id() ), array( '%d' ) ); do_action( 'woocommerce_payment_token_deleted', $this->get_id(), $this ); } } abstract-wc-rest-terms-controller.php 0000666 00000061547 15214130426 0013772 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Abstract Rest Terms Controler Class * * @author WooThemes * @category API * @package WooCommerce/Abstracts * @version 2.6.0 */ abstract class WC_REST_Terms_Controller extends WC_REST_Controller { /** * Route base. * * @var string */ protected $rest_base = ''; /** * Taxonomy. * * @var string */ protected $taxonomy = ''; /** * Register the routes for terms. */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array( 'name' => array( 'type' => 'string', 'description' => __( 'Name for the resource.', 'woocommerce' ), 'required' => true, ), ) ), ), 'schema' => array( $this, 'get_public_item_schema' ), )); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'default' => false, 'type' => 'boolean', 'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ), ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'batch_items' ), 'permission_callback' => array( $this, 'batch_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), 'schema' => array( $this, 'get_public_batch_schema' ), ) ); } /** * Check if a given request has access to read the terms. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function get_items_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'read' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to create a term. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function create_item_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'create' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you cannot create new resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to read a term. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function get_item_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'read' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to update a term. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function update_item_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'edit' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_update', __( 'Sorry, you cannot update resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to delete a term. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function delete_item_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'delete' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you cannot delete resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access batch create, update and delete items. * * @param WP_REST_Request $request Full details about the request. * @return boolean */ public function batch_items_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'batch' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to manipule this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check permissions. * * @param WP_REST_Request $request Full details about the request. * @param string $context Request context. * @return bool|WP_Error */ protected function check_permissions( $request, $context = 'read' ) { // Get taxonomy. $taxonomy = $this->get_taxonomy( $request ); if ( ! $taxonomy ) { return new WP_Error( 'woocommerce_rest_taxonomy_invalid', __( "Taxonomy doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) ); } // Check permissions for a single term. if ( $id = intval( $request['id'] ) ) { $term = get_term( $id, $taxonomy ); if ( ! $term || $term->taxonomy !== $taxonomy ) { return new WP_Error( 'woocommerce_rest_term_invalid', __( "Resource doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) ); } return wc_rest_check_product_term_permissions( $taxonomy, $context, $term->term_id ); } return wc_rest_check_product_term_permissions( $taxonomy, $context ); } /** * Get terms associated with a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error */ public function get_items( $request ) { $taxonomy = $this->get_taxonomy( $request ); $prepared_args = array( 'exclude' => $request['exclude'], 'include' => $request['include'], 'order' => $request['order'], 'orderby' => $request['orderby'], 'product' => $request['product'], 'hide_empty' => $request['hide_empty'], 'number' => $request['per_page'], 'search' => $request['search'], 'slug' => $request['slug'], ); if ( ! empty( $request['offset'] ) ) { $prepared_args['offset'] = $request['offset']; } else { $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; } $taxonomy_obj = get_taxonomy( $taxonomy ); if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) { if ( 0 === $request['parent'] ) { // Only query top-level terms. $prepared_args['parent'] = 0; } else { if ( $request['parent'] ) { $prepared_args['parent'] = $request['parent']; } } } /** * Filter the query arguments, before passing them to `get_terms()`. * * Enables adding extra arguments or setting defaults for a terms * collection request. * * @see https://developer.wordpress.org/reference/functions/get_terms/ * * @param array $prepared_args Array of arguments to be * passed to get_terms. * @param WP_REST_Request $request The current request. */ $prepared_args = apply_filters( "woocommerce_rest_{$taxonomy}_query", $prepared_args, $request ); if ( ! empty( $prepared_args['product'] ) ) { $query_result = $this->get_terms_for_product( $prepared_args ); $total_terms = $this->total_terms; } else { $query_result = get_terms( $taxonomy, $prepared_args ); $count_args = $prepared_args; unset( $count_args['number'] ); unset( $count_args['offset'] ); $total_terms = wp_count_terms( $taxonomy, $count_args ); // Ensure we don't return results when offset is out of bounds. // See https://core.trac.wordpress.org/ticket/35935 if ( $prepared_args['offset'] >= $total_terms ) { $query_result = array(); } // wp_count_terms can return a falsy value when the term has no children. if ( ! $total_terms ) { $total_terms = 0; } } $response = array(); foreach ( $query_result as $term ) { $data = $this->prepare_item_for_response( $term, $request ); $response[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $response ); // Store pagation values for headers then unset for count query. $per_page = (int) $prepared_args['number']; $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); $response->header( 'X-WP-Total', (int) $total_terms ); $max_pages = ceil( $total_terms / $per_page ); $response->header( 'X-WP-TotalPages', (int) $max_pages ); $base = add_query_arg( $request->get_query_params(), rest_url( '/' . $this->namespace . '/' . $this->rest_base ) ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Create a single term for a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Request|WP_Error */ public function create_item( $request ) { $taxonomy = $this->get_taxonomy( $request ); $name = $request['name']; $args = array(); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) { $args['description'] = $request['description']; } if ( isset( $request['slug'] ) ) { $args['slug'] = $request['slug']; } if ( isset( $request['parent'] ) ) { if ( ! is_taxonomy_hierarchical( $taxonomy ) ) { return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) ); } $parent = get_term( (int) $request['parent'], $taxonomy ); if ( ! $parent ) { return new WP_Error( 'woocommerce_rest_term_invalid', __( "Parent resource doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) ); } $args['parent'] = $parent->term_id; } $term = wp_insert_term( $name, $taxonomy, $args ); if ( is_wp_error( $term ) ) { $error_data = array( 'status' => 400 ); // If we're going to inform the client that the term exists, // give them the identifier they can actually use. if ( $term_id = $term->get_error_data( 'term_exists' ) ) { $error_data['resource_id'] = $term_id; } return new WP_Error( $term->get_error_code(), $term->get_error_message(), $error_data ); } $term = get_term( $term['term_id'], $taxonomy ); $this->update_additional_fields_for_object( $term, $request ); // Add term data. $meta_fields = $this->update_term_meta_fields( $term, $request ); if ( is_wp_error( $meta_fields ) ) { wp_delete_term( $term['term_id'], $taxonomy ); return $meta_fields; } /** * Fires after a single term is created or updated via the REST API. * * @param WP_Term $term Inserted Term object. * @param WP_REST_Request $request Request object. * @param boolean $creating True when creating term, false when updating. */ do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, true ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $term, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $base = '/' . $this->namespace . '/' . $this->rest_base; if ( ! empty( $request['attribute_id'] ) ) { $base = str_replace( '(?P<attribute_id>[\d]+)', (int) $request['attribute_id'], $base ); } $response->header( 'Location', rest_url( $base . '/' . $term->term_id ) ); return $response; } /** * Get a single term from a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Request|WP_Error */ public function get_item( $request ) { $taxonomy = $this->get_taxonomy( $request ); $term = get_term( (int) $request['id'], $taxonomy ); if ( is_wp_error( $term ) ) { return $term; } $response = $this->prepare_item_for_response( $term, $request ); return rest_ensure_response( $response ); } /** * Update a single term from a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Request|WP_Error */ public function update_item( $request ) { $taxonomy = $this->get_taxonomy( $request ); $term = get_term( (int) $request['id'], $taxonomy ); $schema = $this->get_item_schema(); $prepared_args = array(); if ( isset( $request['name'] ) ) { $prepared_args['name'] = $request['name']; } if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) { $prepared_args['description'] = $request['description']; } if ( isset( $request['slug'] ) ) { $prepared_args['slug'] = $request['slug']; } if ( isset( $request['parent'] ) ) { if ( ! is_taxonomy_hierarchical( $taxonomy ) ) { return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) ); } $parent = get_term( (int) $request['parent'], $taxonomy ); if ( ! $parent ) { return new WP_Error( 'woocommerce_rest_term_invalid', __( "Parent resource doesn't exist.", 'woocommerce' ), array( 'status' => 400 ) ); } $prepared_args['parent'] = $parent->term_id; } // Only update the term if we haz something to update. if ( ! empty( $prepared_args ) ) { $update = wp_update_term( $term->term_id, $term->taxonomy, $prepared_args ); if ( is_wp_error( $update ) ) { return $update; } } $term = get_term( (int) $request['id'], $taxonomy ); $this->update_additional_fields_for_object( $term, $request ); // Update term data. $meta_fields = $this->update_term_meta_fields( $term, $request ); if ( is_wp_error( $meta_fields ) ) { return $meta_fields; } /** * Fires after a single term is created or updated via the REST API. * * @param WP_Term $term Inserted Term object. * @param WP_REST_Request $request Request object. * @param boolean $creating True when creating term, false when updating. */ do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, false ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $term, $request ); return rest_ensure_response( $response ); } /** * Delete a single term from a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error */ public function delete_item( $request ) { $taxonomy = $this->get_taxonomy( $request ); $force = isset( $request['force'] ) ? (bool) $request['force'] : false; // We don't support trashing for this type, error out. if ( ! $force ) { return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Resource does not support trashing.', 'woocommerce' ), array( 'status' => 501 ) ); } $term = get_term( (int) $request['id'], $taxonomy ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $term, $request ); $retval = wp_delete_term( $term->term_id, $term->taxonomy ); if ( ! $retval ) { return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) ); } /** * Fires after a single term is deleted via the REST API. * * @param WP_Term $term The deleted term. * @param WP_REST_Response $response The response data. * @param WP_REST_Request $request The request sent to the API. */ do_action( "woocommerce_rest_delete_{$taxonomy}", $term, $response, $request ); return $response; } /** * Prepare links for the request. * * @param object $term Term object. * @param WP_REST_Request $request Full details about the request. * @return array Links for the given term. */ protected function prepare_links( $term, $request ) { $base = '/' . $this->namespace . '/' . $this->rest_base; if ( ! empty( $request['attribute_id'] ) ) { $base = str_replace( '(?P<attribute_id>[\d]+)', (int) $request['attribute_id'], $base ); } $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $term->term_id ), ), 'collection' => array( 'href' => rest_url( $base ), ), ); if ( $term->parent ) { $parent_term = get_term( (int) $term->parent, $term->taxonomy ); if ( $parent_term ) { $links['up'] = array( 'href' => rest_url( trailingslashit( $base ) . $parent_term->term_id ), ); } } return $links; } /** * Update term meta fields. * * @param WP_Term $term * @param WP_REST_Request $request * @return bool|WP_Error */ protected function update_term_meta_fields( $term, $request ) { return true; } /** * Get the terms attached to a product. * * This is an alternative to `get_terms()` that uses `get_the_terms()` * instead, which hits the object cache. There are a few things not * supported, notably `include`, `exclude`. In `self::get_items()` these * are instead treated as a full query. * * @param array $prepared_args Arguments for `get_terms()`. * @return array List of term objects. (Total count in `$this->total_terms`). */ protected function get_terms_for_product( $prepared_args ) { $taxonomy = $this->get_taxonomy( $request ); $query_result = get_the_terms( $prepared_args['product'], $taxonomy ); if ( empty( $query_result ) ) { $this->total_terms = 0; return array(); } // get_items() verifies that we don't have `include` set, and default. // ordering is by `name`. if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ) ) ) { switch ( $prepared_args['orderby'] ) { case 'id' : $this->sort_column = 'term_id'; break; case 'slug' : case 'term_group' : case 'description' : case 'count' : $this->sort_column = $prepared_args['orderby']; break; } usort( $query_result, array( $this, 'compare_terms' ) ); } if ( strtolower( $prepared_args['order'] ) !== 'asc' ) { $query_result = array_reverse( $query_result ); } // Pagination. $this->total_terms = count( $query_result ); $query_result = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] ); return $query_result; } /** * Comparison function for sorting terms by a column. * * Uses `$this->sort_column` to determine field to sort by. * * @param stdClass $left Term object. * @param stdClass $right Term object. * @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left. */ protected function compare_terms( $left, $right ) { $col = $this->sort_column; $left_val = $left->$col; $right_val = $right->$col; if ( is_int( $left_val ) && is_int( $right_val ) ) { return $left_val - $right_val; } return strcmp( $left_val, $right_val ); } /** * Get the query params for collections * * @return array */ public function get_collection_params() { $params = parent::get_collection_params(); if ( '' !== $this->taxonomy ) { $taxonomy = get_taxonomy( $this->taxonomy ); } else { $taxonomy = new stdClass(); $taxonomy->hierarchical = true; } $params['context']['default'] = 'view'; $params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); $params['include'] = array( 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); if ( ! $taxonomy->hierarchical ) { $params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); } $params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'default' => 'asc', 'enum' => array( 'asc', 'desc', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort collection by resource attribute.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'default' => 'name', 'enum' => array( 'id', 'include', 'name', 'slug', 'term_group', 'description', 'count', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['hide_empty'] = array( 'description' => __( 'Whether to hide resources not assigned to any products.', 'woocommerce' ), 'type' => 'boolean', 'default' => false, 'validate_callback' => 'rest_validate_request_arg', ); if ( $taxonomy->hierarchical ) { $params['parent'] = array( 'description' => __( 'Limit result set to resources assigned to a specific parent.', 'woocommerce' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); } $params['product'] = array( 'description' => __( 'Limit result set to resources assigned to a specific product.', 'woocommerce' ), 'type' => 'integer', 'default' => null, 'validate_callback' => 'rest_validate_request_arg', ); $params['slug'] = array( 'description' => __( 'Limit result set to resources with a specific slug.', 'woocommerce' ), 'type' => 'string', 'validate_callback' => 'rest_validate_request_arg', ); return $params; } /** * Get taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return int|WP_Error */ protected function get_taxonomy( $request ) { // Check if taxonomy is defined. // Prevents check for attribute taxonomy more than one time for each query. if ( '' !== $this->taxonomy ) { return $this->taxonomy; } if ( ! empty( $request['attribute_id'] ) ) { $taxonomy = wc_attribute_taxonomy_name_by_id( (int) $request['attribute_id'] ); $this->taxonomy = $taxonomy; } return $this->taxonomy; } } abstract-wc-shipping-method.php 0000666 00000035056 15214130426 0012577 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; } /** * WooCommerce Shipping Method Class. * * Extended by shipping methods to handle shipping calculations etc. * * @class WC_Shipping_Method * @version 2.6.0 * @package WooCommerce/Abstracts * @category Abstract Class * @author WooThemes */ abstract class WC_Shipping_Method extends WC_Settings_API { /** * Features this method supports. Possible features used by core: * - shipping-zones Shipping zone functionality + instances * - instance-settings Instance settings screens. * - settings Non-instance settings screens. Enabled by default for BW compatibility with methods before instances existed. * - instance-settings-modal Allows the instance settings to be loaded within a modal in the zones UI. * @var array */ public $supports = array( 'settings' ); /** * Unique ID for the shipping method - must be set. * @var string */ public $id = ''; /** * Method title. * @var string */ public $method_title = ''; /** * Method description. * @var string */ public $method_description = ''; /** * yes or no based on whether the method is enabled. * @var string */ public $enabled = 'yes'; /** * Shipping method title for the frontend. * @var string */ public $title; /** * This is an array of rates - methods must populate this array to register shipping costs. * @var array */ public $rates = array(); /** * If 'taxable' tax will be charged for this method (if applicable). * @var string */ public $tax_status = 'taxable'; /** * Fee for the method (if applicable). * @var string */ public $fee = null; /** * Minimum fee for the method (if applicable). * @var string */ public $minimum_fee = null; /** * Instance ID if used. * @var int */ public $instance_id = 0; /** * Instance form fields. * @var array */ public $instance_form_fields = array(); /** * Instance settings. * @var array */ public $instance_settings = array(); /** * Availability - legacy. Used for method Availability. * No longer useful for instance based shipping methods. * @deprecated 2.6.0 * @var string */ public $availability; /** * Availability countries - legacy. Used for method Availability. * No longer useful for instance based shipping methods. * @deprecated 2.6.0 * @var array */ public $countries = array(); /** * Constructor. * @param int $instance_id */ public function __construct( $instance_id = 0 ) { $this->instance_id = absint( $instance_id ); } /** * Check if a shipping method supports a given feature. * * Methods should override this to declare support (or lack of support) for a feature. * * @param $feature string The name of a feature to test support for. * @return bool True if the shipping method supports the feature, false otherwise. */ public function supports( $feature ) { return apply_filters( 'woocommerce_shipping_method_supports', in_array( $feature, $this->supports ), $feature, $this ); } /** * Called to calculate shipping rates for this method. Rates can be added using the add_rate() method. */ public function calculate_shipping( $package = array() ) {} /** * Whether or not we need to calculate tax on top of the shipping rate. * @return boolean */ public function is_taxable() { return wc_tax_enabled() && 'taxable' === $this->tax_status && ! WC()->customer->is_vat_exempt(); } /** * Whether or not this method is enabled in settings. * @since 2.6.0 * @return boolean */ public function is_enabled() { return 'yes' === $this->enabled; } /** * Return the shipping method instance ID. * @since 2.6.0 * @return int */ public function get_instance_id() { return $this->instance_id; } /** * Return the shipping method title. * @since 2.6.0 * @return string */ public function get_method_title() { return apply_filters( 'woocommerce_shipping_method_title', $this->method_title, $this ); } /** * Return the shipping method description. * @since 2.6.0 * @return string */ public function get_method_description() { return apply_filters( 'woocommerce_shipping_method_description', $this->method_description, $this ); } /** * Return the shipping title which is user set. * * @return string */ public function get_title() { return apply_filters( 'woocommerce_shipping_method_title', $this->title, $this->id ); } /** * Return calculated rates for a package. * @since 2.6.0 * @param object $package * @return array */ public function get_rates_for_package( $package ) { $this->rates = array(); if ( $this->is_available( $package ) && ( empty( $package['ship_via'] ) || in_array( $this->id, $package['ship_via'] ) ) ) { $this->calculate_shipping( $package ); } return $this->rates; } /** * Returns a rate ID based on this methods ID and instance, with an optional * suffix if distinguishing between multiple rates. * @since 2.6.0 * @param string $suffix * @return string */ public function get_rate_id( $suffix = '' ) { $rate_id = array( $this->id ); if ( $this->instance_id ) { $rate_id[] = $this->instance_id; } if ( $suffix ) { $rate_id[] = $suffix; } return implode( ':', $rate_id ); } /** * Add a shipping rate. If taxes are not set they will be calculated based on cost. * @param array $args (default: array()) */ public function add_rate( $args = array() ) { $args = wp_parse_args( $args, array( 'id' => $this->get_rate_id(), // ID for the rate. If not passed, this id:instance default will be used. 'label' => '', // Label for the rate 'cost' => '0', // Amount or array of costs (per item shipping) 'taxes' => '', // Pass taxes, or leave empty to have it calculated for you, or 'false' to disable calculations 'calc_tax' => 'per_order', // Calc tax per_order or per_item. Per item needs an array of costs 'meta_data' => array(), // Array of misc meta data to store along with this rate - key value pairs. 'package' => false, // Package array this rate was generated for @since 2.6.0 ) ); // ID and label are required if ( ! $args['id'] || ! $args['label'] ) { return; } // Total up the cost $total_cost = is_array( $args['cost'] ) ? array_sum( $args['cost'] ) : $args['cost']; $taxes = $args['taxes']; // Taxes - if not an array and not set to false, calc tax based on cost and passed calc_tax variable. This saves shipping methods having to do complex tax calculations. if ( ! is_array( $taxes ) && $taxes !== false && $total_cost > 0 && $this->is_taxable() ) { $taxes = 'per_item' === $args['calc_tax'] ? $this->get_taxes_per_item( $args['cost'] ) : WC_Tax::calc_shipping_tax( $total_cost, WC_Tax::get_shipping_tax_rates() ); } // Round the total cost after taxes have been calculated. $total_cost = wc_format_decimal( $total_cost, wc_get_price_decimals() ); // Create rate object $rate = new WC_Shipping_Rate( $args['id'], $args['label'], $total_cost, $taxes, $this->id ); if ( ! empty( $args['meta_data'] ) ) { foreach ( $args['meta_data'] as $key => $value ) { $rate->add_meta_data( $key, $value ); } } // Store package data if ( $args['package'] ) { $items_in_package = array(); foreach ( $args['package']['contents'] as $item ) { $product = $item['data']; $items_in_package[] = $product->get_title() . ' × ' . $item['quantity']; } $rate->add_meta_data( __( 'Items', 'woocommerce' ), implode( ', ', $items_in_package ) ); } $this->rates[ $args['id'] ] = $rate; } /** * Calc taxes per item being shipping in costs array. * @since 2.6.0 * @access protected * @param array $costs * @return array of taxes */ protected function get_taxes_per_item( $costs ) { $taxes = array(); // If we have an array of costs we can look up each items tax class and add tax accordingly if ( is_array( $costs ) ) { $cart = WC()->cart->get_cart(); foreach ( $costs as $cost_key => $amount ) { if ( ! isset( $cart[ $cost_key ] ) ) { continue; } $item_taxes = WC_Tax::calc_shipping_tax( $amount, WC_Tax::get_shipping_tax_rates( $cart[ $cost_key ]['data']->get_tax_class() ) ); // Sum the item taxes foreach ( array_keys( $taxes + $item_taxes ) as $key ) { $taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 ); } } // Add any cost for the order - order costs are in the key 'order' if ( isset( $costs['order'] ) ) { $item_taxes = WC_Tax::calc_shipping_tax( $costs['order'], WC_Tax::get_shipping_tax_rates() ); // Sum the item taxes foreach ( array_keys( $taxes + $item_taxes ) as $key ) { $taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 ); } } } return $taxes; } /** * Is this method available? * @param array $package * @return bool */ public function is_available( $package ) { $available = $this->is_enabled(); // Country availability (legacy, for non-zone based methods) if ( ! $this->instance_id && $available ) { $countries = is_array( $this->countries ) ? $this->countries : array(); switch ( $this->availability ) { case 'specific' : case 'including' : $available = in_array( $package['destination']['country'], array_intersect( $countries, array_keys( WC()->countries->get_shipping_countries() ) ) ); break; case 'excluding' : $available = in_array( $package['destination']['country'], array_diff( array_keys( WC()->countries->get_shipping_countries() ), $countries ) ); break; default : $available = in_array( $package['destination']['country'], array_keys( WC()->countries->get_shipping_countries() ) ); break; } } return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $available, $package ); } /** * Get fee to add to shipping cost. * @param string|float $fee * @param float $total * @return float */ public function get_fee( $fee, $total ) { if ( strstr( $fee, '%' ) ) { $fee = ( $total / 100 ) * str_replace( '%', '', $fee ); } if ( ! empty( $this->minimum_fee ) && $this->minimum_fee > $fee ) { $fee = $this->minimum_fee; } return $fee; } /** * Does this method have a settings page? * @return bool */ public function has_settings() { return $this->instance_id ? $this->supports( 'instance-settings' ) : $this->supports( 'settings' ); } /** * Return admin options as a html string. * @return string */ public function get_admin_options_html() { if ( $this->instance_id ) { $settings_html = $this->generate_settings_html( $this->get_instance_form_fields(), false ); } else { $settings_html = $this->generate_settings_html( $this->get_form_fields(), false ); } return '<table class="form-table">' . $settings_html . '</table>'; } /** * Output the shipping settings screen. */ public function admin_options() { if ( ! $this->instance_id ) { echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>'; } echo wp_kses_post( wpautop( $this->get_method_description() ) ); echo $this->get_admin_options_html(); } /** * get_option function. * * Gets and option from the settings API, using defaults if necessary to prevent undefined notices. * * @param string $key * @param mixed $empty_value * @return mixed The value specified for the option or a default value for the option. */ public function get_option( $key, $empty_value = null ) { // Instance options take priority over global options if ( $this->instance_id && array_key_exists( $key, $this->get_instance_form_fields() ) ) { return $this->get_instance_option( $key, $empty_value ); } // Return global option return parent::get_option( $key, $empty_value ); } /** * Gets an option from the settings API, using defaults if necessary to prevent undefined notices. * * @param string $key * @param mixed $empty_value * @return mixed The value specified for the option or a default value for the option. */ public function get_instance_option( $key, $empty_value = null ) { if ( empty( $this->instance_settings ) ) { $this->init_instance_settings(); } // Get option default if unset. if ( ! isset( $this->instance_settings[ $key ] ) ) { $form_fields = $this->get_instance_form_fields(); $this->instance_settings[ $key ] = $this->get_field_default( $form_fields[ $key ] ); } if ( ! is_null( $empty_value ) && '' === $this->instance_settings[ $key ] ) { $this->instance_settings[ $key ] = $empty_value; } return $this->instance_settings[ $key ]; } /** * Get settings fields for instances of this shipping method (within zones). * Should be overridden by shipping methods to add options. * @since 2.6.0 * @return array */ public function get_instance_form_fields() { return apply_filters( 'woocommerce_shipping_instance_form_fields_' . $this->id, array_map( array( $this, 'set_defaults' ), $this->instance_form_fields ) ); } /** * Return the name of the option in the WP DB. * @since 2.6.0 * @return string */ public function get_instance_option_key() { return $this->instance_id ? $this->plugin_id . $this->id . '_' . $this->instance_id . '_settings' : ''; } /** * Initialise Settings for instances. * @since 2.6.0 */ public function init_instance_settings() { $this->instance_settings = get_option( $this->get_instance_option_key(), null ); // If there are no settings defined, use defaults. if ( ! is_array( $this->instance_settings ) ) { $form_fields = $this->get_instance_form_fields(); $this->instance_settings = array_merge( array_fill_keys( array_keys( $form_fields ), '' ), wp_list_pluck( $form_fields, 'default' ) ); } } /** * Processes and saves options. * If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out. * @since 2.6.0 * @return bool was anything saved? */ public function process_admin_options() { if ( $this->instance_id ) { $this->init_instance_settings(); $post_data = $this->get_post_data(); foreach ( $this->get_instance_form_fields() as $key => $field ) { if ( 'title' !== $this->get_field_type( $field ) ) { try { $this->instance_settings[ $key ] = $this->get_field_value( $key, $field, $post_data ); } catch ( Exception $e ) { $this->add_error( $e->getMessage() ); } } } return update_option( $this->get_instance_option_key(), apply_filters( 'woocommerce_shipping_' . $this->id . '_instance_settings_values', $this->instance_settings, $this ) ); } else { return parent::process_admin_options(); } } } abstract-wc-session.php 0000666 00000003533 15214130426 0011156 0 ustar 00 <?php /** * Handle data for the current customers session * * @class WC_Session * @version 2.0.0 * @package WooCommerce/Abstracts * @category Abstract Class * @author WooThemes */ abstract class WC_Session { /** @var int $_customer_id */ protected $_customer_id; /** @var array $_data */ protected $_data = array(); /** @var bool $_dirty When something changes */ protected $_dirty = false; /** * __get function. * * @param mixed $key * @return mixed */ public function __get( $key ) { return $this->get( $key ); } /** * __set function. * * @param mixed $key * @param mixed $value */ public function __set( $key, $value ) { $this->set( $key, $value ); } /** * __isset function. * * @param mixed $key * @return bool */ public function __isset( $key ) { return isset( $this->_data[ sanitize_title( $key ) ] ); } /** * __unset function. * * @param mixed $key */ public function __unset( $key ) { if ( isset( $this->_data[ $key ] ) ) { unset( $this->_data[ $key ] ); $this->_dirty = true; } } /** * Get a session variable. * * @param string $key * @param mixed $default used if the session variable isn't set * @return array|string value of session variable */ public function get( $key, $default = null ) { $key = sanitize_key( $key ); return isset( $this->_data[ $key ] ) ? maybe_unserialize( $this->_data[ $key ] ) : $default; } /** * Set a session variable. * * @param string $key * @param mixed $value */ public function set( $key, $value ) { if ( $value !== $this->get( $key ) ) { $this->_data[ sanitize_key( $key ) ] = maybe_serialize( $value ); $this->_dirty = true; } } /** * get_customer_id function. * * @access public * @return int */ public function get_customer_id() { return $this->_customer_id; } } abstract-wc-payment-gateway.php 0000666 00000027421 15214130426 0012611 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; } /** * WooCommerce Payment Gateway class. * * Extended by individual payment gateways to handle payments. * * @class WC_Payment_Gateway * @extends WC_Settings_API * @version 2.1.0 * @package WooCommerce/Abstracts * @category Abstract Class * @author WooThemes */ abstract class WC_Payment_Gateway extends WC_Settings_API { /** * Set if the place order button should be renamed on selection. * @var string */ public $order_button_text; /** * yes or no based on whether the method is enabled. * @var string */ public $enabled = 'yes'; /** * Payment method title for the frontend. * @var string */ public $title; /** * Payment method description for the frontend. * @var string */ public $description; /** * Chosen payment method id. * @var bool */ public $chosen; /** * Gateway title. * @var string */ public $method_title = ''; /** * Gateway description. * @var string */ public $method_description = ''; /** * True if the gateway shows fields on the checkout. * @var bool */ public $has_fields; /** * Countries this gateway is allowed for. * @var array */ public $countries; /** * Available for all counties or specific. * @var string */ public $availability; /** * Icon for the gateway. * @var string */ public $icon; /** * Supported features such as 'default_credit_card_form', 'refunds'. * @var array */ public $supports = array( 'products' ); /** * Maximum transaction amount, zero does not define a maximum. * @var int */ public $max_amount = 0; /** * Optional URL to view a transaction. * @var string */ public $view_transaction_url = ''; /** * Optional label to show for "new payment method" in the payment * method/token selection radio selection. * @var string */ public $new_method_label = ''; /** * Contains a users saved tokens for this gateway. * @var array */ protected $tokens = array(); /** * Returns a users saved tokens for this gateway. * @since 2.6.0 * @return array */ public function get_tokens() { if ( sizeof( $this->tokens ) > 0 ) { return $this->tokens; } if ( is_user_logged_in() && $this->supports( 'tokenization' ) ) { $this->tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id(), $this->id ); } return $this->tokens; } /** * Return the title for admin screens. * @return string */ public function get_method_title() { return apply_filters( 'woocommerce_gateway_method_title', $this->method_title, $this ); } /** * Return the description for admin screens. * @return string */ public function get_method_description() { return apply_filters( 'woocommerce_gateway_method_description', $this->method_description, $this ); } /** * Output the gateway settings screen. */ public function admin_options() { echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>'; echo wp_kses_post( wpautop( $this->get_method_description() ) ); parent::admin_options(); } /** * Init settings for gateways. */ public function init_settings() { parent::init_settings(); $this->enabled = ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled'] ? 'yes' : 'no'; } /** * Get the return url (thank you page). * * @param WC_Order $order * @return string */ public function get_return_url( $order = null ) { if ( $order ) { $return_url = $order->get_checkout_order_received_url(); } else { $return_url = wc_get_endpoint_url( 'order-received', '', wc_get_page_permalink( 'checkout' ) ); } if ( is_ssl() || get_option('woocommerce_force_ssl_checkout') == 'yes' ) { $return_url = str_replace( 'http:', 'https:', $return_url ); } return apply_filters( 'woocommerce_get_return_url', $return_url, $order ); } /** * Get a link to the transaction on the 3rd party gateway size (if applicable). * * @param WC_Order $order the order object. * @return string transaction URL, or empty string. */ public function get_transaction_url( $order ) { $return_url = ''; $transaction_id = $order->get_transaction_id(); if ( ! empty( $this->view_transaction_url ) && ! empty( $transaction_id ) ) { $return_url = sprintf( $this->view_transaction_url, $transaction_id ); } return apply_filters( 'woocommerce_get_transaction_url', $return_url, $order, $this ); } /** * Get the order total in checkout and pay_for_order. * * @return float */ protected function get_order_total() { $total = 0; $order_id = absint( get_query_var( 'order-pay' ) ); // Gets order total from "pay for order" page. if ( 0 < $order_id ) { $order = wc_get_order( $order_id ); $total = (float) $order->get_total(); // Gets order total from cart/checkout. } elseif ( 0 < WC()->cart->total ) { $total = (float) WC()->cart->total; } return $total; } /** * Check if the gateway is available for use. * * @return bool */ public function is_available() { $is_available = ( 'yes' === $this->enabled ); if ( WC()->cart && 0 < $this->get_order_total() && 0 < $this->max_amount && $this->max_amount < $this->get_order_total() ) { $is_available = false; } return $is_available; } /** * Check if the gateway has fields on the checkout. * * @return bool */ public function has_fields() { return $this->has_fields ? true : false; } /** * Return the gateway's title. * * @return string */ public function get_title() { return apply_filters( 'woocommerce_gateway_title', $this->title, $this->id ); } /** * Return the gateway's description. * * @return string */ public function get_description() { return apply_filters( 'woocommerce_gateway_description', $this->description, $this->id ); } /** * Return the gateway's icon. * * @return string */ public function get_icon() { $icon = $this->icon ? '<img src="' . WC_HTTPS::force_https_url( $this->icon ) . '" alt="' . esc_attr( $this->get_title() ) . '" />' : ''; return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id ); } /** * Set as current gateway. * * Set this as the current gateway. */ public function set_current() { $this->chosen = true; } /** * Process Payment. * * Process the payment. Override this in your gateway. When implemented, this should. * return the success and redirect in an array. e.g: * * return array( * 'result' => 'success', * 'redirect' => $this->get_return_url( $order ) * ); * * @param int $order_id * @return array */ public function process_payment( $order_id ) { return array(); } /** * Process refund. * * If the gateway declares 'refunds' support, this will allow it to refund. * a passed in amount. * * @param int $order_id * @param float $amount * @param string $reason * @return boolean True or false based on success, or a WP_Error object. */ public function process_refund( $order_id, $amount = null, $reason = '' ) { return false; } /** * Validate frontend fields. * * Validate payment fields on the frontend. * * @return bool */ public function validate_fields() { return true; } /** * If There are no payment fields show the description if set. * Override this in your gateway if you have some. */ public function payment_fields() { if ( $description = $this->get_description() ) { echo wpautop( wptexturize( $description ) ); } if ( $this->supports( 'default_credit_card_form' ) ) { $this->credit_card_form(); // Deprecated, will be removed in a future version. } } /** * Check if a gateway supports a given feature. * * Gateways should override this to declare support (or lack of support) for a feature. * For backward compatibility, gateways support 'products' by default, but nothing else. * * @param string $feature string The name of a feature to test support for. * @return bool True if the gateway supports the feature, false otherwise. * @since 1.5.7 */ public function supports( $feature ) { return apply_filters( 'woocommerce_payment_gateway_supports', in_array( $feature, $this->supports ) ? true : false, $feature, $this ); } /** * Core credit card form which gateways can used if needed. Deprecated - inheirt WC_Payment_Gateway_CC instead. * @param array $args * @param array $fields */ public function credit_card_form( $args = array(), $fields = array() ) { _deprecated_function( 'credit_card_form', '2.6', 'WC_Payment_Gateway_CC->form' ); $cc_form = new WC_Payment_Gateway_CC; $cc_form->id = $this->id; $cc_form->supports = $this->supports; $cc_form->form(); } /** * Enqueues our tokenization script to handle some of the new form options. * @since 2.6.0 */ public function tokenization_script() { wp_enqueue_script( 'woocommerce-tokenization-form', plugins_url( '/assets/js/frontend/tokenization-form' . ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min' ) . '.js', WC_PLUGIN_FILE ), array( 'jquery' ), WC()->version ); } /** * Grab and display our saved payment methods. * @since 2.6.0 */ public function saved_payment_methods() { $html = '<ul class="woocommerce-SavedPaymentMethods wc-saved-payment-methods" data-count="' . esc_attr( count( $this->get_tokens() ) ) . '">'; foreach ( $this->get_tokens() as $token ) { $html .= $this->get_saved_payment_method_option_html( $token ); } $html .= $this->get_new_payment_method_option_html(); $html .= '</ul>'; echo apply_filters( 'wc_payment_gateway_form_saved_payment_methods_html', $html, $this ); } /** * Gets saved payment method HTML from a token. * @since 2.6.0 * @param WC_Payment_Token $token Payment Token * @return string Generated payment method HTML */ public function get_saved_payment_method_option_html( $token ) { $html = sprintf( '<li class="woocommerce-SavedPaymentMethods-token"> <input id="wc-%1$s-payment-token-%2$s" type="radio" name="wc-%1$s-payment-token" value="%2$s" style="width:auto;" class="woocommerce-SavedPaymentMethods-tokenInput" %4$s /> <label for="wc-%1$s-payment-token-%2$s">%3$s</label> </li>', esc_attr( $this->id ), esc_attr( $token->get_id() ), esc_html( $token->get_display_name() ), checked( $token->is_default(), true, false ) ); return apply_filters( 'woocommerce_payment_gateway_get_saved_payment_method_option_html', $html, $token, $this ); } /** * Displays a radio button for entering a new payment method (new CC details) instead of using a saved method. * Only displayed when a gateway supports tokenization. * @since 2.6.0 */ public function get_new_payment_method_option_html() { $label = apply_filters( 'woocommerce_payment_gateway_get_new_payment_method_option_html_label', $this->new_method_label ? $this->new_method_label : __( 'Use a new payment method', 'woocommerce' ), $this ); $html = sprintf( '<li class="woocommerce-SavedPaymentMethods-new"> <input id="wc-%1$s-payment-token-new" type="radio" name="wc-%1$s-payment-token" value="new" style="width:auto;" class="woocommerce-SavedPaymentMethods-tokenInput" /> <label for="wc-%1$s-payment-token-new">%2$s</label> </li>', esc_attr( $this->id ), esc_html( $label ) ); return apply_filters( 'woocommerce_payment_gateway_get_new_payment_method_option_html', $html, $this ); } /** * Outputs a checkbox for saving a new payment method to the database. * @since 2.6.0 */ public function save_payment_method_checkbox() { echo sprintf( '<p class="form-row woocommerce-SavedPaymentMethods-saveNew"> <input id="wc-%1$s-new-payment-method" name="wc-%1$s-new-payment-method" type="checkbox" value="true" style="width:auto;" /> <label for="wc-%1$s-new-payment-method" style="display:inline;">%2$s</label> </p>', esc_attr( $this->id ), esc_html__( 'Save to Account', 'woocommerce' ) ); } } abstract-wc-order.php 0000666 00000250621 15214130426 0010610 0 ustar 00 <?php /** * Abstract Order * * The WooCommerce order class handles order data. * * @class WC_Order * @version 2.2.0 * @package WooCommerce/Classes * @category Class * @author WooThemes * * @property string $billing_first_name The billing address first name. * @property string $billing_last_name The billing address last name. * @property string $billing_company The billing address company. * @property string $billing_address_1 The first line of the billing address. * @property string $billing_address_2 The second line of the billing address. * @property string $billing_city The city of the billing address. * @property string $billing_state The state of the billing address. * @property string $billing_postcode The postcode of the billing address. * @property string $billing_country The country of the billing address. * @property string $billing_phone The billing phone number. * @property string $billing_email The billing email. * @property string $shipping_first_name The shipping address first name. * @property string $shipping_last_name The shipping address last name. * @property string $shipping_company The shipping address company. * @property string $shipping_address_1 The first line of the shipping address. * @property string $shipping_address_2 The second line of the shipping address. * @property string $shipping_city The city of the shipping address. * @property string $shipping_state The state of the shipping address. * @property string $shipping_postcode The postcode of the shipping address. * @property string $shipping_country The country of the shipping address. * @property string $cart_discount Total amount of discount. * @property string $cart_discount_tax Total amount of discount applied to taxes. * @property string $shipping_method_title < 2.1 was used for shipping method title. Now @deprecated. * @property int $customer_user User ID who the order belongs to. 0 for guests. * @property string $order_key Random key/password unqique to each order. * @property string $order_discount Stored after tax discounts pre-2.3. Now @deprecated. * @property string $order_tax Stores order tax total. * @property string $order_shipping_tax Stores shipping tax total. * @property string $order_shipping Stores shipping total. * @property string $order_total Stores order total. * @property string $order_currency Stores currency code used for the order. * @property string $payment_method method ID. * @property string $payment_method_title Name of the payment method used. * @property string $customer_ip_address Customer IP Address. * @property string $customer_user_agent Customer User agent. */ abstract class WC_Abstract_Order { /** @public int Order (post) ID. */ public $id = 0; /** @var $post WP_Post. */ public $post = null; /** @public string Order type. */ public $order_type = false; /** @public string Order Date. */ public $order_date = ''; /** @public string Order Modified Date. */ public $modified_date = ''; /** @public string Customer Message (excerpt). */ public $customer_message = ''; /** @public string Customer Note */ public $customer_note = ''; /** @public string Order Status. */ public $post_status = ''; /** @public bool Do prices include tax? */ public $prices_include_tax = false; /** @public string Display mode for taxes in cart. */ public $tax_display_cart = ''; /** @public bool Do totals display ex tax? */ public $display_totals_ex_tax = false; /** @public bool Do cart prices display ex tax? */ public $display_cart_ex_tax = false; /** @protected string Formatted address. Accessed via get_formatted_billing_address(). */ protected $formatted_billing_address = ''; /** @protected string Formatted address. Accessed via get_formatted_shipping_address(). */ protected $formatted_shipping_address = ''; /** * Get the order if ID is passed, otherwise the order is new and empty. * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory. * should be used. It is possible, but the aforementioned are preferred and are the only. * methods that will be maintained going forward. * * @param int|object|WC_Order $order Order to init. */ public function __construct( $order = 0 ) { $this->prices_include_tax = get_option('woocommerce_prices_include_tax') == 'yes' ? true : false; $this->tax_display_cart = get_option( 'woocommerce_tax_display_cart' ); $this->display_totals_ex_tax = $this->tax_display_cart == 'excl' ? true : false; $this->display_cart_ex_tax = $this->tax_display_cart == 'excl' ? true : false; $this->init( $order ); } /** * Init/load the order object. Called from the constructor. * * @param int|object|WC_Order $order Order to init. */ protected function init( $order ) { if ( is_numeric( $order ) ) { $this->id = absint( $order ); $this->post = get_post( $order ); $this->get_order( $this->id ); } elseif ( $order instanceof WC_Order ) { $this->id = absint( $order->id ); $this->post = $order->post; $this->get_order( $this->id ); } elseif ( isset( $order->ID ) ) { $this->id = absint( $order->ID ); $this->post = $order; $this->get_order( $this->id ); } } /** * Remove all line items (products, coupons, shipping, taxes) from the order. * * @param string $type Order item type. Default null. */ public function remove_order_items( $type = null ) { global $wpdb; if ( ! empty( $type ) ) { $wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id AND items.order_id = %d AND items.order_item_type = %s", $this->id, $type ) ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->id, $type ) ); } else { $wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id and items.order_id = %d", $this->id ) ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->id ) ); } } /** * Returns a list of all payment tokens associated with the current order * * @since 2.6 * @return array An array of payment token objects */ public function get_payment_tokens() { return WC_Payment_Tokens::get_order_tokens( $this->id ); } /** * Add a payment token to an order * * @since 2.6 * @param WC_Payment_Token $token Payment token object * @return boolean True if the token was added, false if not */ public function add_payment_token( $token ) { if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) { return false; } $token_ids = get_post_meta( $this->id, '_payment_tokens', true ); if ( empty ( $token_ids ) ) { $token_ids = array(); } $token_ids[] = $token->get_id(); update_post_meta( $this->id, '_payment_tokens', $token_ids ); do_action( 'woocommerce_payment_token_added_to_order', $this->id, $token->get_id(), $token, $token_ids ); return true; } /** * Set the payment method for the order. * * @param WC_Payment_Gateway|string $payment_method */ public function set_payment_method( $payment_method = '' ) { if ( is_object( $payment_method ) ) { update_post_meta( $this->id, '_payment_method', $payment_method->id ); update_post_meta( $this->id, '_payment_method_title', $payment_method->get_title() ); } else { update_post_meta( $this->id, '_payment_method', '' ); update_post_meta( $this->id, '_payment_method_title', '' ); } } /** * Set the customer address. * * @param array $address Address data. * @param string $type billing or shipping. */ public function set_address( $address, $type = 'billing' ) { foreach ( $address as $key => $value ) { update_post_meta( $this->id, "_{$type}_" . $key, $value ); } } /** * Returns the requested address in raw, non-formatted way. * @since 2.4.0 * @param string $type Billing or shipping. Anything else besides 'billing' will return shipping address. * @return array The stored address after filter. */ public function get_address( $type = 'billing' ) { if ( 'billing' === $type ) { $address = array( 'first_name' => $this->billing_first_name, 'last_name' => $this->billing_last_name, 'company' => $this->billing_company, 'address_1' => $this->billing_address_1, 'address_2' => $this->billing_address_2, 'city' => $this->billing_city, 'state' => $this->billing_state, 'postcode' => $this->billing_postcode, 'country' => $this->billing_country, 'email' => $this->billing_email, 'phone' => $this->billing_phone, ); } else { $address = array( 'first_name' => $this->shipping_first_name, 'last_name' => $this->shipping_last_name, 'company' => $this->shipping_company, 'address_1' => $this->shipping_address_1, 'address_2' => $this->shipping_address_2, 'city' => $this->shipping_city, 'state' => $this->shipping_state, 'postcode' => $this->shipping_postcode, 'country' => $this->shipping_country, ); } return apply_filters( 'woocommerce_get_order_address', $address, $type, $this ); } /** * Add a product line item to the order. * * @since 2.2 * @param \WC_Product $product * @param int $qty Line item quantity. * @param array $args * @return int|bool Item ID or false. */ public function add_product( $product, $qty = 1, $args = array() ) { $args = wp_parse_args( $args, array( 'variation' => array(), 'totals' => array() ) ); if ( ! $product ) { return false; } $item_id = wc_add_order_item( $this->id, array( 'order_item_name' => $product->get_title(), 'order_item_type' => 'line_item' ) ); if ( ! $item_id ) { return false; } wc_add_order_item_meta( $item_id, '_qty', wc_stock_amount( $qty ) ); wc_add_order_item_meta( $item_id, '_tax_class', $product->get_tax_class() ); wc_add_order_item_meta( $item_id, '_product_id', $product->id ); wc_add_order_item_meta( $item_id, '_variation_id', isset( $product->variation_id ) ? $product->variation_id : 0 ); // Set line item totals, either passed in or from the product wc_add_order_item_meta( $item_id, '_line_subtotal', wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax( $qty ) ) ); wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( isset( $args['totals']['total'] ) ? $args['totals']['total'] : $product->get_price_excluding_tax( $qty ) ) ); wc_add_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) ); wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) ); // Save tax data - Since 2.2 if ( isset( $args['totals']['tax_data'] ) ) { $tax_data = array(); $tax_data['total'] = array_map( 'wc_format_decimal', $args['totals']['tax_data']['total'] ); $tax_data['subtotal'] = array_map( 'wc_format_decimal', $args['totals']['tax_data']['subtotal'] ); wc_add_order_item_meta( $item_id, '_line_tax_data', $tax_data ); } else { wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) ); } // Add variation meta if ( ! empty( $args['variation'] ) ) { foreach ( $args['variation'] as $key => $value ) { wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value ); } } // Backorders if ( $product->backorders_require_notification() && $product->is_on_backorder( $qty ) ) { wc_add_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $qty - max( 0, $product->get_total_stock() ) ); } do_action( 'woocommerce_order_add_product', $this->id, $item_id, $product, $qty, $args ); return $item_id; } /** * Update a line item for the order. * * Note this does not update order totals. * * @since 2.2 * @param int $item_id order item ID. * @param array $args data to update. * @param WC_Product $product * @return bool */ public function update_product( $item_id, $product, $args ) { if ( ! $item_id || ! is_object( $product ) ) { return false; } // quantity if ( isset( $args['qty'] ) ) { wc_update_order_item_meta( $item_id, '_qty', wc_stock_amount( $args['qty'] ) ); } // tax class if ( isset( $args['tax_class'] ) ) { wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] ); } // set item totals, either provided or from product if ( isset( $args['qty'] ) ) { wc_update_order_item_meta( $item_id, '_line_subtotal', wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax( $args['qty'] ) ) ); wc_update_order_item_meta( $item_id, '_line_total', wc_format_decimal( isset( $args['totals']['total'] ) ? $args['totals']['total'] : $product->get_price_excluding_tax( $args['qty'] ) ) ); } // set item tax totals wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) ); wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) ); // variation meta if ( isset( $args['variation'] ) && is_array( $args['variation'] ) ) { foreach ( $args['variation'] as $key => $value ) { wc_update_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value ); } } // backorders if ( isset( $args['qty'] ) && $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) { wc_update_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ) ); } do_action( 'woocommerce_order_edit_product', $this->id, $item_id, $args, $product ); return true; } /** * Add coupon code to the order. * * @param string $code * @param int $discount_amount * @param int $discount_amount_tax "Discounted" tax - used for tax inclusive prices. * @return int|bool Item ID or false. */ public function add_coupon( $code, $discount_amount = 0, $discount_amount_tax = 0 ) { $item_id = wc_add_order_item( $this->id, array( 'order_item_name' => $code, 'order_item_type' => 'coupon' ) ); if ( ! $item_id ) { return false; } wc_add_order_item_meta( $item_id, 'discount_amount', $discount_amount ); wc_add_order_item_meta( $item_id, 'discount_amount_tax', $discount_amount_tax ); do_action( 'woocommerce_order_add_coupon', $this->id, $item_id, $code, $discount_amount, $discount_amount_tax ); return $item_id; } /** * Update coupon for order. * * Note this does not update order totals. * * @since 2.2 * @param int $item_id * @param array $args * @return bool */ public function update_coupon( $item_id, $args ) { if ( ! $item_id ) { return false; } // code if ( isset( $args['code'] ) ) { wc_update_order_item( $item_id, array( 'order_item_name' => $args['code'] ) ); } // amount if ( isset( $args['discount_amount'] ) ) { wc_update_order_item_meta( $item_id, 'discount_amount', wc_format_decimal( $args['discount_amount'] ) ); } if ( isset( $args['discount_amount_tax'] ) ) { wc_add_order_item_meta( $item_id, 'discount_amount_tax', wc_format_decimal( $args['discount_amount_tax'] ) ); } do_action( 'woocommerce_order_update_coupon', $this->id, $item_id, $args ); return true; } /** * Add a tax row to the order. * * @since 2.2 * @param int tax_rate_id * @return int|bool Item ID or false. */ public function add_tax( $tax_rate_id, $tax_amount = 0, $shipping_tax_amount = 0 ) { $code = WC_Tax::get_rate_code( $tax_rate_id ); if ( ! $code ) { return false; } $item_id = wc_add_order_item( $this->id, array( 'order_item_name' => $code, 'order_item_type' => 'tax' ) ); if ( ! $item_id ) { return false; } wc_add_order_item_meta( $item_id, 'rate_id', $tax_rate_id ); wc_add_order_item_meta( $item_id, 'label', WC_Tax::get_rate_label( $tax_rate_id ) ); wc_add_order_item_meta( $item_id, 'compound', WC_Tax::is_compound( $tax_rate_id ) ? 1 : 0 ); wc_add_order_item_meta( $item_id, 'tax_amount', wc_format_decimal( $tax_amount ) ); wc_add_order_item_meta( $item_id, 'shipping_tax_amount', wc_format_decimal( $shipping_tax_amount ) ); do_action( 'woocommerce_order_add_tax', $this->id, $item_id, $tax_rate_id, $tax_amount, $shipping_tax_amount ); return $item_id; } /** * Add a shipping row to the order. * * @param WC_Shipping_Rate shipping_rate * @return int|bool Item ID or false. */ public function add_shipping( $shipping_rate ) { $item_id = wc_add_order_item( $this->id, array( 'order_item_name' => $shipping_rate->label, 'order_item_type' => 'shipping' ) ); if ( ! $item_id ) { return false; } wc_add_order_item_meta( $item_id, 'method_id', $shipping_rate->id ); wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $shipping_rate->cost ) ); // Save shipping taxes - Since 2.2 $taxes = array_map( 'wc_format_decimal', $shipping_rate->taxes ); wc_add_order_item_meta( $item_id, 'taxes', $taxes ); // Store meta $shipping_meta = $shipping_rate->get_meta_data(); if ( ! empty( $shipping_meta ) ) { foreach ( $shipping_rate->get_meta_data() as $key => $value ) { wc_add_order_item_meta( $item_id, $key, $value ); } } do_action( 'woocommerce_order_add_shipping', $this->id, $item_id, $shipping_rate ); // Update total $this->set_total( (float) $this->order_shipping + (float) wc_format_decimal( $shipping_rate->cost ), 'shipping' ); return $item_id; } /** * Update shipping method for order. * * Note this does not update the order total. * * @since 2.2 * @param int $item_id * @param array $args * @return bool */ public function update_shipping( $item_id, $args ) { if ( ! $item_id ) { return false; } // method title if ( isset( $args['method_title'] ) ) { wc_update_order_item( $item_id, array( 'order_item_name' => $args['method_title'] ) ); } // method ID if ( isset( $args['method_id'] ) ) { wc_update_order_item_meta( $item_id, 'method_id', $args['method_id'] ); } // method cost if ( isset( $args['cost'] ) ) { // Get old cost before updating $old_cost = wc_get_order_item_meta( $item_id, 'cost' ); // Update wc_update_order_item_meta( $item_id, 'cost', wc_format_decimal( $args['cost'] ) ); // Update total $this->set_total( $this->order_shipping - wc_format_decimal( $old_cost ) + wc_format_decimal( $args['cost'] ), 'shipping' ); } do_action( 'woocommerce_order_update_shipping', $this->id, $item_id, $args ); return true; } /** * Add a fee to the order. * * @param object $fee * @return int|bool Item ID or false. */ public function add_fee( $fee ) { $item_id = wc_add_order_item( $this->id, array( 'order_item_name' => $fee->name, 'order_item_type' => 'fee' ) ); if ( ! $item_id ) { return false; } if ( $fee->taxable ) { wc_add_order_item_meta( $item_id, '_tax_class', $fee->tax_class ); } else { wc_add_order_item_meta( $item_id, '_tax_class', '0' ); } wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( $fee->amount ) ); wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $fee->tax ) ); // Save tax data - Since 2.2 $tax_data = array_map( 'wc_format_decimal', $fee->tax_data ); wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $tax_data ) ); do_action( 'woocommerce_order_add_fee', $this->id, $item_id, $fee ); return $item_id; } /** * Update fee for order. * * Note this does not update order totals. * * @since 2.2 * @param int $item_id * @param array $args * @return bool */ public function update_fee( $item_id, $args ) { if ( ! $item_id ) { return false; } // name if ( isset( $args['name'] ) ) { wc_update_order_item( $item_id, array( 'order_item_name' => $args['name'] ) ); } // tax class if ( isset( $args['tax_class'] ) ) { wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] ); } // total if ( isset( $args['line_total'] ) ) { wc_update_order_item_meta( $item_id, '_line_total', wc_format_decimal( $args['line_total'] ) ); } // total tax if ( isset( $args['line_tax'] ) ) { wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $args['line_tax'] ) ); } do_action( 'woocommerce_order_update_fee', $this->id, $item_id, $args ); return true; } /** * Set an order total. * * @param float $amount * @param string $total_type * * @return bool */ public function set_total( $amount, $total_type = 'total' ) { if ( ! in_array( $total_type, array( 'shipping', 'tax', 'shipping_tax', 'total', 'cart_discount', 'cart_discount_tax' ) ) ) { return false; } switch ( $total_type ) { case 'total' : $key = '_order_total'; $amount = wc_format_decimal( $amount, wc_get_price_decimals() ); break; case 'cart_discount' : case 'cart_discount_tax' : $key = '_' . $total_type; $amount = wc_format_decimal( $amount ); break; default : $key = '_order_' . $total_type; $amount = wc_format_decimal( $amount ); break; } update_post_meta( $this->id, $key, $amount ); return true; } /** * Get all tax classes for items in the order. * * @since 2.6.3 * @return array */ public function get_items_tax_classes() { $found_tax_classes = array(); foreach ( $this->get_items() as $item ) { if ( $_product = $this->get_product_from_item( $item ) ) { $found_tax_classes[] = $_product->get_tax_class(); } } return array_unique( $found_tax_classes ); } /** * Calculate taxes for all line items and shipping, and store the totals and tax rows. * * Will use the base country unless customer addresses are set. * * @return bool success or fail. */ public function calculate_taxes() { $tax_total = 0; $shipping_tax_total = 0; $taxes = array(); $shipping_taxes = array(); $tax_based_on = get_option( 'woocommerce_tax_based_on' ); // If is_vat_exempt is 'yes', or wc_tax_enabled is false, return and do nothing. if ( 'yes' === $this->is_vat_exempt || ! wc_tax_enabled() ) { return false; } if ( 'billing' === $tax_based_on ) { $country = $this->billing_country; $state = $this->billing_state; $postcode = $this->billing_postcode; $city = $this->billing_city; } elseif ( 'shipping' === $tax_based_on ) { $country = $this->shipping_country; $state = $this->shipping_state; $postcode = $this->shipping_postcode; $city = $this->shipping_city; } // Default to base if ( 'base' === $tax_based_on || empty( $country ) ) { $default = wc_get_base_location(); $country = $default['country']; $state = $default['state']; $postcode = ''; $city = ''; } // Get items foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) { $product = $this->get_product_from_item( $item ); $line_total = isset( $item['line_total'] ) ? $item['line_total'] : 0; $line_subtotal = isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0; $tax_class = $item['tax_class']; $item_tax_status = $product ? $product->get_tax_status() : 'taxable'; if ( '0' !== $tax_class && 'taxable' === $item_tax_status ) { $tax_rates = WC_Tax::find_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, 'city' => $city, 'tax_class' => $tax_class ) ); $line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal, $tax_rates, false ); $line_taxes = WC_Tax::calc_tax( $line_total, $tax_rates, false ); $line_subtotal_tax = max( 0, array_sum( $line_subtotal_taxes ) ); $line_tax = max( 0, array_sum( $line_taxes ) ); $tax_total += $line_tax; wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( $line_subtotal_tax ) ); wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $line_tax ) ); wc_update_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $line_taxes, 'subtotal' => $line_subtotal_taxes ) ); // Sum the item taxes foreach ( array_keys( $taxes + $line_taxes ) as $key ) { $taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 ); } } } // Calc taxes for shipping foreach ( $this->get_shipping_methods() as $item_id => $item ) { $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' ); // Inherit tax class from items if ( '' === $shipping_tax_class ) { $tax_classes = WC_Tax::get_tax_classes(); $found_tax_classes = $this->get_items_tax_classes(); foreach ( $tax_classes as $tax_class ) { $tax_class = sanitize_title( $tax_class ); if ( in_array( $tax_class, $found_tax_classes ) ) { $tax_rates = WC_Tax::find_shipping_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, 'city' => $city, 'tax_class' => $tax_class, ) ); break; } } } else { $tax_rates = WC_Tax::find_shipping_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, 'city' => $city, 'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class, ) ); } $line_taxes = WC_Tax::calc_tax( $item['cost'], $tax_rates, false ); $line_tax = max( 0, array_sum( $line_taxes ) ); $shipping_tax_total += $line_tax; wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $line_tax ) ); wc_update_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $line_taxes ) ); // Sum the item taxes foreach ( array_keys( $shipping_taxes + $line_taxes ) as $key ) { $shipping_taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $shipping_taxes[ $key ] ) ? $shipping_taxes[ $key ] : 0 ); } } // Save tax totals $this->set_total( $shipping_tax_total, 'shipping_tax' ); $this->set_total( $tax_total, 'tax' ); // Tax rows $this->remove_order_items( 'tax' ); // Now merge to keep tax rows foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) { $this->add_tax( $tax_rate_id, isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0, isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 ); } return true; } /** * Calculate shipping total. * * @since 2.2 * @return float */ public function calculate_shipping() { $shipping_total = 0; foreach ( $this->get_shipping_methods() as $shipping ) { $shipping_total += $shipping['cost']; } $this->set_total( $shipping_total, 'shipping' ); return $this->get_total_shipping(); } /** * Update tax lines at order level by looking at the line item taxes themselves. * * @return bool success or fail. */ public function update_taxes() { $order_taxes = array(); $order_shipping_taxes = array(); foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) { $line_tax_data = maybe_unserialize( $item['line_tax_data'] ); if ( isset( $line_tax_data['total'] ) ) { foreach ( $line_tax_data['total'] as $tax_rate_id => $tax ) { if ( ! isset( $order_taxes[ $tax_rate_id ] ) ) { $order_taxes[ $tax_rate_id ] = 0; } $order_taxes[ $tax_rate_id ] += $tax; } } } foreach ( $this->get_items( array( 'shipping' ) ) as $item_id => $item ) { $line_tax_data = maybe_unserialize( $item['taxes'] ); if ( isset( $line_tax_data ) ) { foreach ( $line_tax_data as $tax_rate_id => $tax ) { if ( ! isset( $order_shipping_taxes[ $tax_rate_id ] ) ) { $order_shipping_taxes[ $tax_rate_id ] = 0; } $order_shipping_taxes[ $tax_rate_id ] += $tax; } } } // Remove old existing tax rows. $this->remove_order_items( 'tax' ); // Now merge to keep tax rows. foreach ( array_keys( $order_taxes + $order_shipping_taxes ) as $tax_rate_id ) { $this->add_tax( $tax_rate_id, isset( $order_taxes[ $tax_rate_id ] ) ? $order_taxes[ $tax_rate_id ] : 0, isset( $order_shipping_taxes[ $tax_rate_id ] ) ? $order_shipping_taxes[ $tax_rate_id ] : 0 ); } // Save tax totals $this->set_total( WC_Tax::round( array_sum( $order_shipping_taxes ) ), 'shipping_tax' ); $this->set_total( WC_Tax::round( array_sum( $order_taxes ) ), 'tax' ); return true; } /** * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total. * * @since 2.2 * @param bool $and_taxes Calc taxes if true. * @return float calculated grand total. */ public function calculate_totals( $and_taxes = true ) { $cart_subtotal = 0; $cart_total = 0; $fee_total = 0; $cart_subtotal_tax = 0; $cart_total_tax = 0; if ( $and_taxes && wc_tax_enabled() ) { $this->calculate_taxes(); } // line items foreach ( $this->get_items() as $item ) { $cart_subtotal += wc_format_decimal( isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0 ); $cart_total += wc_format_decimal( isset( $item['line_total'] ) ? $item['line_total'] : 0 ); $cart_subtotal_tax += wc_format_decimal( isset( $item['line_subtotal_tax'] ) ? $item['line_subtotal_tax'] : 0 ); $cart_total_tax += wc_format_decimal( isset( $item['line_tax'] ) ? $item['line_tax'] : 0 ); } $this->calculate_shipping(); foreach ( $this->get_fees() as $item ) { $fee_total += $item['line_total']; } $this->set_total( $cart_subtotal - $cart_total, 'cart_discount' ); $this->set_total( $cart_subtotal_tax - $cart_total_tax, 'cart_discount_tax' ); $grand_total = round( $cart_total + $fee_total + $this->get_total_shipping() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ); $this->set_total( $grand_total, 'total' ); return $grand_total; } /** * Gets an order from the database. * * @param int $id (default: 0). * @return bool */ public function get_order( $id = 0 ) { if ( ! $id ) { return false; } if ( $result = get_post( $id ) ) { $this->populate( $result ); return true; } return false; } /** * Populates an order from the loaded post data. * * @param mixed $result */ public function populate( $result ) { // Standard post data $this->id = $result->ID; $this->order_date = $result->post_date; $this->modified_date = $result->post_modified; $this->customer_message = $result->post_excerpt; $this->customer_note = $result->post_excerpt; $this->post_status = $result->post_status; // Billing email can default to user if set. if ( empty( $this->billing_email ) && ! empty( $this->customer_user ) && ( $user = get_user_by( 'id', $this->customer_user ) ) ) { $this->billing_email = $user->user_email; } // Orders store the state of prices including tax when created. $this->prices_include_tax = metadata_exists( 'post', $this->id, '_prices_include_tax' ) ? get_post_meta( $this->id, '_prices_include_tax', true ) === 'yes' : $this->prices_include_tax; } /** * __isset function. * * @param mixed $key * @return bool */ public function __isset( $key ) { if ( ! $this->id ) { return false; } return metadata_exists( 'post', $this->id, '_' . $key ); } /** * __get function. * * @param mixed $key * @return mixed */ public function __get( $key ) { // Get values or default if not set. if ( 'completed_date' === $key ) { $value = ( $value = get_post_meta( $this->id, '_completed_date', true ) ) ? $value : $this->modified_date; } elseif ( 'user_id' === $key ) { $value = ( $value = get_post_meta( $this->id, '_customer_user', true ) ) ? absint( $value ) : ''; } elseif ( 'status' === $key ) { $value = $this->get_status(); } else { $value = get_post_meta( $this->id, '_' . $key, true ); } return $value; } /** * Return the order statuses without wc- internal prefix. * * Queries get_post_status() directly to avoid having out of date statuses, if updated elsewhere. * * @return string */ public function get_status() { $this->post_status = get_post_status( $this->id ); return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->post_status, 0, 3 ) ? substr( $this->post_status, 3 ) : $this->post_status, $this ); } /** * Checks the order status against a passed in status. * * @return bool */ public function has_status( $status ) { return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status ); } /** * Gets the user ID associated with the order. Guests are 0. * * @since 2.2 * @return int */ public function get_user_id() { return $this->customer_user ? intval( $this->customer_user ) : 0; } /** * Get the user associated with the order. False for guests. * * @since 2.2 * @return WP_User|false */ public function get_user() { return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false; } /** * Get transaction id for the order. * * @return string */ public function get_transaction_id() { return get_post_meta( $this->id, '_transaction_id', true ); } /** * Check if an order key is valid. * * @param mixed $key * @return bool */ public function key_is_valid( $key ) { if ( $key == $this->order_key ) { return true; } return false; } /** * get_order_number function. * * Gets the order number for display (by default, order ID). * * @return string */ public function get_order_number() { return apply_filters( 'woocommerce_order_number', $this->id, $this ); } /** * Get a formatted billing address for the order. * * @return string */ public function get_formatted_billing_address() { if ( ! $this->formatted_billing_address ) { // Formatted Addresses. $address = apply_filters( 'woocommerce_order_formatted_billing_address', array( 'first_name' => $this->billing_first_name, 'last_name' => $this->billing_last_name, 'company' => $this->billing_company, 'address_1' => $this->billing_address_1, 'address_2' => $this->billing_address_2, 'city' => $this->billing_city, 'state' => $this->billing_state, 'postcode' => $this->billing_postcode, 'country' => $this->billing_country ), $this ); $this->formatted_billing_address = WC()->countries->get_formatted_address( $address ); } return $this->formatted_billing_address; } /** * Get a formatted shipping address for the order. * * @return string */ public function get_formatted_shipping_address() { if ( ! $this->formatted_shipping_address ) { if ( $this->shipping_address_1 || $this->shipping_address_2 ) { // Formatted Addresses $address = apply_filters( 'woocommerce_order_formatted_shipping_address', array( 'first_name' => $this->shipping_first_name, 'last_name' => $this->shipping_last_name, 'company' => $this->shipping_company, 'address_1' => $this->shipping_address_1, 'address_2' => $this->shipping_address_2, 'city' => $this->shipping_city, 'state' => $this->shipping_state, 'postcode' => $this->shipping_postcode, 'country' => $this->shipping_country ), $this ); $this->formatted_shipping_address = WC()->countries->get_formatted_address( $address ); } } return $this->formatted_shipping_address; } /** * Get a formatted shipping address for the order. * * @return string */ public function get_shipping_address_map_url() { $address = apply_filters( 'woocommerce_shipping_address_map_url_parts', array( 'address_1' => $this->shipping_address_1, 'address_2' => $this->shipping_address_2, 'city' => $this->shipping_city, 'state' => $this->shipping_state, 'postcode' => $this->shipping_postcode, 'country' => $this->shipping_country ), $this ); return apply_filters( 'woocommerce_shipping_address_map_url', 'https://maps.google.com/maps?&q=' . urlencode( implode( ', ', $address ) ) . '&z=16', $this ); } /** * Get the billing address in an array. * @deprecated 2.3 * @return string */ public function get_billing_address() { _deprecated_function( 'get_billing_address', '2.3', 'get_formatted_billing_address' ); return $this->get_formatted_billing_address(); } /** * Get the shipping address in an array. * @deprecated 2.3 * @return string */ public function get_shipping_address() { _deprecated_function( 'get_shipping_address', '2.3', 'get_formatted_shipping_address' ); return $this->get_formatted_shipping_address(); } /** * Get a formatted billing full name. * * @since 2.4.0 * * @return string */ public function get_formatted_billing_full_name() { return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->billing_first_name, $this->billing_last_name ); } /** * Get a formatted shipping full name. * * @since 2.4.0 * * @return string */ public function get_formatted_shipping_full_name() { return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->shipping_first_name, $this->shipping_last_name ); } /** * Return an array of items/products within this order. * * @param string|array $type Types of line items to get (array or string). * @return array */ public function get_items( $type = '' ) { global $wpdb; if ( empty( $type ) ) { $type = array( 'line_item' ); } if ( ! is_array( $type ) ) { $type = array( $type ); } $items = array(); $get_items_sql = $wpdb->prepare( "SELECT order_item_id, order_item_name, order_item_type FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d ", $this->id ); $get_items_sql .= "AND order_item_type IN ( '" . implode( "','", array_map( 'esc_sql', $type ) ) . "' ) ORDER BY order_item_id;"; $line_items = $wpdb->get_results( $get_items_sql ); // Loop items foreach ( $line_items as $item ) { $items[ $item->order_item_id ]['name'] = $item->order_item_name; $items[ $item->order_item_id ]['type'] = $item->order_item_type; $items[ $item->order_item_id ]['item_meta'] = $this->get_item_meta( $item->order_item_id ); $items[ $item->order_item_id ]['item_meta_array'] = $this->get_item_meta_array( $item->order_item_id ); $items[ $item->order_item_id ] = $this->expand_item_meta( $items[ $item->order_item_id ] ); } return apply_filters( 'woocommerce_order_get_items', $items, $this ); } /** * Expand item meta into the $item array. * @since 2.4.0 * @param array $item before expansion. * @return array */ public function expand_item_meta( $item ) { // Reserved meta keys $reserved_item_meta_keys = array( 'name', 'type', 'item_meta', 'item_meta_array', 'qty', 'tax_class', 'product_id', 'variation_id', 'line_subtotal', 'line_total', 'line_tax', 'line_subtotal_tax' ); // Expand item meta if set. if ( ! empty( $item['item_meta'] ) ) { foreach ( $item['item_meta'] as $name => $value ) { if ( in_array( $name, $reserved_item_meta_keys ) ) { continue; } if ( '_' === substr( $name, 0, 1 ) ) { $item[ substr( $name, 1 ) ] = $value[0]; } elseif ( ! in_array( $name, $reserved_item_meta_keys ) ) { $item[ $name ] = make_clickable( $value[0] ); } } } return $item; } /** * Gets the count of order items of a certain type. * * @param string $item_type * @return string */ public function get_item_count( $item_type = '' ) { if ( empty( $item_type ) ) { $item_type = array( 'line_item' ); } if ( ! is_array( $item_type ) ) { $item_type = array( $item_type ); } $items = $this->get_items( $item_type ); $count = 0; foreach ( $items as $item ) { $count += empty( $item['qty'] ) ? 1 : $item['qty']; } return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this ); } /** * Get refunds * @return array */ public function get_refunds() { return array(); } /** * Return an array of fees within this order. * * @return array */ public function get_fees() { return $this->get_items( 'fee' ); } /** * Return an array of taxes within this order. * * @return array */ public function get_taxes() { return $this->get_items( 'tax' ); } /** * Return an array of shipping costs within this order. * * @return array */ public function get_shipping_methods() { return $this->get_items( 'shipping' ); } /** * Check whether this order has a specific shipping method or not. * * @param string $method_id * * @return bool */ public function has_shipping_method( $method_id ) { $shipping_methods = $this->get_shipping_methods(); $has_method = false; if ( empty( $shipping_methods ) ) { return false; } foreach ( $shipping_methods as $shipping_method ) { if ( strpos( $shipping_method['method_id'], $method_id ) === 0 ) { $has_method = true; } } return $has_method; } /** * Get taxes, merged by code, formatted ready for output. * * @return array */ public function get_tax_totals() { $taxes = $this->get_items( 'tax' ); $tax_totals = array(); foreach ( $taxes as $key => $tax ) { $code = $tax[ 'name' ]; if ( ! isset( $tax_totals[ $code ] ) ) { $tax_totals[ $code ] = new stdClass(); $tax_totals[ $code ]->amount = 0; } $tax_totals[ $code ]->id = $key; $tax_totals[ $code ]->rate_id = $tax['rate_id']; $tax_totals[ $code ]->is_compound = $tax[ 'compound' ]; $tax_totals[ $code ]->label = isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ]; $tax_totals[ $code ]->amount += $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ]; $tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array('currency' => $this->get_order_currency()) ); } if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) { $amounts = array_filter( wp_list_pluck( $tax_totals, 'amount' ) ); $tax_totals = array_intersect_key( $tax_totals, $amounts ); } return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this ); } /** * has_meta function for order items. * * @param string $order_item_id * @return array of meta data. */ public function has_meta( $order_item_id ) { global $wpdb; return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d ORDER BY meta_id", absint( $order_item_id ) ), ARRAY_A ); } /** * Get all item meta data in array format in the order it was saved. Does not group meta by key like get_item_meta(). * * @param mixed $order_item_id * @return array of objects */ public function get_item_meta_array( $order_item_id ) { global $wpdb; // Get cache key - uses get_cache_prefix to invalidate when needed $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $order_item_id; $item_meta_array = wp_cache_get( $cache_key, 'orders' ); if ( false === $item_meta_array ) { $item_meta_array = array(); $metadata = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d ORDER BY meta_id", absint( $order_item_id ) ) ); foreach ( $metadata as $metadata_row ) { $item_meta_array[ $metadata_row->meta_id ] = (object) array( 'key' => $metadata_row->meta_key, 'value' => $metadata_row->meta_value ); } wp_cache_set( $cache_key, $item_meta_array, 'orders' ); } return $item_meta_array ; } /** * Display meta data belonging to an item. * @param array $item */ public function display_item_meta( $item ) { $product = $this->get_product_from_item( $item ); $item_meta = new WC_Order_Item_Meta( $item, $product ); $item_meta->display(); } /** * Get order item meta. * * @param mixed $order_item_id * @param string $key (default: '') * @param bool $single (default: false) * @return array|string */ public function get_item_meta( $order_item_id, $key = '', $single = false ) { return get_metadata( 'order_item', $order_item_id, $key, $single ); } /** Total Getters *******************************************************/ /** * Gets the total discount amount. * @param bool $ex_tax Show discount excl any tax. * @return float */ public function get_total_discount( $ex_tax = true ) { if ( ! $this->order_version || version_compare( $this->order_version, '2.3.7', '<' ) ) { // Backwards compatible total calculation - totals were not stored consistently in old versions. if ( $ex_tax ) { if ( $this->prices_include_tax ) { $total_discount = (double) $this->cart_discount - (double) $this->cart_discount_tax; } else { $total_discount = (double) $this->cart_discount; } } else { if ( $this->prices_include_tax ) { $total_discount = (double) $this->cart_discount; } else { $total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax; } } // New logic - totals are always stored exclusive of tax, tax total is stored in cart_discount_tax } else { if ( $ex_tax ) { $total_discount = (double) $this->cart_discount; } else { $total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax; } } return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, wc_get_rounding_precision() ), $this ); } /** * Gets the discount amount. * @deprecated in favour of get_total_discount() since we now only have one discount type. * @return float */ public function get_cart_discount() { _deprecated_function( 'get_cart_discount', '2.3', 'get_total_discount' ); return apply_filters( 'woocommerce_order_amount_cart_discount', $this->get_total_discount(), $this ); } /** * Get cart discount (formatted). * * @deprecated order (after tax) discounts removed in 2.3.0. * @return string */ public function get_order_discount_to_display() { _deprecated_function( 'get_order_discount_to_display', '2.3' ); } /** * Gets the total (order) discount amount - these are applied after tax. * * @deprecated order (after tax) discounts removed in 2.3.0. * @return float */ public function get_order_discount() { _deprecated_function( 'get_order_discount', '2.3' ); return apply_filters( 'woocommerce_order_amount_order_discount', (double) $this->order_discount, $this ); } /** * Gets cart tax amount. * * @return float */ public function get_cart_tax() { return apply_filters( 'woocommerce_order_amount_cart_tax', (double) $this->order_tax, $this ); } /** * Gets shipping tax amount. * * @return float */ public function get_shipping_tax() { return apply_filters( 'woocommerce_order_amount_shipping_tax', (double) $this->order_shipping_tax, $this ); } /** * Gets shipping and product tax. * * @return float */ public function get_total_tax() { return apply_filters( 'woocommerce_order_amount_total_tax', wc_round_tax_total( $this->get_cart_tax() + $this->get_shipping_tax() ), $this ); } /** * Gets shipping total. * * @return float */ public function get_total_shipping() { return apply_filters( 'woocommerce_order_amount_total_shipping', (double) $this->order_shipping, $this ); } /** * Gets order total. * * @return float */ public function get_total() { return apply_filters( 'woocommerce_order_amount_total', (double) $this->order_total, $this ); } /** * Gets order subtotal. * * @return mixed|void */ public function get_subtotal() { $subtotal = 0; foreach ( $this->get_items() as $item ) { $subtotal += ( isset( $item['line_subtotal'] ) ) ? $item['line_subtotal'] : 0; } return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this ); } /** * Get item subtotal - this is the cost before discount. * * @param mixed $item * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_item_subtotal( $item, $inc_tax = false, $round = true ) { if ( $inc_tax ) { $price = ( $item['line_subtotal'] + $item['line_subtotal_tax'] ) / max( 1, $item['qty'] ); } else { $price = ( $item['line_subtotal'] / max( 1, $item['qty'] ) ); } $price = $round ? number_format( (float) $price, wc_get_price_decimals(), '.', '' ) : $price; return apply_filters( 'woocommerce_order_amount_item_subtotal', $price, $this, $item, $inc_tax, $round ); } /** * Get line subtotal - this is the cost before discount. * * @param mixed $item * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_line_subtotal( $item, $inc_tax = false, $round = true ) { if ( $inc_tax ) { $price = $item['line_subtotal'] + $item['line_subtotal_tax']; } else { $price = $item['line_subtotal']; } $price = $round ? round( $price, wc_get_price_decimals() ) : $price; return apply_filters( 'woocommerce_order_amount_line_subtotal', $price, $this, $item, $inc_tax, $round ); } /** * Calculate item cost - useful for gateways. * * @param mixed $item * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_item_total( $item, $inc_tax = false, $round = true ) { $qty = ( ! empty( $item['qty'] ) ) ? $item['qty'] : 1; if ( $inc_tax ) { $price = ( $item['line_total'] + $item['line_tax'] ) / max( 1, $qty ); } else { $price = $item['line_total'] / max( 1, $qty ); } $price = $round ? round( $price, wc_get_price_decimals() ) : $price; return apply_filters( 'woocommerce_order_amount_item_total', $price, $this, $item, $inc_tax, $round ); } /** * Calculate line total - useful for gateways. * * @param mixed $item * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_line_total( $item, $inc_tax = false, $round = true ) { // Check if we need to add line tax to the line total. $line_total = $inc_tax ? $item['line_total'] + $item['line_tax'] : $item['line_total']; // Check if we need to round. $line_total = $round ? round( $line_total, wc_get_price_decimals() ) : $line_total; return apply_filters( 'woocommerce_order_amount_line_total', $line_total, $this, $item, $inc_tax, $round ); } /** * Calculate item tax - useful for gateways. * * @param mixed $item * @param bool $round (default: true). * @return float */ public function get_item_tax( $item, $round = true ) { $price = $item['line_tax'] / max( 1, $item['qty'] ); $price = $round ? wc_round_tax_total( $price ) : $price; return apply_filters( 'woocommerce_order_amount_item_tax', $price, $item, $round, $this ); } /** * Calculate line tax - useful for gateways. * * @param mixed $item * @return float */ public function get_line_tax( $item ) { return apply_filters( 'woocommerce_order_amount_line_tax', wc_round_tax_total( $item['line_tax'] ), $item, $this ); } /** End Total Getters *******************************************************/ /** * Gets formatted shipping method title. * * @return string */ public function get_shipping_method() { $labels = array(); // Backwards compat < 2.1 - get shipping title stored in meta. if ( $this->shipping_method_title ) { $labels[] = $this->shipping_method_title; } else { // 2.1+ get line items for shipping. $shipping_methods = $this->get_shipping_methods(); foreach ( $shipping_methods as $shipping ) { $labels[] = $shipping['name'] ? $shipping['name'] : __( 'Shipping', 'woocommerce' ); } } return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $labels ), $this ); } /** * Gets line subtotal - formatted for display. * * @param array $item * @param string $tax_display * @return string */ public function get_formatted_line_subtotal( $item, $tax_display = '' ) { if ( ! $tax_display ) { $tax_display = $this->tax_display_cart; } if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) { return ''; } if ( 'excl' == $tax_display ) { $ex_tax_label = $this->prices_include_tax ? 1 : 0; $subtotal = wc_price( $this->get_line_subtotal( $item ), array( 'ex_tax_label' => $ex_tax_label, 'currency' => $this->get_order_currency() ) ); } else { $subtotal = wc_price( $this->get_line_subtotal( $item, true ), array('currency' => $this->get_order_currency()) ); } return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this ); } /** * Gets order currency. * * @return string */ public function get_order_currency() { return apply_filters( 'woocommerce_get_order_currency', $this->order_currency, $this ); } /** * Gets order total - formatted for display. * * @return string */ public function get_formatted_order_total() { $formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_order_currency() ) ); return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this ); } /** * Gets subtotal - subtotal is shown before discounts, but with localised taxes. * * @param bool $compound (default: false). * @param string $tax_display (default: the tax_display_cart value). * @return string */ public function get_subtotal_to_display( $compound = false, $tax_display = '' ) { if ( ! $tax_display ) { $tax_display = $this->tax_display_cart; } $subtotal = 0; if ( ! $compound ) { foreach ( $this->get_items() as $item ) { if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) { return ''; } $subtotal += $item['line_subtotal']; if ( 'incl' == $tax_display ) { $subtotal += $item['line_subtotal_tax']; } } $subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) ); if ( $tax_display == 'excl' && $this->prices_include_tax ) { $subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; } } else { if ( 'incl' == $tax_display ) { return ''; } foreach ( $this->get_items() as $item ) { $subtotal += $item['line_subtotal']; } // Add Shipping Costs. $subtotal += $this->get_total_shipping(); // Remove non-compound taxes. foreach ( $this->get_taxes() as $tax ) { if ( ! empty( $tax['compound'] ) ) { continue; } $subtotal = $subtotal + $tax['tax_amount'] + $tax['shipping_tax_amount']; } // Remove discounts. $subtotal = $subtotal - $this->get_total_discount(); $subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) ); } return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this ); } /** * Gets shipping (formatted). * * @return string */ public function get_shipping_to_display( $tax_display = '' ) { if ( ! $tax_display ) { $tax_display = $this->tax_display_cart; } if ( $this->order_shipping != 0 ) { if ( $tax_display == 'excl' ) { // Show shipping excluding tax. $shipping = wc_price( $this->order_shipping, array('currency' => $this->get_order_currency()) ); if ( $this->order_shipping_tax != 0 && $this->prices_include_tax ) { $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>', $this, $tax_display ); } } else { // Show shipping including tax. $shipping = wc_price( $this->order_shipping + $this->order_shipping_tax, array('currency' => $this->get_order_currency()) ); if ( $this->order_shipping_tax != 0 && ! $this->prices_include_tax ) { $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display ); } } $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_shipped_via', ' <small class="shipped_via">' . sprintf( __( 'via %s', 'woocommerce' ), $this->get_shipping_method() ) . '</small>', $this ); } elseif ( $this->get_shipping_method() ) { $shipping = $this->get_shipping_method(); } else { $shipping = __( 'Free!', 'woocommerce' ); } return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this ); } /** * Get the discount amount (formatted). * @since 2.3.0 * @return string */ public function get_discount_to_display( $tax_display = '' ) { if ( ! $tax_display ) { $tax_display = $this->tax_display_cart; } return apply_filters( 'woocommerce_order_discount_to_display', wc_price( $this->get_total_discount( $tax_display === 'excl' && $this->display_totals_ex_tax ), array( 'currency' => $this->get_order_currency() ) ), $this ); } /** * Get cart discount (formatted). * @deprecated * @return string */ public function get_cart_discount_to_display( $tax_display = '' ) { _deprecated_function( 'get_cart_discount_to_display', '2.3', 'get_discount_to_display' ); return apply_filters( 'woocommerce_order_cart_discount_to_display', $this->get_discount_to_display( $tax_display ), $this ); } /** * Get a product (either product or variation). * * @param mixed $item * @return WC_Product */ public function get_product_from_item( $item ) { if ( ! empty( $item['variation_id'] ) && 'product_variation' === get_post_type( $item['variation_id'] ) ) { $_product = wc_get_product( $item['variation_id'] ); } elseif ( ! empty( $item['product_id'] ) ) { $_product = wc_get_product( $item['product_id'] ); } else { $_product = false; } return apply_filters( 'woocommerce_get_product_from_item', $_product, $item, $this ); } /** * Get totals for display on pages and in emails. * * @param mixed $tax_display * @return array */ public function get_order_item_totals( $tax_display = '' ) { if ( ! $tax_display ) { $tax_display = $this->tax_display_cart; } $total_rows = array(); if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) { $total_rows['cart_subtotal'] = array( 'label' => __( 'Subtotal:', 'woocommerce' ), 'value' => $subtotal ); } if ( $this->get_total_discount() > 0 ) { $total_rows['discount'] = array( 'label' => __( 'Discount:', 'woocommerce' ), 'value' => '-' . $this->get_discount_to_display( $tax_display ) ); } if ( $this->get_shipping_method() ) { $total_rows['shipping'] = array( 'label' => __( 'Shipping:', 'woocommerce' ), 'value' => $this->get_shipping_to_display( $tax_display ) ); } if ( $fees = $this->get_fees() ) { foreach ( $fees as $id => $fee ) { if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', $fee['line_total'] + $fee['line_tax'] == 0, $id ) ) { continue; } if ( 'excl' == $tax_display ) { $total_rows[ 'fee_' . $id ] = array( 'label' => ( $fee['name'] ? $fee['name'] : __( 'Fee', 'woocommerce' ) ) . ':', 'value' => wc_price( $fee['line_total'], array('currency' => $this->get_order_currency()) ) ); } else { $total_rows[ 'fee_' . $id ] = array( 'label' => $fee['name'] . ':', 'value' => wc_price( $fee['line_total'] + $fee['line_tax'], array('currency' => $this->get_order_currency()) ) ); } } } // Tax for tax exclusive prices. if ( 'excl' === $tax_display ) { if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) { foreach ( $this->get_tax_totals() as $code => $tax ) { $total_rows[ sanitize_title( $code ) ] = array( 'label' => $tax->label . ':', 'value' => $tax->formatted_amount ); } } else { $total_rows['tax'] = array( 'label' => WC()->countries->tax_or_vat() . ':', 'value' => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_order_currency() ) ) ); } } if ( $this->get_total() > 0 && $this->payment_method_title ) { $total_rows['payment_method'] = array( 'label' => __( 'Payment Method:', 'woocommerce' ), 'value' => $this->payment_method_title ); } if ( $refunds = $this->get_refunds() ) { foreach ( $refunds as $id => $refund ) { $total_rows[ 'refund_' . $id ] = array( 'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':', 'value' => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_order_currency() ) ) ); } } $total_rows['order_total'] = array( 'label' => __( 'Total:', 'woocommerce' ), 'value' => $this->get_formatted_order_total( $tax_display ) ); return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this ); } /** * Output items for display in html emails. * * @param array $args Items args. * @param null $deprecated1 Deprecated arg. * @param null $deprecated2 Deprecated arg. * @param null $deprecated3 Deprecated arg. * @param null $deprecated4 Deprecated arg. * @param null $deprecated5 Deprecated arg. * @return string */ public function email_order_items_table( $args = array(), $deprecated1 = null, $deprecated2 = null, $deprecated3 = null, $deprecated4 = null, $deprecated5 = null ) { ob_start(); if ( ! is_null( $deprecated1 ) || ! is_null( $deprecated2 ) || ! is_null( $deprecated3 ) || ! is_null( $deprecated4 ) || ! is_null( $deprecated5 ) ) { _deprecated_argument( __FUNCTION__, '2.5.0' ); } $defaults = array( 'show_sku' => false, 'show_image' => false, 'image_size' => array( 32, 32 ), 'plain_text' => false, 'sent_to_admin' => false ); $args = wp_parse_args( $args, $defaults ); $template = $args['plain_text'] ? 'emails/plain/email-order-items.php' : 'emails/email-order-items.php'; wc_get_template( $template, apply_filters( 'woocommerce_email_order_items_args', array( 'order' => $this, 'items' => $this->get_items(), 'show_download_links' => $this->is_download_permitted() && ! $args['sent_to_admin'], 'show_sku' => $args['show_sku'], 'show_purchase_note' => $this->is_paid() && ! $args['sent_to_admin'], 'show_image' => $args['show_image'], 'image_size' => $args['image_size'], 'plain_text' => $args['plain_text'], 'sent_to_admin' => $args['sent_to_admin'] ) ) ); return apply_filters( 'woocommerce_email_order_items_table', ob_get_clean(), $this ); } /** * Returns if an order has been paid for based on the order status. * @since 2.5.0 * @return bool */ public function is_paid() { return apply_filters( 'woocommerce_order_is_paid', $this->has_status( apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ) ), $this ); } /** * Checks if product download is permitted. * * @return bool */ public function is_download_permitted() { return apply_filters( 'woocommerce_order_is_download_permitted', $this->has_status( 'completed' ) || ( get_option( 'woocommerce_downloads_grant_access_after_payment' ) == 'yes' && $this->has_status( 'processing' ) ), $this ); } /** * Returns true if the order contains a downloadable product. * @return bool */ public function has_downloadable_item() { foreach ( $this->get_items() as $item ) { $_product = $this->get_product_from_item( $item ); if ( $_product && $_product->exists() && $_product->is_downloadable() && $_product->has_file() ) { return true; } } return false; } /** * Returns true if the order contains a free product. * @since 2.5.0 * @return bool */ public function has_free_item() { foreach ( $this->get_items() as $item ) { if ( ! $item['line_total'] ) { return true; } } return false; } /** * Generates a URL so that a customer can pay for their (unpaid - pending) order. Pass 'true' for the checkout version which doesn't offer gateway choices. * * @param bool $on_checkout * @return string */ public function get_checkout_payment_url( $on_checkout = false ) { $pay_url = wc_get_endpoint_url( 'order-pay', $this->id, wc_get_page_permalink( 'checkout' ) ); if ( 'yes' == get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) { $pay_url = str_replace( 'http:', 'https:', $pay_url ); } if ( $on_checkout ) { $pay_url = add_query_arg( 'key', $this->order_key, $pay_url ); } else { $pay_url = add_query_arg( array( 'pay_for_order' => 'true', 'key' => $this->order_key ), $pay_url ); } return apply_filters( 'woocommerce_get_checkout_payment_url', $pay_url, $this ); } /** * Generates a URL for the thanks page (order received). * * @return string */ public function get_checkout_order_received_url() { $order_received_url = wc_get_endpoint_url( 'order-received', $this->id, wc_get_page_permalink( 'checkout' ) ); if ( 'yes' == get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) { $order_received_url = str_replace( 'http:', 'https:', $order_received_url ); } $order_received_url = add_query_arg( 'key', $this->order_key, $order_received_url ); return apply_filters( 'woocommerce_get_checkout_order_received_url', $order_received_url, $this ); } /** * Generates a URL so that a customer can cancel their (unpaid - pending) order. * * @param string $redirect * * @return string */ public function get_cancel_order_url( $redirect = '' ) { // Get cancel endpoint $cancel_endpoint = $this->get_cancel_endpoint(); return apply_filters( 'woocommerce_get_cancel_order_url', esc_url( add_query_arg( array( 'cancel_order' => 'true', 'order' => $this->order_key, 'order_id' => $this->id, 'redirect' => $redirect, ), $cancel_endpoint ) ) ); } /** * Generates a raw (unescaped) cancel-order URL for use by payment gateways. * * @param string $redirect * @return string The unescaped cancel-order URL. */ public function get_cancel_order_url_raw( $redirect = '' ) { // Get cancel endpoint $cancel_endpoint = $this->get_cancel_endpoint(); return apply_filters( 'woocommerce_get_cancel_order_url_raw', add_query_arg( array( 'cancel_order' => 'true', 'order' => $this->order_key, 'order_id' => $this->id, 'redirect' => $redirect, ), $cancel_endpoint ) ); } /** * Helper method to return the cancel endpoint. * * @return string the cancel endpoint; either the cart page or the home page. */ public function get_cancel_endpoint() { $cancel_endpoint = wc_get_page_permalink( 'cart' ); if ( ! $cancel_endpoint ) { $cancel_endpoint = home_url(); } if ( false === strpos( $cancel_endpoint, '?' ) ) { $cancel_endpoint = trailingslashit( $cancel_endpoint ); } return $cancel_endpoint; } /** * Generates a URL to view an order from the my account page. * * @return string */ public function get_view_order_url() { $view_order_url = wc_get_endpoint_url( 'view-order', $this->id, wc_get_page_permalink( 'myaccount' ) ); return apply_filters( 'woocommerce_get_view_order_url', $view_order_url, $this ); } /** * Get the downloadable files for an item in this order. * * @param array $item * @return array */ public function get_item_downloads( $item ) { global $wpdb; $product_id = $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id']; $product = wc_get_product( $product_id ); if ( ! $product ) { /** * $product can be `false`. Example: checking an old order, when a product or variation has been deleted. * @see \WC_Product_Factory::get_product */ return array(); } $download_ids = $wpdb->get_col( $wpdb->prepare(" SELECT download_id FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE user_email = %s AND order_key = %s AND product_id = %s ORDER BY permission_id ", $this->billing_email, $this->order_key, $product_id ) ); $files = array(); foreach ( $download_ids as $download_id ) { if ( $product->has_file( $download_id ) ) { $files[ $download_id ] = $product->get_file( $download_id ); $files[ $download_id ]['download_url'] = $this->get_download_url( $product_id, $download_id ); } } return apply_filters( 'woocommerce_get_item_downloads', $files, $item, $this ); } /** * Display download links for an order item. * @param array $item */ public function display_item_downloads( $item ) { $product = $this->get_product_from_item( $item ); if ( $product && $product->exists() && $product->is_downloadable() && $this->is_download_permitted() ) { $download_files = $this->get_item_downloads( $item ); $i = 0; $links = array(); foreach ( $download_files as $download_id => $file ) { $i++; $prefix = count( $download_files ) > 1 ? sprintf( __( 'Download %d', 'woocommerce' ), $i ) : __( 'Download', 'woocommerce' ); $links[] = '<small class="download-url">' . $prefix . ': <a href="' . esc_url( $file['download_url'] ) . '" target="_blank">' . esc_html( $file['name'] ) . '</a></small>' . "\n"; } echo '<br/>' . implode( '<br/>', $links ); } } /** * Get the Download URL. * * @param int $product_id * @param int $download_id * @return string */ public function get_download_url( $product_id, $download_id ) { return add_query_arg( array( 'download_file' => $product_id, 'order' => $this->order_key, 'email' => urlencode( $this->billing_email ), 'key' => $download_id ), trailingslashit( home_url() ) ); } /** * Adds a note (comment) to the order. * * @param string $note Note to add. * @param int $is_customer_note (default: 0) Is this a note for the customer? * @param bool added_by_user Was the note added by a user? * @return int Comment ID. */ public function add_order_note( $note, $is_customer_note = 0, $added_by_user = false ) { if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->id ) && $added_by_user ) { $user = get_user_by( 'id', get_current_user_id() ); $comment_author = $user->display_name; $comment_author_email = $user->user_email; } else { $comment_author = __( 'WooCommerce', 'woocommerce' ); $comment_author_email = strtolower( __( 'WooCommerce', 'woocommerce' ) ) . '@'; $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com'; $comment_author_email = sanitize_email( $comment_author_email ); } $comment_post_ID = $this->id; $comment_author_url = ''; $comment_content = $note; $comment_agent = 'WooCommerce'; $comment_type = 'order_note'; $comment_parent = 0; $comment_approved = 1; $commentdata = apply_filters( 'woocommerce_new_order_note_data', compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ), array( 'order_id' => $this->id, 'is_customer_note' => $is_customer_note ) ); $comment_id = wp_insert_comment( $commentdata ); if ( $is_customer_note ) { add_comment_meta( $comment_id, 'is_customer_note', 1 ); do_action( 'woocommerce_new_customer_note', array( 'order_id' => $this->id, 'customer_note' => $commentdata['comment_content'] ) ); } return $comment_id; } /** * Updates status of order. * * @param string $new_status Status to change the order to. No internal wc- prefix is required. * @param string $note (default: '') Optional note to add. * @param bool $manual is this a manual order status change? * @return bool Successful change or not */ public function update_status( $new_status, $note = '', $manual = false ) { if ( ! $this->id ) { return false; } // Standardise status names. $new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status; $old_status = $this->get_status(); // If the old status is unknown (e.g. draft) assume its pending for action usage. if ( ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) { $old_status = 'pending'; } // If the statuses are the same there is no need to update, unless the post status is not a valid 'wc' status. if ( $new_status === $old_status && in_array( $this->post_status, array_keys( wc_get_order_statuses() ) ) ) { return false; } $this->post_status = 'wc-' . $new_status; $update_post_data = array( 'ID' => $this->id, 'post_status' => $this->post_status, ); if ( 'pending' === $old_status && ! $manual ) { $update_post_data[ 'post_date' ] = current_time( 'mysql', 0 ); $update_post_data[ 'post_date_gmt' ] = current_time( 'mysql', 1 ); } if ( ! wp_update_post( $update_post_data ) ) { $this->add_order_note( sprintf( __( 'Unable to update order from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $old_status ), wc_get_order_status_name( $new_status ) ), 0, $manual ); return false; } // Status was set. do_action( 'woocommerce_order_status_' . $new_status, $this->id ); // Status was changed. if ( $new_status !== $old_status ) { $this->add_order_note( trim( $note . ' ' . sprintf( __( 'Order status changed from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $old_status ), wc_get_order_status_name( $new_status ) ) ), 0, $manual ); do_action( 'woocommerce_order_status_' . $old_status . '_to_' . $new_status, $this->id ); do_action( 'woocommerce_order_status_changed', $this->id, $old_status, $new_status ); } else { $this->add_order_note( trim( $note . ' ' . sprintf( __( 'Order status changed to %s.', 'woocommerce' ), wc_get_order_status_name( $new_status ) ) ), 0, $manual ); } switch ( $new_status ) { case 'completed' : // Record the sales. $this->record_product_sales(); // Increase coupon usage counts. $this->increase_coupon_usage_counts(); // Record the completed date of the order. update_post_meta( $this->id, '_completed_date', current_time('mysql') ); // Update reports. wc_delete_shop_order_transients( $this->id ); break; case 'processing' : case 'on-hold' : // Record the sales. $this->record_product_sales(); // Increase coupon usage counts. $this->increase_coupon_usage_counts(); // Update reports. wc_delete_shop_order_transients( $this->id ); break; case 'cancelled' : // If the order is cancelled, restore used coupons. $this->decrease_coupon_usage_counts(); // Update reports. wc_delete_shop_order_transients( $this->id ); break; } return true; } /** * Cancel the order and restore the cart (before payment). * * @param string $note (default: '') Optional note to add. */ public function cancel_order( $note = '' ) { WC()->session->set( 'order_awaiting_payment', false ); $this->update_status( 'cancelled', $note ); } /** * When a payment is complete this function is called. * * Most of the time this should mark an order as 'processing' so that admin can process/post the items. * If the cart contains only downloadable items then the order is 'completed' since the admin needs to take no action. * Stock levels are reduced at this point. * Sales are also recorded for products. * Finally, record the date of payment. * * @param string $transaction_id Optional transaction id to store in post meta. */ public function payment_complete( $transaction_id = '' ) { do_action( 'woocommerce_pre_payment_complete', $this->id ); if ( null !== WC()->session ) { WC()->session->set( 'order_awaiting_payment', false ); } $valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment_complete', array( 'on-hold', 'pending', 'failed', 'cancelled' ), $this ); if ( $this->id && $this->has_status( $valid_order_statuses ) ) { $order_needs_processing = false; if ( sizeof( $this->get_items() ) > 0 ) { foreach ( $this->get_items() as $item ) { if ( $_product = $this->get_product_from_item( $item ) ) { $virtual_downloadable_item = $_product->is_downloadable() && $_product->is_virtual(); if ( apply_filters( 'woocommerce_order_item_needs_processing', ! $virtual_downloadable_item, $_product, $this->id ) ) { $order_needs_processing = true; break; } } else { $order_needs_processing = true; break; } } } $this->update_status( apply_filters( 'woocommerce_payment_complete_order_status', $order_needs_processing ? 'processing' : 'completed', $this->id ) ); add_post_meta( $this->id, '_paid_date', current_time( 'mysql' ), true ); if ( ! empty( $transaction_id ) ) { update_post_meta( $this->id, '_transaction_id', $transaction_id ); } // Payment is complete so reduce stock levels if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $this->id, '_order_stock_reduced', true ), $this->id ) ) { $this->reduce_order_stock(); } do_action( 'woocommerce_payment_complete', $this->id ); } else { do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->id ); } } /** * Record sales. */ public function record_product_sales() { if ( 'yes' === get_post_meta( $this->id, '_recorded_sales', true ) ) { return; } if ( sizeof( $this->get_items() ) > 0 ) { foreach ( $this->get_items() as $item ) { if ( $item['product_id'] > 0 ) { $sales = (int) get_post_meta( $item['product_id'], 'total_sales', true ); $sales += (int) $item['qty']; if ( $sales ) { update_post_meta( $item['product_id'], 'total_sales', $sales ); } } } } update_post_meta( $this->id, '_recorded_sales', 'yes' ); /** * Called when sales for an order are recorded * * @param int $order_id order id */ do_action( 'woocommerce_recorded_sales', $this->id ); } /** * Get coupon codes only. * * @return array */ public function get_used_coupons() { $codes = array(); $coupons = $this->get_items( 'coupon' ); foreach ( $coupons as $item_id => $item ) { $codes[] = trim( $item['name'] ); } return $codes; } /** * Increase applied coupon counts. */ public function increase_coupon_usage_counts() { if ( 'yes' == get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) { return; } if ( sizeof( $this->get_used_coupons() ) > 0 ) { foreach ( $this->get_used_coupons() as $code ) { if ( ! $code ) { continue; } $coupon = new WC_Coupon( $code ); $used_by = $this->get_user_id(); if ( ! $used_by ) { $used_by = $this->billing_email; } $coupon->inc_usage_count( $used_by ); } update_post_meta( $this->id, '_recorded_coupon_usage_counts', 'yes' ); } } /** * Decrease applied coupon counts. */ public function decrease_coupon_usage_counts() { if ( 'yes' != get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) { return; } if ( sizeof( $this->get_used_coupons() ) > 0 ) { foreach ( $this->get_used_coupons() as $code ) { if ( ! $code ) { continue; } $coupon = new WC_Coupon( $code ); $used_by = $this->get_user_id(); if ( ! $used_by ) { $used_by = $this->billing_email; } $coupon->dcr_usage_count( $used_by ); } delete_post_meta( $this->id, '_recorded_coupon_usage_counts' ); } } /** * Reduce stock levels for all line items in the order. * Runs if stock management is enabled, but can be disabled on per-order basis by extensions @since 2.4.0 via woocommerce_can_reduce_order_stock hook. */ public function reduce_order_stock() { if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && apply_filters( 'woocommerce_can_reduce_order_stock', true, $this ) && sizeof( $this->get_items() ) > 0 ) { foreach ( $this->get_items() as $item ) { if ( $item['product_id'] > 0 ) { $_product = $this->get_product_from_item( $item ); if ( $_product && $_product->exists() && $_product->managing_stock() ) { $qty = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $this, $item ); $new_stock = $_product->reduce_stock( $qty ); $item_name = $_product->get_sku() ? $_product->get_sku(): $item['product_id']; if ( isset( $item['variation_id'] ) && $item['variation_id'] ) { $this->add_order_note( sprintf( __( 'Item %1$s variation #%2$s stock reduced from %3$s to %4$s.', 'woocommerce' ), $item_name, $item['variation_id'], $new_stock + $qty, $new_stock) ); } else { $this->add_order_note( sprintf( __( 'Item %1$s stock reduced from %2$s to %3$s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock) ); } $this->send_stock_notifications( $_product, $new_stock, $item['qty'] ); } } } add_post_meta( $this->id, '_order_stock_reduced', '1', true ); do_action( 'woocommerce_reduce_order_stock', $this ); } } /** * Send the stock notifications. * * @param WC_Product $product * @param int $new_stock * @param int $qty_ordered */ public function send_stock_notifications( $product, $new_stock, $qty_ordered ) { // Backorders if ( $new_stock < 0 ) { do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $this->id, 'quantity' => $qty_ordered ) ); } // stock status notifications $notification_sent = false; if ( 'yes' == get_option( 'woocommerce_notify_no_stock' ) && get_option( 'woocommerce_notify_no_stock_amount' ) >= $new_stock ) { do_action( 'woocommerce_no_stock', $product ); $notification_sent = true; } if ( ! $notification_sent && 'yes' == get_option( 'woocommerce_notify_low_stock' ) && get_option( 'woocommerce_notify_low_stock_amount' ) >= $new_stock ) { do_action( 'woocommerce_low_stock', $product ); } do_action( 'woocommerce_after_send_stock_notifications', $product, $new_stock, $qty_ordered ); } /** * List order notes (public) for the customer. * * @return array */ public function get_customer_order_notes() { $notes = array(); $args = array( 'post_id' => $this->id, 'approve' => 'approve', 'type' => '' ); remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) ); $comments = get_comments( $args ); foreach ( $comments as $comment ) { if ( ! get_comment_meta( $comment->comment_ID, 'is_customer_note', true ) ) { continue; } $comment->comment_content = make_clickable( $comment->comment_content ); $notes[] = $comment; } add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) ); return $notes; } /** * Checks if an order needs payment, based on status and order total. * * @return bool */ public function needs_payment() { $valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $this ); if ( $this->has_status( $valid_order_statuses ) && $this->get_total() > 0 ) { $needs_payment = true; } else { $needs_payment = false; } return apply_filters( 'woocommerce_order_needs_payment', $needs_payment, $this, $valid_order_statuses ); } /** * Checks if an order needs display the shipping address, based on shipping method. * * @return boolean */ public function needs_shipping_address() { if ( ! wc_shipping_enabled() ) { return false; } $hide = apply_filters( 'woocommerce_order_hide_shipping_address', array( 'local_pickup' ), $this ); $needs_address = false; foreach ( $this->get_shipping_methods() as $shipping_method ) { // Remove any instance IDs after : $shipping_method_id = current( explode( ':', $shipping_method['method_id'] ) ); if ( ! in_array( $shipping_method_id, $hide ) ) { $needs_address = true; break; } } return apply_filters( 'woocommerce_order_needs_shipping_address', $needs_address, $hide, $this ); } /** * Checks if an order can be edited, specifically for use on the Edit Order screen. * * @return bool */ public function is_editable() { return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft', 'failed' ) ), $this ); } } abstract-wc-rest-posts-controller.php 0000666 00000054335 15214130426 0014005 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Abstract Rest Posts Controler Class * * @author WooThemes * @category API * @package WooCommerce/Abstracts * @version 2.6.0 */ abstract class WC_REST_Posts_Controller extends WC_REST_Controller { /** * Endpoint namespace. * * @var string */ protected $namespace = 'wc/v1'; /** * Route base. * * @var string */ protected $rest_base = ''; /** * Post type. * * @var string */ protected $post_type = ''; /** * Controls visibility on frontend. * * @var string */ protected $public = false; /** * Check if a given request has access to read items. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function get_items_permissions_check( $request ) { if ( ! wc_rest_check_post_permissions( $this->post_type, 'read' ) ) { return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to create an item. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function create_item_permissions_check( $request ) { if ( ! wc_rest_check_post_permissions( $this->post_type, 'create' ) ) { return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to read an item. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function get_item_permissions_check( $request ) { $post = get_post( (int) $request['id'] ); if ( $post && ! wc_rest_check_post_permissions( $this->post_type, 'read', $post->ID ) ) { return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to update an item. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function update_item_permissions_check( $request ) { $post = get_post( (int) $request['id'] ); if ( $post && ! wc_rest_check_post_permissions( $this->post_type, 'edit', $post->ID ) ) { return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to delete an item. * * @param WP_REST_Request $request Full details about the request. * @return bool|WP_Error */ public function delete_item_permissions_check( $request ) { $post = get_post( (int) $request['id'] ); if ( $post && ! wc_rest_check_post_permissions( $this->post_type, 'delete', $post->ID ) ) { return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access batch create, update and delete items. * * @param WP_REST_Request $request Full details about the request. * @return boolean */ public function batch_items_permissions_check( $request ) { if ( ! wc_rest_check_post_permissions( $this->post_type, 'batch' ) ) { return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to manipule this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Get post types. * * @return array */ protected function get_post_types() { return array( $this->post_type ); } /** * Get a single item. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|WP_REST_Response */ public function get_item( $request ) { $id = (int) $request['id']; $post = get_post( $id ); if ( empty( $id ) || empty( $post->ID ) || ! in_array( $post->post_type, $this->get_post_types() ) ) { return new WP_Error( "woocommerce_rest_invalid_{$this->post_type}_id", __( 'Invalid id.', 'woocommerce' ), array( 'status' => 404 ) ); } $data = $this->prepare_item_for_response( $post, $request ); $response = rest_ensure_response( $data ); if ( $this->public ) { $response->link_header( 'alternate', get_permalink( $id ), array( 'type' => 'text/html' ) ); } return $response; } /** * Create a single item. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|WP_REST_Response */ public function create_item( $request ) { if ( ! empty( $request['id'] ) ) { return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) ); } $post = $this->prepare_item_for_database( $request ); if ( is_wp_error( $post ) ) { return $post; } $post->post_type = $this->post_type; $post_id = wp_insert_post( $post, true ); if ( is_wp_error( $post_id ) ) { if ( in_array( $post_id->get_error_code(), array( 'db_insert_error' ) ) ) { $post_id->add_data( array( 'status' => 500 ) ); } else { $post_id->add_data( array( 'status' => 400 ) ); } return $post_id; } $post->ID = $post_id; $post = get_post( $post_id ); $this->update_additional_fields_for_object( $post, $request ); // Add meta fields. $meta_fields = $this->add_post_meta_fields( $post, $request ); if ( is_wp_error( $meta_fields ) ) { // Remove post. $this->delete_post( $post ); return $meta_fields; } /** * Fires after a single item is created or updated via the REST API. * * @param object $post Inserted object (not a WP_Post object). * @param WP_REST_Request $request Request object. * @param boolean $creating True when creating item, false when updating. */ do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $post, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) ); return $response; } /** * Add post meta fields. * * @param WP_Post $post * @param WP_REST_Request $request * @return bool|WP_Error */ protected function add_post_meta_fields( $post, $request ) { return true; } /** * Delete post. * * @param WP_Post $post */ protected function delete_post( $post ) { wp_delete_post( $post->ID, true ); } /** * Update a single post. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|WP_REST_Response */ public function update_item( $request ) { $id = (int) $request['id']; $post = get_post( $id ); if ( empty( $id ) || empty( $post->ID ) || ! in_array( $post->post_type, $this->get_post_types() ) ) { return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) ); } $post = $this->prepare_item_for_database( $request ); if ( is_wp_error( $post ) ) { return $post; } // Convert the post object to an array, otherwise wp_update_post will expect non-escaped input. $post_id = wp_update_post( (array) $post, true ); if ( is_wp_error( $post_id ) ) { if ( in_array( $post_id->get_error_code(), array( 'db_update_error' ) ) ) { $post_id->add_data( array( 'status' => 500 ) ); } else { $post_id->add_data( array( 'status' => 400 ) ); } return $post_id; } $post = get_post( $post_id ); $this->update_additional_fields_for_object( $post, $request ); // Update meta fields. $meta_fields = $this->update_post_meta_fields( $post, $request ); if ( is_wp_error( $meta_fields ) ) { return $meta_fields; } /** * Fires after a single item is created or updated via the REST API. * * @param object $post Inserted object (not a WP_Post object). * @param WP_REST_Request $request Request object. * @param boolean $creating True when creating item, false when updating. */ do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, false ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $post, $request ); return rest_ensure_response( $response ); } /** * Get a collection of posts. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|WP_REST_Response */ public function get_items( $request ) { $args = array(); $args['offset'] = $request['offset']; $args['order'] = $request['order']; $args['orderby'] = $request['orderby']; $args['paged'] = $request['page']; $args['post__in'] = $request['include']; $args['post__not_in'] = $request['exclude']; $args['posts_per_page'] = $request['per_page']; $args['name'] = $request['slug']; $args['post_parent__in'] = $request['parent']; $args['post_parent__not_in'] = $request['parent_exclude']; $args['s'] = $request['search']; $args['date_query'] = array(); // Set before into date query. Date query must be specified as an array of an array. if ( isset( $request['before'] ) ) { $args['date_query'][0]['before'] = $request['before']; } // Set after into date query. Date query must be specified as an array of an array. if ( isset( $request['after'] ) ) { $args['date_query'][0]['after'] = $request['after']; } if ( is_array( $request['filter'] ) ) { $args = array_merge( $args, $request['filter'] ); unset( $args['filter'] ); } // Force the post_type argument, since it's not a user input variable. $args['post_type'] = $this->post_type; /** * Filter the query arguments for a request. * * Enables adding extra arguments or setting defaults for a post * collection request. * * @param array $args Key value array of query var to query value. * @param WP_REST_Request $request The request used. */ $args = apply_filters( "woocommerce_rest_{$this->post_type}_query", $args, $request ); $query_args = $this->prepare_items_query( $args, $request ); $posts_query = new WP_Query(); $query_result = $posts_query->query( $query_args ); $posts = array(); foreach ( $query_result as $post ) { if ( ! wc_rest_check_post_permissions( $this->post_type, 'read', $post->ID ) ) { continue; } $data = $this->prepare_item_for_response( $post, $request ); $posts[] = $this->prepare_response_for_collection( $data ); } $page = (int) $query_args['paged']; $total_posts = $posts_query->found_posts; if ( $total_posts < 1 ) { // Out-of-bounds, run the query again without LIMIT for total count unset( $query_args['paged'] ); $count_query = new WP_Query(); $count_query->query( $query_args ); $total_posts = $count_query->found_posts; } $max_pages = ceil( $total_posts / (int) $query_args['posts_per_page'] ); $response = rest_ensure_response( $posts ); $response->header( 'X-WP-Total', (int) $total_posts ); $response->header( 'X-WP-TotalPages', (int) $max_pages ); $request_params = $request->get_query_params(); if ( ! empty( $request_params['filter'] ) ) { // Normalize the pagination params. unset( $request_params['filter']['posts_per_page'] ); unset( $request_params['filter']['paged'] ); } $base = add_query_arg( $request_params, rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Delete a single item. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error */ public function delete_item( $request ) { $id = (int) $request['id']; $force = (bool) $request['force']; $post = get_post( $id ); if ( empty( $id ) || empty( $post->ID ) || ! in_array( $post->post_type, $this->get_post_types() ) ) { return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid post id.', 'woocommerce' ), array( 'status' => 404 ) ); } $supports_trash = EMPTY_TRASH_DAYS > 0; /** * Filter whether an item is trashable. * * Return false to disable trash support for the item. * * @param boolean $supports_trash Whether the item type support trashing. * @param WP_Post $post The Post object being considered for trashing support. */ $supports_trash = apply_filters( "woocommerce_rest_{$this->post_type}_trashable", $supports_trash, $post ); if ( ! wc_rest_check_post_permissions( $this->post_type, 'delete', $post->ID ) ) { return new WP_Error( "woocommerce_rest_user_cannot_delete_{$this->post_type}", sprintf( __( 'Sorry, you are not allowed to delete %s.', 'woocommerce' ), $this->post_type ), array( 'status' => rest_authorization_required_code() ) ); } $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $post, $request ); // If we're forcing, then delete permanently. if ( $force ) { $result = wp_delete_post( $id, true ); } else { // If we don't support trashing for this type, error out. if ( ! $supports_trash ) { return new WP_Error( 'woocommerce_rest_trash_not_supported', sprintf( __( 'The %s does not support trashing.', 'woocommerce' ), $this->post_type ), array( 'status' => 501 ) ); } // Otherwise, only trash if we haven't already. if ( 'trash' === $post->post_status ) { return new WP_Error( 'woocommerce_rest_already_trashed', sprintf( __( 'The %s has already been deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 410 ) ); } // (Note that internally this falls through to `wp_delete_post` if // the trash is disabled.) $result = wp_trash_post( $id ); } if ( ! $result ) { return new WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 500 ) ); } /** * Fires after a single item is deleted or trashed via the REST API. * * @param object $post The deleted or trashed item. * @param WP_REST_Response $response The response data. * @param WP_REST_Request $request The request sent to the API. */ do_action( "woocommerce_rest_delete_{$this->post_type}", $post, $response, $request ); return $response; } /** * Prepare links for the request. * * @param WP_Post $post Post object. * @return array Links for the given post. */ protected function prepare_links( $post ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ), ), 'collection' => array( 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ), ), ); return $links; } /** * Determine the allowed query_vars for a get_items() response and * prepare for WP_Query. * * @param array $prepared_args * @param WP_REST_Request $request * @return array $query_args */ protected function prepare_items_query( $prepared_args = array(), $request = null ) { $valid_vars = array_flip( $this->get_allowed_query_vars() ); $query_args = array(); foreach ( $valid_vars as $var => $index ) { if ( isset( $prepared_args[ $var ] ) ) { /** * Filter the query_vars used in `get_items` for the constructed query. * * The dynamic portion of the hook name, $var, refers to the query_var key. * * @param mixed $prepared_args[ $var ] The query_var value. * */ $query_args[ $var ] = apply_filters( "woocommerce_rest_query_var-{$var}", $prepared_args[ $var ] ); } } $query_args['ignore_sticky_posts'] = true; if ( 'include' === $query_args['orderby'] ) { $query_args['orderby'] = 'post__in'; } elseif ( 'id' === $query_args['orderby'] ) { $query_args['orderby'] = 'ID'; // ID must be capitalized } return $query_args; } /** * Get all the WP Query vars that are allowed for the API request. * * @return array */ protected function get_allowed_query_vars() { global $wp; /** * Filter the publicly allowed query vars. * * Allows adjusting of the default query vars that are made public. * * @param array Array of allowed WP_Query query vars. */ $valid_vars = apply_filters( 'query_vars', $wp->public_query_vars ); $post_type_obj = get_post_type_object( $this->post_type ); if ( current_user_can( $post_type_obj->cap->edit_posts ) ) { /** * Filter the allowed 'private' query vars for authorized users. * * If the user has the `edit_posts` capability, we also allow use of * private query parameters, which are only undesirable on the * frontend, but are safe for use in query strings. * * To disable anyway, use * `add_filter( 'woocommerce_rest_private_query_vars', '__return_empty_array' );` * * @param array $private_query_vars Array of allowed query vars for authorized users. * } */ $private = apply_filters( 'woocommerce_rest_private_query_vars', $wp->private_query_vars ); $valid_vars = array_merge( $valid_vars, $private ); } // Define our own in addition to WP's normal vars. $rest_valid = array( 'date_query', 'ignore_sticky_posts', 'offset', 'post__in', 'post__not_in', 'post_parent', 'post_parent__in', 'post_parent__not_in', 'posts_per_page', 'meta_query', 'tax_query', 'meta_key', 'meta_value', 'meta_compare', 'meta_value_num', ); $valid_vars = array_merge( $valid_vars, $rest_valid ); /** * Filter allowed query vars for the REST API. * * This filter allows you to add or remove query vars from the final allowed * list for all requests, including unauthenticated ones. To alter the * vars for editors only. * * @param array { * Array of allowed WP_Query query vars. * * @param string $allowed_query_var The query var to allow. * } */ $valid_vars = apply_filters( 'woocommerce_rest_query_vars', $valid_vars ); return $valid_vars; } /** * Get the query params for collections of attachments. * * @return array */ public function get_collection_params() { $params = parent::get_collection_params(); $params['context']['default'] = 'view'; $params['after'] = array( 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); $params['before'] = array( 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); $params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); $params['include'] = array( 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); $params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'id', 'include', 'title', 'slug', ), 'validate_callback' => 'rest_validate_request_arg', ); $post_type_obj = get_post_type_object( $this->post_type ); if ( isset( $post_type_obj->hierarchical ) && $post_type_obj->hierarchical ) { $params['parent'] = array( 'description' => __( 'Limit result set to those of particular parent ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'sanitize_callback' => 'wp_parse_id_list', 'default' => array(), ); $params['parent_exclude'] = array( 'description' => __( 'Limit result set to all items except those of a particular parent id.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'sanitize_callback' => 'wp_parse_id_list', 'default' => array(), ); } $params['filter'] = array( 'type' => 'object', 'description' => __( 'Use WP Query arguments to modify the response; private query vars require appropriate authorization.', 'woocommerce' ), ); return $params; } /** * Update post meta fields. * * @param WP_Post $post * @param WP_REST_Request $request * @return bool|WP_Error */ protected function update_post_meta_fields( $post, $request ) { return true; } }
dvadf
dvadf
| ver. 1.4 |
Github
|
.
| PHP 7.0.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings