/** * WooCommerce Stock Functions * * Functions used to manage product stock levels. * * @package WooCommerce\Functions * @version 3.4.0 */ defined( 'ABSPATH' ) || exit; use Automattic\WooCommerce\Checkout\Helpers\ReserveStock; use Automattic\WooCommerce\Enums\ProductType; /** * Update a product's stock amount. * * Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues). * * @since 3.0.0 this supports set, increase and decrease. * * @param int|WC_Product $product Product ID or product instance. * @param int|null $stock_quantity Stock quantity. * @param string $operation Type of operation, allows 'set', 'increase' and 'decrease'. * @param bool $updating If true, the product object won't be saved here as it will be updated later. * @return bool|int|null */ function wc_update_product_stock( $product, $stock_quantity = null, $operation = 'set', $updating = false ) { if ( ! is_a( $product, 'WC_Product' ) ) { $product = wc_get_product( $product ); } if ( ! $product ) { return false; } if ( ! is_null( $stock_quantity ) && $product->managing_stock() ) { // Some products (variations) can have their stock managed by their parent. Get the correct object to be updated here. $product_id_with_stock = $product->get_stock_managed_by_id(); $product_with_stock = $product_id_with_stock !== $product->get_id() ? wc_get_product( $product_id_with_stock ) : $product; $data_store = WC_Data_Store::load( 'product' ); // Fire actions to let 3rd parties know the stock is about to be changed. if ( $product_with_stock->is_type( ProductType::VARIATION ) ) { // phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment /** This action is documented in includes/data-stores/class-wc-product-data-store-cpt.php */ do_action( 'woocommerce_variation_before_set_stock', $product_with_stock ); } else { // phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment /** This action is documented in includes/data-stores/class-wc-product-data-store-cpt.php */ do_action( 'woocommerce_product_before_set_stock', $product_with_stock ); } // Update the database. $new_stock = $data_store->update_product_stock( $product_id_with_stock, $stock_quantity, $operation ); // Update the product object. $data_store->read_stock_quantity( $product_with_stock, $new_stock ); // If this is not being called during an update routine, save the product so stock status etc is in sync, and caches are cleared. if ( ! $updating ) { $product_with_stock->save(); } // Fire actions to let 3rd parties know the stock changed. if ( $product_with_stock->is_type( ProductType::VARIATION ) ) { // phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment /** This action is documented in includes/data-stores/class-wc-product-data-store-cpt.php */ do_action( 'woocommerce_variation_set_stock', $product_with_stock ); } else { // phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment /** This action is documented in includes/data-stores/class-wc-product-data-store-cpt.php */ do_action( 'woocommerce_product_set_stock', $product_with_stock ); } return $product_with_stock->get_stock_quantity(); } return $product->get_stock_quantity(); } /** * Update a product's stock status. * * @param int $product_id Product ID. * @param string $status Status. */ function wc_update_product_stock_status( $product_id, $status ) { $product = wc_get_product( $product_id ); if ( $product ) { $product->set_stock_status( $status ); $product->save(); } } /** * When a payment is complete, we can reduce stock levels for items within an order. * * @since 3.0.0 * @param int $order_id Order ID. */ function wc_maybe_reduce_stock_levels( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order ) { return; } $stock_reduced = $order->get_data_store()->get_stock_reduced( $order_id ); $trigger_reduce = apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! $stock_reduced, $order_id ); // Only continue if we're reducing stock. if ( ! $trigger_reduce ) { return; } wc_reduce_stock_levels( $order ); // Ensure stock is marked as "reduced" in case payment complete or other stock actions are called. $order->get_data_store()->set_stock_reduced( $order_id, true ); } add_action( 'woocommerce_payment_complete', 'wc_maybe_reduce_stock_levels' ); add_action( 'woocommerce_order_status_completed', 'wc_maybe_reduce_stock_levels' ); add_action( 'woocommerce_order_status_processing', 'wc_maybe_reduce_stock_levels' ); add_action( 'woocommerce_order_status_on-hold', 'wc_maybe_reduce_stock_levels' ); /** * When a payment is cancelled, restore stock. * * @since 3.0.0 * @param int $order_id Order ID. */ function wc_maybe_increase_stock_levels( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order ) { return; } $stock_reduced = $order->get_data_store()->get_stock_reduced( $order_id ); $trigger_increase = (bool) $stock_reduced; // Only continue if we're increasing stock. if ( ! $trigger_increase ) { return; } wc_increase_stock_levels( $order ); // Ensure stock is not marked as "reduced" anymore. $order->get_data_store()->set_stock_reduced( $order_id, false ); } add_action( 'woocommerce_order_status_cancelled', 'wc_maybe_increase_stock_levels' ); add_action( 'woocommerce_order_status_pending', 'wc_maybe_increase_stock_levels' ); /** * Reduce stock levels for items within an order, if stock has not already been reduced for the items. * * @since 3.0.0 * @param int|WC_Order $order_id Order ID or order instance. */ function wc_reduce_stock_levels( $order_id ) { if ( is_a( $order_id, 'WC_Order' ) ) { $order = $order_id; $order_id = $order->get_id(); } else { $order = wc_get_order( $order_id ); } // We need an order, and a store with stock management to continue. if ( ! $order || 'yes' !== get_option( 'woocommerce_manage_stock' ) || ! apply_filters( 'woocommerce_can_reduce_order_stock', true, $order ) ) { return; } $changes = array(); // Loop over all items. foreach ( $order->get_items() as $item ) { if ( ! $item->is_type( 'line_item' ) ) { continue; } // Only reduce stock once for each item. $product = $item->get_product(); $item_stock_reduced = $item->get_meta( '_reduced_stock', true ); if ( $item_stock_reduced || ! $product || ! $product->managing_stock() ) { continue; } /** * Filter order item quantity. * * @param int|float $quantity Quantity. * @param WC_Order $order Order data. * @param WC_Order_Item_Product $item Order item data. */ $qty = apply_filters( 'woocommerce_order_item_quantity', $item->get_quantity(), $order, $item ); $item_name = $product->get_formatted_name(); $new_stock = wc_update_product_stock( $product, $qty, 'decrease' ); if ( is_wp_error( $new_stock ) ) { /* translators: %s item name. */ $order->add_order_note( sprintf( __( 'Unable to reduce stock for item %s.', 'woocommerce' ), $item_name ) ); continue; } $item->add_meta_data( '_reduced_stock', $qty, true ); $item->save(); $change = array( 'product' => $product, 'from' => $new_stock + $qty, 'to' => $new_stock, ); $changes[] = $change; /** * Fires when stock reduced to a specific line item * * @param WC_Order_Item_Product $item Order item data. * @param array $change Change Details. * @param WC_Order $order Order data. * @since 7.6.0 */ do_action( 'woocommerce_reduce_order_item_stock', $item, $change, $order ); } wc_trigger_stock_change_notifications( $order, $changes ); do_action( 'woocommerce_reduce_order_stock', $order ); } /** * After stock change events, triggers emails and adds order notes. * * @since 3.5.0 * @param WC_Order $order order object. * @param array $changes Array of changes. */ function wc_trigger_stock_change_notifications( $order, $changes ) { if ( empty( $changes ) ) { return; } $order_notes = array(); $no_stock_amount = absint( get_option( 'woocommerce_notify_no_stock_amount', 0 ) ); foreach ( $changes as $change ) { $order_notes[] = $change['product']->get_formatted_name() . ' ' . $change['from'] . '→' . $change['to']; $low_stock_amount = absint( wc_get_low_stock_amount( wc_get_product( $change['product']->get_id() ) ) ); if ( $change['to'] <= $no_stock_amount ) { /** * Action to signal that the value of 'stock_quantity' for a variation is about to change. * * @since 4.9 * * @param int $product The variation whose stock is about to change. */ do_action( 'woocommerce_no_stock', wc_get_product( $change['product']->get_id() ) ); } elseif ( $change['to'] <= $low_stock_amount ) { /** * Action to signal that the value of 'stock_quantity' for a product is about to change. * * @since 4.9 * * @param int $product The product whose stock is about to change. */ do_action( 'woocommerce_low_stock', wc_get_product( $change['product']->get_id() ) ); } if ( $change['to'] < 0 ) { /** * Action fires when an item in an order is backordered. * * @since 3.0 * * @param array $args { * @type WC_Product $product The product that is on backorder. * @type int $order_id The ID of the order. * @type int|float $quantity The amount of product on backorder. * } */ do_action( 'woocommerce_product_on_backorder', array( 'product' => wc_get_product( $change['product']->get_id() ), 'order_id' => $order->get_id(), 'quantity' => abs( $change['from'] - $change['to'] ), ) ); } } $order->add_order_note( __( 'Stock levels reduced:', 'woocommerce' ) . ' ' . implode( ', ', $order_notes ) ); } /** * Check if a product's stock quantity has reached certain thresholds and trigger appropriate actions. * * This functionality was moved out of `wc_trigger_stock_change_notifications` in order to decouple it from orders, * since stock quantity can also be updated in other ways. * * @param WC_Product $product The product whose stock level has changed. * * @return void */ function wc_trigger_stock_change_actions( $product ) { if ( true !== $product->get_manage_stock() ) { return; } $no_stock_amount = absint( get_option( 'woocommerce_notify_no_stock_amount', 0 ) ); $low_stock_amount = absint( wc_get_low_stock_amount( $product ) ); $stock_quantity = $product->get_stock_quantity(); if ( $stock_quantity <= $no_stock_amount ) { /** * Action fires when a product's stock quantity reaches the "no stock" threshold. * * @since 3.0 * * @param WC_Product $product The product whose stock quantity has changed. */ do_action( 'woocommerce_no_stock', $product ); } elseif ( $stock_quantity <= $low_stock_amount ) { /** * Action fires when a product's stock quantity reaches the "low stock" threshold. * * @since 3.0 * * @param WC_Product $product The product whose stock quantity has changed. */ do_action( 'woocommerce_low_stock', $product ); } } /** * Increase stock levels for items within an order. * * @since 3.0.0 * @param int|WC_Order $order_id Order ID or order instance. */ function wc_increase_stock_levels( $order_id ) { if ( is_a( $order_id, 'WC_Order' ) ) { $order = $order_id; $order_id = $order->get_id(); } else { $order = wc_get_order( $order_id ); } // We need an order, and a store with stock management to continue. if ( ! $order || 'yes' !== get_option( 'woocommerce_manage_stock' ) || ! apply_filters( 'woocommerce_can_restore_order_stock', true, $order ) ) { return; } $changes = array(); // Loop over all items. foreach ( $order->get_items() as $item ) { if ( ! $item->is_type( 'line_item' ) ) { continue; } // Only increase stock once for each item. $product = $item->get_product(); $item_stock_reduced = $item->get_meta( '_reduced_stock', true ); if ( ! $item_stock_reduced || ! $product || ! $product->managing_stock() ) { continue; } $item_name = $product->get_formatted_name(); $new_stock = wc_update_product_stock( $product, $item_stock_reduced, 'increase' ); $old_stock = $new_stock - $item_stock_reduced; if ( is_wp_error( $new_stock ) ) { /* translators: %s item name. */ $order->add_order_note( sprintf( __( 'Unable to restore stock for item %s.', 'woocommerce' ), $item_name ) ); continue; } $item->delete_meta_data( '_reduced_stock' ); $item->save(); $changes[] = $item_name . ' ' . $old_stock . '→' . $new_stock; /** * Fires when stock restored to a specific line item * * @since 9.1.0 * @param WC_Order_Item_Product $item Order item data. * @param int $new_stock New stock. * @param int $old_stock Old stock. * @param WC_Order $order Order data. */ do_action( 'woocommerce_restore_order_item_stock', $item, $new_stock, $old_stock, $order ); } if ( $changes ) { $order->add_order_note( __( 'Stock levels increased:', 'woocommerce' ) . ' ' . implode( ', ', $changes ) ); } do_action( 'woocommerce_restore_order_stock', $order ); } /** * See how much stock is being held in pending orders. * * @since 3.5.0 * @param WC_Product $product Product to check. * @param integer $exclude_order_id Order ID to exclude. * @return int */ function wc_get_held_stock_quantity( WC_Product $product, $exclude_order_id = 0 ) { /** * Filter: woocommerce_hold_stock_for_checkout * Allows enable/disable hold stock functionality on checkout. * * @since 4.3.0 * @param bool $enabled Default to true if managing stock globally. */ if ( ! apply_filters( 'woocommerce_hold_stock_for_checkout', wc_string_to_bool( get_option( 'woocommerce_manage_stock', 'yes' ) ) ) ) { return 0; } $reserve_stock = new ReserveStock(); return $reserve_stock->get_reserved_stock( $product, $exclude_order_id ); } /** * Hold stock for an order. * * @throws ReserveStockException If reserve stock fails. * * @since 4.1.0 * @param \WC_Order|int $order Order ID or instance. */ function wc_reserve_stock_for_order( $order ) { /** * Filter: woocommerce_hold_stock_for_checkout * Allows enable/disable hold stock functionality on checkout. * * @since @since 4.1.0 * @param bool $enabled Default to true if managing stock globally. */ if ( ! apply_filters( 'woocommerce_hold_stock_for_checkout', wc_string_to_bool( get_option( 'woocommerce_manage_stock', 'yes' ) ) ) ) { return; } $order = $order instanceof WC_Order ? $order : wc_get_order( $order ); if ( $order ) { $reserve_stock = new ReserveStock(); $reserve_stock->reserve_stock_for_order( $order ); } } add_action( 'woocommerce_checkout_order_created', 'wc_reserve_stock_for_order' ); /** * Release held stock for an order. * * @since 4.3.0 * @param \WC_Order|int $order Order ID or instance. */ function wc_release_stock_for_order( $order ) { /** * Filter: woocommerce_hold_stock_for_checkout * Allows enable/disable hold stock functionality on checkout. * * @since 4.3.0 * @param bool $enabled Default to true if managing stock globally. */ if ( ! apply_filters( 'woocommerce_hold_stock_for_checkout', wc_string_to_bool( get_option( 'woocommerce_manage_stock', 'yes' ) ) ) ) { return; } $order = $order instanceof WC_Order ? $order : wc_get_order( $order ); if ( $order ) { $reserve_stock = new ReserveStock(); $reserve_stock->release_stock_for_order( $order ); } } add_action( 'woocommerce_checkout_order_exception', 'wc_release_stock_for_order' ); add_action( 'woocommerce_payment_complete', 'wc_release_stock_for_order', 11 ); add_action( 'woocommerce_order_status_cancelled', 'wc_release_stock_for_order', 11 ); add_action( 'woocommerce_order_status_completed', 'wc_release_stock_for_order', 11 ); add_action( 'woocommerce_order_status_processing', 'wc_release_stock_for_order', 11 ); add_action( 'woocommerce_order_status_on-hold', 'wc_release_stock_for_order', 11 ); /** * Release coupons used for another order. * * @since 9.5.2 * @param \WC_Order|int $order Order ID or instance. * @param bool $save Save the order after releasing coupons. */ function wc_release_coupons_for_order( $order, bool $save = true ) { $order = $order instanceof WC_Order ? $order : wc_get_order( $order ); if ( $order ) { $order->get_data_store()->release_held_coupons( $order, $save ); } } /** * Return low stock amount to determine if notification needs to be sent * * Since 5.2.0, this function no longer redirects from variation to its parent product. * Low stock amount can now be attached to the variation itself and if it isn't, only * then we check the parent product, and if it's not there, then we take the default * from the store-wide setting. * * @param WC_Product $product Product to get data from. * @since 3.5.0 * @return int */ function wc_get_low_stock_amount( WC_Product $product ) { $low_stock_amount = $product->get_low_stock_amount(); if ( '' === $low_stock_amount && $product->is_type( ProductType::VARIATION ) ) { $product = wc_get_product( $product->get_parent_id() ); $low_stock_amount = $product->get_low_stock_amount(); } if ( '' === $low_stock_amount ) { $low_stock_amount = get_option( 'woocommerce_notify_low_stock_amount', 2 ); } return (int) $low_stock_amount; } /*! * Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) */ @font-face { font-family: 'Font Awesome 5 Brands'; font-style: normal; font-weight: 400; font-display: block; src: url("../webfonts/fa-brands-400.eot"); src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); } .fab { font-family: 'Font Awesome 5 Brands'; font-weight: 400; } /** * WooCommerce Message Functions * * Functions for error/message handling and display. * * @package WooCommerce\Functions * @version 2.1.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Get the count of notices added, either for all notices (default) or for one. * particular notice type specified by $notice_type. * * @since 2.1 * @param string $notice_type Optional. The name of the notice type - either error, success or notice. * @return int */ function wc_notice_count( $notice_type = '' ) { if ( ! did_action( 'woocommerce_init' ) ) { wc_doing_it_wrong( __FUNCTION__, __( 'This function should not be called before woocommerce_init.', 'woocommerce' ), '2.3' ); return; } $notice_count = 0; $all_notices = WC()->session->get( 'wc_notices', array() ); if ( isset( $all_notices[ $notice_type ] ) && is_array( $all_notices[ $notice_type ] ) ) { $notice_count = count( $all_notices[ $notice_type ] ); } elseif ( empty( $notice_type ) ) { foreach ( $all_notices as $notices ) { if ( is_countable( $notices ) ) { $notice_count += count( $notices ); } } } return $notice_count; } /** * Check if a notice has already been added. * * @since 2.1 * @param string $message The text to display in the notice. * @param string $notice_type Optional. The name of the notice type - either error, success or notice. * @return bool */ function wc_has_notice( $message, $notice_type = 'success' ) { if ( ! did_action( 'woocommerce_init' ) ) { wc_doing_it_wrong( __FUNCTION__, __( 'This function should not be called before woocommerce_init.', 'woocommerce' ), '2.3' ); return false; } $notices = WC()->session->get( 'wc_notices', array() ); $notices = isset( $notices[ $notice_type ] ) ? $notices[ $notice_type ] : array(); return array_search( $message, wp_list_pluck( $notices, 'notice' ), true ) !== false; } /** * Add and store a notice. * * @since 2.1 * @version 3.9.0 * @param string $message The text to display in the notice. * @param string $notice_type Optional. The name of the notice type - either error, success or notice. * @param array $data Optional notice data. */ function wc_add_notice( $message, $notice_type = 'success', $data = array() ) { if ( ! did_action( 'woocommerce_init' ) ) { wc_doing_it_wrong( __FUNCTION__, __( 'This function should not be called before woocommerce_init.', 'woocommerce' ), '2.3' ); return; } $notices = WC()->session->get( 'wc_notices', array() ); // Backward compatibility. if ( 'success' === $notice_type ) { $message = apply_filters( 'woocommerce_add_message', $message ); } $message = apply_filters( 'woocommerce_add_' . $notice_type, $message ); if ( ! empty( $message ) ) { $notices[ $notice_type ][] = array( 'notice' => $message, 'data' => $data, ); } WC()->session->set( 'wc_notices', $notices ); } /** * Set all notices at once. * * @since 2.6.0 * @param array[] $notices Array of notices. */ function wc_set_notices( $notices ) { if ( ! did_action( 'woocommerce_init' ) ) { wc_doing_it_wrong( __FUNCTION__, __( 'This function should not be called before woocommerce_init.', 'woocommerce' ), '2.6' ); return; } WC()->session->set( 'wc_notices', empty( $notices ) ? null : $notices ); } /** * Unset all notices. * * @since 2.1 */ function wc_clear_notices() { if ( ! did_action( 'woocommerce_init' ) ) { wc_doing_it_wrong( __FUNCTION__, __( 'This function should not be called before woocommerce_init.', 'woocommerce' ), '2.3' ); return; } WC()->session->set( 'wc_notices', null ); } /** * Prints messages and errors which are stored in the session, then clears them. * * @since 2.1 * @param bool $return true to return rather than echo. @since 3.5.0. * @return string|void */ function wc_print_notices( $return = false ) { if ( ! did_action( 'woocommerce_init' ) ) { wc_doing_it_wrong( __FUNCTION__, __( 'This function should not be called before woocommerce_init.', 'woocommerce' ), '2.3' ); return; } $session = WC()->session; // If the session handler has not initialized, there will be no notices for us to read. if ( null === $session ) { return; } $all_notices = $session->get( 'wc_notices', array() ); $notice_types = apply_filters( 'woocommerce_notice_types', array( 'error', 'success', 'notice' ) ); // Buffer output. ob_start(); foreach ( $notice_types as $notice_type ) { if ( wc_notice_count( $notice_type ) > 0 ) { $messages = array(); foreach ( $all_notices[ $notice_type ] as $notice ) { $messages[] = isset( $notice['notice'] ) ? $notice['notice'] : $notice; } wc_get_template( "notices/{$notice_type}.php", array( 'messages' => array_filter( $messages ), // @deprecated 3.9.0 'notices' => array_filter( $all_notices[ $notice_type ] ), ) ); } } wc_clear_notices(); $notices = wc_kses_notice( ob_get_clean() ); if ( $return ) { return $notices; } echo $notices; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Print a single notice immediately. * * @since 2.1 * @version 3.9.0 * @param string $message The text to display in the notice. * @param string $notice_type Optional. The singular name of the notice type - either error, success or notice. * @param array $data Optional notice data. @since 3.9.0. * @param bool $return true to return rather than echo. @since 7.7.0. */ function wc_print_notice( $message, $notice_type = 'success', $data = array(), $return = false ) { if ( 'success' === $notice_type ) { $message = apply_filters( 'woocommerce_add_message', $message ); } $message = apply_filters( 'woocommerce_add_' . $notice_type, $message ); // Buffer output. ob_start(); wc_get_template( "notices/{$notice_type}.php", array( 'messages' => array( $message ), // @deprecated 3.9.0 'notices' => array( array( 'notice' => $message, 'data' => $data, ), ), ) ); $notice = wc_kses_notice( ob_get_clean() ); if ( $return ) { return $notice; } echo $notice; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Returns all queued notices, optionally filtered by a notice type. * * @since 2.1 * @version 3.9.0 * @param string $notice_type Optional. The singular name of the notice type - either error, success or notice. * @return array[] */ function wc_get_notices( $notice_type = '' ) { if ( ! did_action( 'woocommerce_init' ) ) { wc_doing_it_wrong( __FUNCTION__, __( 'This function should not be called before woocommerce_init.', 'woocommerce' ), '2.3' ); return; } $notices = array(); if ( ! WC()->session ) { return $notices; } $all_notices = WC()->session->get( 'wc_notices', array() ); if ( empty( $notice_type ) ) { $notices = $all_notices; } elseif ( isset( $all_notices[ $notice_type ] ) ) { $notices = $all_notices[ $notice_type ]; } return $notices; } /** * Add notices for WP Errors. * * @param WP_Error $errors Errors. */ function wc_add_wp_error_notices( $errors ) { if ( is_wp_error( $errors ) && $errors->get_error_messages() ) { foreach ( $errors->get_error_messages() as $error ) { wc_add_notice( $error, 'error' ); } } } /** * Filters out the same tags as wp_kses_post, but allows tabindex for element. * * @since 3.5.0 * @param string $message Content to filter through kses. * @return string */ function wc_kses_notice( $message ) { $allowed_tags = array_replace_recursive( wp_kses_allowed_html( 'post' ), array( 'a' => array( 'tabindex' => true, ), ) ); /** * Kses notice allowed tags. * * @since 3.9.0 * @param array[]|string $allowed_tags An array of allowed HTML elements and attributes, or a context name such as 'post'. */ return wp_kses( $message, apply_filters( 'woocommerce_kses_notice_allowed_tags', $allowed_tags ) ); } /** * Get notice data attribute. * * @since 3.9.0 * @param array $notice Notice data. * @return string */ function wc_get_notice_data_attr( $notice ) { if ( empty( $notice['data'] ) ) { return; } $attr = ''; foreach ( $notice['data'] as $key => $value ) { $attr .= sprintf( ' data-%1$s="%2$s"', sanitize_title( $key ), esc_attr( $value ) ); } return $attr; } /** * Brands Helper Functions * * Important: For internal use only by the Automattic\WooCommerce\Internal\Brands package. * * @package WooCommerce * @version 9.4.0 */ declare( strict_types = 1); /** * Helper function :: wc_get_brand_thumbnail_url function. * * @param int $brand_id Brand ID. * @param string $size Thumbnail image size. * @return string */ function wc_get_brand_thumbnail_url( $brand_id, $size = 'full' ) { $thumbnail_id = get_term_meta( $brand_id, 'thumbnail_id', true ); if ( $thumbnail_id ) { $thumb_src = wp_get_attachment_image_src( $thumbnail_id, $size ); } return ! empty( $thumb_src ) ? current( $thumb_src ) : ''; } /** * Helper function :: wc_get_brand_thumbnail_image function. * * @since 9.4.0 * * @param object $brand Brand term. * @param string $size Thumbnail image size. * @return string */ function wc_get_brand_thumbnail_image( $brand, $size = '' ) { $thumbnail_id = get_term_meta( $brand->term_id, 'thumbnail_id', true ); if ( '' === $size || 'brand-thumb' === $size ) { /** * Filter the brand's thumbnail size. * * @since 9.4.0 * * @param string $size Brand's thumbnail size. */ $size = apply_filters( 'woocommerce_brand_thumbnail_size', 'shop_catalog' ); } if ( $thumbnail_id ) { $image_src = wp_get_attachment_image_src( $thumbnail_id, $size ); $image_src = $image_src[0]; $dimensions = wc_get_image_size( $size ); $image_srcset = function_exists( 'wp_get_attachment_image_srcset' ) ? wp_get_attachment_image_srcset( $thumbnail_id, $size ) : false; $image_sizes = function_exists( 'wp_get_attachment_image_sizes' ) ? wp_get_attachment_image_sizes( $thumbnail_id, $size ) : false; } else { $image_src = wc_placeholder_img_src(); $dimensions = wc_get_image_size( $size ); $image_srcset = false; $image_sizes = false; } // Add responsive image markup if available. if ( $image_srcset && $image_sizes ) { $image = '' . esc_attr( $brand->name ) . ''; } else { $image = '' . esc_attr( $brand->name ) . ''; } return $image; } /** * Retrieves product's brands. * * @param int $post_id Post ID (default: 0). * @param string $sep Seperator (default: '). * @param string $before Before item (default: ''). * @param string $after After item (default: ''). * @return array List of terms */ function wc_get_brands( $post_id = 0, $sep = ', ', $before = '', $after = '' ) { global $post; if ( ! $post_id ) { $post_id = $post->ID; } return get_the_term_list( $post_id, 'product_brand', $before, $sep, $after ); } /** * Polyfills for backwards compatibility with the WooCommerce Brands plugin. */ if ( ! function_exists( 'get_brand_thumbnail_url' ) ) { /** * Polyfill for get_brand_thumbnail_image. * * @param int $brand_id Brand ID. * @param string $size Thumbnail image size. * @return string */ function get_brand_thumbnail_url( $brand_id, $size = 'full' ) { return wc_get_brand_thumbnail_url( $brand_id, $size ); } } if ( ! function_exists( 'get_brand_thumbnail_image' ) ) { /** * Polyfill for get_brand_thumbnail_image. * * @param object $brand Brand term. * @param string $size Thumbnail image size. * @return string */ function get_brand_thumbnail_image( $brand, $size = '' ) { return wc_get_brand_thumbnail_image( $brand, $size ); } } if ( ! function_exists( 'get_brands' ) ) { /** * Polyfill for get_brands. * * @param int $post_id Post ID (default: 0). * @param string $sep Seperator (default: '). * @param string $before Before item (default: ''). * @param string $after After item (default: ''). * @return array List of terms */ function get_brands( $post_id = 0, $sep = ', ', $before = '', $after = '' ) { return wc_get_brands( $post_id, $sep, $before, $after ); } } /** * Core Functions * * Holds core functions for wc-admin. * * @package WooCommerce\Admin\Functions */ use Automattic\WooCommerce\Internal\Admin\Settings; /** * Format a number using the decimal and thousands separator settings in WooCommerce. * * @param mixed $number Number to be formatted. * @return string */ function wc_admin_number_format( $number ) { $currency_settings = Settings::get_currency_settings(); return number_format( $number, 0, $currency_settings['decimalSeparator'], $currency_settings['thousandSeparator'] ); } /** * Retrieves a URL to relative path inside WooCommerce admin with * the provided query parameters. * * @param string $path Relative path of the desired page. * @param array $query Query parameters to append to the path. * * @return string Fully qualified URL pointing to the desired path. */ function wc_admin_url( $path = null, $query = array() ) { if ( ! empty( $query ) ) { $query_string = http_build_query( $query ); $path = $path ? '&path=' . $path . '&' . $query_string : ''; } return admin_url( 'admin.php?page=wc-admin' . $path, dirname( __FILE__ ) ); } /** * Record an event using Tracks. * * @internal WooCommerce core only includes Tracks in admin, not the REST API, so we need to include it. * @param string $event_name Event name for tracks. * @param array $properties Properties to pass along with event. */ function wc_admin_record_tracks_event( $event_name, $properties = array() ) { // WC post types must be registered first for WC_Tracks to work. if ( ! post_type_exists( 'product' ) ) { return; } if ( ! class_exists( 'WC_Tracks' ) ) { if ( ! defined( 'WC_ABSPATH' ) || ! file_exists( WC_ABSPATH . 'includes/tracks/class-wc-tracks.php' ) ) { return; } include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks.php'; include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-event.php'; include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-client.php'; include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-footer-pixel.php'; include_once WC_ABSPATH . 'includes/tracks/class-wc-site-tracking.php'; } WC_Tracks::record_event( $event_name, $properties ); }