.elementor-alert{padding:15px;border-left:5px solid transparent;position:relative;text-align:right}.elementor-alert .elementor-alert-title{display:block;font-weight:700}.elementor-alert .elementor-alert-description{font-size:13px}.elementor-alert button.elementor-alert-dismiss{position:absolute;left:var(--dismiss-icon-horizontal-position,10px);top:var(--dismiss-icon-vertical-position,10px);padding:3px;font-size:var(--dismiss-icon-size,20px);line-height:1;background:transparent;color:var(--dismiss-icon-normal-color,inherit);border:none;cursor:pointer;transition-duration:var(--dismiss-icon-hover-transition-duration,.3s)}.elementor-alert button.elementor-alert-dismiss:hover{color:var(--dismiss-icon-hover-color,inherit)}.elementor-alert button.elementor-alert-dismiss svg{width:var(--dismiss-icon-size,20px);height:var(--dismiss-icon-size,20px);fill:var(--dismiss-icon-normal-color,currentColor);transition-duration:var(--dismiss-icon-hover-transition-duration,.3s)}.elementor-alert button.elementor-alert-dismiss svg:hover{fill:var(--dismiss-icon-hover-color,currentColor)}.elementor-alert.elementor-alert-info{color:#31708f;background-color:#d9edf7;border-color:#bcdff1}.elementor-alert.elementor-alert-success{color:#3c763d;background-color:#dff0d8;border-color:#cae6be}.elementor-alert.elementor-alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#f9f0c3}.elementor-alert.elementor-alert-danger{color:#a94442;background-color:#f2dede;border-color:#e8c4c4}@media (max-width:ELEMENTOR_SCREEN_MOBILE_MAX){.elementor-alert{padding:10px}.elementor-alert button.elementor-alert-dismiss{right:7px;top:7px}}/** * WooCommerce Product Functions * * Functions for product specific things. * * @package WooCommerce\Functions * @version 3.0.0 */ use Automattic\Jetpack\Constants; use Automattic\WooCommerce\Enums\ProductStatus; use Automattic\WooCommerce\Enums\ProductStockStatus; use Automattic\WooCommerce\Enums\ProductType; use Automattic\WooCommerce\Enums\CatalogVisibility; use Automattic\WooCommerce\Internal\Utilities\ProductUtil; use Automattic\WooCommerce\Proxies\LegacyProxy; use Automattic\WooCommerce\Utilities\ArrayUtil; use Automattic\WooCommerce\Utilities\NumberUtil; use Automattic\WooCommerce\Internal\ProductImage\MatchImageBySKU; defined( 'ABSPATH' ) || exit; /** * Standard way of retrieving products based on certain parameters. * * This function should be used for product retrieval so that we have a data agnostic * way to get a list of products. * * Args and usage: https://github.com/woocommerce/woocommerce/wiki/wc_get_products-and-WC_Product_Query * * @since 3.0.0 * @param array $args Array of args (above). * @return array|stdClass Number of pages and an array of product objects if * paginate is true, or just an array of values. */ function wc_get_products( $args ) { // Handle some BW compatibility arg names where wp_query args differ in naming. $map_legacy = array( 'numberposts' => 'limit', 'post_status' => 'status', 'post_parent' => 'parent', 'posts_per_page' => 'limit', 'paged' => 'page', ); foreach ( $map_legacy as $from => $to ) { if ( isset( $args[ $from ] ) ) { $args[ $to ] = $args[ $from ]; } } $query = new WC_Product_Query( $args ); return $query->get_products(); } /** * Main function for returning products, uses the WC_Product_Factory class. * * This function should only be called after 'init' action is finished, as there might be taxonomies that are getting * registered during the init action. * * @since 2.2.0 * * @param mixed $the_product Post object or post ID of the product. * @param array $deprecated Previously used to pass arguments to the factory, e.g. to force a type. * @return WC_Product|null|false */ function wc_get_product( $the_product = false, $deprecated = array() ) { if ( ! did_action( 'woocommerce_init' ) || ! did_action( 'woocommerce_after_register_taxonomy' ) || ! did_action( 'woocommerce_after_register_post_type' ) ) { /* translators: 1: wc_get_product 2: woocommerce_init 3: woocommerce_after_register_taxonomy 4: woocommerce_after_register_post_type */ wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%1$s should not be called before the %2$s, %3$s and %4$s actions have finished.', 'woocommerce' ), 'wc_get_product', 'woocommerce_init', 'woocommerce_after_register_taxonomy', 'woocommerce_after_register_post_type' ), '3.9' ); return false; } if ( ! empty( $deprecated ) ) { wc_deprecated_argument( 'args', '3.0', 'Passing args to wc_get_product is deprecated. If you need to force a type, construct the product class directly.' ); } return WC()->product_factory->get_product( $the_product, $deprecated ); } /** * Get a product object. * * @see WC_Product_Factory::get_product_classname * @since 3.9.0 * @param string $product_type Product type. If used an invalid type a WC_Product_Simple instance will be returned. * @param int $product_id Product ID. * @return WC_Product */ function wc_get_product_object( $product_type, $product_id = 0 ) { $classname = WC_Product_Factory::get_product_classname( $product_id, $product_type ); return new $classname( $product_id ); } /** * Returns whether or not SKUS are enabled. * * @return bool */ function wc_product_sku_enabled() { return apply_filters( 'wc_product_sku_enabled', true ); } /** * Returns whether or not product weights are enabled. * * @return bool */ function wc_product_weight_enabled() { return apply_filters( 'wc_product_weight_enabled', true ); } /** * Returns whether or not product dimensions (HxWxD) are enabled. * * @return bool */ function wc_product_dimensions_enabled() { return apply_filters( 'wc_product_dimensions_enabled', true ); } /** * Clear transient cache for product data. * * @param int $post_id (default: 0) The product ID. */ function wc_delete_product_transients( $post_id = 0 ) { // Transient data to clear with a fixed name which may be stale after product updates. $transients_to_clear = array( 'wc_products_onsale', 'wc_featured_products', 'wc_outofstock_count', 'wc_low_stock_count', ); foreach ( $transients_to_clear as $transient ) { delete_transient( $transient ); } if ( $post_id > 0 ) { // Transient names that include an ID - since they are dynamic they cannot be cleaned in bulk without the ID. wc_get_container()->get( ProductUtil::class )->delete_product_specific_transients( $post_id ); } // Kept for compatibility, WooCommerce core doesn't use product transient versions anymore. WC_Cache_Helper::get_transient_version( 'product', true ); do_action( 'woocommerce_delete_product_transients', $post_id ); } /** * Delete all related products transients when a product is updated/created. * This is necessary because changing one product affects all related products too. * * @since 9.8.0 * @deprecated 10.1.0 This function is deprecated and will be removed in a future version. * @param int $post_id The product ID updated/created. */ function wc_delete_related_product_transients( $post_id ) { wc_deprecated_function( 'wc_delete_related_product_transients', '10.1.0', 'This function is deprecated and will be removed in a future version.' ); if ( ! is_numeric( $post_id ) ) { return; } $transient_name = 'wc_related_' . $post_id; $old_transient = get_transient( $transient_name ); $old_related_product_ids = array(); if ( is_array( $old_transient ) && ! empty( $old_transient ) ) { $old_related_product_ids = $old_transient[ array_key_first( $old_transient ) ]; } // Delete current product transient so that it can be refreshed below. delete_transient( $transient_name ); // Gets new related products and sets current product transient. $new_related_product_ids = wc_get_related_products( $post_id, 1000 ); // Combine all product IDs that need their transients cleared. $related_product_ids = array_unique( array_merge( $old_related_product_ids, $new_related_product_ids ) ); if ( empty( $related_product_ids ) ) { return; } // Create the list of transient names to delete. $related_product_transients = array_map( function ( $id ) { return 'wc_related_' . $id; }, $related_product_ids ); _wc_delete_transients( $related_product_transients ); } /** * Function that returns an array containing the IDs of the products that are on sale. * * @since 2.0 * @return array */ function wc_get_product_ids_on_sale() { // Load from cache. $product_ids_on_sale = get_transient( 'wc_products_onsale' ); // Valid cache found. if ( false !== $product_ids_on_sale ) { return $product_ids_on_sale; } $data_store = WC_Data_Store::load( 'product' ); $on_sale_products = $data_store->get_on_sale_products(); $product_ids_on_sale = wp_parse_id_list( array_merge( wp_list_pluck( $on_sale_products, 'id' ), array_diff( wp_list_pluck( $on_sale_products, 'parent_id' ), array( 0 ) ) ) ); set_transient( 'wc_products_onsale', $product_ids_on_sale, DAY_IN_SECONDS * 30 ); return $product_ids_on_sale; } /** * Function that returns an array containing the IDs of the featured products. * * @since 2.1 * @return array */ function wc_get_featured_product_ids() { // Load from cache. $featured_product_ids = get_transient( 'wc_featured_products' ); // Valid cache found. if ( false !== $featured_product_ids ) { return $featured_product_ids; } $data_store = WC_Data_Store::load( 'product' ); $featured = $data_store->get_featured_product_ids(); $product_ids = array_keys( $featured ); $parent_ids = array_values( array_filter( $featured ) ); $featured_product_ids = array_unique( array_merge( $product_ids, $parent_ids ) ); set_transient( 'wc_featured_products', $featured_product_ids, DAY_IN_SECONDS * 30 ); return $featured_product_ids; } /** * Filter to allow product_cat in the permalinks for products. * * @param string $permalink The existing permalink URL. * @param WP_Post $post WP_Post object. * @return string */ function wc_product_post_type_link( $permalink, $post ) { // Abort if post is not a product. if ( 'product' !== $post->post_type ) { return $permalink; } // Abort early if the placeholder rewrite tag isn't in the generated URL. if ( false === strpos( $permalink, '%' ) ) { return $permalink; } // Get the custom taxonomy terms in use by this post. $terms = get_the_terms( $post->ID, 'product_cat' ); if ( ! empty( $terms ) ) { $terms = wp_list_sort( $terms, array( 'parent' => 'DESC', 'term_id' => 'ASC', ) ); $category_object = apply_filters( 'wc_product_post_type_link_product_cat', $terms[0], $terms, $post ); $product_cat = $category_object->slug; if ( $category_object->parent ) { $ancestors = get_ancestors( $category_object->term_id, 'product_cat' ); foreach ( $ancestors as $ancestor ) { $ancestor_object = get_term( $ancestor, 'product_cat' ); if ( apply_filters( 'woocommerce_product_post_type_link_parent_category_only', false ) ) { $product_cat = $ancestor_object->slug; } else { $product_cat = $ancestor_object->slug . '/' . $product_cat; } } } } else { // If no terms are assigned to this post, use a string instead (can't leave the placeholder there). $product_cat = _x( 'uncategorized', 'slug', 'woocommerce' ); } $find = array( '%year%', '%monthnum%', '%day%', '%hour%', '%minute%', '%second%', '%post_id%', '%category%', '%product_cat%', ); $replace = array( date_i18n( 'Y', strtotime( $post->post_date ) ), date_i18n( 'm', strtotime( $post->post_date ) ), date_i18n( 'd', strtotime( $post->post_date ) ), date_i18n( 'H', strtotime( $post->post_date ) ), date_i18n( 'i', strtotime( $post->post_date ) ), date_i18n( 's', strtotime( $post->post_date ) ), $post->ID, $product_cat, $product_cat, ); $permalink = str_replace( $find, $replace, $permalink ); return $permalink; } add_filter( 'post_type_link', 'wc_product_post_type_link', 10, 2 ); /** * Ensure that the product_cat value determined in `wc_product_post_type_link` is the canonical value. * * If other values are used in this part of the permalink, it will be redirected. * * @return void */ function wc_product_canonical_redirect(): void { global $wp_rewrite; if ( ! did_action( 'woocommerce_init' ) || ! is_product() || ! is_a( $wp_rewrite, WP_Rewrite::class ) ) { return; } // In the event we are dealing with ugly permalinks, this will be empty. $specified_category_slug = get_query_var( 'product_cat' ); $specified_category_slug = urldecode( $specified_category_slug ); if ( ! is_string( $specified_category_slug ) || strlen( $specified_category_slug ) < 1 ) { return; } // What category slug did we expect? Normally this maps back to the first assigned product_cat // term. However, this is filterable so we use the relevant helper function to figure this out. $expected_category_slug = wc_product_post_type_link( '%product_cat%', get_post( get_the_ID() ) ); $expected_category_slug = urldecode( $expected_category_slug ); if ( $specified_category_slug === $expected_category_slug ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $query_vars = isset( $_GET ) && is_array( $_GET ) ? $_GET : array(); wp_safe_redirect( add_query_arg( $query_vars, wc_get_product( get_the_ID() )->get_permalink() ), 301 ); exit(); } add_action( 'template_redirect', 'wc_product_canonical_redirect', 5 ); /** * Get the placeholder image URL either from media, or use the fallback image. * * @param string $size Thumbnail size to use. * @return string */ function wc_placeholder_img_src( $size = 'woocommerce_thumbnail' ) { $src = WC()->plugin_url() . '/assets/images/placeholder.webp'; $placeholder_image = get_option( 'woocommerce_placeholder_image', 0 ); if ( ! empty( $placeholder_image ) ) { if ( is_numeric( $placeholder_image ) ) { $image = wp_get_attachment_image_src( $placeholder_image, $size ); if ( ! empty( $image[0] ) ) { $src = $image[0]; } } else { $src = $placeholder_image; } } return apply_filters( 'woocommerce_placeholder_img_src', $src ); } /** * Get the placeholder image. * * Uses wp_get_attachment_image if using an attachment ID @since 3.6.0 to handle responsiveness. * * @param string $size Image size. * @param string|array $attr Optional. Attributes for the image markup. Default empty. * @return string */ function wc_placeholder_img( $size = 'woocommerce_thumbnail', $attr = '' ) { $dimensions = wc_get_image_size( $size ); $placeholder_image = get_option( 'woocommerce_placeholder_image', 0 ); $default_attr = array( 'class' => 'woocommerce-placeholder wp-post-image', 'alt' => __( 'Placeholder', 'woocommerce' ), ); $attr = wp_parse_args( $attr, $default_attr ); if ( wp_attachment_is_image( $placeholder_image ) ) { $image_html = wp_get_attachment_image( $placeholder_image, $size, false, $attr ); } else { $image = wc_placeholder_img_src( $size ); $hwstring = image_hwstring( $dimensions['width'], $dimensions['height'] ); $attributes = array(); foreach ( $attr as $name => $value ) { $attributes[] = esc_attr( $name ) . '="' . esc_attr( $value ) . '"'; } $image_html = ''; } return apply_filters( 'woocommerce_placeholder_img', $image_html, $size, $dimensions ); } /** * Variation Formatting. * * Gets a formatted version of variation data or item meta. * * @param array|WC_Product_Variation $variation Variation object. * @param bool $flat Should this be a flat list or HTML list? (default: false). * @param bool $include_names include attribute names/labels in the list. * @param bool $skip_attributes_in_name Do not list attributes already part of the variation name. * @return string */ function wc_get_formatted_variation( $variation, $flat = false, $include_names = true, $skip_attributes_in_name = false ) { $return = ''; if ( is_a( $variation, 'WC_Product_Variation' ) ) { $variation_attributes = $variation->get_attributes(); $product = $variation; $variation_name = $variation->get_name(); } else { $product = false; $variation_name = ''; // Remove attribute_ prefix from names. $variation_attributes = array(); if ( is_array( $variation ) ) { foreach ( $variation as $key => $value ) { $variation_attributes[ str_replace( 'attribute_', '', $key ) ] = $value; } } } $list_type = $include_names ? 'dl' : 'ul'; if ( is_array( $variation_attributes ) && ! empty( $variation_attributes ) ) { if ( ! $flat ) { $return = '<' . $list_type . ' class="variation">'; } $variation_list = array(); foreach ( $variation_attributes as $name => $value ) { // If this is a term slug, get the term's nice name. if ( taxonomy_exists( $name ) ) { $term = get_term_by( 'slug', $value, $name ); if ( ! is_wp_error( $term ) && $term && null !== $term->name && '' !== $term->name ) { $value = $term->name; } } // Do not list attributes already part of the variation name. if ( '' === $value || ( $skip_attributes_in_name && wc_is_attribute_in_product_name( $value, $variation_name ) ) ) { continue; } if ( $include_names ) { if ( $flat ) { $variation_list[] = wc_attribute_label( $name, $product ) . ': ' . rawurldecode( $value ); } else { $variation_list[] = '
' . wc_attribute_label( $name, $product ) . ':
' . rawurldecode( $value ) . '
'; } } elseif ( $flat ) { $variation_list[] = rawurldecode( $value ); } else { $variation_list[] = '
  • ' . rawurldecode( $value ) . '
  • '; } } if ( $flat ) { $return .= implode( ', ', $variation_list ); } else { $return .= implode( '', $variation_list ); } if ( ! $flat ) { $return .= ''; } } return $return; } /** * Function which handles the start and end of scheduled sales via cron. */ function wc_scheduled_sales() { $data_store = WC_Data_Store::load( 'product' ); $product_util = wc_get_container()->get( ProductUtil::class ); $must_refresh_transient = false; // Sales which are due to start. $product_ids = $data_store->get_starting_sales(); if ( $product_ids ) { $must_refresh_transient = true; do_action( 'wc_before_products_starting_sales', $product_ids ); foreach ( $product_ids as $product_id ) { $product = wc_get_product( $product_id ); if ( $product ) { $sale_price = $product->get_sale_price(); if ( $sale_price ) { $product->set_price( $sale_price ); $product->set_date_on_sale_from( '' ); } else { $product->set_date_on_sale_to( '' ); $product->set_date_on_sale_from( '' ); } $product->save(); } $product_util->delete_product_specific_transients( $product ? $product : $product_id ); } do_action( 'wc_after_products_starting_sales', $product_ids ); delete_transient( 'wc_products_onsale' ); } // Sales which are due to end. $product_ids = $data_store->get_ending_sales(); if ( $product_ids ) { $must_refresh_transient = true; do_action( 'wc_before_products_ending_sales', $product_ids ); foreach ( $product_ids as $product_id ) { $product = wc_get_product( $product_id ); if ( $product ) { $regular_price = $product->get_regular_price(); $product->set_price( $regular_price ); $product->set_sale_price( '' ); $product->set_date_on_sale_to( '' ); $product->set_date_on_sale_from( '' ); $product->save(); } $product_util->delete_product_specific_transients( $product ? $product : $product_id ); } do_action( 'wc_after_products_ending_sales', $product_ids ); delete_transient( 'wc_products_onsale' ); } if ( $must_refresh_transient ) { // Kept for compatibility, WooCommerce core doesn't use product transient versions anymore. WC_Cache_Helper::get_transient_version( 'product', true ); } } add_action( 'woocommerce_scheduled_sales', 'wc_scheduled_sales' ); /** * Get attachment image attributes. * * @param array $attr Image attributes. * @return array */ function wc_get_attachment_image_attributes( $attr ) { /* * If the user can manage woocommerce, allow them to * see the image content. */ if ( current_user_can( 'manage_woocommerce' ) ) { return $attr; } /* * If the user does not have the right capabilities, * filter out the image source and replace with placeholder * image. */ if ( isset( $attr['src'] ) && strstr( $attr['src'], 'woocommerce_uploads/' ) ) { $attr['src'] = wc_placeholder_img_src(); if ( isset( $attr['srcset'] ) ) { $attr['srcset'] = ''; } } return $attr; } add_filter( 'wp_get_attachment_image_attributes', 'wc_get_attachment_image_attributes' ); /** * Prepare attachment for JavaScript. * * @param array $response JS version of a attachment post object. * @return array */ function wc_prepare_attachment_for_js( $response ) { /* * If the user can manage woocommerce, allow them to * see the image content. */ if ( current_user_can( 'manage_woocommerce' ) ) { return $response; } /* * If the user does not have the right capabilities, * filter out the image source and replace with placeholder * image. */ if ( isset( $response['url'] ) && strstr( $response['url'], 'woocommerce_uploads/' ) ) { $response['full']['url'] = wc_placeholder_img_src(); if ( isset( $response['sizes'] ) ) { foreach ( $response['sizes'] as $size => $value ) { $response['sizes'][ $size ]['url'] = wc_placeholder_img_src(); } } } return $response; } add_filter( 'wp_prepare_attachment_for_js', 'wc_prepare_attachment_for_js' ); /** * Track product views. */ function wc_track_product_view() { if ( ! is_singular( 'product' ) || ! is_active_widget( false, false, 'woocommerce_recently_viewed_products', true ) ) { return; } global $post; if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) ) { // @codingStandardsIgnoreLine. $viewed_products = array(); } else { $viewed_products = wp_parse_id_list( (array) explode( '|', wp_unslash( $_COOKIE['woocommerce_recently_viewed'] ) ) ); // @codingStandardsIgnoreLine. } // Unset if already in viewed products list. $keys = array_flip( $viewed_products ); if ( isset( $keys[ $post->ID ] ) ) { unset( $viewed_products[ $keys[ $post->ID ] ] ); } $viewed_products[] = $post->ID; if ( count( $viewed_products ) > 15 ) { array_shift( $viewed_products ); } // Store for session only. wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) ); } add_action( 'template_redirect', 'wc_track_product_view', 20 ); /** * Get product types. * * @since 2.2 * @return array */ function wc_get_product_types() { return (array) apply_filters( 'product_type_selector', array( ProductType::SIMPLE => __( 'Simple product', 'woocommerce' ), ProductType::GROUPED => __( 'Grouped product', 'woocommerce' ), ProductType::EXTERNAL => __( 'External/Affiliate product', 'woocommerce' ), ProductType::VARIABLE => __( 'Variable product', 'woocommerce' ), ) ); } /** * Check if product sku is unique. * * @since 2.2 * @param int $product_id Product ID. * @param string $sku Product SKU. * @return bool */ function wc_product_has_unique_sku( $product_id, $sku ) { /** * Gives plugins an opportunity to verify SKU uniqueness themselves. * * @since 9.0.0 * * @param bool|null $has_unique_sku Set to a boolean value to short-circuit the default SKU check. * @param int $product_id The ID of the current product. * @param string $sku The SKU to check for uniqueness. */ $has_unique_sku = apply_filters( 'wc_product_pre_has_unique_sku', null, $product_id, $sku ); if ( ! is_null( $has_unique_sku ) ) { return boolval( $has_unique_sku ); } $data_store = WC_Data_Store::load( 'product' ); $sku_found = $data_store->is_existing_sku( $product_id, $sku ); if ( apply_filters( 'wc_product_has_unique_sku', $sku_found, $product_id, $sku ) ) { return false; } return true; } /** * Check if product unique ID is unique. * * @since 9.1.0 * @param int $product_id Product ID. * @param string $global_unique_id Product Unique ID. * @return bool */ function wc_product_has_global_unique_id( $product_id, $global_unique_id ) { /** * Gives plugins an opportunity to verify Unique ID uniqueness themselves. * * @since 9.1.0 * * @param bool|null $has_global_unique_id Set to a boolean value to short-circuit the default Unique ID check. * @param int $product_id The ID of the current product. * @param string $sku The Unique ID to check for uniqueness. */ $has_global_unique_id = apply_filters( 'wc_product_pre_has_global_unique_id', null, $product_id, $global_unique_id ); if ( ! is_null( $has_global_unique_id ) ) { return boolval( $has_global_unique_id ); } $data_store = WC_Data_Store::load( 'product' ); if ( $data_store->has_callable( 'is_existing_global_unique_id' ) ) { $global_unique_id_found = $data_store->is_existing_global_unique_id( $product_id, $global_unique_id ); } else { $logger = wc_get_logger(); $logger->error( 'The method is_existing_global_unique_id is not implemented in the data store.', array( 'source' => 'wc_product_has_global_unique_id' ) ); } /** * Gives plugins an opportunity to verify Unique ID uniqueness themselves. * * @since 9.1.0 * * @param boolean $global_unique_id_found Whether the Unique ID is found. * @param int $product_id The ID of the current product. * @param string $sku The Unique ID to check for uniqueness. */ if ( apply_filters( 'wc_product_has_global_unique_id', $global_unique_id_found, $product_id, $global_unique_id ) ) { return false; } return true; } /** * Force a unique SKU. * * @since 3.0.0 * @param integer $product_id Product ID. */ function wc_product_force_unique_sku( $product_id ) { $product = wc_get_product( $product_id ); $current_sku = $product ? $product->get_sku( 'edit' ) : ''; if ( $current_sku ) { try { $new_sku = wc_product_generate_unique_sku( $product_id, $current_sku ); if ( $current_sku !== $new_sku ) { $product->set_sku( $new_sku ); $product->save(); } } catch ( Exception $e ) {} // @codingStandardsIgnoreLine. } } /** * Recursively appends a suffix until a unique SKU is found. * * @since 3.0.0 * @param integer $product_id Product ID. * @param string $sku Product SKU. * @param integer $index An optional index that can be added to the product SKU. * @return string */ function wc_product_generate_unique_sku( $product_id, $sku, $index = 0 ) { $generated_sku = 0 < $index ? $sku . '-' . $index : $sku; if ( ! wc_product_has_unique_sku( $product_id, $generated_sku ) ) { $generated_sku = wc_product_generate_unique_sku( $product_id, $sku, ( $index + 1 ) ); } return $generated_sku; } /** * Get product ID by SKU. * * @since 2.3.0 * @param string $sku Product SKU. * @return int */ function wc_get_product_id_by_sku( $sku ) { $data_store = WC_Data_Store::load( 'product' ); return $data_store->get_product_id_by_sku( $sku ); } /** * Get product ID by Unique ID. * * @since 9.1.0 * @param string $global_unique_id Product Unique ID. * @return int|null */ function wc_get_product_id_by_global_unique_id( $global_unique_id ) { $data_store = WC_Data_Store::load( 'product' ); if ( $data_store->has_callable( 'get_product_id_by_global_unique_id' ) ) { return $data_store->get_product_id_by_global_unique_id( $global_unique_id ); } else { $logger = wc_get_logger(); $logger->error( 'The method get_product_id_by_global_unique_id is not implemented in the data store.', array( 'source' => 'wc_get_product_id_by_global_unique_id' ) ); } return null; } /** * Get attributes/data for an individual variation from the database and maintain its integrity. * * @since 2.4.0 * @param int $variation_id Variation ID. * @return array */ function wc_get_product_variation_attributes( $variation_id ) { // Build variation data from meta. $all_meta = is_array( get_post_meta( $variation_id ) ) ? get_post_meta( $variation_id ) : array(); $parent_id = wp_get_post_parent_id( $variation_id ); $parent_attributes = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) ); $found_parent_attributes = array(); $variation_attributes = array(); // Compare to parent variable product attributes and ensure they match. foreach ( $parent_attributes as $attribute_name => $options ) { if ( ! empty( $options['is_variation'] ) ) { $attribute = 'attribute_' . sanitize_title( $attribute_name ); $found_parent_attributes[] = $attribute; if ( ! array_key_exists( $attribute, $variation_attributes ) ) { $variation_attributes[ $attribute ] = ''; // Add it - 'any' will be assumed. } } } // Get the variation attributes from meta. foreach ( $all_meta as $name => $value ) { // Only look at valid attribute meta, and also compare variation level attributes and remove any which do not exist at parent level. if ( 0 !== strpos( $name, 'attribute_' ) || ! in_array( $name, $found_parent_attributes, true ) ) { unset( $variation_attributes[ $name ] ); continue; } /** * Pre 2.4 handling where 'slugs' were saved instead of the full text attribute. * Attempt to get full version of the text attribute from the parent. */ if ( sanitize_title( $value[0] ) === $value[0] && version_compare( get_post_meta( $parent_id, '_product_version', true ), '2.4.0', '<' ) ) { foreach ( $parent_attributes as $attribute ) { if ( 'attribute_' . sanitize_title( $attribute['name'] ) !== $name ) { continue; } $text_attributes = wc_get_text_attributes( $attribute['value'] ); foreach ( $text_attributes as $text_attribute ) { if ( sanitize_title( $text_attribute ) === $value[0] ) { $value[0] = $text_attribute; break; } } } } $variation_attributes[ $name ] = $value[0]; } return $variation_attributes; } /** * Get all product cats for a product by ID, including hierarchy * * @since 2.5.0 * @param int $product_id Product ID. * @return array */ function wc_get_product_cat_ids( $product_id ) { $product_cats = wc_get_product_term_ids( $product_id, 'product_cat' ); foreach ( $product_cats as $product_cat ) { $product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) ); } return $product_cats; } /** * Gets data about an attachment, such as alt text and captions. * * @since 2.6.0 * * @param int|null $attachment_id Attachment ID. * @param WC_Product|bool $product WC_Product object. * * @return array */ function wc_get_product_attachment_props( $attachment_id = null, $product = false ) { $props = array( 'title' => '', 'caption' => '', 'url' => '', 'alt' => '', 'src' => '', 'srcset' => false, 'sizes' => false, ); $attachment = get_post( $attachment_id ); if ( $attachment && 'attachment' === $attachment->post_type ) { $props['title'] = wp_strip_all_tags( $attachment->post_title ); $props['caption'] = wp_strip_all_tags( $attachment->post_excerpt ); $props['url'] = wp_get_attachment_url( $attachment_id ); // Alt text. $alt_text = array( wp_strip_all_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ), $props['caption'], wp_strip_all_tags( $attachment->post_title ) ); if ( $product && $product instanceof WC_Product ) { $alt_text[] = wp_strip_all_tags( get_the_title( $product->get_id() ) ); } $alt_text = array_filter( $alt_text ); $props['alt'] = $alt_text ? reset( $alt_text ) : ''; // Large version. $full_size = apply_filters( 'woocommerce_gallery_full_size', apply_filters( 'woocommerce_product_thumbnails_large_size', 'full' ) ); $src = wp_get_attachment_image_src( $attachment_id, $full_size ); $props['full_src'] = $src[0]; $props['full_src_w'] = $src[1]; $props['full_src_h'] = $src[2]; // Gallery thumbnail. $gallery_thumbnail = wc_get_image_size( 'gallery_thumbnail' ); $gallery_thumbnail_size = apply_filters( 'woocommerce_gallery_thumbnail_size', array( $gallery_thumbnail['width'], $gallery_thumbnail['height'] ) ); $src = wp_get_attachment_image_src( $attachment_id, $gallery_thumbnail_size ); $props['gallery_thumbnail_src'] = $src[0]; $props['gallery_thumbnail_src_w'] = $src[1]; $props['gallery_thumbnail_src_h'] = $src[2]; // Thumbnail version. $thumbnail_size = apply_filters( 'woocommerce_thumbnail_size', 'woocommerce_thumbnail' ); $src = wp_get_attachment_image_src( $attachment_id, $thumbnail_size ); $props['thumb_src'] = $src[0]; $props['thumb_src_w'] = $src[1]; $props['thumb_src_h'] = $src[2]; // Image source. $image_size = apply_filters( 'woocommerce_gallery_image_size', 'woocommerce_single' ); $src = wp_get_attachment_image_src( $attachment_id, $image_size ); $props['src'] = $src[0]; $props['src_w'] = $src[1]; $props['src_h'] = $src[2]; $props['srcset'] = function_exists( 'wp_get_attachment_image_srcset' ) ? wp_get_attachment_image_srcset( $attachment_id, $image_size ) : false; $props['sizes'] = function_exists( 'wp_get_attachment_image_sizes' ) ? wp_get_attachment_image_sizes( $attachment_id, $image_size ) : false; } return $props; } /** * Get product visibility options. * * @since 3.0.0 * @return array */ function wc_get_product_visibility_options() { return apply_filters( 'woocommerce_product_visibility_options', array( CatalogVisibility::VISIBLE => __( 'Shop and search results', 'woocommerce' ), CatalogVisibility::CATALOG => __( 'Shop only', 'woocommerce' ), CatalogVisibility::SEARCH => __( 'Search results only', 'woocommerce' ), CatalogVisibility::HIDDEN => __( 'Hidden', 'woocommerce' ), ) ); } /** * Get product tax class options. * * @since 3.0.0 * @return array */ function wc_get_product_tax_class_options() { $tax_classes = WC_Tax::get_tax_classes(); $tax_class_options = array(); $tax_class_options[''] = __( 'Standard', 'woocommerce' ); if ( ! empty( $tax_classes ) ) { foreach ( $tax_classes as $class ) { $tax_class_options[ sanitize_title( $class ) ] = $class; } } return $tax_class_options; } /** * Get stock status options. * * @since 3.0.0 * @return array */ function wc_get_product_stock_status_options() { return apply_filters( 'woocommerce_product_stock_status_options', array( ProductStockStatus::IN_STOCK => __( 'In stock', 'woocommerce' ), ProductStockStatus::OUT_OF_STOCK => __( 'Out of stock', 'woocommerce' ), ProductStockStatus::ON_BACKORDER => __( 'On backorder', 'woocommerce' ), ) ); } /** * Get backorder options. * * @since 3.0.0 * @return array */ function wc_get_product_backorder_options() { return array( 'no' => __( 'Do not allow', 'woocommerce' ), 'notify' => __( 'Allow, but notify customer', 'woocommerce' ), 'yes' => __( 'Allow', 'woocommerce' ), ); } /** * Get related products based on product category and tags. * * @since 3.0.0 * @param int $product_id Product ID. * @param int $limit Limit of results. * @param array $exclude_ids Exclude IDs from the results. * @param array $related_by Related by category and tags boolean flags. * @return array */ function wc_get_related_products( $product_id, $limit = 5, $exclude_ids = array(), $related_by = array() ) { // Log an error if the limit is not an integer since this is what we expect. // However this is not a problem and we can continue. if ( ! is_int( $limit ) ) { wc_get_logger()->error( sprintf( 'Invalid limit type passed to wc_get_related_products. Expected integer, got %s with value: %s', gettype( $limit ), wp_json_encode( $limit ) ), array( 'source' => 'wc_get_related_products' ) ); } // If the limit is not numeric, set it to null. $limit = is_numeric( $limit ) ? (int) $limit : null; if ( null === $limit ) { return array(); } $product_id = absint( $product_id ); $limit = $limit >= -1 ? $limit : 5; $exclude_ids = array_merge( array( 0, $product_id ), $exclude_ids ); $transient_name = 'wc_related_' . $product_id; $query_args = http_build_query( array( 'limit' => $limit, 'exclude_ids' => $exclude_ids, 'related_by' => $related_by, ) ); $transient = get_transient( $transient_name ); $related_posts = $transient && is_array( $transient ) && isset( $transient[ $query_args ] ) ? $transient[ $query_args ] : false; // We want to query related posts if they are not cached, or we don't have enough. if ( false === $related_posts || count( $related_posts ) < $limit ) { $cats_array = apply_filters( 'woocommerce_product_related_posts_relate_by_category', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_cat_terms', wc_get_product_term_ids( $product_id, 'product_cat' ), $product_id ) : array(); $tags_array = apply_filters( 'woocommerce_product_related_posts_relate_by_tag', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_tag_terms', wc_get_product_term_ids( $product_id, 'product_tag' ), $product_id ) : array(); // Don't bother if none are set, unless woocommerce_product_related_posts_force_display is set to true in which case all products are related. if ( empty( $cats_array ) && empty( $tags_array ) && ! apply_filters( 'woocommerce_product_related_posts_force_display', false, $product_id ) ) { $related_posts = array(); } else { $data_store = WC_Data_Store::load( 'product' ); $related_posts = $data_store->get_related_products( $cats_array, $tags_array, $exclude_ids, $limit + 10, $product_id ); } if ( $transient && is_array( $transient ) ) { $transient[ $query_args ] = $related_posts; } else { $transient = array( $query_args => $related_posts ); } set_transient( $transient_name, $transient, DAY_IN_SECONDS ); } $related_posts = apply_filters( 'woocommerce_related_products', $related_posts, $product_id, array( 'limit' => $limit, 'excluded_ids' => $exclude_ids, ) ); $related_posts = is_array( $related_posts ) ? $related_posts : array(); if ( apply_filters( 'woocommerce_product_related_posts_shuffle', true ) ) { shuffle( $related_posts ); } return array_slice( $related_posts, 0, $limit ); } /** * Retrieves product term ids for a taxonomy. * * @since 3.0.0 * @param int $product_id Product ID. * @param string $taxonomy Taxonomy slug. * @return array */ function wc_get_product_term_ids( $product_id, $taxonomy ) { $terms = get_the_terms( $product_id, $taxonomy ); return ( empty( $terms ) || is_wp_error( $terms ) ) ? array() : wp_list_pluck( $terms, 'term_id' ); } /** * For a given product, and optionally price/qty, work out the price with tax included, based on store settings. * * @since 3.0.0 * @param WC_Product $product WC_Product object. * @param array $args Optional arguments to pass product quantity and price. * @return float|string Price with tax included, or an empty string if price calculation failed. */ function wc_get_price_including_tax( $product, $args = array() ) { $args = wp_parse_args( $args, array( 'qty' => '', 'price' => '', ) ); $price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : (float) $product->get_price(); $qty = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1; if ( empty( $qty ) ) { return 0.0; } $line_price = $price * $qty; $return_price = $line_price; if ( $product->is_taxable() ) { if ( ! wc_prices_include_tax() ) { // If the customer is exempt from VAT, set tax total to 0. if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) { $taxes_total = 0.00; } else { $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); $taxes = WC_Tax::calc_tax( $line_price, $tax_rates, false ); if ( 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ) ) { $taxes_total = array_sum( $taxes ); } else { $taxes_total = array_sum( array_map( 'wc_round_tax_total', $taxes ) ); } } $return_price = NumberUtil::round( $line_price + $taxes_total, wc_get_price_decimals() ); } else { $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); $base_tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) ); /** * If the customer is exempt from VAT, remove the taxes here. * Either remove the base or the user taxes depending on woocommerce_adjust_non_base_location_prices setting. */ if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) { // @codingStandardsIgnoreLine. $remove_taxes = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $line_price, $base_tax_rates, true ) : WC_Tax::calc_tax( $line_price, $tax_rates, true ); if ( 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ) ) { $remove_taxes_total = array_sum( $remove_taxes ); } else { $remove_taxes_total = array_sum( array_map( 'wc_round_tax_total', $remove_taxes ) ); } $return_price = NumberUtil::round( $line_price - $remove_taxes_total, wc_get_price_decimals() ); /** * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. */ } elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { $base_taxes = WC_Tax::calc_tax( $line_price, $base_tax_rates, true ); $modded_taxes = WC_Tax::calc_tax( $line_price - array_sum( $base_taxes ), $tax_rates, false ); if ( 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ) ) { $base_taxes_total = array_sum( $base_taxes ); $modded_taxes_total = array_sum( $modded_taxes ); } else { $base_taxes_total = array_sum( array_map( 'wc_round_tax_total', $base_taxes ) ); $modded_taxes_total = array_sum( array_map( 'wc_round_tax_total', $modded_taxes ) ); } $return_price = NumberUtil::round( $line_price - $base_taxes_total + $modded_taxes_total, wc_get_price_decimals() ); } } } return apply_filters( 'woocommerce_get_price_including_tax', $return_price, $qty, $product ); } /** * For a given product, and optionally price/qty, work out the price with tax excluded, based on store settings. * * @since 3.0.0 * @param WC_Product $product WC_Product object. * @param array $args Optional arguments to pass product quantity and price. * @return float|string Price with tax excluded, or an empty string if price calculation failed. */ function wc_get_price_excluding_tax( $product, $args = array() ) { if ( ! ( $product instanceof WC_Product ) ) { return ''; } $args = wp_parse_args( $args, array( 'qty' => '', 'price' => '', ) ); $price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : (float) $product->get_price(); $qty = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1; if ( empty( $qty ) ) { return 0.0; } $line_price = $price * $qty; if ( $product->is_taxable() && wc_prices_include_tax() ) { $order = ArrayUtil::get_value_or_default( $args, 'order' ); $customer_id = $order ? $order->get_customer_id() : 0; if ( apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { $tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) ); } else { $customer = $customer_id ? wc_get_container()->get( LegacyProxy::class )->get_instance_of( WC_Customer::class, $customer_id ) : null; $tax_rates = WC_Tax::get_rates( $product->get_tax_class(), $customer ); } $remove_taxes = WC_Tax::calc_tax( $line_price, $tax_rates, true ); $return_price = $line_price - array_sum( $remove_taxes ); // Unrounded since we're dealing with tax inclusive prices. Matches logic in cart-totals class. @see adjust_non_base_location_price. } else { $return_price = $line_price; } return apply_filters( 'woocommerce_get_price_excluding_tax', $return_price, $qty, $product ); } /** * Returns the price including or excluding tax. * * By default it's based on the 'woocommerce_tax_display_shop' setting. * Set `$arg['display_context']` to 'cart' to base on the 'woocommerce_tax_display_cart' setting instead. * * @since 3.0.0 * @since 7.6.0 Added `display_context` argument. * * @param WC_Product $product WC_Product object. * @param array $args Optional arguments to pass product quantity and price. * @return float */ function wc_get_price_to_display( $product, $args = array() ) { $args = wp_parse_args( $args, array( 'qty' => 1, 'price' => $product->get_price(), 'display_context' => 'shop', ) ); $price = $args['price']; $qty = $args['qty']; $tax_display = get_option( 'cart' === $args['display_context'] ? 'woocommerce_tax_display_cart' : 'woocommerce_tax_display_shop' ); return 'incl' === $tax_display ? wc_get_price_including_tax( $product, array( 'qty' => $qty, 'price' => $price, ) ) : wc_get_price_excluding_tax( $product, array( 'qty' => $qty, 'price' => $price, ) ); } /** * Returns the product categories in a list. * * @param int $product_id Product ID. * @param string $sep (default: ', '). * @param string $before (default: ''). * @param string $after (default: ''). * @return string */ function wc_get_product_category_list( $product_id, $sep = ', ', $before = '', $after = '' ) { return get_the_term_list( $product_id, 'product_cat', $before, $sep, $after ); } /** * Returns the product tags in a list. * * @param int $product_id Product ID. * @param string $sep (default: ', '). * @param string $before (default: ''). * @param string $after (default: ''). * @return string */ function wc_get_product_tag_list( $product_id, $sep = ', ', $before = '', $after = '' ) { return get_the_term_list( $product_id, 'product_tag', $before, $sep, $after ); } /** * Callback for array filter to get visible only. * * @since 3.0.0 * @param WC_Product $product WC_Product object. * @return bool */ function wc_products_array_filter_visible( $product ) { return $product && is_a( $product, 'WC_Product' ) && $product->is_visible(); } /** * Callback for array filter to get visible grouped products only. * * @since 3.1.0 * @param WC_Product $product WC_Product object. * @return bool */ function wc_products_array_filter_visible_grouped( $product ) { return $product && is_a( $product, 'WC_Product' ) && ( ProductStatus::PUBLISH === $product->get_status() || current_user_can( 'edit_product', $product->get_id() ) ); } /** * Callback for array filter to get products the user can edit only. * * @since 3.0.0 * @param WC_Product $product WC_Product object. * @return bool */ function wc_products_array_filter_editable( $product ) { return $product && is_a( $product, 'WC_Product' ) && current_user_can( 'edit_product', $product->get_id() ); } /** * Callback for array filter to get products the user can view only. * * @since 3.4.0 * @param WC_Product $product WC_Product object. * @return bool */ function wc_products_array_filter_readable( $product ) { return $product && is_a( $product, 'WC_Product' ) && current_user_can( 'read_product', $product->get_id() ); } /** * Sort an array of products by a value. * * @since 3.0.0 * * @param array $products List of products to be ordered. * @param string $orderby Optional order criteria. * @param string $order Ascending or descending order. * * @return array */ function wc_products_array_orderby( $products, $orderby = 'date', $order = 'desc' ) { $orderby = strtolower( $orderby ); $order = strtolower( $order ); switch ( $orderby ) { case 'title': case 'id': case 'date': case 'modified': case 'menu_order': case 'price': usort( $products, 'wc_products_array_orderby_' . $orderby ); break; case 'none': break; default: shuffle( $products ); break; } if ( 'desc' === $order ) { $products = array_reverse( $products ); } return $products; } /** * Sort by title. * * @since 3.0.0 * @param WC_Product $a First WC_Product object. * @param WC_Product $b Second WC_Product object. * @return int */ function wc_products_array_orderby_title( $a, $b ) { return strcasecmp( $a->get_name(), $b->get_name() ); } /** * Sort by id. * * @since 3.0.0 * @param WC_Product $a First WC_Product object. * @param WC_Product $b Second WC_Product object. * @return int */ function wc_products_array_orderby_id( $a, $b ) { if ( $a->get_id() === $b->get_id() ) { return 0; } return ( $a->get_id() < $b->get_id() ) ? -1 : 1; } /** * Sort by date. * * @since 3.0.0 * @param WC_Product $a First WC_Product object. * @param WC_Product $b Second WC_Product object. * @return int */ function wc_products_array_orderby_date( $a, $b ) { if ( $a->get_date_created() === $b->get_date_created() ) { return 0; } return ( $a->get_date_created() < $b->get_date_created() ) ? -1 : 1; } /** * Sort by modified. * * @since 3.0.0 * @param WC_Product $a First WC_Product object. * @param WC_Product $b Second WC_Product object. * @return int */ function wc_products_array_orderby_modified( $a, $b ) { if ( $a->get_date_modified() === $b->get_date_modified() ) { return 0; } return ( $a->get_date_modified() < $b->get_date_modified() ) ? -1 : 1; } /** * Sort by menu order. * * @since 3.0.0 * @param WC_Product $a First WC_Product object. * @param WC_Product $b Second WC_Product object. * @return int */ function wc_products_array_orderby_menu_order( $a, $b ) { if ( $a->get_menu_order() === $b->get_menu_order() ) { return 0; } return ( $a->get_menu_order() < $b->get_menu_order() ) ? -1 : 1; } /** * Sort by price low to high. * * @since 3.0.0 * @param WC_Product $a First WC_Product object. * @param WC_Product $b Second WC_Product object. * @return int */ function wc_products_array_orderby_price( $a, $b ) { if ( $a->get_price() === $b->get_price() ) { return 0; } return ( $a->get_price() < $b->get_price() ) ? -1 : 1; } /** * Queue a product for syncing at the end of the request. * * @param int $product_id Product ID. */ function wc_deferred_product_sync( $product_id ) { global $wc_deferred_product_sync; if ( empty( $wc_deferred_product_sync ) ) { $wc_deferred_product_sync = array(); } $wc_deferred_product_sync[] = $product_id; } /** * See if the lookup table is being generated already. * * @since 3.6.0 * @return bool */ function wc_update_product_lookup_tables_is_running() { $table_updates_pending = WC()->queue()->search( array( 'status' => 'pending', 'group' => 'wc_update_product_lookup_tables', 'per_page' => 1, ) ); return (bool) count( $table_updates_pending ); } /** * Populate lookup table data for products. * * @since 3.6.0 */ function wc_update_product_lookup_tables() { global $wpdb; $is_cli = Constants::is_true( 'WP_CLI' ); if ( ! $is_cli ) { WC_Admin_Notices::add_notice( 'regenerating_lookup_table' ); } // Note that the table is not yet generated. update_option( 'woocommerce_product_lookup_table_is_generating', true ); // Make a row per product in lookup table. $wpdb->query( " INSERT IGNORE INTO {$wpdb->wc_product_meta_lookup} (`product_id`) SELECT posts.ID FROM {$wpdb->posts} posts WHERE posts.post_type IN ('product', 'product_variation') " ); // List of column names in the lookup table we need to populate. $columns = array( 'min_max_price', 'stock_quantity', 'sku', 'global_unique_id', 'stock_status', 'average_rating', 'total_sales', 'downloadable', 'virtual', 'onsale', 'tax_class', 'tax_status', // When last column is updated, woocommerce_product_lookup_table_is_generating is updated. ); foreach ( $columns as $index => $column ) { if ( $is_cli ) { wc_update_product_lookup_tables_column( $column ); } else { WC()->queue()->schedule_single( time() + $index, 'wc_update_product_lookup_tables_column', array( 'column' => $column, ), 'wc_update_product_lookup_tables' ); } } // Rating counts are serialised so they have to be unserialised before populating the lookup table. if ( $is_cli ) { $rating_count_rows = $wpdb->get_results( " SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_wc_rating_count' AND meta_value != '' AND meta_value != 'a:0:{}' ", ARRAY_A ); wc_update_product_lookup_tables_rating_count( $rating_count_rows ); } else { WC()->queue()->schedule_single( time() + 10, 'wc_update_product_lookup_tables_rating_count_batch', array( 'offset' => 0, 'limit' => 50, ), 'wc_update_product_lookup_tables' ); } } /** * Populate lookup table column data. * * @since 3.6.0 * @param string $column Column name to set. */ function wc_update_product_lookup_tables_column( $column ) { if ( empty( $column ) ) { return; } global $wpdb; switch ( $column ) { case 'min_max_price': $wpdb->query( " UPDATE {$wpdb->wc_product_meta_lookup} lookup_table INNER JOIN ( SELECT lookup_table.product_id, MIN( meta_value+0 ) as min_price, MAX( meta_value+0 ) as max_price FROM {$wpdb->wc_product_meta_lookup} lookup_table LEFT JOIN {$wpdb->postmeta} meta1 ON lookup_table.product_id = meta1.post_id AND meta1.meta_key = '_price' WHERE meta1.meta_value <> '' GROUP BY lookup_table.product_id ) as source on source.product_id = lookup_table.product_id SET lookup_table.min_price = source.min_price, lookup_table.max_price = source.max_price " ); break; case 'stock_quantity': $wpdb->query( " UPDATE {$wpdb->wc_product_meta_lookup} lookup_table LEFT JOIN {$wpdb->postmeta} meta1 ON lookup_table.product_id = meta1.post_id AND meta1.meta_key = '_manage_stock' LEFT JOIN {$wpdb->postmeta} meta2 ON lookup_table.product_id = meta2.post_id AND meta2.meta_key = '_stock' SET lookup_table.stock_quantity = meta2.meta_value WHERE meta1.meta_value = 'yes' " ); break; case 'sku': case 'global_unique_id': case 'stock_status': case 'average_rating': case 'total_sales': case 'tax_class': case 'tax_status': if ( 'total_sales' === $column ) { $meta_key = 'total_sales'; } elseif ( 'average_rating' === $column ) { $meta_key = '_wc_average_rating'; } else { $meta_key = '_' . $column; } $column = esc_sql( $column ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( $wpdb->prepare( " UPDATE {$wpdb->wc_product_meta_lookup} lookup_table LEFT JOIN {$wpdb->postmeta} meta ON lookup_table.product_id = meta.post_id AND meta.meta_key = %s SET lookup_table.`{$column}` = meta.meta_value ", $meta_key ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared break; case 'downloadable': case 'virtual': $column = esc_sql( $column ); $meta_key = '_' . $column; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( $wpdb->prepare( " UPDATE {$wpdb->wc_product_meta_lookup} lookup_table LEFT JOIN {$wpdb->postmeta} meta1 ON lookup_table.product_id = meta1.post_id AND meta1.meta_key = %s SET lookup_table.`{$column}` = IF ( meta1.meta_value = 'yes', 1, 0 ) ", $meta_key ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared break; case 'onsale': $column = esc_sql( $column ); $decimals = absint( wc_get_price_decimals() ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( $wpdb->prepare( " UPDATE {$wpdb->wc_product_meta_lookup} lookup_table LEFT JOIN {$wpdb->postmeta} meta1 ON lookup_table.product_id = meta1.post_id AND meta1.meta_key = '_price' LEFT JOIN {$wpdb->postmeta} meta2 ON lookup_table.product_id = meta2.post_id AND meta2.meta_key = '_sale_price' LEFT JOIN {$wpdb->postmeta} meta3 ON lookup_table.product_id = meta3.post_id AND meta3.meta_key = '_regular_price' SET lookup_table.`{$column}` = IF ( CAST( meta1.meta_value AS DECIMAL ) >= 0 AND CAST( meta2.meta_value AS CHAR ) != '' AND CAST( meta1.meta_value AS DECIMAL( 10, %d ) ) = CAST( meta2.meta_value AS DECIMAL( 10, %d ) ) AND CAST( meta3.meta_value AS DECIMAL( 10, %d ) ) > CAST( meta2.meta_value AS DECIMAL( 10, %d ) ) , 1, 0 ) ", $decimals, $decimals, $decimals, $decimals ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared break; } // Final column - mark complete. if ( 'tax_status' === $column ) { delete_option( 'woocommerce_product_lookup_table_is_generating' ); } } add_action( 'wc_update_product_lookup_tables_column', 'wc_update_product_lookup_tables_column' ); /** * Populate rating count lookup table data for products. * * @since 3.6.0 * @param array $rows Rows of rating counts to update in lookup table. */ function wc_update_product_lookup_tables_rating_count( $rows ) { if ( ! $rows || ! is_array( $rows ) ) { return; } global $wpdb; foreach ( $rows as $row ) { $count = array_sum( (array) maybe_unserialize( $row['meta_value'] ) ); $wpdb->update( $wpdb->wc_product_meta_lookup, array( 'rating_count' => absint( $count ), ), array( 'product_id' => absint( $row['post_id'] ), ) ); } } /** * Populate a batch of rating count lookup table data for products. * * @since 3.6.2 * @param array $offset Offset to query. * @param array $limit Limit to query. */ function wc_update_product_lookup_tables_rating_count_batch( $offset = 0, $limit = 0 ) { global $wpdb; if ( ! $limit ) { return; } $rating_count_rows = $wpdb->get_results( $wpdb->prepare( " SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_wc_rating_count' AND meta_value != '' AND meta_value != 'a:0:{}' ORDER BY post_id ASC LIMIT %d, %d ", $offset, $limit ), ARRAY_A ); if ( $rating_count_rows ) { wc_update_product_lookup_tables_rating_count( $rating_count_rows ); WC()->queue()->schedule_single( time() + 1, 'wc_update_product_lookup_tables_rating_count_batch', array( 'offset' => $offset + $limit, 'limit' => $limit, ), 'wc_update_product_lookup_tables' ); } } add_action( 'wc_update_product_lookup_tables_rating_count_batch', 'wc_update_product_lookup_tables_rating_count_batch', 10, 2 ); /** * Attach product featured image. Use image filename to match a product sku when product is not provided. * * @since 8.5.0 * @param int $attachment_id Media attachment ID. * @param WC_Product $product Optional product object. * @param bool $save_product If true, the changes in the product will be saved before the method returns. * @return void */ function wc_product_attach_featured_image( $attachment_id, $product = null, $save_product = true ) { $attachment_post = get_post( $attachment_id ); if ( ! $attachment_post ) { return; } if ( null === $product && wc_get_container()->get( MatchImageBySKU::class )->is_enabled() ) { // On upload the attachment post title is the uploaded file's filename. $file_name = pathinfo( $attachment_post->post_title, PATHINFO_FILENAME ); if ( ! $file_name ) { return; } $product_id = wc_get_product_id_by_sku( $file_name ); $product = wc_get_product( $product_id ); } if ( ! $product ) { return; } $product->set_image_id( $attachment_id ); if ( $save_product ) { $product->save(); } if ( 0 === $attachment_post->post_parent ) { wp_update_post( array( 'ID' => $attachment_id, 'post_parent' => $product->get_id(), ) ); } } add_action( 'add_attachment', 'wc_product_attach_featured_image' ); /*! elementor-pro - v3.16.0 - 20-09-2023 */ "use strict";(self.webpackChunkelementor_pro=self.webpackChunkelementor_pro||[]).push([[241],{2910:(e,r)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.default=void 0;var s=class CircularProgress{constructor(e,r){this.settings=r,this.lastKnownProgress=null,this.circularProgressTracker=e.find(".elementor-scrolling-tracker-circular")[0],this.circularCurrentProgress=this.circularProgressTracker.getElementsByClassName("current-progress")[0],this.circularCurrentProgressPercentage=this.circularProgressTracker.getElementsByClassName("current-progress-percentage")[0];const s=2*this.circularCurrentProgress.r.baseVal.value*Math.PI;this.circularCurrentProgress.style.strokeDasharray=`${s} ${s}`,this.circularCurrentProgress.style.strokeDashoffset=s,this.elements=this.cacheElements(),this.resizeObserver=new ResizeObserver((()=>{this.lastKnownProgress&&this.updateProgress(this.lastKnownProgress)})),this.resizeObserver.observe(this.circularProgressTracker)}cacheElements(){return{circularProgressTracker:this.circularProgressTracker,circularCurrentProgress:this.circularCurrentProgress,circularCurrentProgressPercentage:this.circularCurrentProgressPercentage}}updateProgress(e){if(e<=0)return this.elements.circularCurrentProgress.style.display="none",void(this.elements.circularCurrentProgressPercentage.style.display="none");this.elements.circularCurrentProgress.style.display="block",this.elements.circularCurrentProgressPercentage.style.display="block";const r=2*this.elements.circularCurrentProgress.r.baseVal.value*Math.PI,s=r-e/100*r;this.lastKnownProgress=e,this.elements.circularCurrentProgress.style.strokeDasharray=`${r} ${r}`,this.elements.circularCurrentProgress.style.strokeDashoffset="ltr"===this.settings.direction?-s:s,"yes"===this.settings.percentage&&(this.elements.circularCurrentProgressPercentage.innerHTML=Math.round(e)+"%")}onDestroy(){this.resizeObserver.unobserve(this.circularProgressTracker)}};r.default=s},3931:(e,r)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.default=void 0;var s=class LinearProgress{constructor(e,r){this.settings=r,this.linearProgressTracker=e.find(".elementor-scrolling-tracker-horizontal")[0],this.linearCurrentProgress=this.linearProgressTracker.getElementsByClassName("current-progress")[0],this.linearCurrentProgressPercentage=this.linearProgressTracker.getElementsByClassName("current-progress-percentage")[0],this.elements=this.cacheElements()}cacheElements(){return{linearProgressTracker:this.linearProgressTracker,linearCurrentProgress:this.linearCurrentProgress,linearCurrentProgressPercentage:this.linearCurrentProgressPercentage}}updateProgress(e){e<1?this.elements.linearCurrentProgress.style.display="none":(this.elements.linearCurrentProgress.style.display="flex",this.elements.linearCurrentProgress.style.width=e+"%","yes"===this.settings.percentage&&this.elements.linearCurrentProgress.getBoundingClientRect().width>1.5*this.elements.linearCurrentProgressPercentage.getBoundingClientRect().width?(this.elements.linearCurrentProgressPercentage.innerHTML=Math.round(e)+"%",this.elements.linearCurrentProgressPercentage.style.color=getComputedStyle(this.linearCurrentProgress).getPropertyValue("--percentage-color")):this.elements.linearCurrentProgressPercentage.style.color="transparent")}};r.default=s},2177:(e,r,s)=>{var t=s(3203);Object.defineProperty(r,"__esModule",{value:!0}),r.default=void 0;var n=t(s(2910)),l=t(s(3931));class ProgressTracker extends elementorModules.frontend.handlers.Base{onInit(){elementorModules.frontend.handlers.Base.prototype.onInit.apply(this,arguments),this.circular="circular"===this.getElementSettings().type;const e=this.circular?n.default:l.default;this.progressBar=new e(this.$element,this.getElementSettings()),this.progressPercentage=0,this.scrollHandler(),this.handler=this.scrollHandler.bind(this),this.initListeners()}getTrackingElementSelector(){let e;switch(this.getElementSettings().relative_to){case"selector":e=jQuery(this.getElementSettings().selector);break;case"post_content":e=jQuery(".elementor-widget-theme-post-content");break;default:e=this.isScrollSnap()?jQuery("#e-scroll-snap-container"):elementorFrontend.elements.$body}return e}isScrollSnap(){return"yes"===(this.isEdit?elementor.settings.page.model.attributes.scroll_snap:elementorFrontend.config.settings.page.scroll_snap)}addScrollSnapContainer(){this.isScrollSnap()&&!jQuery("#e-scroll-snap-container").length&&jQuery("body").wrapInner('
    ')}scrollHandler(){this.addScrollSnapContainer();const e=this.getTrackingElementSelector(),r=e.is(elementorFrontend.elements.$body)||e.is(jQuery("#e-scroll-snap-container"))?-100:0;this.progressPercentage=elementorModules.utils.Scroll.getElementViewportPercentage(this.getTrackingElementSelector(),{start:r,end:-100}),this.progressBar.updateProgress(this.progressPercentage)}initListeners(){window.addEventListener("scroll",this.handler),elementorFrontend.elements.$body[0].addEventListener("scroll",this.handler)}onDestroy(){this.progressBar.onDestroy&&this.progressBar.onDestroy(),window.removeEventListener("scroll",this.handler),elementorFrontend.elements.$body[0].removeEventListener("scroll",this.handler)}}var i=ProgressTracker;r.default=i}}]);/** * Price Functions * * @package Give * @subpackage Functions * @copyright Copyright (c) 2016, GiveWP * @license https://opensource.org/licenses/gpl-license GNU Public License * @since 1.0 */ // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Checks to see if a Give form has variable prices enabled. * * @since 1.0 * * @param int $form_id ID number of the form to check * * @return bool true if has variable prices, false otherwise */ function give_has_variable_prices( $form_id = 0 ) { if ( empty( $form_id ) ) { return false; } $form = new Give_Donate_Form( $form_id ); return $form->has_variable_prices(); } /** * Retrieves the variable prices for a form * * @since 1.0 * * @param int $form_id ID of the Give form * * @return array|bool Variable prices */ function give_get_variable_prices( $form_id = 0 ) { if ( empty( $form_id ) ) { return false; } $form = new Give_Donate_Form( $form_id ); return $form->prices; } /** * Retrieves the variable price ids for a form * * @since 1.8.8 * * @param int $form_id ID of the Give form * * @return array Variable prices */ function give_get_variable_price_ids( $form_id = 0 ) { if ( ! ( $prices = give_get_variable_prices( $form_id ) ) ) { return array(); } $price_ids = array(); foreach ( $prices as $price ) { $price_ids[] = $price['_give_id']['level_id']; } return $price_ids; } /** * Get the default amount for multi-level forms * * @access public * @since 1.0 * * @param int $form_id * * @return string $default_price */ function give_get_default_multilevel_amount( $form_id ) { $default_price = '1.00'; // Get default level price data. $default_level = give_form_get_default_level( $form_id ); $default_price = isset( $default_level['_give_amount'] ) ? $default_level['_give_amount'] : $default_price; return $default_price; } /** * Get Default Form Amount * * Grabs the default amount for set and level forms * * @param int $form_id * * @return string $default_price * @since 1.0 */ function give_get_default_form_amount( $form_id ) { if ( give_has_variable_prices( $form_id ) ) { $default_amount = give_get_default_multilevel_amount( $form_id ); } else { $default_amount = give_get_meta( $form_id, '_give_set_price', true ); } return apply_filters( 'give_default_form_amount', $default_amount, $form_id ); } /** * Determine if custom price mode is enabled or disabled. * * This function is wrapper function to Give_Donate_Form::is_custom_price_mode() * * @since 1.6 * * @param int $form_id Form ID. * * @use Give_Donate_Form::is_custom_price_mode() * * @return bool */ function give_is_custom_price_mode( $form_id = 0 ) { if ( empty( $form_id ) ) { return false; } $form = new Give_Donate_Form( $form_id ); return $form->is_custom_price_mode(); } iNGAGE™ – Kreation Avenue Academy
    Menu Close
    Our Misson
    iNGAGE Hero

    iNGAGE

     

    Preparation for What Comes After.

    A bold evolution of our Go Beyond Entrepreneurship experience — now reimagined for high school students.

     

    Why?

    Because no one should wait until after graduation to figure out who they are.

    iNGAGE™ is more than a program —
    it’s a launchpad.

    A pre-college, pre-career, pre-label experience that helps students discover:
    → Who they are
    → What they’re capable of
    → And where they’re headed

    Life doesn’t start after high school — it starts now.

    What Sets iNGAGE Apart?

    Prep for Shark Tank (and Life)

    Students will build real-world presentation skills to:

    • Pitch their startup or project idea

    • Lead with poise and confidence

    • Respond to feedback without fear

    • Win hearts and minds in 2 minutes or less

    Whether it’s the classroom, a college interview, or a boardroom — they’ll be ready to own the moment.

    🌟 One of our proudest moments? Watching a student coached by our Executive Director Sedric Simon win on Shark Tank.

    Video Credit: Shark Tank is a registered trademark of Sony Pictures Television and ABC. All rights belong to their respective owners. This program is inspired by real experiences and does not imply direct affiliation with the show.</small>

    What Students Gain

    iNGAGE™ equips young people with:

    • An entrepreneurial mindset

    • Practical life skills

    • Real-world confidence

    All delivered through our Immersive Learning X™ experience.

    They’ll:
    ✔ Create real-world projects
    ✔ Collaborate and present with confidence
    ✔ Learn through mentorship
    ✔ Build character through values-based leadership


    PROGRAM INCLUDES LIVE RECORDED COACHING SESSIONS TO TRACK PROGRESS AND FINE TUNE PERFORMANCE SIMIALR TO A PROFFESIONAL ATHLETE COURTESY OF IMMERSIVE LEARNING X.

     

     

    Who It’s For

    iNGAGE is for high school students who are:

    • Bold, curious, and driven

    • Ready to lead, not just follow

    • Looking for a real advantage — and a community of forward-thinkers

    • Entrepreneurs, creators, innovators, and quiet disruptors

     

     

     iNGAGE™ Launches Soon

    The future doesn’t wait — and neither should you.

    iNGAGE is launching soon, and spots will be limited. This is your chance to get ahead of the curve, sharpen your edge, and prepare for life beyond the classroom.

    Join the waiting list now and be the first to receive exclusive early access, updates, and behind-the-scenes previews.

    Don’t Miss it.