File manager - Edit - /home/theblueo/tv/fb4e3b/abstracts.tar
Back
abstract-wc-product.php 0000666 00000155645 15214036663 0011177 0 ustar 00 <?php /** * WooCommerce product base class. * * @package WooCommerce/Abstracts */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Legacy product contains all deprecated methods for this class and can be * removed in the future. */ require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-product.php'; /** * Abstract Product Class * * The WooCommerce product class handles individual product data. * * @version 3.0.0 * @package WooCommerce/Abstracts */ class WC_Product extends WC_Abstract_Legacy_Product { /** * This is the name of this object type. * * @var string */ protected $object_type = 'product'; /** * Post type. * * @var string */ protected $post_type = 'product'; /** * Cache group. * * @var string */ protected $cache_group = 'products'; /** * Stores product data. * * @var array */ protected $data = array( 'name' => '', 'slug' => '', 'date_created' => null, 'date_modified' => null, 'status' => false, 'featured' => false, 'catalog_visibility' => 'visible', 'description' => '', 'short_description' => '', 'sku' => '', 'price' => '', 'regular_price' => '', 'sale_price' => '', 'date_on_sale_from' => null, 'date_on_sale_to' => null, 'total_sales' => '0', 'tax_status' => 'taxable', 'tax_class' => '', 'manage_stock' => false, 'stock_quantity' => null, 'stock_status' => 'instock', 'backorders' => 'no', 'low_stock_amount' => '', 'sold_individually' => false, 'weight' => '', 'length' => '', 'width' => '', 'height' => '', 'upsell_ids' => array(), 'cross_sell_ids' => array(), 'parent_id' => 0, 'reviews_allowed' => true, 'purchase_note' => '', 'attributes' => array(), 'default_attributes' => array(), 'menu_order' => 0, 'post_password' => '', 'virtual' => false, 'downloadable' => false, 'category_ids' => array(), 'tag_ids' => array(), 'shipping_class_id' => 0, 'downloads' => array(), 'image_id' => '', 'gallery_image_ids' => array(), 'download_limit' => -1, 'download_expiry' => -1, 'rating_counts' => array(), 'average_rating' => 0, 'review_count' => 0, ); /** * Supported features such as 'ajax_add_to_cart'. * * @var array */ protected $supports = array(); /** * Get the product if ID is passed, otherwise the product is new and empty. * This class should NOT be instantiated, but the wc_get_product() function * should be used. It is possible, but the wc_get_product() is preferred. * * @param int|WC_Product|object $product Product to init. */ public function __construct( $product = 0 ) { parent::__construct( $product ); if ( is_numeric( $product ) && $product > 0 ) { $this->set_id( $product ); } elseif ( $product instanceof self ) { $this->set_id( absint( $product->get_id() ) ); } elseif ( ! empty( $product->ID ) ) { $this->set_id( absint( $product->ID ) ); } else { $this->set_object_read( true ); } $this->data_store = WC_Data_Store::load( 'product-' . $this->get_type() ); if ( $this->get_id() > 0 ) { $this->data_store->read( $this ); } } /** * Get internal type. Should return string and *should be overridden* by child classes. * * The product_type property is deprecated but is used here for BW compatibility with child classes which may be defining product_type and not have a get_type method. * * @since 3.0.0 * @return string */ public function get_type() { return isset( $this->product_type ) ? $this->product_type : 'simple'; } /** * Get product name. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_name( $context = 'view' ) { return $this->get_prop( 'name', $context ); } /** * Get product slug. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_slug( $context = 'view' ) { return $this->get_prop( 'slug', $context ); } /** * Get product created date. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return WC_DateTime|NULL object if the date is set or null if there is no date. */ public function get_date_created( $context = 'view' ) { return $this->get_prop( 'date_created', $context ); } /** * Get product modified date. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return WC_DateTime|NULL object if the date is set or null if there is no date. */ public function get_date_modified( $context = 'view' ) { return $this->get_prop( 'date_modified', $context ); } /** * Get product status. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_status( $context = 'view' ) { return $this->get_prop( 'status', $context ); } /** * If the product is featured. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return boolean */ public function get_featured( $context = 'view' ) { return $this->get_prop( 'featured', $context ); } /** * Get catalog visibility. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_catalog_visibility( $context = 'view' ) { return $this->get_prop( 'catalog_visibility', $context ); } /** * Get product description. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_description( $context = 'view' ) { return $this->get_prop( 'description', $context ); } /** * Get product short description. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_short_description( $context = 'view' ) { return $this->get_prop( 'short_description', $context ); } /** * Get SKU (Stock-keeping unit) - product unique ID. * * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_sku( $context = 'view' ) { return $this->get_prop( 'sku', $context ); } /** * Returns the product's active price. * * @param string $context What the value is for. Valid values are view and edit. * @return string price */ public function get_price( $context = 'view' ) { return $this->get_prop( 'price', $context ); } /** * Returns the product's regular price. * * @param string $context What the value is for. Valid values are view and edit. * @return string price */ public function get_regular_price( $context = 'view' ) { return $this->get_prop( 'regular_price', $context ); } /** * Returns the product's sale price. * * @param string $context What the value is for. Valid values are view and edit. * @return string price */ public function get_sale_price( $context = 'view' ) { return $this->get_prop( 'sale_price', $context ); } /** * Get date on sale from. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return WC_DateTime|NULL object if the date is set or null if there is no date. */ public function get_date_on_sale_from( $context = 'view' ) { return $this->get_prop( 'date_on_sale_from', $context ); } /** * Get date on sale to. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return WC_DateTime|NULL object if the date is set or null if there is no date. */ public function get_date_on_sale_to( $context = 'view' ) { return $this->get_prop( 'date_on_sale_to', $context ); } /** * Get number total of sales. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return int */ public function get_total_sales( $context = 'view' ) { return $this->get_prop( 'total_sales', $context ); } /** * Returns the tax status. * * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_tax_status( $context = 'view' ) { return $this->get_prop( 'tax_status', $context ); } /** * Returns the tax class. * * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_tax_class( $context = 'view' ) { return $this->get_prop( 'tax_class', $context ); } /** * Return if product manage stock. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return boolean */ public function get_manage_stock( $context = 'view' ) { return $this->get_prop( 'manage_stock', $context ); } /** * Returns number of items available for sale. * * @param string $context What the value is for. Valid values are view and edit. * @return int|null */ public function get_stock_quantity( $context = 'view' ) { return $this->get_prop( 'stock_quantity', $context ); } /** * Return the stock status. * * @param string $context What the value is for. Valid values are view and edit. * @since 3.0.0 * @return string */ public function get_stock_status( $context = 'view' ) { return $this->get_prop( 'stock_status', $context ); } /** * Get backorders. * * @param string $context What the value is for. Valid values are view and edit. * @since 3.0.0 * @return string yes no or notify */ public function get_backorders( $context = 'view' ) { return $this->get_prop( 'backorders', $context ); } /** * Get low stock amount. * * @param string $context What the value is for. Valid values are view and edit. * @since 3.5.0 * @return int|string Returns empty string if value not set */ public function get_low_stock_amount( $context = 'view' ) { return $this->get_prop( 'low_stock_amount', $context ); } /** * Return if should be sold individually. * * @param string $context What the value is for. Valid values are view and edit. * @since 3.0.0 * @return boolean */ public function get_sold_individually( $context = 'view' ) { return $this->get_prop( 'sold_individually', $context ); } /** * Returns the product's weight. * * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_weight( $context = 'view' ) { return $this->get_prop( 'weight', $context ); } /** * Returns the product length. * * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_length( $context = 'view' ) { return $this->get_prop( 'length', $context ); } /** * Returns the product width. * * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_width( $context = 'view' ) { return $this->get_prop( 'width', $context ); } /** * Returns the product height. * * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_height( $context = 'view' ) { return $this->get_prop( 'height', $context ); } /** * Returns formatted dimensions. * * @param bool $formatted True by default for legacy support - will be false/not set in future versions to return the array only. Use wc_format_dimensions for formatted versions instead. * @return string|array */ public function get_dimensions( $formatted = true ) { if ( $formatted ) { wc_deprecated_argument( 'WC_Product::get_dimensions', '3.0', 'By default, get_dimensions has an argument set to true so that HTML is returned. This is to support the legacy version of the method. To get HTML dimensions, instead use wc_format_dimensions() function. Pass false to this method to return an array of dimensions. This will be the new default behavior in future versions.' ); return apply_filters( 'woocommerce_product_dimensions', wc_format_dimensions( $this->get_dimensions( false ) ), $this ); } return array( 'length' => $this->get_length(), 'width' => $this->get_width(), 'height' => $this->get_height(), ); } /** * Get upsell IDs. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return array */ public function get_upsell_ids( $context = 'view' ) { return $this->get_prop( 'upsell_ids', $context ); } /** * Get cross sell IDs. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return array */ public function get_cross_sell_ids( $context = 'view' ) { return $this->get_prop( 'cross_sell_ids', $context ); } /** * Get parent ID. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return int */ public function get_parent_id( $context = 'view' ) { return $this->get_prop( 'parent_id', $context ); } /** * Return if reviews is allowed. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return bool */ public function get_reviews_allowed( $context = 'view' ) { return $this->get_prop( 'reviews_allowed', $context ); } /** * Get purchase note. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_purchase_note( $context = 'view' ) { return $this->get_prop( 'purchase_note', $context ); } /** * Returns product attributes. * * @param string $context What the value is for. Valid values are view and edit. * @return array */ public function get_attributes( $context = 'view' ) { return $this->get_prop( 'attributes', $context ); } /** * Get default attributes. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return array */ public function get_default_attributes( $context = 'view' ) { return $this->get_prop( 'default_attributes', $context ); } /** * Get menu order. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return int */ public function get_menu_order( $context = 'view' ) { return $this->get_prop( 'menu_order', $context ); } /** * Get post password. * * @since 3.6.0 * @param string $context What the value is for. Valid values are view and edit. * @return int */ public function get_post_password( $context = 'view' ) { return $this->get_prop( 'post_password', $context ); } /** * Get category ids. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return array */ public function get_category_ids( $context = 'view' ) { return $this->get_prop( 'category_ids', $context ); } /** * Get tag ids. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return array */ public function get_tag_ids( $context = 'view' ) { return $this->get_prop( 'tag_ids', $context ); } /** * Get virtual. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return bool */ public function get_virtual( $context = 'view' ) { return $this->get_prop( 'virtual', $context ); } /** * Returns the gallery attachment ids. * * @param string $context What the value is for. Valid values are view and edit. * @return array */ public function get_gallery_image_ids( $context = 'view' ) { return $this->get_prop( 'gallery_image_ids', $context ); } /** * Get shipping class ID. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return int */ public function get_shipping_class_id( $context = 'view' ) { return $this->get_prop( 'shipping_class_id', $context ); } /** * Get downloads. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return array */ public function get_downloads( $context = 'view' ) { return $this->get_prop( 'downloads', $context ); } /** * Get download expiry. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return int */ public function get_download_expiry( $context = 'view' ) { return $this->get_prop( 'download_expiry', $context ); } /** * Get downloadable. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return bool */ public function get_downloadable( $context = 'view' ) { return $this->get_prop( 'downloadable', $context ); } /** * Get download limit. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return int */ public function get_download_limit( $context = 'view' ) { return $this->get_prop( 'download_limit', $context ); } /** * Get main image ID. * * @since 3.0.0 * @param string $context What the value is for. Valid values are view and edit. * @return string */ public function get_image_id( $context = 'view' ) { return $this->get_prop( 'image_id', $context ); } /** * Get rating count. * * @param string $context What the value is for. Valid values are view and edit. * @return array of counts */ public function get_rating_counts( $context = 'view' ) { return $this->get_prop( 'rating_counts', $context ); } /** * Get average rating. * * @param string $context What the value is for. Valid values are view and edit. * @return float */ public function get_average_rating( $context = 'view' ) { return $this->get_prop( 'average_rating', $context ); } /** * Get review count. * * @param string $context What the value is for. Valid values are view and edit. * @return int */ public function get_review_count( $context = 'view' ) { return $this->get_prop( 'review_count', $context ); } /* |-------------------------------------------------------------------------- | Setters |-------------------------------------------------------------------------- | | Functions for setting product data. These should not update anything in the | database itself and should only change what is stored in the class | object. */ /** * Set product name. * * @since 3.0.0 * @param string $name Product name. */ public function set_name( $name ) { $this->set_prop( 'name', $name ); } /** * Set product slug. * * @since 3.0.0 * @param string $slug Product slug. */ public function set_slug( $slug ) { $this->set_prop( 'slug', $slug ); } /** * Set product created date. * * @since 3.0.0 * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date. */ public function set_date_created( $date = null ) { $this->set_date_prop( 'date_created', $date ); } /** * Set product modified date. * * @since 3.0.0 * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date. */ public function set_date_modified( $date = null ) { $this->set_date_prop( 'date_modified', $date ); } /** * Set product status. * * @since 3.0.0 * @param string $status Product status. */ public function set_status( $status ) { $this->set_prop( 'status', $status ); } /** * Set if the product is featured. * * @since 3.0.0 * @param bool|string $featured Whether the product is featured or not. */ public function set_featured( $featured ) { $this->set_prop( 'featured', wc_string_to_bool( $featured ) ); } /** * Set catalog visibility. * * @since 3.0.0 * @throws WC_Data_Exception Throws exception when invalid data is found. * @param string $visibility Options: 'hidden', 'visible', 'search' and 'catalog'. */ public function set_catalog_visibility( $visibility ) { $options = array_keys( wc_get_product_visibility_options() ); if ( ! in_array( $visibility, $options, true ) ) { $this->error( 'product_invalid_catalog_visibility', __( 'Invalid catalog visibility option.', 'woocommerce' ) ); } $this->set_prop( 'catalog_visibility', $visibility ); } /** * Set product description. * * @since 3.0.0 * @param string $description Product description. */ public function set_description( $description ) { $this->set_prop( 'description', $description ); } /** * Set product short description. * * @since 3.0.0 * @param string $short_description Product short description. */ public function set_short_description( $short_description ) { $this->set_prop( 'short_description', $short_description ); } /** * Set SKU. * * @since 3.0.0 * @throws WC_Data_Exception Throws exception when invalid data is found. * @param string $sku Product SKU. */ public function set_sku( $sku ) { $sku = (string) $sku; if ( $this->get_object_read() && ! empty( $sku ) && ! wc_product_has_unique_sku( $this->get_id(), $sku ) ) { $sku_found = wc_get_product_id_by_sku( $sku ); $this->error( 'product_invalid_sku', __( 'Invalid or duplicated SKU.', 'woocommerce' ), 400, array( 'resource_id' => $sku_found ) ); } $this->set_prop( 'sku', $sku ); } /** * Set the product's active price. * * @param string $price Price. */ public function set_price( $price ) { $this->set_prop( 'price', wc_format_decimal( $price ) ); } /** * Set the product's regular price. * * @since 3.0.0 * @param string $price Regular price. */ public function set_regular_price( $price ) { $this->set_prop( 'regular_price', wc_format_decimal( $price ) ); } /** * Set the product's sale price. * * @since 3.0.0 * @param string $price sale price. */ public function set_sale_price( $price ) { $this->set_prop( 'sale_price', wc_format_decimal( $price ) ); } /** * Set date on sale from. * * @since 3.0.0 * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date. */ public function set_date_on_sale_from( $date = null ) { $this->set_date_prop( 'date_on_sale_from', $date ); } /** * Set date on sale to. * * @since 3.0.0 * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date. */ public function set_date_on_sale_to( $date = null ) { $this->set_date_prop( 'date_on_sale_to', $date ); } /** * Set number total of sales. * * @since 3.0.0 * @param int $total Total of sales. */ public function set_total_sales( $total ) { $this->set_prop( 'total_sales', absint( $total ) ); } /** * Set the tax status. * * @since 3.0.0 * @throws WC_Data_Exception Throws exception when invalid data is found. * @param string $status Tax status. */ public function set_tax_status( $status ) { $options = array( 'taxable', 'shipping', 'none', ); // Set default if empty. if ( empty( $status ) ) { $status = 'taxable'; } if ( ! in_array( $status, $options, true ) ) { $this->error( 'product_invalid_tax_status', __( 'Invalid product tax status.', 'woocommerce' ) ); } $this->set_prop( 'tax_status', $status ); } /** * Set the tax class. * * @since 3.0.0 * @param string $class Tax class. */ public function set_tax_class( $class ) { $class = sanitize_title( $class ); $class = 'standard' === $class ? '' : $class; $valid_classes = $this->get_valid_tax_classes(); if ( ! in_array( $class, $valid_classes, true ) ) { $class = ''; } $this->set_prop( 'tax_class', $class ); } /** * Return an array of valid tax classes * * @return array valid tax classes */ protected function get_valid_tax_classes() { return WC_Tax::get_tax_class_slugs(); } /** * Set if product manage stock. * * @since 3.0.0 * @param bool $manage_stock Whether or not manage stock is enabled. */ public function set_manage_stock( $manage_stock ) { $this->set_prop( 'manage_stock', wc_string_to_bool( $manage_stock ) ); } /** * Set number of items available for sale. * * @since 3.0.0 * @param float|null $quantity Stock quantity. */ public function set_stock_quantity( $quantity ) { $this->set_prop( 'stock_quantity', '' !== $quantity ? wc_stock_amount( $quantity ) : null ); } /** * Set stock status. * * @param string $status New status. */ public function set_stock_status( $status = 'instock' ) { $valid_statuses = wc_get_product_stock_status_options(); if ( isset( $valid_statuses[ $status ] ) ) { $this->set_prop( 'stock_status', $status ); } else { $this->set_prop( 'stock_status', 'instock' ); } } /** * Set backorders. * * @since 3.0.0 * @param string $backorders Options: 'yes', 'no' or 'notify'. */ public function set_backorders( $backorders ) { $this->set_prop( 'backorders', $backorders ); } /** * Set low stock amount. * * @param int|string $amount Empty string if value not set. * @since 3.5.0 */ public function set_low_stock_amount( $amount ) { $this->set_prop( 'low_stock_amount', '' === $amount ? '' : absint( $amount ) ); } /** * Set if should be sold individually. * * @since 3.0.0 * @param bool $sold_individually Whether or not product is sold individually. */ public function set_sold_individually( $sold_individually ) { $this->set_prop( 'sold_individually', wc_string_to_bool( $sold_individually ) ); } /** * Set the product's weight. * * @since 3.0.0 * @param float|string $weight Total weight. */ public function set_weight( $weight ) { $this->set_prop( 'weight', '' === $weight ? '' : wc_format_decimal( $weight ) ); } /** * Set the product length. * * @since 3.0.0 * @param float|string $length Total length. */ public function set_length( $length ) { $this->set_prop( 'length', '' === $length ? '' : wc_format_decimal( $length ) ); } /** * Set the product width. * * @since 3.0.0 * @param float|string $width Total width. */ public function set_width( $width ) { $this->set_prop( 'width', '' === $width ? '' : wc_format_decimal( $width ) ); } /** * Set the product height. * * @since 3.0.0 * @param float|string $height Total height. */ public function set_height( $height ) { $this->set_prop( 'height', '' === $height ? '' : wc_format_decimal( $height ) ); } /** * Set upsell IDs. * * @since 3.0.0 * @param array $upsell_ids IDs from the up-sell products. */ public function set_upsell_ids( $upsell_ids ) { $this->set_prop( 'upsell_ids', array_filter( (array) $upsell_ids ) ); } /** * Set crosssell IDs. * * @since 3.0.0 * @param array $cross_sell_ids IDs from the cross-sell products. */ public function set_cross_sell_ids( $cross_sell_ids ) { $this->set_prop( 'cross_sell_ids', array_filter( (array) $cross_sell_ids ) ); } /** * Set parent ID. * * @since 3.0.0 * @param int $parent_id Product parent ID. */ public function set_parent_id( $parent_id ) { $this->set_prop( 'parent_id', absint( $parent_id ) ); } /** * Set if reviews is allowed. * * @since 3.0.0 * @param bool $reviews_allowed Reviews allowed or not. */ public function set_reviews_allowed( $reviews_allowed ) { $this->set_prop( 'reviews_allowed', wc_string_to_bool( $reviews_allowed ) ); } /** * Set purchase note. * * @since 3.0.0 * @param string $purchase_note Purchase note. */ public function set_purchase_note( $purchase_note ) { $this->set_prop( 'purchase_note', $purchase_note ); } /** * Set product attributes. * * Attributes are made up of: * id - 0 for product level attributes. ID for global attributes. * name - Attribute name. * options - attribute value or array of term ids/names. * position - integer sort order. * visible - If visible on frontend. * variation - If used for variations. * Indexed by unqiue key to allow clearing old ones after a set. * * @since 3.0.0 * @param array $raw_attributes Array of WC_Product_Attribute objects. */ public function set_attributes( $raw_attributes ) { $attributes = array_fill_keys( array_keys( $this->get_attributes( 'edit' ) ), null ); foreach ( $raw_attributes as $attribute ) { if ( is_a( $attribute, 'WC_Product_Attribute' ) ) { $attributes[ sanitize_title( $attribute->get_name() ) ] = $attribute; } } uasort( $attributes, 'wc_product_attribute_uasort_comparison' ); $this->set_prop( 'attributes', $attributes ); } /** * Set default attributes. These will be saved as strings and should map to attribute values. * * @since 3.0.0 * @param array $default_attributes List of default attributes. */ public function set_default_attributes( $default_attributes ) { $this->set_prop( 'default_attributes', array_map( 'strval', array_filter( (array) $default_attributes, 'wc_array_filter_default_attributes' ) ) ); } /** * Set menu order. * * @since 3.0.0 * @param int $menu_order Menu order. */ public function set_menu_order( $menu_order ) { $this->set_prop( 'menu_order', intval( $menu_order ) ); } /** * Set post password. * * @since 3.6.0 * @param int $post_password Post password. */ public function set_post_password( $post_password ) { $this->set_prop( 'post_password', $post_password ); } /** * Set the product categories. * * @since 3.0.0 * @param array $term_ids List of terms IDs. */ public function set_category_ids( $term_ids ) { $this->set_prop( 'category_ids', array_unique( array_map( 'intval', $term_ids ) ) ); } /** * Set the product tags. * * @since 3.0.0 * @param array $term_ids List of terms IDs. */ public function set_tag_ids( $term_ids ) { $this->set_prop( 'tag_ids', array_unique( array_map( 'intval', $term_ids ) ) ); } /** * Set if the product is virtual. * * @since 3.0.0 * @param bool|string $virtual Whether product is virtual or not. */ public function set_virtual( $virtual ) { $this->set_prop( 'virtual', wc_string_to_bool( $virtual ) ); } /** * Set shipping class ID. * * @since 3.0.0 * @param int $id Product shipping class id. */ public function set_shipping_class_id( $id ) { $this->set_prop( 'shipping_class_id', absint( $id ) ); } /** * Set if the product is downloadable. * * @since 3.0.0 * @param bool|string $downloadable Whether product is downloadable or not. */ public function set_downloadable( $downloadable ) { $this->set_prop( 'downloadable', wc_string_to_bool( $downloadable ) ); } /** * Set downloads. * * @since 3.0.0 * @param array $downloads_array Array of WC_Product_Download objects or arrays. */ public function set_downloads( $downloads_array ) { $downloads = array(); $errors = array(); foreach ( $downloads_array as $download ) { if ( is_a( $download, 'WC_Product_Download' ) ) { $download_object = $download; } else { $download_object = new WC_Product_Download(); // If we don't have a previous hash, generate UUID for download. if ( empty( $download['download_id'] ) ) { $download['download_id'] = wp_generate_uuid4(); } $download_object->set_id( $download['download_id'] ); $download_object->set_name( $download['name'] ); $download_object->set_file( $download['file'] ); } // Validate the file extension. if ( ! $download_object->is_allowed_filetype() ) { if ( $this->get_object_read() ) { /* translators: %1$s: Downloadable file */ $errors[] = sprintf( __( 'The downloadable file %1$s cannot be used as it does not have an allowed file type. Allowed types include: %2$s', 'woocommerce' ), '<code>' . basename( $download_object->get_file() ) . '</code>', '<code>' . implode( ', ', array_keys( $download_object->get_allowed_mime_types() ) ) . '</code>' ); } continue; } // Validate the file exists. if ( ! $download_object->file_exists() ) { if ( $this->get_object_read() ) { /* translators: %s: Downloadable file */ $errors[] = sprintf( __( 'The downloadable file %s cannot be used as it does not exist on the server.', 'woocommerce' ), '<code>' . $download_object->get_file() . '</code>' ); } continue; } $downloads[ $download_object->get_id() ] = $download_object; } if ( $errors ) { $this->error( 'product_invalid_download', $errors[0] ); } $this->set_prop( 'downloads', $downloads ); } /** * Set download limit. * * @since 3.0.0 * @param int|string $download_limit Product download limit. */ public function set_download_limit( $download_limit ) { $this->set_prop( 'download_limit', -1 === (int) $download_limit || '' === $download_limit ? -1 : absint( $download_limit ) ); } /** * Set download expiry. * * @since 3.0.0 * @param int|string $download_expiry Product download expiry. */ public function set_download_expiry( $download_expiry ) { $this->set_prop( 'download_expiry', -1 === (int) $download_expiry || '' === $download_expiry ? -1 : absint( $download_expiry ) ); } /** * Set gallery attachment ids. * * @since 3.0.0 * @param array $image_ids List of image ids. */ public function set_gallery_image_ids( $image_ids ) { $image_ids = wp_parse_id_list( $image_ids ); $this->set_prop( 'gallery_image_ids', $image_ids ); } /** * Set main image ID. * * @since 3.0.0 * @param int|string $image_id Product image id. */ public function set_image_id( $image_id = '' ) { $this->set_prop( 'image_id', $image_id ); } /** * Set rating counts. Read only. * * @param array $counts Product rating counts. */ public function set_rating_counts( $counts ) { $this->set_prop( 'rating_counts', array_filter( array_map( 'absint', (array) $counts ) ) ); } /** * Set average rating. Read only. * * @param float $average Product average rating. */ public function set_average_rating( $average ) { $this->set_prop( 'average_rating', wc_format_decimal( $average ) ); } /** * Set review count. Read only. * * @param int $count Product review count. */ public function set_review_count( $count ) { $this->set_prop( 'review_count', absint( $count ) ); } /* |-------------------------------------------------------------------------- | Other Methods |-------------------------------------------------------------------------- */ /** * Ensure properties are set correctly before save. * * @since 3.0.0 */ public function validate_props() { // Before updating, ensure stock props are all aligned. Qty, backorders and low stock amount are not needed if not stock managed. if ( ! $this->get_manage_stock() ) { $this->set_stock_quantity( '' ); $this->set_backorders( 'no' ); $this->set_low_stock_amount( '' ); // If we are stock managing and we don't have stock, force out of stock status. } elseif ( $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount', 0 ) && 'no' === $this->get_backorders() ) { $this->set_stock_status( 'outofstock' ); // If we are stock managing, backorders are allowed, and we don't have stock, force on backorder status. } elseif ( $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount', 0 ) && 'no' !== $this->get_backorders() ) { $this->set_stock_status( 'onbackorder' ); // If the stock level is changing and we do now have enough, force in stock status. } elseif ( $this->get_stock_quantity() > get_option( 'woocommerce_notify_no_stock_amount', 0 ) ) { $this->set_stock_status( 'instock' ); } } /** * Save data (either create or update depending on if we are working on an existing product). * * @since 3.0.0 * @return int */ public function save() { $this->validate_props(); if ( ! $this->data_store ) { return $this->get_id(); } /** * Trigger action before saving to the DB. Allows you to adjust object props before save. * * @param WC_Data $this The object being saved. * @param WC_Data_Store_WP $data_store THe data store persisting the data. */ do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store ); if ( $this->get_id() ) { $this->data_store->update( $this ); } else { $this->data_store->create( $this ); } if ( $this->get_parent_id() ) { wc_deferred_product_sync( $this->get_parent_id() ); } /** * Trigger action after saving to the DB. * * @param WC_Data $this The object being saved. * @param WC_Data_Store_WP $data_store THe data store persisting the data. */ do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store ); return $this->get_id(); } /* |-------------------------------------------------------------------------- | Conditionals |-------------------------------------------------------------------------- */ /** * 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 ), $feature, $this ); } /** * Returns whether or not the product post exists. * * @return bool */ public function exists() { return false !== $this->get_status(); } /** * Checks the product type. * * Backwards compatibility with downloadable/virtual. * * @param string|array $type Array or string of types. * @return bool */ public function is_type( $type ) { return ( $this->get_type() === $type || ( is_array( $type ) && in_array( $this->get_type(), $type, true ) ) ); } /** * Checks if a product is downloadable. * * @return bool */ public function is_downloadable() { return apply_filters( 'woocommerce_is_downloadable', true === $this->get_downloadable(), $this ); } /** * Checks if a product is virtual (has no shipping). * * @return bool */ public function is_virtual() { return apply_filters( 'woocommerce_is_virtual', true === $this->get_virtual(), $this ); } /** * Returns whether or not the product is featured. * * @return bool */ public function is_featured() { return true === $this->get_featured(); } /** * Check if a product is sold individually (no quantities). * * @return bool */ public function is_sold_individually() { return apply_filters( 'woocommerce_is_sold_individually', true === $this->get_sold_individually(), $this ); } /** * Returns whether or not the product is visible in the catalog. * * @return bool */ public function is_visible() { $visible = 'visible' === $this->get_catalog_visibility() || ( is_search() && 'search' === $this->get_catalog_visibility() ) || ( ! is_search() && 'catalog' === $this->get_catalog_visibility() ); if ( 'trash' === $this->get_status() ) { $visible = false; } elseif ( 'publish' !== $this->get_status() && ! current_user_can( 'edit_post', $this->get_id() ) ) { $visible = false; } if ( $this->get_parent_id() ) { $parent_product = wc_get_product( $this->get_parent_id() ); if ( $parent_product && 'publish' !== $parent_product->get_status() ) { $visible = false; } } if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $this->is_in_stock() ) { $visible = false; } return apply_filters( 'woocommerce_product_is_visible', $visible, $this->get_id() ); } /** * Returns false if the product cannot be bought. * * @return bool */ public function is_purchasable() { return apply_filters( 'woocommerce_is_purchasable', $this->exists() && ( 'publish' === $this->get_status() || current_user_can( 'edit_post', $this->get_id() ) ) && '' !== $this->get_price(), $this ); } /** * Returns whether or not the product is on sale. * * @param string $context What the value is for. Valid values are view and edit. * @return bool */ public function is_on_sale( $context = 'view' ) { if ( '' !== (string) $this->get_sale_price( $context ) && $this->get_regular_price( $context ) > $this->get_sale_price( $context ) ) { $on_sale = true; if ( $this->get_date_on_sale_from( $context ) && $this->get_date_on_sale_from( $context )->getTimestamp() > time() ) { $on_sale = false; } if ( $this->get_date_on_sale_to( $context ) && $this->get_date_on_sale_to( $context )->getTimestamp() < time() ) { $on_sale = false; } } else { $on_sale = false; } return 'view' === $context ? apply_filters( 'woocommerce_product_is_on_sale', $on_sale, $this ) : $on_sale; } /** * Returns whether or not the product has dimensions set. * * @return bool */ public function has_dimensions() { return ( $this->get_length() || $this->get_height() || $this->get_width() ) && ! $this->get_virtual(); } /** * Returns whether or not the product has weight set. * * @return bool */ public function has_weight() { return $this->get_weight() && ! $this->get_virtual(); } /** * Returns whether or not the product can be purchased. * This returns true for 'instock' and 'onbackorder' stock statuses. * * @return bool */ public function is_in_stock() { return apply_filters( 'woocommerce_product_is_in_stock', 'outofstock' !== $this->get_stock_status(), $this ); } /** * Checks if a product needs shipping. * * @return bool */ public function needs_shipping() { return apply_filters( 'woocommerce_product_needs_shipping', ! $this->is_virtual(), $this ); } /** * Returns whether or not the product is taxable. * * @return bool */ public function is_taxable() { return apply_filters( 'woocommerce_product_is_taxable', $this->get_tax_status() === 'taxable' && wc_tax_enabled(), $this ); } /** * Returns whether or not the product shipping is taxable. * * @return bool */ public function is_shipping_taxable() { return $this->needs_shipping() && ( $this->get_tax_status() === 'taxable' || $this->get_tax_status() === 'shipping' ); } /** * Returns whether or not the product is stock managed. * * @return bool */ public function managing_stock() { if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { return $this->get_manage_stock(); } return false; } /** * Returns whether or not the product can be backordered. * * @return bool */ public function backorders_allowed() { return apply_filters( 'woocommerce_product_backorders_allowed', ( 'yes' === $this->get_backorders() || 'notify' === $this->get_backorders() ), $this->get_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() && 'notify' === $this->get_backorders() ), $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 ) { if ( 'onbackorder' === $this->get_stock_status() ) { return true; } return $this->managing_stock() && $this->backorders_allowed() && ( $this->get_stock_quantity() - $qty_in_cart ) < 0; } /** * Returns whether or not the product has enough stock for the order. * * @param mixed $quantity Quantity of a product added to an order. * @return bool */ public function has_enough_stock( $quantity ) { return ! $this->managing_stock() || $this->backorders_allowed() || $this->get_stock_quantity() >= $quantity; } /** * Returns whether or not the product has any visible attributes. * * @return boolean */ public function has_attributes() { foreach ( $this->get_attributes() as $attribute ) { if ( $attribute->get_visible() ) { return true; } } return false; } /** * Returns whether or not the product has any child product. * * @return bool */ public function has_child() { return 0 < count( $this->get_children() ); } /** * Does a child have dimensions? * * @since 3.0.0 * @return bool */ public function child_has_dimensions() { return false; } /** * Does a child have a weight? * * @since 3.0.0 * @return boolean */ public function child_has_weight() { return 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 ); } /** * Returns whether or not the product has additional options that need * selecting before adding to cart. * * @since 3.0.0 * @return boolean */ public function has_options() { return false; } /* |-------------------------------------------------------------------------- | Non-CRUD Getters |-------------------------------------------------------------------------- */ /** * Get the product's title. For products this is the product name. * * @return string */ public function get_title() { return apply_filters( 'woocommerce_product_title', $this->get_name(), $this ); } /** * Product permalink. * * @return string */ public function get_permalink() { return get_permalink( $this->get_id() ); } /** * Returns the children IDs if applicable. Overridden by child classes. * * @return array of IDs */ public function get_children() { return array(); } /** * If the stock level comes from another product ID, this should be modified. * * @since 3.0.0 * @return int */ public function get_stock_managed_by_id() { return $this->get_id(); } /** * Returns the price in html format. * * @param string $deprecated Deprecated param. * * @return string */ public function get_price_html( $deprecated = '' ) { if ( '' === $this->get_price() ) { $price = apply_filters( 'woocommerce_empty_price_html', '', $this ); } elseif ( $this->is_on_sale() ) { $price = wc_format_sale_price( wc_get_price_to_display( $this, array( 'price' => $this->get_regular_price() ) ), wc_get_price_to_display( $this ) ) . $this->get_price_suffix(); } else { $price = wc_price( wc_get_price_to_display( $this ) ) . $this->get_price_suffix(); } return apply_filters( 'woocommerce_get_price_html', $price, $this ); } /** * 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->get_id(); } return sprintf( '%2$s (%1$s)', $identifier, $this->get_name() ); } /** * Get min quantity which can be purchased at once. * * @since 3.0.0 * @return int */ public function get_min_purchase_quantity() { return 1; } /** * Get max quantity which can be purchased at once. * * @since 3.0.0 * @return int Quantity or -1 if unlimited. */ public function get_max_purchase_quantity() { return $this->is_sold_individually() ? 1 : ( $this->backorders_allowed() || ! $this->managing_stock() ? -1 : $this->get_stock_quantity() ); } /** * 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', $this->get_permalink(), $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 ); } /** * Get the add to cart button text description - used in aria tags. * * @since 3.3.0 * @return string */ public function add_to_cart_description() { /* translators: %s: Product title */ return apply_filters( 'woocommerce_product_add_to_cart_description', sprintf( __( 'Read more about “%s”', 'woocommerce' ), $this->get_name() ), $this ); } /** * Returns the main product image. * * @param string $size (default: 'woocommerce_thumbnail'). * @param array $attr Image attributes. * @param bool $placeholder True to return $placeholder if no image is found, or false to return an empty string. * @return string */ public function get_image( $size = 'woocommerce_thumbnail', $attr = array(), $placeholder = true ) { $image = ''; if ( $this->get_image_id() ) { $image = wp_get_attachment_image( $this->get_image_id(), $size, false, $attr ); } elseif ( $this->get_parent_id() ) { $parent_product = wc_get_product( $this->get_parent_id() ); if ( $parent_product ) { $image = $parent_product->get_image( $size, $attr, $placeholder ); } } if ( ! $image && $placeholder ) { $image = wc_placeholder_img( $size, $attr ); } return apply_filters( 'woocommerce_product_get_image', $image, $this, $size, $attr, $placeholder, $image ); } /** * Returns the product shipping class SLUG. * * @return string */ public function get_shipping_class() { if ( $class_id = $this->get_shipping_class_id() ) { // @phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found, WordPress.CodeAnalysis.AssignmentInCondition.Found $term = get_term_by( 'id', $class_id, 'product_shipping_class' ); if ( $term && ! is_wp_error( $term ) ) { return $term->slug; } } return ''; } /** * Returns a single product attribute as a string. * * @param string $attribute to get. * @return string */ public function get_attribute( $attribute ) { $attributes = $this->get_attributes(); $attribute = sanitize_title( $attribute ); if ( isset( $attributes[ $attribute ] ) ) { $attribute_object = $attributes[ $attribute ]; } elseif ( isset( $attributes[ 'pa_' . $attribute ] ) ) { $attribute_object = $attributes[ 'pa_' . $attribute ]; } else { return ''; } return $attribute_object->is_taxonomy() ? implode( ', ', wc_get_product_terms( $this->get_id(), $attribute_object->get_name(), array( 'fields' => 'names' ) ) ) : wc_implode_text_attributes( $attribute_object->get_options() ); } /** * Get the total amount (COUNT) of ratings, or just the count for one rating e.g. number of 5 star 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 ) { $counts = $this->get_rating_counts(); if ( is_null( $value ) ) { return array_sum( $counts ); } elseif ( isset( $counts[ $value ] ) ) { return absint( $counts[ $value ] ); } else { return 0; } } /** * 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_downloads(); if ( '' === $download_id ) { $file = count( $files ) ? current( $files ) : false; } elseif ( isset( $files[ $download_id ] ) ) { $file = $files[ $download_id ]; } else { $file = false; } 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_downloads(); $file_path = isset( $files[ $download_id ] ) ? $files[ $download_id ]->get_file() : ''; // allow overriding based on the particular file being requested. return apply_filters( 'woocommerce_product_file_download_path', $file_path, $this, $download_id ); } /** * 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 ) { $html = ''; if ( ( $suffix = get_option( 'woocommerce_price_display_suffix' ) ) && wc_tax_enabled() && 'taxable' === $this->get_tax_status() ) { // @phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found, WordPress.CodeAnalysis.AssignmentInCondition.Found if ( '' === $price ) { $price = $this->get_price(); } $replacements = array( '{price_including_tax}' => wc_price( wc_get_price_including_tax( $this, array( 'qty' => $qty, 'price' => $price ) ) ), // @phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine, WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound '{price_excluding_tax}' => wc_price( wc_get_price_excluding_tax( $this, array( 'qty' => $qty, 'price' => $price ) ) ), // @phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ); $html = str_replace( array_keys( $replacements ), array_values( $replacements ), ' <small class="woocommerce-price-suffix">' . wp_kses_post( $suffix ) . '</small>' ); } return apply_filters( 'woocommerce_get_price_suffix', $html, $this, $price, $qty ); } /** * Returns the availability of the product. * * @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' ) : ''; } elseif ( ! $this->managing_stock() && $this->is_on_backorder( 1 ) ) { $availability = __( 'Available on backorder', 'woocommerce' ); } elseif ( $this->managing_stock() ) { $availability = wc_format_stock_for_display( $this ); } 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->managing_stock() && $this->is_on_backorder( 1 ) ) ) { $class = 'available-on-backorder'; } else { $class = 'in-stock'; } return apply_filters( 'woocommerce_get_availability_class', $class, $this ); } } abstract-wc-rest-controller.php 0000666 00000013611 15214036663 0012637 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 00000030167 15214036663 0010771 0 ustar 00 <?php /** * Abstract widget class * * @class WC_Widget * @package WooCommerce/Abstracts */ use Automattic\Jetpack\Constants; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * WC_Widget * * @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 Arguments. * @return bool true if the widget is cached otherwise false */ public function get_cached_widget( $args ) { // Don't get cache if widget_id doesn't exists. if ( empty( $args['widget_id'] ) ) { return false; } $cache = wp_cache_get( $this->get_widget_id_for_cache( $this->widget_id ), 'widget' ); if ( ! is_array( $cache ) ) { $cache = array(); } if ( isset( $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ] ) ) { echo $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ]; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped return true; } return false; } /** * Cache the widget. * * @param array $args Arguments. * @param string $content Content. * @return string the content that was cached */ public function cache_widget( $args, $content ) { // Don't set any cache if widget_id doesn't exist. if ( empty( $args['widget_id'] ) ) { return $content; } $cache = wp_cache_get( $this->get_widget_id_for_cache( $this->widget_id ), 'widget' ); if ( ! is_array( $cache ) ) { $cache = array(); } $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ] = $content; wp_cache_set( $this->get_widget_id_for_cache( $this->widget_id ), $cache, 'widget' ); return $content; } /** * Flush the cache. */ public function flush_widget_cache() { foreach ( array( 'https', 'http' ) as $scheme ) { wp_cache_delete( $this->get_widget_id_for_cache( $this->widget_id, $scheme ), 'widget' ); } } /** * Get this widgets title. * * @param array $instance Array of instance options. * @return string */ protected function get_instance_title( $instance ) { if ( isset( $instance['title'] ) ) { return $instance['title']; } if ( isset( $this->settings, $this->settings['title'], $this->settings['title']['std'] ) ) { return $this->settings['title']['std']; } return ''; } /** * Output the html at the start of a widget. * * @param array $args Arguments. * @param array $instance Instance. */ public function widget_start( $args, $instance ) { echo $args['before_widget']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped $title = apply_filters( 'widget_title', $this->get_instance_title( $instance ), $instance, $this->id_base ); if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped } } /** * Output the html at the end of a widget. * * @param array $args Arguments. */ public function widget_end( $args ) { echo $args['after_widget']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped } /** * Updates a particular instance of a widget. * * @see WP_Widget->update * @param array $new_instance New instance. * @param array $old_instance 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 ] = isset( $new_instance[ $key ] ) ? sanitize_text_field( $new_instance[ $key ] ) : $setting['std']; 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 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 esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo wp_kses_post( $setting['label'] ); ?></label><?php // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?> <input class="widefat <?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="text" value="<?php echo esc_attr( $value ); ?>" /> </p> <?php break; case 'number': ?> <p> <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label> <input class="widefat <?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="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 esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label> <select class="widefat <?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 ) ); ?>"> <?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 esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label> <textarea class="widefat <?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 ) ); ?>" 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 esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label> </p> <?php break; // Default: run an action. default: do_action( 'woocommerce_widget_field_' . $setting['type'], $key, $value, $setting, $instance ); break; } } } /** * Get current page URL with various filtering props supported by WC. * * @return string * @since 3.3.0 */ protected function get_current_page_url() { if ( Constants::is_defined( 'SHOP_IS_ON_FRONT' ) ) { $link = home_url(); } elseif ( is_shop() ) { $link = get_permalink( wc_get_page_id( 'shop' ) ); } elseif ( is_product_category() ) { $link = get_term_link( get_query_var( 'product_cat' ), 'product_cat' ); } elseif ( is_product_tag() ) { $link = get_term_link( get_query_var( 'product_tag' ), 'product_tag' ); } else { $queried_object = get_queried_object(); $link = get_term_link( $queried_object->slug, $queried_object->taxonomy ); } // Min/Max. if ( isset( $_GET['min_price'] ) ) { $link = add_query_arg( 'min_price', wc_clean( wp_unslash( $_GET['min_price'] ) ), $link ); } if ( isset( $_GET['max_price'] ) ) { $link = add_query_arg( 'max_price', wc_clean( wp_unslash( $_GET['max_price'] ) ), $link ); } // Order by. if ( isset( $_GET['orderby'] ) ) { $link = add_query_arg( 'orderby', wc_clean( wp_unslash( $_GET['orderby'] ) ), $link ); } /** * Search Arg. * To support quote characters, first they are decoded from " entities, then URL encoded. */ if ( get_search_query() ) { $link = add_query_arg( 's', rawurlencode( htmlspecialchars_decode( get_search_query() ) ), $link ); } // Post Type Arg. if ( isset( $_GET['post_type'] ) ) { $link = add_query_arg( 'post_type', wc_clean( wp_unslash( $_GET['post_type'] ) ), $link ); // Prevent post type and page id when pretty permalinks are disabled. if ( is_shop() ) { $link = remove_query_arg( 'page_id', $link ); } } // Min Rating Arg. if ( isset( $_GET['rating_filter'] ) ) { $link = add_query_arg( 'rating_filter', wc_clean( wp_unslash( $_GET['rating_filter'] ) ), $link ); } // All current filters. if ( $_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes() ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found, WordPress.CodeAnalysis.AssignmentInCondition.Found foreach ( $_chosen_attributes as $name => $data ) { $filter_name = wc_attribute_taxonomy_slug( $name ); if ( ! empty( $data['terms'] ) ) { $link = add_query_arg( 'filter_' . $filter_name, implode( ',', $data['terms'] ), $link ); } if ( 'or' === $data['query_type'] ) { $link = add_query_arg( 'query_type_' . $filter_name, 'or', $link ); } } } return apply_filters( 'woocommerce_widget_get_current_page_url', $link, $this ); } /** * Get widget id plus scheme/protocol to prevent serving mixed content from (persistently) cached widgets. * * @since 3.4.0 * @param string $widget_id Id of the cached widget. * @param string $scheme Scheme for the widget id. * @return string Widget id including scheme/protocol. */ protected function get_widget_id_for_cache( $widget_id, $scheme = '' ) { if ( $scheme ) { $widget_id_for_cache = $widget_id . '-' . $scheme; } else { $widget_id_for_cache = $widget_id . '-' . ( is_ssl() ? 'https' : 'http' ); } return apply_filters( 'woocommerce_cached_widget_id', $widget_id_for_cache ); } } abstract-wc-integration.php 0000666 00000003535 15214036663 0012030 0 ustar 00 <?php /** * Abstract Integration class * * Extension of the Settings API which in turn gets extended * by individual integrations to offer additional functionality. * * @class WC_Settings_API * @version 2.6.0 * @package WooCommerce/Abstracts */ 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 */ 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 00000047364 15214036663 0010426 0 ustar 00 <?php /** * Abstract Data. * * Handles generic data interaction which is implemented by * the different data store classes. * * @class WC_Data * @version 3.0.0 * @package WooCommerce/Classes */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Abstract WC Data Class * * Implemented by classes using the same CRUD(s) pattern. * * @version 2.6.0 * @package WooCommerce/Abstracts */ abstract class WC_Data { /** * ID for this object. * * @since 3.0.0 * @var int */ protected $id = 0; /** * Core data for this object. Name value pairs (name + default value). * * @since 3.0.0 * @var array */ protected $data = array(); /** * Core data changes for this object. * * @since 3.0.0 * @var array */ protected $changes = array(); /** * This is false until the object is read from the DB. * * @since 3.0.0 * @var bool */ protected $object_read = false; /** * This is the name of this object type. * * @since 3.0.0 * @var string */ protected $object_type = 'data'; /** * Extra data for this object. Name value pairs (name + default value). * Used as a standard way for sub classes (like product types) to add * additional information to an inherited class. * * @since 3.0.0 * @var array */ protected $extra_data = array(); /** * Set to _data on construct so we can track and reset data if needed. * * @since 3.0.0 * @var array */ protected $default_data = array(); /** * Contains a reference to the data store for this class. * * @since 3.0.0 * @var object */ protected $data_store; /** * Stores meta in cache for future reads. * A group must be set to to enable caching. * * @since 3.0.0 * @var string */ protected $cache_group = ''; /** * Stores additional meta data. * * @since 3.0.0 * @var array */ protected $meta_data = null; /** * Default constructor. * * @param int|object|array $read ID to load from the DB (optional) or already queried data. */ public function __construct( $read = 0 ) { $this->data = array_merge( $this->data, $this->extra_data ); $this->default_data = $this->data; } /** * Only store the object ID to avoid serializing the data object instance. * * @return array */ public function __sleep() { return array( 'id' ); } /** * Re-run the constructor with the object ID. * * If the object no longer exists, remove the ID. */ public function __wakeup() { try { $this->__construct( absint( $this->id ) ); } catch ( Exception $e ) { $this->set_id( 0 ); $this->set_object_read( true ); } } /** * When the object is cloned, make sure meta is duplicated correctly. * * @since 3.0.2 */ public function __clone() { $this->maybe_read_meta_data(); if ( ! empty( $this->meta_data ) ) { foreach ( $this->meta_data as $array_key => $meta ) { $this->meta_data[ $array_key ] = clone $meta; if ( ! empty( $meta->id ) ) { $this->meta_data[ $array_key ]->id = null; } } } } /** * Get the data store. * * @since 3.0.0 * @return object */ public function get_data_store() { return $this->data_store; } /** * Returns the unique ID for this object. * * @since 2.6.0 * @return int */ public function get_id() { return $this->id; } /** * Delete an object, set the ID to 0, and return result. * * @since 2.6.0 * @param bool $force_delete Should the date be deleted permanently. * @return bool result */ public function delete( $force_delete = false ) { if ( $this->data_store ) { $this->data_store->delete( $this, array( 'force_delete' => $force_delete ) ); $this->set_id( 0 ); return true; } return false; } /** * Save should create or update based on object existence. * * @since 2.6.0 * @return int */ public function save() { if ( ! $this->data_store ) { return $this->get_id(); } /** * Trigger action before saving to the DB. Allows you to adjust object props before save. * * @param WC_Data $this The object being saved. * @param WC_Data_Store_WP $data_store THe data store persisting the data. */ do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store ); if ( $this->get_id() ) { $this->data_store->update( $this ); } else { $this->data_store->create( $this ); } /** * Trigger action after saving to the DB. * * @param WC_Data $this The object being saved. * @param WC_Data_Store_WP $data_store THe data store persisting the data. */ do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store ); return $this->get_id(); } /** * Change data to JSON format. * * @since 2.6.0 * @return string Data in JSON format. */ public function __toString() { return wp_json_encode( $this->get_data() ); } /** * Returns all data for this object. * * @since 2.6.0 * @return array */ public function get_data() { return array_merge( array( 'id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) ); } /** * Returns array of expected data keys for this object. * * @since 3.0.0 * @return array */ public function get_data_keys() { return array_keys( $this->data ); } /** * Returns all "extra" data keys for an object (for sub objects like product types). * * @since 3.0.0 * @return array */ public function get_extra_data_keys() { return array_keys( $this->extra_data ); } /** * Filter null meta values from array. * * @since 3.0.0 * @param mixed $meta Meta value to check. * @return bool */ protected function filter_null_meta( $meta ) { return ! is_null( $meta->value ); } /** * Get All Meta Data. * * @since 2.6.0 * @return array of objects. */ public function get_meta_data() { $this->maybe_read_meta_data(); return array_values( array_filter( $this->meta_data, array( $this, 'filter_null_meta' ) ) ); } /** * Check if the key is an internal one. * * @since 3.2.0 * @param string $key Key to check. * @return bool true if it's an internal key, false otherwise */ protected function is_internal_meta_key( $key ) { $internal_meta_key = ! empty( $key ) && $this->data_store && in_array( $key, $this->data_store->get_internal_meta_keys(), true ); if ( ! $internal_meta_key ) { return false; } $has_setter_or_getter = is_callable( array( $this, 'set_' . $key ) ) || is_callable( array( $this, 'get_' . $key ) ); if ( ! $has_setter_or_getter ) { return false; } /* translators: %s: $key Key to check */ wc_doing_it_wrong( __FUNCTION__, sprintf( __( 'Generic add/update/get meta methods should not be used for internal meta data, including "%s". Use getters and setters.', 'woocommerce' ), $key ), '3.2.0' ); return true; } /** * Get Meta Data by Key. * * @since 2.6.0 * @param string $key Meta Key. * @param bool $single return first found meta with key, or all with $key. * @param string $context What the value is for. Valid values are view and edit. * @return mixed */ public function get_meta( $key = '', $single = true, $context = 'view' ) { if ( $this->is_internal_meta_key( $key ) ) { $function = 'get_' . $key; if ( is_callable( array( $this, $function ) ) ) { return $this->{$function}(); } } $this->maybe_read_meta_data(); $meta_data = $this->get_meta_data(); $array_keys = array_keys( wp_list_pluck( $meta_data, 'key' ), $key, true ); $value = $single ? '' : array(); if ( ! empty( $array_keys ) ) { // We don't use the $this->meta_data property directly here because we don't want meta with a null value (i.e. meta which has been deleted via $this->delete_meta_data()). if ( $single ) { $value = $meta_data[ current( $array_keys ) ]->value; } else { $value = array_intersect_key( $meta_data, array_flip( $array_keys ) ); } } if ( 'view' === $context ) { $value = apply_filters( $this->get_hook_prefix() . $key, $value, $this ); } return $value; } /** * See if meta data exists, since get_meta always returns a '' or array(). * * @since 3.0.0 * @param string $key Meta Key. * @return boolean */ public function meta_exists( $key = '' ) { $this->maybe_read_meta_data(); $array_keys = wp_list_pluck( $this->get_meta_data(), 'key' ); return in_array( $key, $array_keys, true ); } /** * 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 ) ) { $this->maybe_read_meta_data(); foreach ( $data as $meta ) { $meta = (array) $meta; if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) { $this->meta_data[] = new WC_Meta_Data( array( 'id' => $meta['id'], 'key' => $meta['key'], 'value' => $meta['value'], ) ); } } } } /** * Add meta data. * * @since 2.6.0 * * @param string $key Meta key. * @param string|array $value Meta value. * @param bool $unique Should this be a unique key?. */ public function add_meta_data( $key, $value, $unique = false ) { if ( $this->is_internal_meta_key( $key ) ) { $function = 'set_' . $key; if ( is_callable( array( $this, $function ) ) ) { return $this->{$function}( $value ); } } $this->maybe_read_meta_data(); if ( $unique ) { $this->delete_meta_data( $key ); } $this->meta_data[] = new WC_Meta_Data( array( 'key' => $key, 'value' => $value, ) ); } /** * Update meta data by key or ID, if provided. * * @since 2.6.0 * * @param string $key Meta key. * @param string|array $value Meta value. * @param int $meta_id Meta ID. */ public function update_meta_data( $key, $value, $meta_id = 0 ) { if ( $this->is_internal_meta_key( $key ) ) { $function = 'set_' . $key; if ( is_callable( array( $this, $function ) ) ) { return $this->{$function}( $value ); } } $this->maybe_read_meta_data(); $array_key = false; if ( $meta_id ) { $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id, true ); $array_key = $array_keys ? current( $array_keys ) : false; } else { // Find matches by key. $matches = array(); foreach ( $this->meta_data as $meta_data_array_key => $meta ) { if ( $meta->key === $key ) { $matches[] = $meta_data_array_key; } } if ( ! empty( $matches ) ) { // Set matches to null so only one key gets the new value. foreach ( $matches as $meta_data_array_key ) { $this->meta_data[ $meta_data_array_key ]->value = null; } $array_key = current( $matches ); } } if ( false !== $array_key ) { $meta = $this->meta_data[ $array_key ]; $meta->key = $key; $meta->value = $value; } else { $this->add_meta_data( $key, $value, true ); } } /** * Delete meta data. * * @since 2.6.0 * @param string $key Meta key. */ public function delete_meta_data( $key ) { $this->maybe_read_meta_data(); $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key, true ); if ( $array_keys ) { foreach ( $array_keys as $array_key ) { $this->meta_data[ $array_key ]->value = null; } } } /** * Delete meta data. * * @since 2.6.0 * @param int $mid Meta ID. */ public function delete_meta_data_by_mid( $mid ) { $this->maybe_read_meta_data(); $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), (int) $mid, true ); if ( $array_keys ) { foreach ( $array_keys as $array_key ) { $this->meta_data[ $array_key ]->value = null; } } } /** * Read meta data if null. * * @since 3.0.0 */ protected function maybe_read_meta_data() { if ( is_null( $this->meta_data ) ) { $this->read_meta_data(); } } /** * Read Meta Data from the database. Ignore any internal properties. * Uses it's own caches because get_metadata does not provide meta_ids. * * @since 2.6.0 * @param bool $force_read True to force a new DB read (and update cache). */ public function read_meta_data( $force_read = false ) { $this->meta_data = array(); $cache_loaded = false; if ( ! $this->get_id() ) { return; } if ( ! $this->data_store ) { return; } if ( ! empty( $this->cache_group ) ) { // Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented. $cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id(); } if ( ! $force_read ) { if ( ! empty( $this->cache_group ) ) { $cached_meta = wp_cache_get( $cache_key, $this->cache_group ); $cache_loaded = ! empty( $cached_meta ); } } $raw_meta_data = $cache_loaded ? $cached_meta : $this->data_store->read_meta( $this ); if ( $raw_meta_data ) { foreach ( $raw_meta_data as $meta ) { $this->meta_data[] = new WC_Meta_Data( array( 'id' => (int) $meta->meta_id, 'key' => $meta->meta_key, 'value' => maybe_unserialize( $meta->meta_value ), ) ); } if ( ! $cache_loaded && ! empty( $this->cache_group ) ) { wp_cache_set( $cache_key, $raw_meta_data, $this->cache_group ); } } } /** * Update Meta Data in the database. * * @since 2.6.0 */ public function save_meta_data() { if ( ! $this->data_store || is_null( $this->meta_data ) ) { return; } foreach ( $this->meta_data as $array_key => $meta ) { if ( is_null( $meta->value ) ) { if ( ! empty( $meta->id ) ) { $this->data_store->delete_meta( $this, $meta ); unset( $this->meta_data[ $array_key ] ); } } elseif ( empty( $meta->id ) ) { $meta->id = $this->data_store->add_meta( $this, $meta ); $meta->apply_changes(); } else { if ( $meta->get_changes() ) { $this->data_store->update_meta( $this, $meta ); $meta->apply_changes(); } } } if ( ! empty( $this->cache_group ) ) { $cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id(); wp_cache_delete( $cache_key, $this->cache_group ); } } /** * Set ID. * * @since 3.0.0 * @param int $id ID. */ public function set_id( $id ) { $this->id = absint( $id ); } /** * Set all props to default values. * * @since 3.0.0 */ public function set_defaults() { $this->data = $this->default_data; $this->changes = array(); $this->set_object_read( false ); } /** * Set object read property. * * @since 3.0.0 * @param boolean $read Should read?. */ public function set_object_read( $read = true ) { $this->object_read = (bool) $read; } /** * Get object read property. * * @since 3.0.0 * @return boolean */ public function get_object_read() { return (bool) $this->object_read; } /** * Set a collection of props in one go, collect any errors, and return the result. * Only sets using public methods. * * @since 3.0.0 * * @param array $props Key value pairs to set. Key is the prop and should map to a setter function name. * @param string $context In what context to run this. * * @return bool|WP_Error */ public function set_props( $props, $context = 'set' ) { $errors = false; foreach ( $props as $prop => $value ) { try { /** * Checks if the prop being set is allowed, and the value is not null. */ if ( is_null( $value ) || in_array( $prop, array( 'prop', 'date_prop', 'meta_data' ), true ) ) { continue; } $setter = "set_$prop"; if ( is_callable( array( $this, $setter ) ) ) { $this->{$setter}( $value ); } } catch ( WC_Data_Exception $e ) { if ( ! $errors ) { $errors = new WP_Error(); } $errors->add( $e->getErrorCode(), $e->getMessage() ); } } return $errors && count( $errors->get_error_codes() ) ? $errors : true; } /** * Sets a prop for a setter method. * * This stores changes in a special array so we can track what needs saving * the the DB later. * * @since 3.0.0 * @param string $prop Name of prop to set. * @param mixed $value Value of the prop. */ protected function set_prop( $prop, $value ) { if ( array_key_exists( $prop, $this->data ) ) { if ( true === $this->object_read ) { if ( $value !== $this->data[ $prop ] || array_key_exists( $prop, $this->changes ) ) { $this->changes[ $prop ] = $value; } } else { $this->data[ $prop ] = $value; } } } /** * Return data changes only. * * @since 3.0.0 * @return array */ public function get_changes() { return $this->changes; } /** * Merge changes with data and clear. * * @since 3.0.0 */ public function apply_changes() { $this->data = array_replace_recursive( $this->data, $this->changes ); // @codingStandardsIgnoreLine $this->changes = array(); } /** * Prefix for action and filter hooks on data. * * @since 3.0.0 * @return string */ protected function get_hook_prefix() { return 'woocommerce_' . $this->object_type . '_get_'; } /** * Gets a prop for a getter method. * * Gets the value from either current pending changes, or the data itself. * Context controls what happens to the value before it's returned. * * @since 3.0.0 * @param string $prop Name of prop to get. * @param string $context What the value is for. Valid values are view and edit. * @return mixed */ protected function get_prop( $prop, $context = 'view' ) { $value = null; if ( array_key_exists( $prop, $this->data ) ) { $value = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : $this->data[ $prop ]; if ( 'view' === $context ) { $value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this ); } } return $value; } /** * Sets a date prop whilst handling formatting and datetime objects. * * @since 3.0.0 * @param string $prop Name of prop to set. * @param string|integer $value Value of the prop. */ protected function set_date_prop( $prop, $value ) { try { if ( empty( $value ) ) { $this->set_prop( $prop, null ); return; } if ( is_a( $value, 'WC_DateTime' ) ) { $datetime = $value; } elseif ( is_numeric( $value ) ) { // Timestamps are handled as UTC timestamps in all cases. $datetime = new WC_DateTime( "@{$value}", new DateTimeZone( 'UTC' ) ); } else { // Strings are defined in local WP timezone. Convert to UTC. if ( 1 === preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|((-|\+)\d{2}:\d{2}))$/', $value, $date_bits ) ) { $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset(); $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset; } else { $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) ); } $datetime = new WC_DateTime( "@{$timestamp}", new DateTimeZone( 'UTC' ) ); } // Set local timezone or offset. if ( get_option( 'timezone_string' ) ) { $datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) ); } else { $datetime->set_utc_offset( wc_timezone_offset() ); } $this->set_prop( $prop, $datetime ); } catch ( Exception $e ) {} // @codingStandardsIgnoreLine. } /** * When invalid data is found, throw an exception unless reading from the DB. * * @throws WC_Data_Exception Data Exception. * @since 3.0.0 * @param string $code Error code. * @param string $message Error message. * @param int $http_status_code HTTP status code. * @param array $data Extra error data. */ protected function error( $code, $message, $http_status_code = 400, $data = array() ) { throw new WC_Data_Exception( $code, $message, $http_status_code, $data ); } } abstract-wc-settings-api.php 0000666 00000071425 15214036663 0012117 0 ustar 00 <?php /** * Abstract Settings API Class * * Admin Settings API used by Integrations, Shipping Methods, and Payment Gateways. * * @package WooCommerce/Abstracts */ defined( 'ABSPATH' ) || exit; /** * WC_Settings_API class. */ 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 $field Setting field array. * @return 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>'; // WPCS: XSS ok. } /** * Initialise settings form fields. * * Add an array of fields to be displayed on the gateway's settings screen. * * @since 1.0.0 */ 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 Field key. * @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 Field key. * @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 Field key. * @param array $field Field array. * @param array $post_data Posted 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; // WPCS: CSRF ok, input var ok. $value = isset( $post_data[ $field_key ] ) ? $post_data[ $field_key ] : null; if ( isset( $field['sanitize_callback'] ) && is_callable( $field['sanitize_callback'] ) ) { return call_user_func( $field['sanitize_callback'], $value ); } // 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 Posted 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; // WPCS: CSRF ok, input var ok. } /** * Update a single option. * * @since 3.4.0 * @param string $key Option key. * @param mixed $value Value to set. * @return bool was anything saved? */ public function update_option( $key, $value = '' ) { if ( empty( $this->settings ) ) { $this->init_settings(); } $this->settings[ $key ] = $value; return update_option( $this->get_option_key(), apply_filters( 'woocommerce_settings_api_sanitized_fields_' . $this->id, $this->settings ), 'yes' ); } /** * 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 ), 'yes' ); } /** * Add an error message for display in admin on save. * * @param string $error Error message. */ 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 from DB. * * Gets an option from the settings API, using defaults if necessary to prevent undefined notices. * * @param string $key Option key. * @param mixed $empty_value Value when empty. * @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 string $key Field 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()) Array of form fields. * @param bool $echo Echo or return. * @return string the html for the settings * @since 1.0.0 * @uses method_exists() */ 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; // WPCS: XSS ok. } else { return $html; } } /** * Get HTML for tooltips. * * @param array $data Data for the tooltip. * @return string */ public function get_tooltip_html( $data ) { if ( true === $data['desc_tip'] ) { $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 Data for the description. * @return string */ public function get_description_html( $data ) { if ( true === $data['desc_tip'] ) { $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 Field 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 string $key Field key. * @param array $data Field 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'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label> </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 ); // WPCS: XSS ok. ?> /> <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Price Input HTML. * * @param string $key Field key. * @param array $data Field 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'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label> </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 ); // WPCS: XSS ok. ?> /> <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Decimal Input HTML. * * @param string $key Field key. * @param array $data Field 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'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label> </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 ); // WPCS: XSS ok. ?> /> <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Password Input HTML. * * @param string $key Field key. * @param array $data Field 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 string $key Field key. * @param array $data Field 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'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label> </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 ); // WPCS: XSS ok. ?> /> <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 ); // WPCS: XSS ok. ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Textarea HTML. * * @param string $key Field key. * @param array $data Field 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'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label> </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 ); // WPCS: XSS ok. ?>><?php echo esc_textarea( $this->get_option( $key ) ); ?></textarea> <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Checkbox HTML. * * @param string $key Field key. * @param array $data Field 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'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label> </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 ); // WPCS: XSS ok. ?> /> <?php echo wp_kses_post( $data['label'] ); ?></label><br/> <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Select HTML. * * @param string $key Field key. * @param array $data Field 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'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label> </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 ); // WPCS: XSS ok. ?>> <?php foreach ( (array) $data['options'] as $option_key => $option_value ) : ?> <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( (string) $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 ); // WPCS: XSS ok. ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Multiselect HTML. * * @param string $key Field key. * @param array $data Field 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'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label> </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 ); // WPCS: XSS ok. ?>> <?php foreach ( (array) $data['options'] as $option_key => $option_value ) : ?> <?php if ( is_array( $option_value ) ) : ?> <optgroup label="<?php echo esc_attr( $option_key ); ?>"> <?php foreach ( $option_value as $option_key_inner => $option_value_inner ) : ?> <option value="<?php echo esc_attr( $option_key_inner ); ?>" <?php selected( in_array( (string) $option_key_inner, $value, true ), true ); ?>><?php echo esc_attr( $option_value_inner ); ?></option> <?php endforeach; ?> </optgroup> <?php else : ?> <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( in_array( (string) $option_key, $value, true ), true ); ?>><?php echo esc_attr( $option_value ); ?></option> <?php endif; ?> <?php endforeach; ?> </select> <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> <?php if ( $data['select_buttons'] ) : ?> <br/><a class="select_all button" href="#"><?php esc_html_e( 'Select all', 'woocommerce' ); ?></a> <a class="select_none button" href="#"><?php esc_html_e( 'Select none', 'woocommerce' ); ?></a> <?php endif; ?> </fieldset> </td> </tr> <?php return ob_get_clean(); } /** * Generate Title HTML. * * @param string $key Field key. * @param array $data Field 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 $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 Field key. * @param string $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 Field key. * @param string $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 Field key. * @param string $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 Field key. * @param string $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 Field key. * @param string $value Posted Value. * @return string */ public function validate_checkbox_field( $key, $value ) { return ! is_null( $value ) ? 'yes' : 'no'; } /** * Validate Select Field. * * @param string $key Field 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 Field key. * @param string $value Posted Value. * @return string|array */ 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. * @param array $form_fields Array of fields. */ public function validate_settings_fields( $form_fields = array() ) { wc_deprecated_function( 'validate_settings_fields', '2.6' ); } /** * Format settings if needed. * * @deprecated 2.6.0 Unused. * @param array $value Value to format. * @return array */ public function format_settings( $value ) { wc_deprecated_function( 'format_settings', '2.6' ); return $value; } } abstract-wc-payment-token.php 0000666 00000013262 15214036663 0012276 0 ustar 00 <?php /** * Abstract payment tokens * * Generic payment tokens functionality which can be extended by idividual types of payment tokens. * * @class WC_Payment_Token * @package WooCommerce/Abstracts */ if ( ! defined( 'ABSPATH' ) ) { exit; } require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-payment-token.php'; /** * 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 * @version 3.0.0 * @since 2.6.0 * @package WooCommerce/Abstracts */ abstract class WC_Payment_Token extends WC_Legacy_Payment_Token { /** * Token Data (stored in the payment_tokens table). * * @var array */ protected $data = array( 'gateway_id' => '', 'token' => '', 'is_default' => false, 'user_id' => 0, 'type' => '', ); /** * Token Type (CC, eCheck, or a custom type added by an extension). * Set by child classes. * * @var string */ protected $type = ''; /** * 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 Token. */ public function __construct( $token = '' ) { parent::__construct( $token ); if ( is_numeric( $token ) ) { $this->set_id( $token ); } elseif ( is_object( $token ) ) { $token_id = $token->get_id(); if ( ! empty( $token_id ) ) { $this->set_id( $token->get_id() ); } } else { $this->set_object_read( true ); } $this->data_store = WC_Data_Store::load( 'payment-token' ); if ( $this->get_id() > 0 ) { $this->data_store->read( $this ); } } /* *-------------------------------------------------------------------------- * Getters *-------------------------------------------------------------------------- */ /** * Returns the raw payment token. * * @since 2.6.0 * @param string $context Context in which to call this. * @return string Raw token */ public function get_token( $context = 'view' ) { return $this->get_prop( 'token', $context ); } /** * Returns the type of this payment token (CC, eCheck, or something else). * Overwritten by child classes. * * @since 2.6.0 * @param string $deprecated Deprecated since WooCommerce 3.0. * @return string Payment Token Type (CC, eCheck) */ public function get_type( $deprecated = '' ) { return $this->type; } /** * Get type to display to user. * Get's overwritten by child classes. * * @since 2.6.0 * @param string $deprecated Deprecated since WooCommerce 3.0. * @return string */ public function get_display_name( $deprecated = '' ) { return $this->get_type(); } /** * Returns the user ID associated with the token or false if this token is not associated. * * @since 2.6.0 * @param string $context In what context to execute this. * @return int User ID if this token is associated with a user or 0 if no user is associated */ public function get_user_id( $context = 'view' ) { return $this->get_prop( 'user_id', $context ); } /** * Returns the ID of the gateway associated with this payment token. * * @since 2.6.0 * @param string $context In what context to execute this. * @return string Gateway ID */ public function get_gateway_id( $context = 'view' ) { return $this->get_prop( 'gateway_id', $context ); } /** * Returns the ID of the gateway associated with this payment token. * * @since 2.6.0 * @param string $context In what context to execute this. * @return string Gateway ID */ public function get_is_default( $context = 'view' ) { return $this->get_prop( 'is_default', $context ); } /* |-------------------------------------------------------------------------- | Setters |-------------------------------------------------------------------------- */ /** * Set the raw payment token. * * @since 2.6.0 * @param string $token Payment token. */ public function set_token( $token ) { $this->set_prop( 'token', $token ); } /** * Set the user ID for the user associated with this order. * * @since 2.6.0 * @param int $user_id User ID. */ public function set_user_id( $user_id ) { $this->set_prop( 'user_id', absint( $user_id ) ); } /** * Set the gateway ID. * * @since 2.6.0 * @param string $gateway_id Gateway ID. */ public function set_gateway_id( $gateway_id ) { $this->set_prop( 'gateway_id', $gateway_id ); } /** * 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->set_prop( 'is_default', (bool) $is_default ); } /* |-------------------------------------------------------------------------- | Other Methods |-------------------------------------------------------------------------- */ /** * 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 (bool) $this->get_prop( 'is_default', 'view' ); } /** * 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() { $token = $this->get_prop( 'token', 'edit' ); if ( empty( $token ) ) { return false; } return true; } } abstract-wc-rest-terms-controller.php 0000666 00000061547 15214036663 0014002 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 00000037504 15214036663 0012607 0 ustar 00 <?php /** * Abstract shipping method * * @class WC_Shipping_Method * @package WooCommerce/Abstracts */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * WooCommerce Shipping Method Class. * * Extended by shipping methods to handle shipping calculations etc. * * @class WC_Shipping_Method * @version 3.0.0 * @package WooCommerce/Abstracts */ 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 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 string $feature 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. * * @param array $package Package array. */ 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 && ! WC()->customer->get_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 array $package Package array. * @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 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 Arguments (default: array()). */ public function add_rate( $args = array() ) { $args = apply_filters( 'woocommerce_shipping_method_add_rate_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. 'price_decimals' => wc_get_price_decimals(), ) ), $this ); // 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 ) && false !== $taxes && $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, $args['price_decimals'] ); // Create rate object. $rate = new WC_Shipping_Rate(); $rate->set_id( $args['id'] ); $rate->set_method_id( $this->id ); $rate->set_instance_id( $this->instance_id ); $rate->set_label( $args['label'] ); $rate->set_cost( $total_cost ); $rate->set_taxes( $taxes ); 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_name() . ' × ' . $item['quantity']; } $rate->add_meta_data( __( 'Items', 'woocommerce' ), implode( ', ', $items_in_package ) ); } $this->rates[ $args['id'] ] = apply_filters( 'woocommerce_shipping_method_add_rate', $rate, $args, $this ); } /** * Calc taxes per item being shipping in costs array. * * @since 2.6.0 * @param array $costs 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 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, $this ); } /** * Get fee to add to shipping cost. * * @param string|float $fee Fee. * @param float $total 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(); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped } /** * Get_option function. * * Gets and option from the settings API, using defaults if necessary to prevent undefined notices. * * @param string $key Key. * @param mixed $empty_value 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. $option = apply_filters( 'woocommerce_shipping_' . $this->id . '_option', parent::get_option( $key, $empty_value ), $key, $this ); return $option; } /** * Gets an option from the settings API, using defaults if necessary to prevent undefined notices. * * @param string $key Key. * @param mixed $empty_value 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; } $instance_option = apply_filters( 'woocommerce_shipping_' . $this->id . '_instance_option', $this->instance_settings[ $key ], $key, $this ); return $instance_option; } /** * 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 global shipping method options in the admin area. * * This method is usually attached to woocommerce_update_options_x hooks. * * @since 2.6.0 * @return bool was anything saved? */ public function process_admin_options() { if ( ! $this->instance_id ) { return parent::process_admin_options(); } // Check we are processing the correct form for this instance. if ( ! isset( $_REQUEST['instance_id'] ) || absint( $_REQUEST['instance_id'] ) !== $this->instance_id ) { // WPCS: input var ok, CSRF ok. return false; } $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 ), 'yes' ); } } abstract-wc-session.php 0000666 00000004457 15214036663 0011174 0 ustar 00 <?php /** * Handle data for the current customers session * * @class WC_Session * @version 2.0.0 * @package WooCommerce/Abstracts */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * WC_Session */ abstract class WC_Session { /** * Customer ID. * * @var int $_customer_id Customer ID. */ protected $_customer_id; /** * Session Data. * * @var array $_data Data array. */ protected $_data = array(); /** * Dirty when the session needs saving. * * @var bool $_dirty When something changes */ protected $_dirty = false; /** * Init hooks and session data. Extended by child classes. * * @since 3.3.0 */ public function init() {} /** * Cleanup session data. Extended by child classes. */ public function cleanup_sessions() {} /** * Magic get method. * * @param mixed $key Key to get. * @return mixed */ public function __get( $key ) { return $this->get( $key ); } /** * Magic set method. * * @param mixed $key Key to set. * @param mixed $value Value to set. */ public function __set( $key, $value ) { $this->set( $key, $value ); } /** * Magic isset method. * * @param mixed $key Key to check. * @return bool */ public function __isset( $key ) { return isset( $this->_data[ sanitize_title( $key ) ] ); } /** * Magic unset method. * * @param mixed $key Key to unset. */ public function __unset( $key ) { if ( isset( $this->_data[ $key ] ) ) { unset( $this->_data[ $key ] ); $this->_dirty = true; } } /** * Get a session variable. * * @param string $key Key to get. * @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 Key to set. * @param mixed $value Value to set. */ public function set( $key, $value ) { if ( $value !== $this->get( $key ) ) { $this->_data[ sanitize_key( $key ) ] = maybe_serialize( $value ); $this->_dirty = true; } } /** * Get customer ID. * * @return int */ public function get_customer_id() { return $this->_customer_id; } } abstract-wc-payment-gateway.php 0000666 00000033470 15214036663 0012622 0 ustar 00 <?php /** * Abstract payment gateway * * Hanldes generic payment gateway functionality which is extended by idividual payment gateways. * * @class WC_Payment_Gateway * @version 2.1.0 * @package WooCommerce/Abstracts */ use Automattic\Jetpack\Constants; 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 */ 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 = ''; /** * Pay button ID if supported. * * @var string */ public $pay_button_id = ''; /** * 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 ( count( $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() ); wc_back_link( __( 'Return to payments', 'woocommerce' ), admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ); echo '</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'; } /** * Return whether or not this gateway still requires setup to function. * * When this gateway is toggled on via AJAX, if this returns true a * redirect will occur to the settings page instead. * * @since 3.4.0 * @return bool */ public function needs_setup() { return false; } /** * Get the return url (thank you page). * * @param WC_Order $order Order object. * @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_checkout_url() ); } return apply_filters( 'woocommerce_get_return_url', $return_url, $order ); } /** * Get a link to the transaction on the 3rd party gateway site (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 (bool) $this->has_fields; } /** * 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 ); } /** * Return the gateway's pay button ID. * * @since 3.9.0 * @return string */ public function get_pay_button_id() { return sanitize_html_class( $this->pay_button_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 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 Order ID. * @param float $amount Refund amount. * @param string $reason Refund 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() { $description = $this->get_description(); if ( $description ) { echo wpautop( wptexturize( $description ) ); // @codingStandardsIgnoreLine. } 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 ), $feature, $this ); } /** * Can the order be refunded via this gateway? * * Should be extended by gateways to do their own checks. * * @param WC_Order $order Order object. * @return bool If false, the automatic refund button is hidden in the UI. */ public function can_refund_order( $order ) { return $order && $this->supports( 'refunds' ); } /** * Core credit card form which gateways can use if needed. Deprecated - inherit WC_Payment_Gateway_CC instead. * * @param array $args Arguments. * @param array $fields Fields. */ public function credit_card_form( $args = array(), $fields = array() ) { wc_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' . ( Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min' ) . '.js', WC_PLUGIN_FILE ), array( 'jquery' ), WC()->version ); wp_localize_script( 'woocommerce-tokenization-form', 'wc_tokenization_form_params', array( 'is_registration_required' => WC()->checkout()->is_registration_required(), 'is_logged_in' => is_user_logged_in(), ) ); } /** * 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 ); // @codingStandardsIgnoreLine } /** * 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() { $html = 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' ) ); echo apply_filters( 'woocommerce_payment_gateway_save_new_payment_method_option_html', $html, $this ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Add payment method via account screen. This should be extended by gateway plugins. * * @since 3.2.0 Included here from 3.2.0, but supported from 3.0.0. * @return array */ public function add_payment_method() { return array( 'result' => 'failure', 'redirect' => wc_get_endpoint_url( 'payment-methods' ), ); } } abstract-wc-order.php 0000666 00000202260 15214036663 0010614 0 ustar 00 <?php /** * Abstract Order * * Handles generic order data and database interaction which is extended by both * WC_Order (regular orders) and WC_Order_Refund (refunds are negative orders). * * @class WC_Abstract_Order * @version 3.0.0 * @package WooCommerce/Classes */ defined( 'ABSPATH' ) || exit; require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-order.php'; /** * WC_Abstract_Order class. */ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { use WC_Item_Totals; /** * Order Data array. This is the core order data exposed in APIs since 3.0.0. * * Notes: cart_tax = cart_tax is the new name for the legacy 'order_tax' * which is the tax for items only, not shipping. * * @since 3.0.0 * @var array */ protected $data = array( 'parent_id' => 0, 'status' => '', 'currency' => '', 'version' => '', 'prices_include_tax' => false, 'date_created' => null, 'date_modified' => null, 'discount_total' => 0, 'discount_tax' => 0, 'shipping_total' => 0, 'shipping_tax' => 0, 'cart_tax' => 0, 'total' => 0, 'total_tax' => 0, ); /** * Order items will be stored here, sometimes before they persist in the DB. * * @since 3.0.0 * @var array */ protected $items = array(); /** * Order items that need deleting are stored here. * * @since 3.0.0 * @var array */ protected $items_to_delete = array(); /** * Stores meta in cache for future reads. * * A group must be set to to enable caching. * * @var string */ protected $cache_group = 'orders'; /** * Which data store to load. * * @var string */ protected $data_store_name = 'order'; /** * This is the name of this object type. * * @var string */ protected $object_type = 'order'; /** * 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 read. */ public function __construct( $order = 0 ) { parent::__construct( $order ); if ( is_numeric( $order ) && $order > 0 ) { $this->set_id( $order ); } elseif ( $order instanceof self ) { $this->set_id( $order->get_id() ); } elseif ( ! empty( $order->ID ) ) { $this->set_id( $order->ID ); } else { $this->set_object_read( true ); } $this->data_store = WC_Data_Store::load( $this->data_store_name ); if ( $this->get_id() > 0 ) { $this->data_store->read( $this ); } } /** * Get internal type. * * @return string */ public function get_type() { return 'shop_order'; } /** * Get all class data in array format. * * @since 3.0.0 * @return array */ public function get_data() { return array_merge( array( 'id' => $this->get_id(), ), $this->data, array( 'meta_data' => $this->get_meta_data(), 'line_items' => $this->get_items( 'line_item' ), 'tax_lines' => $this->get_items( 'tax' ), 'shipping_lines' => $this->get_items( 'shipping' ), 'fee_lines' => $this->get_items( 'fee' ), 'coupon_lines' => $this->get_items( 'coupon' ), ) ); } /* |-------------------------------------------------------------------------- | CRUD methods |-------------------------------------------------------------------------- | | Methods which create, read, update and delete orders from the database. | Written in abstract fashion so that the way orders are stored can be | changed more easily in the future. | | A save method is included for convenience (chooses update or create based | on if the order exists yet). | */ /** * Save data to the database. * * @since 3.0.0 * @return int order ID */ public function save() { if ( ! $this->data_store ) { return $this->get_id(); } try { /** * Trigger action before saving to the DB. Allows you to adjust object props before save. * * @param WC_Data $this The object being saved. * @param WC_Data_Store_WP $data_store THe data store persisting the data. */ do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store ); if ( $this->get_id() ) { $this->data_store->update( $this ); } else { $this->data_store->create( $this ); } $this->save_items(); /** * Trigger action after saving to the DB. * * @param WC_Data $this The object being saved. * @param WC_Data_Store_WP $data_store THe data store persisting the data. */ do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store ); } catch ( Exception $e ) { $this->handle_exception( $e, __( 'Error saving order.', 'woocommerce' ) ); } return $this->get_id(); } /** * Log an error about this order is exception is encountered. * * @param Exception $e Exception object. * @param string $message Message regarding exception thrown. * @since 3.7.0 */ protected function handle_exception( $e, $message = 'Error' ) { wc_get_logger()->error( $message, array( 'order' => $this, 'error' => $e, ) ); } /** * Save all order items which are part of this order. */ protected function save_items() { $items_changed = false; foreach ( $this->items_to_delete as $item ) { $item->delete(); $items_changed = true; } $this->items_to_delete = array(); // Add/save items. foreach ( $this->items as $item_group => $items ) { if ( is_array( $items ) ) { $items = array_filter( $items ); foreach ( $items as $item_key => $item ) { $item->set_order_id( $this->get_id() ); $item_id = $item->save(); // If ID changed (new item saved to DB)... if ( $item_id !== $item_key ) { $this->items[ $item_group ][ $item_id ] = $item; unset( $this->items[ $item_group ][ $item_key ] ); $items_changed = true; } } } } if ( $items_changed ) { delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' ); } } /* |-------------------------------------------------------------------------- | Getters |-------------------------------------------------------------------------- */ /** * Get parent order ID. * * @since 3.0.0 * @param string $context View or edit context. * @return integer */ public function get_parent_id( $context = 'view' ) { return $this->get_prop( 'parent_id', $context ); } /** * Gets order currency. * * @param string $context View or edit context. * @return string */ public function get_currency( $context = 'view' ) { return $this->get_prop( 'currency', $context ); } /** * Get order_version. * * @param string $context View or edit context. * @return string */ public function get_version( $context = 'view' ) { return $this->get_prop( 'version', $context ); } /** * Get prices_include_tax. * * @param string $context View or edit context. * @return bool */ public function get_prices_include_tax( $context = 'view' ) { return $this->get_prop( 'prices_include_tax', $context ); } /** * Get date_created. * * @param string $context View or edit context. * @return WC_DateTime|NULL object if the date is set or null if there is no date. */ public function get_date_created( $context = 'view' ) { return $this->get_prop( 'date_created', $context ); } /** * Get date_modified. * * @param string $context View or edit context. * @return WC_DateTime|NULL object if the date is set or null if there is no date. */ public function get_date_modified( $context = 'view' ) { return $this->get_prop( 'date_modified', $context ); } /** * Return the order statuses without wc- internal prefix. * * @param string $context View or edit context. * @return string */ public function get_status( $context = 'view' ) { $status = $this->get_prop( 'status', $context ); if ( empty( $status ) && 'view' === $context ) { // In view context, return the default status if no status has been set. $status = apply_filters( 'woocommerce_default_order_status', 'pending' ); } return $status; } /** * Get discount_total. * * @param string $context View or edit context. * @return string */ public function get_discount_total( $context = 'view' ) { return $this->get_prop( 'discount_total', $context ); } /** * Get discount_tax. * * @param string $context View or edit context. * @return string */ public function get_discount_tax( $context = 'view' ) { return $this->get_prop( 'discount_tax', $context ); } /** * Get shipping_total. * * @param string $context View or edit context. * @return string */ public function get_shipping_total( $context = 'view' ) { return $this->get_prop( 'shipping_total', $context ); } /** * Get shipping_tax. * * @param string $context View or edit context. * @return string */ public function get_shipping_tax( $context = 'view' ) { return $this->get_prop( 'shipping_tax', $context ); } /** * Gets cart tax amount. * * @param string $context View or edit context. * @return float */ public function get_cart_tax( $context = 'view' ) { return $this->get_prop( 'cart_tax', $context ); } /** * Gets order grand total. incl. taxes. Used in gateways. * * @param string $context View or edit context. * @return float */ public function get_total( $context = 'view' ) { return $this->get_prop( 'total', $context ); } /** * Get total tax amount. Alias for get_order_tax(). * * @param string $context View or edit context. * @return float */ public function get_total_tax( $context = 'view' ) { return $this->get_prop( 'total_tax', $context ); } /* |-------------------------------------------------------------------------- | Non-CRUD 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 ( $ex_tax ) { $total_discount = $this->get_discount_total(); } else { $total_discount = $this->get_discount_total() + $this->get_discount_tax(); } return apply_filters( 'woocommerce_order_get_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this ); } /** * Gets order subtotal. * * @return float */ public function get_subtotal() { $subtotal = 0; foreach ( $this->get_items() as $item ) { $subtotal += $item->get_subtotal(); } return apply_filters( 'woocommerce_order_get_subtotal', (float) $subtotal, $this ); } /** * Get taxes, merged by code, formatted ready for output. * * @return array */ public function get_tax_totals() { $tax_totals = array(); foreach ( $this->get_items( 'tax' ) as $key => $tax ) { $code = $tax->get_rate_code(); 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->get_rate_id(); $tax_totals[ $code ]->is_compound = $tax->is_compound(); $tax_totals[ $code ]->label = $tax->get_label(); $tax_totals[ $code ]->amount += (float) $tax->get_tax_total() + (float) $tax->get_shipping_tax_total(); $tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_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_get_tax_totals', $tax_totals, $this ); } /** * Get all valid statuses for this order * * @since 3.0.0 * @return array Internal status keys e.g. 'wc-processing' */ protected function get_valid_statuses() { return array_keys( wc_get_order_statuses() ); } /** * Get user ID. Used by orders, not other order types like refunds. * * @param string $context View or edit context. * @return int */ public function get_user_id( $context = 'view' ) { return 0; } /** * Get user. Used by orders, not other order types like refunds. * * @return WP_User|false */ public function get_user() { return false; } /* |-------------------------------------------------------------------------- | Setters |-------------------------------------------------------------------------- | | Functions for setting order data. These should not update anything in the | database itself and should only change what is stored in the class | object. However, for backwards compatibility pre 3.0.0 some of these | setters may handle both. */ /** * Set parent order ID. * * @since 3.0.0 * @param int $value Value to set. * @throws WC_Data_Exception Exception thrown if parent ID does not exist or is invalid. */ public function set_parent_id( $value ) { if ( $value && ( $value === $this->get_id() || ! wc_get_order( $value ) ) ) { $this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) ); } $this->set_prop( 'parent_id', absint( $value ) ); } /** * Set order status. * * @since 3.0.0 * @param string $new_status Status to change the order to. No internal wc- prefix is required. * @return array details of change */ public function set_status( $new_status ) { $old_status = $this->get_status(); $new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status; // If setting the status, ensure it's set to a valid status. if ( true === $this->object_read ) { // Only allow valid new status. if ( ! in_array( 'wc-' . $new_status, $this->get_valid_statuses(), true ) && 'trash' !== $new_status ) { $new_status = 'pending'; } // If the old status is set but unknown (e.g. draft) assume its pending for action usage. if ( $old_status && ! in_array( 'wc-' . $old_status, $this->get_valid_statuses(), true ) && 'trash' !== $old_status ) { $old_status = 'pending'; } } $this->set_prop( 'status', $new_status ); return array( 'from' => $old_status, 'to' => $new_status, ); } /** * Set order_version. * * @param string $value Value to set. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_version( $value ) { $this->set_prop( 'version', $value ); } /** * Set order_currency. * * @param string $value Value to set. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_currency( $value ) { if ( $value && ! in_array( $value, array_keys( get_woocommerce_currencies() ), true ) ) { $this->error( 'order_invalid_currency', __( 'Invalid currency code', 'woocommerce' ) ); } $this->set_prop( 'currency', $value ? $value : get_woocommerce_currency() ); } /** * Set prices_include_tax. * * @param bool $value Value to set. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_prices_include_tax( $value ) { $this->set_prop( 'prices_include_tax', (bool) $value ); } /** * Set date_created. * * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_date_created( $date = null ) { $this->set_date_prop( 'date_created', $date ); } /** * Set date_modified. * * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_date_modified( $date = null ) { $this->set_date_prop( 'date_modified', $date ); } /** * Set discount_total. * * @param string $value Value to set. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_discount_total( $value ) { $this->set_prop( 'discount_total', wc_format_decimal( $value ) ); } /** * Set discount_tax. * * @param string $value Value to set. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_discount_tax( $value ) { $this->set_prop( 'discount_tax', wc_format_decimal( $value ) ); } /** * Set shipping_total. * * @param string $value Value to set. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_shipping_total( $value ) { $this->set_prop( 'shipping_total', wc_format_decimal( $value ) ); } /** * Set shipping_tax. * * @param string $value Value to set. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_shipping_tax( $value ) { $this->set_prop( 'shipping_tax', wc_format_decimal( $value ) ); $this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() ); } /** * Set cart tax. * * @param string $value Value to set. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_cart_tax( $value ) { $this->set_prop( 'cart_tax', wc_format_decimal( $value ) ); $this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() ); } /** * Sets order tax (sum of cart and shipping tax). Used internally only. * * @param string $value Value to set. * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ protected function set_total_tax( $value ) { $this->set_prop( 'total_tax', wc_format_decimal( $value ) ); } /** * Set total. * * @param string $value Value to set. * @param string $deprecated Function used to set different totals based on this. * * @return bool|void * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_total( $value, $deprecated = '' ) { if ( $deprecated ) { wc_deprecated_argument( 'total_type', '3.0', 'Use dedicated total setter methods instead.' ); return $this->legacy_set_total( $value, $deprecated ); } $this->set_prop( 'total', wc_format_decimal( $value, wc_get_price_decimals() ) ); } /* |-------------------------------------------------------------------------- | Order Item Handling |-------------------------------------------------------------------------- | | Order items are used for products, taxes, shipping, and fees within | each order. */ /** * 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 ) { if ( ! empty( $type ) ) { $this->data_store->delete_items( $this, $type ); $group = $this->type_to_group( $type ); if ( $group ) { unset( $this->items[ $group ] ); } } else { $this->data_store->delete_items( $this ); $this->items = array(); } } /** * Convert a type to a types group. * * @param string $type type to lookup. * @return string */ protected function type_to_group( $type ) { $type_to_group = apply_filters( 'woocommerce_order_type_to_group', array( 'line_item' => 'line_items', 'tax' => 'tax_lines', 'shipping' => 'shipping_lines', 'fee' => 'fee_lines', 'coupon' => 'coupon_lines', ) ); return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : ''; } /** * Return an array of items/products within this order. * * @param string|array $types Types of line items to get (array or string). * @return WC_Order_Item[] */ public function get_items( $types = 'line_item' ) { $items = array(); $types = array_filter( (array) $types ); foreach ( $types as $type ) { $group = $this->type_to_group( $type ); if ( $group ) { if ( ! isset( $this->items[ $group ] ) ) { $this->items[ $group ] = array_filter( $this->data_store->read_items( $this, $type ) ); } // Don't use array_merge here because keys are numeric. $items = $items + $this->items[ $group ]; } } return apply_filters( 'woocommerce_order_get_items', $items, $this, $types ); } /** * Return array of values for calculations. * * @param string $field Field name to return. * * @return array Array of values. */ protected function get_values_for_total( $field ) { $items = array_map( function ( $item ) use ( $field ) { return wc_add_number_precision( $item[ $field ], false ); }, array_values( $this->get_items() ) ); return $items; } /** * Return an array of coupons within this order. * * @since 3.7.0 * @return WC_Order_Item_Coupon[] */ public function get_coupons() { return $this->get_items( 'coupon' ); } /** * Return an array of fees within this order. * * @return WC_Order_item_Fee[] */ public function get_fees() { return $this->get_items( 'fee' ); } /** * Return an array of taxes within this order. * * @return WC_Order_Item_Tax[] */ public function get_taxes() { return $this->get_items( 'tax' ); } /** * Return an array of shipping costs within this order. * * @return WC_Order_Item_Shipping[] */ public function get_shipping_methods() { return $this->get_items( 'shipping' ); } /** * Gets formatted shipping method title. * * @return string */ public function get_shipping_method() { $names = array(); foreach ( $this->get_shipping_methods() as $shipping_method ) { $names[] = $shipping_method->get_name(); } return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this ); } /** * Get used coupon codes only. * * @since 3.7.0 * @return array */ public function get_coupon_codes() { $coupon_codes = array(); $coupons = $this->get_items( 'coupon' ); if ( $coupons ) { foreach ( $coupons as $coupon ) { $coupon_codes[] = $coupon->get_code(); } } return $coupon_codes; } /** * Gets the count of order items of a certain type. * * @param string $item_type Item type to lookup. * @return int|string */ public function get_item_count( $item_type = '' ) { $items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type ); $count = 0; foreach ( $items as $item ) { $count += $item->get_quantity(); } return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this ); } /** * Get an order item object, based on its type. * * @since 3.0.0 * @param int $item_id ID of item to get. * @param bool $load_from_db Prior to 3.2 this item was loaded direct from WC_Order_Factory, not this object. This param is here for backwards compatility with that. If false, uses the local items variable instead. * @return WC_Order_Item|false */ public function get_item( $item_id, $load_from_db = true ) { if ( $load_from_db ) { return WC_Order_Factory::get_order_item( $item_id ); } // Search for item id. if ( $this->items ) { foreach ( $this->items as $group => $items ) { if ( isset( $items[ $item_id ] ) ) { return $items[ $item_id ]; } } } // Load all items of type and cache. $type = $this->data_store->get_order_item_type( $this, $item_id ); if ( ! $type ) { return false; } $items = $this->get_items( $type ); return ! empty( $items[ $item_id ] ) ? $items[ $item_id ] : false; } /** * Get key for where a certain item type is stored in _items. * * @since 3.0.0 * @param string $item object Order item (product, shipping, fee, coupon, tax). * @return string */ protected function get_items_key( $item ) { if ( is_a( $item, 'WC_Order_Item_Product' ) ) { return 'line_items'; } elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) { return 'fee_lines'; } elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) { return 'shipping_lines'; } elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) { return 'tax_lines'; } elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) { return 'coupon_lines'; } return apply_filters( 'woocommerce_get_items_key', '', $item ); } /** * Remove item from the order. * * @param int $item_id Item ID to delete. * @return false|void */ public function remove_item( $item_id ) { $item = $this->get_item( $item_id, false ); $items_key = $item ? $this->get_items_key( $item ) : false; if ( ! $items_key ) { return false; } // Unset and remove later. $this->items_to_delete[] = $item; unset( $this->items[ $items_key ][ $item->get_id() ] ); } /** * Adds an order item to this order. The order item will not persist until save. * * @since 3.0.0 * @param WC_Order_Item $item Order item object (product, shipping, fee, coupon, tax). * @return false|void */ public function add_item( $item ) { $items_key = $this->get_items_key( $item ); if ( ! $items_key ) { return false; } // Make sure existing items are loaded so we can append this new one. if ( ! isset( $this->items[ $items_key ] ) ) { $this->items[ $items_key ] = $this->get_items( $item->get_type() ); } // Set parent. $item->set_order_id( $this->get_id() ); // Append new row with generated temporary ID. $item_id = $item->get_id(); if ( $item_id ) { $this->items[ $items_key ][ $item_id ] = $item; } else { $this->items[ $items_key ][ 'new:' . $items_key . count( $this->items[ $items_key ] ) ] = $item; } } /** * Check and records coupon usage tentatively so that counts validation is correct. Display an error if coupon usage limit has been reached. * * If you are using this method, make sure to `release_held_coupons` in case an Exception is thrown. * * @throws Exception When not able to apply coupon. * * @param string $billing_email Billing email of order. */ public function hold_applied_coupons( $billing_email ) { $held_keys = array(); $held_keys_for_user = array(); $error = null; try { foreach ( WC()->cart->get_applied_coupons() as $code ) { $coupon = new WC_Coupon( $code ); if ( ! $coupon->get_data_store() ) { continue; } // Hold coupon for when global coupon usage limit is present. if ( 0 < $coupon->get_usage_limit() ) { $held_key = $this->hold_coupon( $coupon ); if ( $held_key ) { $held_keys[ $coupon->get_id() ] = $held_key; } } // Hold coupon for when usage limit per customer is enabled. if ( 0 < $coupon->get_usage_limit_per_user() ) { if ( ! isset( $user_ids_and_emails ) ) { $user_alias = get_current_user_id() ? wp_get_current_user()->ID : sanitize_email( $billing_email ); $user_ids_and_emails = $this->get_billing_and_current_user_aliases( $billing_email ); } $held_key_for_user = $this->hold_coupon_for_users( $coupon, $user_ids_and_emails, $user_alias ); if ( $held_key_for_user ) { $held_keys_for_user[ $coupon->get_id() ] = $held_key_for_user; } } } } catch ( Exception $e ) { $error = $e; } finally { // Even in case of error, we will save keys for whatever coupons that were held so our data remains accurate. // We save them in bulk instead of one by one for performance reasons. if ( 0 < count( $held_keys_for_user ) || 0 < count( $held_keys ) ) { $this->get_data_store()->set_coupon_held_keys( $this, $held_keys, $held_keys_for_user ); } if ( $error instanceof Exception ) { throw $error; } } } /** * Hold coupon if a global usage limit is defined. * * @param WC_Coupon $coupon Coupon object. * * @return string Meta key which indicates held coupon. * @throws Exception When can't be held. */ private function hold_coupon( $coupon ) { $result = $coupon->get_data_store()->check_and_hold_coupon( $coupon ); if ( false === $result ) { // translators: Actual coupon code. throw new Exception( sprintf( __( 'An unexpected error happened while applying the Coupon %s.', 'woocommerce' ), esc_html( $coupon->get_code() ) ) ); } elseif ( 0 === $result ) { // translators: Actual coupon code. throw new Exception( sprintf( __( 'Coupon %s was used in another transaction during this checkout, and coupon usage limit is reached. Please remove the coupon and try again.', 'woocommerce' ), esc_html( $coupon->get_code() ) ) ); } return $result; } /** * Hold coupon if usage limit per customer is defined. * * @param WC_Coupon $coupon Coupon object. * @param array $user_ids_and_emails Array of user Id and emails to check for usage limit. * @param string $user_alias User ID or email to use to record current usage. * * @return string Meta key which indicates held coupon. * @throws Exception When coupon can't be held. */ private function hold_coupon_for_users( $coupon, $user_ids_and_emails, $user_alias ) { $result = $coupon->get_data_store()->check_and_hold_coupon_for_user( $coupon, $user_ids_and_emails, $user_alias ); if ( false === $result ) { // translators: Actual coupon code. throw new Exception( sprintf( __( 'An unexpected error happened while applying the Coupon %s.', 'woocommerce' ), esc_html( $coupon->get_code() ) ) ); } elseif ( 0 === $result ) { // translators: Actual coupon code. throw new Exception( sprintf( __( 'You have used this coupon %s in another transaction during this checkout, and coupon usage limit is reached. Please remove the coupon and try again.', 'woocommerce' ), esc_html( $coupon->get_code() ) ) ); } return $result; } /** * Helper method to get all aliases for current user and provide billing email. * * @param string $billing_email Billing email provided in form. * * @return array Array of all aliases. * @throws Exception When validation fails. */ private function get_billing_and_current_user_aliases( $billing_email ) { $emails = array( $billing_email ); if ( get_current_user_id() ) { $emails[] = wp_get_current_user()->user_email; } $emails = array_unique( array_map( 'strtolower', array_map( 'sanitize_email', $emails ) ) ); $customer_data_store = WC_Data_Store::load( 'customer' ); $user_ids = $customer_data_store->get_user_ids_for_billing_email( $emails ); return array_merge( $user_ids, $emails ); } /** * Apply a coupon to the order and recalculate totals. * * @since 3.2.0 * @param string|WC_Coupon $raw_coupon Coupon code or object. * @return true|WP_Error True if applied, error if not. */ public function apply_coupon( $raw_coupon ) { if ( is_a( $raw_coupon, 'WC_Coupon' ) ) { $coupon = $raw_coupon; } elseif ( is_string( $raw_coupon ) ) { $code = wc_format_coupon_code( $raw_coupon ); $coupon = new WC_Coupon( $code ); if ( $coupon->get_code() !== $code ) { return new WP_Error( 'invalid_coupon', __( 'Invalid coupon code', 'woocommerce' ) ); } } else { return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) ); } // Check to make sure coupon is not already applied. $applied_coupons = $this->get_items( 'coupon' ); foreach ( $applied_coupons as $applied_coupon ) { if ( $applied_coupon->get_code() === $coupon->get_code() ) { return new WP_Error( 'invalid_coupon', __( 'Coupon code already applied!', 'woocommerce' ) ); } } $discounts = new WC_Discounts( $this ); $applied = $discounts->apply_coupon( $coupon ); if ( is_wp_error( $applied ) ) { return $applied; } $data_store = $coupon->get_data_store(); // Check specific for guest checkouts here as well since WC_Cart handles that seperately in check_customer_coupons. if ( $data_store && 0 === $this->get_customer_id() ) { $usage_count = $data_store->get_usage_by_email( $coupon, $this->get_billing_email() ); if ( 0 < $coupon->get_usage_limit_per_user() && $usage_count >= $coupon->get_usage_limit_per_user() ) { return new WP_Error( 'invalid_coupon', $coupon->get_coupon_error( 106 ), array( 'status' => 400, ) ); } } $this->set_coupon_discount_amounts( $discounts ); $this->save(); // Recalculate totals and taxes. $this->recalculate_coupons(); // Record usage so counts and validation is correct. $used_by = $this->get_user_id(); if ( ! $used_by ) { $used_by = $this->get_billing_email(); } $coupon->increase_usage_count( $used_by ); return true; } /** * Remove a coupon from the order and recalculate totals. * * Coupons affect line item totals, but there is no relationship between * coupon and line total, so to remove a coupon we need to work from the * line subtotal (price before discount) and re-apply all coupons in this * order. * * Manual discounts are not affected; those are separate and do not affect * stored line totals. * * @since 3.2.0 * @param string $code Coupon code. * @return void */ public function remove_coupon( $code ) { $coupons = $this->get_items( 'coupon' ); // Remove the coupon line. foreach ( $coupons as $item_id => $coupon ) { if ( $coupon->get_code() === $code ) { $this->remove_item( $item_id ); $coupon_object = new WC_Coupon( $code ); $coupon_object->decrease_usage_count( $this->get_user_id() ); $this->recalculate_coupons(); break; } } } /** * Apply all coupons in this order again to all line items. * This method is public since WooCommerce 3.8.0. * * @since 3.2.0 */ public function recalculate_coupons() { // Reset line item totals. foreach ( $this->get_items() as $item ) { $item->set_total( $item->get_subtotal() ); $item->set_total_tax( $item->get_subtotal_tax() ); } $discounts = new WC_Discounts( $this ); foreach ( $this->get_items( 'coupon' ) as $coupon_item ) { $coupon_code = $coupon_item->get_code(); $coupon_id = wc_get_coupon_id_by_code( $coupon_code ); // If we have a coupon ID (loaded via wc_get_coupon_id_by_code) we can simply load the new coupon object using the ID. if ( $coupon_id ) { $coupon_object = new WC_Coupon( $coupon_id ); } else { // If we do not have a coupon ID (was it virtual? has it been deleted?) we must create a temporary coupon using what data we have stored during checkout. $coupon_object = new WC_Coupon(); $coupon_object->set_props( (array) $coupon_item->get_meta( 'coupon_data', true ) ); $coupon_object->set_code( $coupon_code ); $coupon_object->set_virtual( true ); // If there is no coupon amount (maybe dynamic?), set it to the given **discount** amount so the coupon's same value is applied. if ( ! $coupon_object->get_amount() ) { // If the order originally had prices including tax, remove the discount + discount tax. if ( $this->get_prices_include_tax() ) { $coupon_object->set_amount( $coupon_item->get_discount() + $coupon_item->get_discount_tax() ); } else { $coupon_object->set_amount( $coupon_item->get_discount() ); } $coupon_object->set_discount_type( 'fixed_cart' ); } } /** * Allow developers to filter this coupon before it get's re-applied to the order. * * @since 3.2.0 */ $coupon_object = apply_filters( 'woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this ); if ( $coupon_object ) { $discounts->apply_coupon( $coupon_object, false ); } } $this->set_coupon_discount_amounts( $discounts ); $this->set_item_discount_amounts( $discounts ); // Recalculate totals and taxes. $this->calculate_totals( true ); } /** * After applying coupons via the WC_Discounts class, update line items. * * @since 3.2.0 * @param WC_Discounts $discounts Discounts class. */ protected function set_item_discount_amounts( $discounts ) { $item_discounts = $discounts->get_discounts_by_item(); if ( $item_discounts ) { foreach ( $item_discounts as $item_id => $amount ) { $item = $this->get_item( $item_id, false ); // If the prices include tax, discounts should be taken off the tax inclusive prices like in the cart. if ( $this->get_prices_include_tax() && wc_tax_enabled() ) { $taxes = WC_Tax::calc_tax( $amount, WC_Tax::get_rates( $item->get_tax_class() ), true ); if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) { $taxes = array_map( 'wc_round_tax_total', $taxes ); } $amount = $amount - array_sum( $taxes ); $item->set_total( max( 0, round( $item->get_total() - $amount, wc_get_price_decimals() ) ) ); } else { $item->set_total( max( 0, $item->get_total() - $amount ) ); } } } } /** * After applying coupons via the WC_Discounts class, update or create coupon items. * * @since 3.2.0 * @param WC_Discounts $discounts Discounts class. */ protected function set_coupon_discount_amounts( $discounts ) { $coupons = $this->get_items( 'coupon' ); $coupon_code_to_id = wc_list_pluck( $coupons, 'get_id', 'get_code' ); $all_discounts = $discounts->get_discounts(); $coupon_discounts = $discounts->get_discounts_by_coupon(); if ( $coupon_discounts ) { foreach ( $coupon_discounts as $coupon_code => $amount ) { $item_id = isset( $coupon_code_to_id[ $coupon_code ] ) ? $coupon_code_to_id[ $coupon_code ] : 0; if ( ! $item_id ) { $coupon_item = new WC_Order_Item_Coupon(); $coupon_item->set_code( $coupon_code ); } else { $coupon_item = $this->get_item( $item_id, false ); } $discount_tax = 0; // Work out how much tax has been removed as a result of the discount from this coupon. foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) { $item = $this->get_item( $item_id, false ); if ( $this->get_prices_include_tax() && wc_tax_enabled() ) { $taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ), true ); if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) { $taxes = array_map( 'wc_round_tax_total', $taxes ); } $discount_tax += array_sum( $taxes ); $amount = $amount - array_sum( $taxes ); } else { $taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ) ); if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) { $taxes = array_map( 'wc_round_tax_total', $taxes ); } $discount_tax += array_sum( $taxes ); } } $coupon_item->set_discount( $amount ); $coupon_item->set_discount_tax( $discount_tax ); $this->add_item( $coupon_item ); } } } /** * Add a product line item to the order. This is the only line item type with * its own method because it saves looking up order amounts (costs are added up for you). * * @param WC_Product $product Product object. * @param int $qty Quantity to add. * @param array $args Args for the added product. * @return int * @throws WC_Data_Exception Exception thrown if the item cannot be added to the cart. */ public function add_product( $product, $qty = 1, $args = array() ) { if ( $product ) { $default_args = array( 'name' => $product->get_name(), 'tax_class' => $product->get_tax_class(), 'product_id' => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(), 'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0, 'variation' => $product->is_type( 'variation' ) ? $product->get_attributes() : array(), 'subtotal' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ), 'total' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ), 'quantity' => $qty, ); } else { $default_args = array( 'quantity' => $qty, ); } $args = wp_parse_args( $args, $default_args ); // BW compatibility with old args. if ( isset( $args['totals'] ) ) { foreach ( $args['totals'] as $key => $value ) { if ( 'tax' === $key ) { $args['total_tax'] = $value; } elseif ( 'tax_data' === $key ) { $args['taxes'] = $value; } else { $args[ $key ] = $value; } } } $item = new WC_Order_Item_Product(); $item->set_props( $args ); $item->set_backorder_meta(); $item->set_order_id( $this->get_id() ); $item->save(); $this->add_item( $item ); wc_do_deprecated_action( 'woocommerce_order_add_product', array( $this->get_id(), $item->get_id(), $product, $qty, $args ), '3.0', 'woocommerce_new_order_item action instead' ); delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' ); return $item->get_id(); } /* |-------------------------------------------------------------------------- | Payment Token Handling |-------------------------------------------------------------------------- | | Payment tokens are hashes used to take payments by certain gateways. | */ /** * Add a payment token to an order * * @since 2.6 * @param WC_Payment_Token $token Payment token object. * @return boolean|int The new token ID or false if it failed. */ public function add_payment_token( $token ) { if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) { return false; } $token_ids = $this->data_store->get_payment_token_ids( $this ); $token_ids[] = $token->get_id(); $this->data_store->update_payment_token_ids( $this, $token_ids ); do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids ); return $token->get_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 $this->data_store->get_payment_token_ids( $this ); } /* |-------------------------------------------------------------------------- | Calculations. |-------------------------------------------------------------------------- | | These methods calculate order totals and taxes based on the current data. | */ /** * 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->get_total(); } $this->set_shipping_total( $shipping_total ); $this->save(); return $this->get_shipping_total(); } /** * 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 ( is_callable( array( $item, 'get_tax_status' ) ) && in_array( $item->get_tax_status(), array( 'taxable', 'shipping' ), true ) ) { $found_tax_classes[] = $item->get_tax_class(); } } return array_unique( $found_tax_classes ); } /** * Get tax location for this order. * * @since 3.2.0 * @param array $args array Override the location. * @return array */ protected function get_tax_location( $args = array() ) { $tax_based_on = get_option( 'woocommerce_tax_based_on' ); if ( 'shipping' === $tax_based_on && ! $this->get_shipping_country() ) { $tax_based_on = 'billing'; } $args = wp_parse_args( $args, array( 'country' => 'billing' === $tax_based_on ? $this->get_billing_country() : $this->get_shipping_country(), 'state' => 'billing' === $tax_based_on ? $this->get_billing_state() : $this->get_shipping_state(), 'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(), 'city' => 'billing' === $tax_based_on ? $this->get_billing_city() : $this->get_shipping_city(), ) ); // Default to base. if ( 'base' === $tax_based_on || empty( $args['country'] ) ) { $args['country'] = WC()->countries->get_base_country(); $args['state'] = WC()->countries->get_base_state(); $args['postcode'] = WC()->countries->get_base_postcode(); $args['city'] = WC()->countries->get_base_city(); } return $args; } /** * Calculate taxes for all line items and shipping, and store the totals and tax rows. * * If by default the taxes are based on the shipping address and the current order doesn't * have any, it would use the billing address rather than using the Shopping base location. * * Will use the base country unless customer addresses are set. * * @param array $args Added in 3.0.0 to pass things like location. */ public function calculate_taxes( $args = array() ) { do_action( 'woocommerce_order_before_calculate_taxes', $args, $this ); $calculate_tax_for = $this->get_tax_location( $args ); $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' ); if ( 'inherit' === $shipping_tax_class ) { $found_classes = array_intersect( array_merge( array( '' ), WC_Tax::get_tax_class_slugs() ), $this->get_items_tax_classes() ); $shipping_tax_class = count( $found_classes ) ? current( $found_classes ) : false; } $is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta( 'is_vat_exempt' ), $this ); // Trigger tax recalculation for all items. foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) { if ( ! $is_vat_exempt ) { $item->calculate_taxes( $calculate_tax_for ); } else { $item->set_taxes( false ); } } foreach ( $this->get_shipping_methods() as $item_id => $item ) { if ( false !== $shipping_tax_class && ! $is_vat_exempt ) { $item->calculate_taxes( array_merge( $calculate_tax_for, array( 'tax_class' => $shipping_tax_class ) ) ); } else { $item->set_taxes( false ); } } $this->update_taxes(); } /** * Calculate fees for all line items. * * @return float Fee total. */ public function get_total_fees() { return array_reduce( $this->get_fees(), function( $carry, $item ) { return $carry + $item->get_total(); } ); } /** * Update tax lines for the order based on the line item taxes themselves. */ public function update_taxes() { $cart_taxes = array(); $shipping_taxes = array(); $existing_taxes = $this->get_taxes(); $saved_rate_ids = array(); foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) { $taxes = $item->get_taxes(); foreach ( $taxes['total'] as $tax_rate_id => $tax ) { $tax_amount = (float) $tax; if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) { $tax_amount = wc_round_tax_total( $tax_amount ); } $cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount; } } foreach ( $this->get_shipping_methods() as $item_id => $item ) { $taxes = $item->get_taxes(); foreach ( $taxes['total'] as $tax_rate_id => $tax ) { $tax_amount = (float) $tax; if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) { $tax_amount = wc_round_tax_total( $tax_amount ); } $shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount; } } foreach ( $existing_taxes as $tax ) { // Remove taxes which no longer exist for cart/shipping. if ( ( ! array_key_exists( $tax->get_rate_id(), $cart_taxes ) && ! array_key_exists( $tax->get_rate_id(), $shipping_taxes ) ) || in_array( $tax->get_rate_id(), $saved_rate_ids, true ) ) { $this->remove_item( $tax->get_id() ); continue; } $saved_rate_ids[] = $tax->get_rate_id(); $tax->set_tax_total( isset( $cart_taxes[ $tax->get_rate_id() ] ) ? $cart_taxes[ $tax->get_rate_id() ] : 0 ); $tax->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax->get_rate_id() ] ) ? $shipping_taxes[ $tax->get_rate_id() ] : 0 ); $tax->save(); } $new_rate_ids = wp_parse_id_list( array_diff( array_keys( $cart_taxes + $shipping_taxes ), $saved_rate_ids ) ); // New taxes. foreach ( $new_rate_ids as $tax_rate_id ) { $item = new WC_Order_Item_Tax(); $item->set_rate( $tax_rate_id ); $item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 ); $item->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 ); $this->add_item( $item ); } $this->set_shipping_tax( wc_round_tax_total( array_sum( $shipping_taxes ) ) ); $this->set_cart_tax( wc_round_tax_total( array_sum( $cart_taxes ) ) ); $this->save(); } /** * Helper function. * If you add all items in this order in cart again, this would be the cart subtotal (assuming all other settings are same). * * @return float Cart subtotal. */ protected function get_cart_subtotal_for_order() { return wc_remove_number_precision( $this->get_rounded_items_total( $this->get_values_for_total( 'subtotal' ) ) ); } /** * Helper function. * If you add all items in this order in cart again, this would be the cart total (assuming all other settings are same). * * @return float Cart total. */ protected function get_cart_total_for_order() { return wc_remove_number_precision( $this->get_rounded_items_total( $this->get_values_for_total( 'total' ) ) ); } /** * 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 ) { do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this ); $fees_total = 0; $shipping_total = 0; $cart_subtotal_tax = 0; $cart_total_tax = 0; $cart_subtotal = $this->get_cart_subtotal_for_order(); $cart_total = $this->get_cart_total_for_order(); // Sum shipping costs. foreach ( $this->get_shipping_methods() as $shipping ) { $shipping_total += round( $shipping->get_total(), wc_get_price_decimals() ); } $this->set_shipping_total( $shipping_total ); // Sum fee costs. foreach ( $this->get_fees() as $item ) { $fee_total = $item->get_total(); if ( 0 > $fee_total ) { $max_discount = round( $cart_total + $fees_total + $shipping_total, wc_get_price_decimals() ) * -1; if ( $fee_total < $max_discount && 0 > $max_discount ) { $item->set_total( $max_discount ); } } $fees_total += $item->get_total(); } // Calculate taxes for items, shipping, discounts. Note; this also triggers save(). if ( $and_taxes ) { $this->calculate_taxes(); } // Sum taxes again so we can work out how much tax was discounted. This uses original values, not those possibly rounded to 2dp. foreach ( $this->get_items() as $item ) { $taxes = $item->get_taxes(); foreach ( $taxes['total'] as $tax_rate_id => $tax ) { $cart_total_tax += (float) $tax; } foreach ( $taxes['subtotal'] as $tax_rate_id => $tax ) { $cart_subtotal_tax += (float) $tax; } } $this->set_discount_total( $cart_subtotal - $cart_total ); $this->set_discount_tax( wc_round_tax_total( $cart_subtotal_tax - $cart_total_tax ) ); $this->set_total( round( $cart_total + $fees_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ) ); do_action( 'woocommerce_order_after_calculate_totals', $and_taxes, $this ); $this->save(); return $this->get_total(); } /** * Get item subtotal - this is the cost before discount. * * @param object $item Item to get total from. * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_item_subtotal( $item, $inc_tax = false, $round = true ) { $subtotal = 0; if ( is_callable( array( $item, 'get_subtotal' ) ) && $item->get_quantity() ) { if ( $inc_tax ) { $subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / $item->get_quantity(); } else { $subtotal = floatval( $item->get_subtotal() ) / $item->get_quantity(); } $subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal; } return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round ); } /** * Get line subtotal - this is the cost before discount. * * @param object $item Item to get total from. * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_line_subtotal( $item, $inc_tax = false, $round = true ) { $subtotal = 0; if ( is_callable( array( $item, 'get_subtotal' ) ) ) { if ( $inc_tax ) { $subtotal = $item->get_subtotal() + $item->get_subtotal_tax(); } else { $subtotal = $item->get_subtotal(); } $subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal; } return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round ); } /** * Calculate item cost - useful for gateways. * * @param object $item Item to get total from. * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_item_total( $item, $inc_tax = false, $round = true ) { $total = 0; if ( is_callable( array( $item, 'get_total' ) ) && $item->get_quantity() ) { if ( $inc_tax ) { $total = ( $item->get_total() + $item->get_total_tax() ) / $item->get_quantity(); } else { $total = floatval( $item->get_total() ) / $item->get_quantity(); } $total = $round ? round( $total, wc_get_price_decimals() ) : $total; } return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round ); } /** * Calculate line total - useful for gateways. * * @param object $item Item to get total from. * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_line_total( $item, $inc_tax = false, $round = true ) { $total = 0; if ( is_callable( array( $item, 'get_total' ) ) ) { // Check if we need to add line tax to the line total. $total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total(); // Check if we need to round. $total = $round ? round( $total, wc_get_price_decimals() ) : $total; } return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round ); } /** * Get item tax - useful for gateways. * * @param mixed $item Item to get total from. * @param bool $round (default: true). * @return float */ public function get_item_tax( $item, $round = true ) { $tax = 0; if ( is_callable( array( $item, 'get_total_tax' ) ) && $item->get_quantity() ) { $tax = $item->get_total_tax() / $item->get_quantity(); $tax = $round ? wc_round_tax_total( $tax ) : $tax; } return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this ); } /** * Get line tax - useful for gateways. * * @param mixed $item Item to get total from. * @return float */ public function get_line_tax( $item ) { return apply_filters( 'woocommerce_order_amount_line_tax', is_callable( array( $item, 'get_total_tax' ) ) ? wc_round_tax_total( $item->get_total_tax() ) : 0, $item, $this ); } /** * Gets line subtotal - formatted for display. * * @param object $item Item to get total from. * @param string $tax_display Incl or excl tax display mode. * @return string */ public function get_formatted_line_subtotal( $item, $tax_display = '' ) { $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); if ( 'excl' === $tax_display ) { $ex_tax_label = $this->get_prices_include_tax() ? 1 : 0; $subtotal = wc_price( $this->get_line_subtotal( $item ), array( 'ex_tax_label' => $ex_tax_label, 'currency' => $this->get_currency(), ) ); } else { $subtotal = wc_price( $this->get_line_subtotal( $item, true ), array( 'currency' => $this->get_currency() ) ); } return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $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_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 = '' ) { $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); $subtotal = $this->get_cart_subtotal_for_order(); if ( ! $compound ) { if ( 'incl' === $tax_display ) { $subtotal_taxes = 0; foreach ( $this->get_items() as $item ) { $subtotal_taxes += self::round_line_tax( $item->get_subtotal_tax(), false ); } $subtotal += wc_round_tax_total( $subtotal_taxes ); } $subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) ); if ( 'excl' === $tax_display && $this->get_prices_include_tax() && wc_tax_enabled() ) { $subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; } } else { if ( 'incl' === $tax_display ) { return ''; } // Add Shipping Costs. $subtotal += $this->get_shipping_total(); // Remove non-compound taxes. foreach ( $this->get_taxes() as $tax ) { if ( $tax->is_compound() ) { continue; } $subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total(); } // Remove discounts. $subtotal = $subtotal - $this->get_total_discount(); $subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) ); } return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this ); } /** * Gets shipping (formatted). * * @param string $tax_display Excl or incl tax display mode. * @return string */ public function get_shipping_to_display( $tax_display = '' ) { $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); if ( 0 < abs( (float) $this->get_shipping_total() ) ) { if ( 'excl' === $tax_display ) { // Show shipping excluding tax. $shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) ); if ( (float) $this->get_shipping_tax() > 0 && $this->get_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->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) ); if ( (float) $this->get_shipping_tax() > 0 && ! $this->get_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 ); } } /* translators: %s: method */ $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, $tax_display ); } /** * Get the discount amount (formatted). * * @since 2.3.0 * @param string $tax_display Excl or incl tax display mode. * @return string */ public function get_discount_to_display( $tax_display = '' ) { $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); return apply_filters( 'woocommerce_order_discount_to_display', wc_price( $this->get_total_discount( 'excl' === $tax_display && 'excl' === get_option( 'woocommerce_tax_display_cart' ) ), array( 'currency' => $this->get_currency() ) ), $this ); } /** * Add total row for subtotal. * * @param array $total_rows Reference to total rows array. * @param string $tax_display Excl or incl tax display mode. */ protected function add_order_item_totals_subtotal_row( &$total_rows, $tax_display ) { $subtotal = $this->get_subtotal_to_display( false, $tax_display ); if ( $subtotal ) { $total_rows['cart_subtotal'] = array( 'label' => __( 'Subtotal:', 'woocommerce' ), 'value' => $subtotal, ); } } /** * Add total row for discounts. * * @param array $total_rows Reference to total rows array. * @param string $tax_display Excl or incl tax display mode. */ protected function add_order_item_totals_discount_row( &$total_rows, $tax_display ) { if ( $this->get_total_discount() > 0 ) { $total_rows['discount'] = array( 'label' => __( 'Discount:', 'woocommerce' ), 'value' => '-' . $this->get_discount_to_display( $tax_display ), ); } } /** * Add total row for shipping. * * @param array $total_rows Reference to total rows array. * @param string $tax_display Excl or incl tax display mode. */ protected function add_order_item_totals_shipping_row( &$total_rows, $tax_display ) { if ( $this->get_shipping_method() ) { $total_rows['shipping'] = array( 'label' => __( 'Shipping:', 'woocommerce' ), 'value' => $this->get_shipping_to_display( $tax_display ), ); } } /** * Add total row for fees. * * @param array $total_rows Reference to total rows array. * @param string $tax_display Excl or incl tax display mode. */ protected function add_order_item_totals_fee_rows( &$total_rows, $tax_display ) { $fees = $this->get_fees(); if ( $fees ) { foreach ( $fees as $id => $fee ) { if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) { continue; } $total_rows[ 'fee_' . $fee->get_id() ] = array( 'label' => $fee->get_name() . ':', 'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array( 'currency' => $this->get_currency() ) ), ); } } } /** * Add total row for taxes. * * @param array $total_rows Reference to total rows array. * @param string $tax_display Excl or incl tax display mode. */ protected function add_order_item_totals_tax_rows( &$total_rows, $tax_display ) { // Tax for tax exclusive prices. if ( 'excl' === $tax_display && wc_tax_enabled() ) { if ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ) { 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_currency() ) ), ); } } } /** * Add total row for grand total. * * @param array $total_rows Reference to total rows array. * @param string $tax_display Excl or incl tax display mode. */ protected function add_order_item_totals_total_row( &$total_rows, $tax_display ) { $total_rows['order_total'] = array( 'label' => __( 'Total:', 'woocommerce' ), 'value' => $this->get_formatted_order_total( $tax_display ), ); } /** * Get totals for display on pages and in emails. * * @param mixed $tax_display Excl or incl tax display mode. * @return array */ public function get_order_item_totals( $tax_display = '' ) { $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); $total_rows = array(); $this->add_order_item_totals_subtotal_row( $total_rows, $tax_display ); $this->add_order_item_totals_discount_row( $total_rows, $tax_display ); $this->add_order_item_totals_shipping_row( $total_rows, $tax_display ); $this->add_order_item_totals_fee_rows( $total_rows, $tax_display ); $this->add_order_item_totals_tax_rows( $total_rows, $tax_display ); $this->add_order_item_totals_total_row( $total_rows, $tax_display ); return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this, $tax_display ); } /* |-------------------------------------------------------------------------- | Conditionals |-------------------------------------------------------------------------- | | Checks if a condition is true or false. | */ /** * Checks the order status against a passed in status. * * @param array|string $status Status to check. * @return bool */ public function has_status( $status ) { return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status, true ) ) || $this->get_status() === $status, $this, $status ); } /** * Check whether this order has a specific shipping method or not. * * @param string $method_id Method ID to check. * @return bool */ public function has_shipping_method( $method_id ) { foreach ( $this->get_shipping_methods() as $shipping_method ) { if ( strpos( $shipping_method->get_method_id(), $method_id ) === 0 ) { 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->get_total() ) { return true; } } return false; } } abstract-wc-rest-posts-controller.php 0000666 00000054335 15214036663 0014015 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; } } abstract-wc-log-handler.php 0000666 00000002612 15214053223 0011663 0 ustar 00 <?php /** * Log handling functionality. * * @class WC_Log_Handler * @package WooCommerce/Abstracts */ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Abstract WC Log Handler Class * * @version 1.0.0 * @package WooCommerce/Abstracts */ abstract class WC_Log_Handler implements WC_Log_Handler_Interface { /** * Formats a timestamp for use in log messages. * * @param int $timestamp Log timestamp. * @return string Formatted time for use in log entry. */ protected static function format_time( $timestamp ) { return date( 'c', $timestamp ); } /** * Builds a log entry text from level, timestamp and message. * * @param int $timestamp Log timestamp. * @param string $level emergency|alert|critical|error|warning|notice|info|debug. * @param string $message Log message. * @param array $context Additional information for log handlers. * * @return string Formatted log entry. */ protected static function format_entry( $timestamp, $level, $message, $context ) { $time_string = self::format_time( $timestamp ); $level_string = strtoupper( $level ); $entry = "{$time_string} {$level_string} {$message}"; return apply_filters( 'woocommerce_format_log_entry', $entry, array( 'timestamp' => $timestamp, 'level' => $level, 'message' => $message, 'context' => $context, ) ); } } abstract-wc-deprecated-hooks.php 0000666 00000006160 15214053223 0012712 0 ustar 00 <?php /** * Abstract deprecated hooks * * @package WooCommerce\Abstracts * @since 3.0.0 * @version 3.3.0 */ use Automattic\Jetpack\Constants; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * WC_Deprecated_Hooks class maps old actions and filters to new ones. This is the base class for handling those deprecated hooks. * * Based on the WCS_Hook_Deprecator class by Prospress. */ abstract class WC_Deprecated_Hooks { /** * Array of deprecated hooks we need to handle. * * @var array */ protected $deprecated_hooks = array(); /** * Array of versions on each hook has been deprecated. * * @var array */ protected $deprecated_version = array(); /** * Constructor. */ public function __construct() { $new_hooks = array_keys( $this->deprecated_hooks ); array_walk( $new_hooks, array( $this, 'hook_in' ) ); } /** * Hook into the new hook so we can handle deprecated hooks once fired. * * @param string $hook_name Hook name. */ abstract public function hook_in( $hook_name ); /** * Get old hooks to map to new hook. * * @param string $new_hook New hook name. * @return array */ public function get_old_hooks( $new_hook ) { $old_hooks = isset( $this->deprecated_hooks[ $new_hook ] ) ? $this->deprecated_hooks[ $new_hook ] : array(); $old_hooks = is_array( $old_hooks ) ? $old_hooks : array( $old_hooks ); return $old_hooks; } /** * If the hook is Deprecated, call the old hooks here. */ public function maybe_handle_deprecated_hook() { $new_hook = current_filter(); $old_hooks = $this->get_old_hooks( $new_hook ); $new_callback_args = func_get_args(); $return_value = $new_callback_args[0]; foreach ( $old_hooks as $old_hook ) { $return_value = $this->handle_deprecated_hook( $new_hook, $old_hook, $new_callback_args, $return_value ); } return $return_value; } /** * If the old hook is in-use, trigger it. * * @param string $new_hook New hook name. * @param string $old_hook Old hook name. * @param array $new_callback_args New callback args. * @param mixed $return_value Returned value. * @return mixed */ abstract public function handle_deprecated_hook( $new_hook, $old_hook, $new_callback_args, $return_value ); /** * Get deprecated version. * * @param string $old_hook Old hook name. * @return string */ protected function get_deprecated_version( $old_hook ) { return ! empty( $this->deprecated_version[ $old_hook ] ) ? $this->deprecated_version[ $old_hook ] : Constants::get_constant( 'WC_VERSION' ); } /** * Display a deprecated notice for old hooks. * * @param string $old_hook Old hook. * @param string $new_hook New hook. */ protected function display_notice( $old_hook, $new_hook ) { wc_deprecated_hook( esc_html( $old_hook ), esc_html( $this->get_deprecated_version( $old_hook ) ), esc_html( $new_hook ) ); } /** * Fire off a legacy hook with it's args. * * @param string $old_hook Old hook name. * @param array $new_callback_args New callback args. * @return mixed */ abstract protected function trigger_hook( $old_hook, $new_callback_args ); } abstract-wc-privacy.php 0000666 00000007547 15214053223 0011160 0 ustar 00 <?php /** * WooCommerce abstract privacy class. * * @since 3.4.0 * @package WooCommerce/Abstracts */ defined( 'ABSPATH' ) || exit; /** * Abstract class that is intended to be extended by * specific privacy class. It handles the display * of the privacy message of the privacy id to the admin, * privacy data to be exported and privacy data to be deleted. * * @version 3.4.0 * @package WooCommerce/Abstracts */ abstract class WC_Abstract_Privacy { /** * This is the name of this object type. * * @var string */ public $name; /** * This is a list of exporters. * * @var array */ protected $exporters = array(); /** * This is a list of erasers. * * @var array */ protected $erasers = array(); /** * This is a priority for the wp_privacy_personal_data_exporters filter * * @var int */ protected $export_priority; /** * This is a priority for the wp_privacy_personal_data_erasers filter * * @var int */ protected $erase_priority; /** * WC_Abstract_Privacy Constructor. * * @param string $name Plugin identifier. * @param int $export_priority Export priority. * @param int $erase_priority Erase priority. */ public function __construct( $name = '', $export_priority = 5, $erase_priority = 10 ) { $this->name = $name; $this->export_priority = $export_priority; $this->erase_priority = $erase_priority; $this->init(); } /** * Hook in events. */ protected function init() { add_action( 'admin_init', array( $this, 'add_privacy_message' ) ); // We set priority to 5 to help WooCommerce's findings appear before those from extensions in exported items. add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_exporters' ), $this->export_priority ); add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_erasers' ), $this->erase_priority ); } /** * Adds the privacy message on WC privacy page. */ public function add_privacy_message() { if ( function_exists( 'wp_add_privacy_policy_content' ) ) { $content = $this->get_privacy_message(); if ( $content ) { wp_add_privacy_policy_content( $this->name, $this->get_privacy_message() ); } } } /** * Gets the message of the privacy to display. * To be overloaded by the implementor. * * @return string */ public function get_privacy_message() { return ''; } /** * Integrate this exporter implementation within the WordPress core exporters. * * @param array $exporters List of exporter callbacks. * @return array */ public function register_exporters( $exporters = array() ) { foreach ( $this->exporters as $id => $exporter ) { $exporters[ $id ] = $exporter; } return $exporters; } /** * Integrate this eraser implementation within the WordPress core erasers. * * @param array $erasers List of eraser callbacks. * @return array */ public function register_erasers( $erasers = array() ) { foreach ( $this->erasers as $id => $eraser ) { $erasers[ $id ] = $eraser; } return $erasers; } /** * Add exporter to list of exporters. * * @param string $id ID of the Exporter. * @param string $name Exporter name. * @param string|array $callback Exporter callback. * * @return array */ public function add_exporter( $id, $name, $callback ) { $this->exporters[ $id ] = array( 'exporter_friendly_name' => $name, 'callback' => $callback, ); return $this->exporters; } /** * Add eraser to list of erasers. * * @param string $id ID of the Eraser. * @param string $name Exporter name. * @param string|array $callback Exporter callback. * * @return array */ public function add_eraser( $id, $name, $callback ) { $this->erasers[ $id ] = array( 'eraser_friendly_name' => $name, 'callback' => $callback, ); return $this->erasers; } } abstract-wc-object-query.php 0000666 00000003703 15214053223 0012102 0 ustar 00 <?php /** * Query abstraction layer functionality. * * @package WooCommerce/Abstracts */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Abstract WC Object Query Class * * Extended by classes to provide a query abstraction layer for safe object searching. * * @version 3.1.0 * @package WooCommerce/Abstracts */ abstract class WC_Object_Query { /** * Stores query data. * * @var array */ protected $query_vars = array(); /** * Create a new query. * * @param array $args Criteria to query on in a format similar to WP_Query. */ public function __construct( $args = array() ) { $this->query_vars = wp_parse_args( $args, $this->get_default_query_vars() ); } /** * Get the current query vars. * * @return array */ public function get_query_vars() { return $this->query_vars; } /** * Get the value of a query variable. * * @param string $query_var Query variable to get value for. * @param mixed $default Default value if query variable is not set. * @return mixed Query variable value if set, otherwise default. */ public function get( $query_var, $default = '' ) { if ( isset( $this->query_vars[ $query_var ] ) ) { return $this->query_vars[ $query_var ]; } return $default; } /** * Set a query variable. * * @param string $query_var Query variable to set. * @param mixed $value Value to set for query variable. */ public function set( $query_var, $value ) { $this->query_vars[ $query_var ] = $value; } /** * Get the default allowed query vars. * * @return array */ protected function get_default_query_vars() { return array( 'name' => '', 'parent' => '', 'parent_exclude' => '', 'exclude' => '', 'limit' => get_option( 'posts_per_page' ), 'page' => 1, 'offset' => '', 'paginate' => false, 'order' => 'DESC', 'orderby' => 'date', 'return' => 'objects', ); } } class-wc-background-process.php 0000666 00000011566 15214053223 0012574 0 ustar 00 <?php /** * Abstract WP_Background_Process class. * * Uses https://github.com/A5hleyRich/wp-background-processing to handle DB * updates in the background. * * @package WooCommerce/Classes */ defined( 'ABSPATH' ) || exit; if ( ! class_exists( 'WP_Async_Request', false ) ) { include_once dirname( WC_PLUGIN_FILE ) . '/includes/libraries/wp-async-request.php'; } if ( ! class_exists( 'WP_Background_Process', false ) ) { include_once dirname( WC_PLUGIN_FILE ) . '/includes/libraries/wp-background-process.php'; } /** * WC_Background_Process class. */ abstract class WC_Background_Process extends WP_Background_Process { /** * Is queue empty. * * @return bool */ protected function is_queue_empty() { global $wpdb; $table = $wpdb->options; $column = 'option_name'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine. return ! ( $count > 0 ); } /** * Get batch. * * @return stdClass Return the first batch from the queue. */ protected function get_batch() { global $wpdb; $table = $wpdb->options; $column = 'option_name'; $key_column = 'option_id'; $value_column = 'option_value'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; $key_column = 'meta_id'; $value_column = 'meta_value'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; $query = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE {$column} LIKE %s ORDER BY {$key_column} ASC LIMIT 1", $key ) ); // @codingStandardsIgnoreLine. $batch = new stdClass(); $batch->key = $query->$column; $batch->data = array_filter( (array) maybe_unserialize( $query->$value_column ) ); return $batch; } /** * See if the batch limit has been exceeded. * * @return bool */ protected function batch_limit_exceeded() { return $this->time_exceeded() || $this->memory_exceeded(); } /** * Handle. * * Pass each queue item to the task handler, while remaining * within server memory and time limit constraints. */ protected function handle() { $this->lock_process(); do { $batch = $this->get_batch(); foreach ( $batch->data as $key => $value ) { $task = $this->task( $value ); if ( false !== $task ) { $batch->data[ $key ] = $task; } else { unset( $batch->data[ $key ] ); } if ( $this->batch_limit_exceeded() ) { // Batch limits reached. break; } } // Update or delete current batch. if ( ! empty( $batch->data ) ) { $this->update( $batch->key, $batch->data ); } else { $this->delete( $batch->key ); } } while ( ! $this->batch_limit_exceeded() && ! $this->is_queue_empty() ); $this->unlock_process(); // Start next batch or complete process. if ( ! $this->is_queue_empty() ) { $this->dispatch(); } else { $this->complete(); } } /** * Get memory limit. * * @return int */ protected function get_memory_limit() { if ( function_exists( 'ini_get' ) ) { $memory_limit = ini_get( 'memory_limit' ); } else { // Sensible default. $memory_limit = '128M'; } if ( ! $memory_limit || -1 === intval( $memory_limit ) ) { // Unlimited, set to 32GB. $memory_limit = '32G'; } return wp_convert_hr_to_bytes( $memory_limit ); } /** * Schedule cron healthcheck. * * @param array $schedules Schedules. * @return array */ public function schedule_cron_healthcheck( $schedules ) { $interval = apply_filters( $this->identifier . '_cron_interval', 5 ); if ( property_exists( $this, 'cron_interval' ) ) { $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval ); } // Adds every 5 minutes to the existing schedules. $schedules[ $this->identifier . '_cron_interval' ] = array( 'interval' => MINUTE_IN_SECONDS * $interval, /* translators: %d: interval */ 'display' => sprintf( __( 'Every %d minutes', 'woocommerce' ), $interval ), ); return $schedules; } /** * Delete all batches. * * @return WC_Background_Process */ public function delete_all_batches() { global $wpdb; $table = $wpdb->options; $column = 'option_name'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; $wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine. return $this; } /** * Kill process. * * Stop processing queue items, clear cronjob and delete all batches. */ public function kill_process() { if ( ! $this->is_queue_empty() ) { $this->delete_all_batches(); wp_clear_scheduled_hook( $this->cron_hook_identifier ); } } }
| ver. 1.4 |
Github
|
.
| PHP 7.0.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings