Default -1. * @type string $category Comma-separated list of category IDs to include links from. * Default empty. * @type string $category_name Category to retrieve links for by name. Default empty. * @type int|bool $hide_invisible Whether to show or hide links marked as 'invisible'. Accepts * 1|true or 0|false. Default 1|true. * @type int|bool $show_updated Whether to display the time the bookmark was last updated. * Accepts 1|true or 0|false. Default 0|false. * @type string $include Comma-separated list of bookmark IDs to include. Default empty. * @type string $exclude Comma-separated list of bookmark IDs to exclude. Default empty. * @type string $search Search terms. Will be SQL-formatted with wildcards before and after * and searched in 'link_url', 'link_name' and 'link_description'. * Default empty. * } * @return object[] List of bookmark row objects. */ function get_bookmarks( $args = '' ) { global $wpdb; $defaults = array( 'orderby' => 'name', 'order' => 'ASC', 'limit' => -1, 'category' => '', 'category_name' => '', 'hide_invisible' => 1, 'show_updated' => 0, 'include' => '', 'exclude' => '', 'search' => '', ); $parsed_args = wp_parse_args( $args, $defaults ); $key = md5( serialize( $parsed_args ) ); $cache = wp_cache_get( 'get_bookmarks', 'bookmark' ); if ( 'rand' !== $parsed_args['orderby'] && $cache ) { if ( is_array( $cache ) && isset( $cache[ $key ] ) ) { $bookmarks = $cache[ $key ]; /** * Filters the returned list of bookmarks. * * The first time the hook is evaluated in this file, it returns the cached * bookmarks list. The second evaluation returns a cached bookmarks list if the * link category is passed but does not exist. The third evaluation returns * the full cached results. * * @since 2.1.0 * * @see get_bookmarks() * * @param array $bookmarks List of the cached bookmarks. * @param array $parsed_args An array of bookmark query arguments. */ return apply_filters( 'get_bookmarks', $bookmarks, $parsed_args ); } } if ( ! is_array( $cache ) ) { $cache = array(); } $inclusions = ''; if ( ! empty( $parsed_args['include'] ) ) { $parsed_args['exclude'] = ''; // Ignore exclude, category, and category_name params if using include. $parsed_args['category'] = ''; $parsed_args['category_name'] = ''; $inclinks = wp_parse_id_list( $parsed_args['include'] ); if ( count( $inclinks ) ) { foreach ( $inclinks as $inclink ) { if ( empty( $inclusions ) ) { $inclusions = ' AND ( link_id = ' . $inclink . ' '; } else { $inclusions .= ' OR link_id = ' . $inclink . ' '; } } } } if ( ! empty( $inclusions ) ) { $inclusions .= ')'; } $exclusions = ''; if ( ! empty( $parsed_args['exclude'] ) ) { $exlinks = wp_parse_id_list( $parsed_args['exclude'] ); if ( count( $exlinks ) ) { foreach ( $exlinks as $exlink ) { if ( empty( $exclusions ) ) { $exclusions = ' AND ( link_id <> ' . $exlink . ' '; } else { $exclusions .= ' AND link_id <> ' . $exlink . ' '; } } } } if ( ! empty( $exclusions ) ) { $exclusions .= ')'; } if ( ! empty( $parsed_args['category_name'] ) ) { $parsed_args['category'] = get_term_by( 'name', $parsed_args['category_name'], 'link_category' ); if ( $parsed_args['category'] ) { $parsed_args['category'] = $parsed_args['category']->term_id; } else { $cache[ $key ] = array(); wp_cache_set( 'get_bookmarks', $cache, 'bookmark' ); /** This filter is documented in wp-includes/bookmark.php */ return apply_filters( 'get_bookmarks', array(), $parsed_args ); } } $search = ''; if ( ! empty( $parsed_args['search'] ) ) { $like = '%' . $wpdb->esc_like( $parsed_args['search'] ) . '%'; $search = $wpdb->prepare( ' AND ( (link_url LIKE %s) OR (link_name LIKE %s) OR (link_description LIKE %s) ) ', $like, $like, $like ); } $category_query = ''; $join = ''; if ( ! empty( $parsed_args['category'] ) ) { $incategories = wp_parse_id_list( $parsed_args['category'] ); if ( count( $incategories ) ) { foreach ( $incategories as $incat ) { if ( empty( $category_query ) ) { $category_query = ' AND ( tt.term_id = ' . $incat . ' '; } else { $category_query .= ' OR tt.term_id = ' . $incat . ' '; } } } } if ( ! empty( $category_query ) ) { $category_query .= ") AND taxonomy = 'link_category'"; $join = " INNER JOIN $wpdb->term_relationships AS tr ON ($wpdb->links.link_id = tr.object_id) INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_taxonomy_id = tr.term_taxonomy_id"; } if ( $parsed_args['show_updated'] ) { $recently_updated_test = ', IF (DATE_ADD(link_updated, INTERVAL 120 MINUTE) >= NOW(), 1,0) as recently_updated '; } else { $recently_updated_test = ''; } $get_updated = ( $parsed_args['show_updated'] ) ? ', UNIX_TIMESTAMP(link_updated) AS link_updated_f ' : ''; $orderby = strtolower( $parsed_args['orderby'] ); $length = ''; switch ( $orderby ) { case 'length': $length = ', CHAR_LENGTH(link_name) AS length'; break; case 'rand': $orderby = 'rand()'; break; case 'link_id': $orderby = "$wpdb->links.link_id"; break; default: $orderparams = array(); $keys = array( 'link_id', 'link_name', 'link_url', 'link_visible', 'link_rating', 'link_owner', 'link_updated', 'link_notes', 'link_description' ); foreach ( explode( ',', $orderby ) as $ordparam ) { $ordparam = trim( $ordparam ); if ( in_array( 'link_' . $ordparam, $keys, true ) ) { $orderparams[] = 'link_' . $ordparam; } elseif ( in_array( $ordparam, $keys, true ) ) { $orderparams[] = $ordparam; } } $orderby = implode( ',', $orderparams ); } if ( empty( $orderby ) ) { $orderby = 'link_name'; } $order = strtoupper( $parsed_args['order'] ); if ( '' !== $order && ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) { $order = 'ASC'; } $visible = ''; if ( $parsed_args['hide_invisible'] ) { $visible = "AND link_visible = 'Y'"; } $query = "SELECT * $length $recently_updated_test $get_updated FROM $wpdb->links $join WHERE 1=1 $visible $category_query"; $query .= " $exclusions $inclusions $search"; $query .= " ORDER BY $orderby $order"; if ( -1 != $parsed_args['limit'] ) { $query .= ' LIMIT ' . absint( $parsed_args['limit'] ); } $results = $wpdb->get_results( $query ); if ( 'rand()' !== $orderby ) { $cache[ $key ] = $results; wp_cache_set( 'get_bookmarks', $cache, 'bookmark' ); } /** This filter is documented in wp-includes/bookmark.php */ return apply_filters( 'get_bookmarks', $results, $parsed_args ); } /** * Sanitizes all bookmark fields. * * @since 2.3.0 * * @param stdClass|array $bookmark Bookmark row. * @param string $context Optional. How to filter the fields. Default 'display'. * @return stdClass|array Same type as $bookmark but with fields sanitized. */ function sanitize_bookmark( $bookmark, $context = 'display' ) { $fields = array( 'link_id', 'link_url', 'link_name', 'link_image', 'link_target', 'link_category', 'link_description', 'link_visible', 'link_owner', 'link_rating', 'link_updated', 'link_rel', 'link_notes', 'link_rss', ); if ( is_object( $bookmark ) ) { $do_object = true; $link_id = $bookmark->link_id; } else { $do_object = false; $link_id = $bookmark['link_id']; } foreach ( $fields as $field ) { if ( $do_object ) { if ( isset( $bookmark->$field ) ) { $bookmark->$field = sanitize_bookmark_field( $field, $bookmark->$field, $link_id, $context ); } } else { if ( isset( $bookmark[ $field ] ) ) { $bookmark[ $field ] = sanitize_bookmark_field( $field, $bookmark[ $field ], $link_id, $context ); } } } return $bookmark; } /** * Sanitizes a bookmark field. * * Sanitizes the bookmark fields based on what the field name is. If the field * has a strict value set, then it will be tested for that, else a more generic * filtering is applied. After the more strict filter is applied, if the `$context` * is 'raw' then the value is immediately return. * * Hooks exist for the more generic cases. With the 'edit' context, the {@see 'edit_$field'} * filter will be called and passed the `$value` and `$bookmark_id` respectively. * * With the 'db' context, the {@see 'pre_$field'} filter is called and passed the value. * The 'display' context is the final context and has the `$field` has the filter name * and is passed the `$value`, `$bookmark_id`, and `$context`, respectively. * * @since 2.3.0 * * @param string $field The bookmark field. * @param mixed $value The bookmark field value. * @param int $bookmark_id Bookmark ID. * @param string $context How to filter the field value. Accepts 'raw', 'edit', 'db', * 'display', 'attribute', or 'js'. Default 'display'. * @return mixed The filtered value. */ function sanitize_bookmark_field( $field, $value, $bookmark_id, $context ) { $int_fields = array( 'link_id', 'link_rating' ); if ( in_array( $field, $int_fields, true ) ) { $value = (int) $value; } switch ( $field ) { case 'link_category': // array( ints ) $value = array_map( 'absint', (array) $value ); /* * We return here so that the categories aren't filtered. * The 'link_category' filter is for the name of a link category, not an array of a link's link categories. */ return $value; case 'link_visible': // bool stored as Y|N $value = preg_replace( '/[^YNyn]/', '', $value ); break; case 'link_target': // "enum" $targets = array( '_top', '_blank' ); if ( ! in_array( $value, $targets, true ) ) { $value = ''; } break; } if ( 'raw' === $context ) { return $value; } if ( 'edit' === $context ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "edit_{$field}", $value, $bookmark_id ); if ( 'link_notes' === $field ) { $value = esc_html( $value ); // textarea_escaped } else { $value = esc_attr( $value ); } } elseif ( 'db' === $context ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "pre_{$field}", $value ); } else { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "{$field}", $value, $bookmark_id, $context ); if ( 'attribute' === $context ) { $value = esc_attr( $value ); } elseif ( 'js' === $context ) { $value = esc_js( $value ); } } // Restore the type for integer fields after esc_attr(). if ( in_array( $field, $int_fields, true ) ) { $value = (int) $value; } return $value; } /** * Deletes the bookmark cache. * * @since 2.7.0 * * @param int $bookmark_id Bookmark ID. */ function clean_bookmark_cache( $bookmark_id ) { wp_cache_delete( $bookmark_id, 'bookmark' ); wp_cache_delete( 'get_bookmarks', 'bookmark' ); clean_object_term_cache( $bookmark_id, 'link' ); } ck?', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'is_on_backorder' => [ 'description' => __( 'Is the product stock backordered? This will also return false if backorder notifications are turned off.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'low_stock_remaining' => [ 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woocommerce' ), 'type' => [ 'integer', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sold_individually' => [ 'description' => __( 'If true, only one item of this product is allowed for purchase in a single order.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'add_to_cart' => [ 'description' => __( 'Add to cart button parameters.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'text' => [ 'description' => __( 'Button text.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'description' => [ 'description' => __( 'Button description.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'url' => [ 'description' => __( 'Add to cart URL.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'minimum' => [ 'description' => __( 'The minimum quantity that can be added to the cart.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'maximum' => [ 'description' => __( 'The maximum quantity that can be added to the cart.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'multiple_of' => [ 'description' => __( 'The amount that quantities increment by. Quantity must be an multiple of this value.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'default' => 1, ], ], ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } /** * Convert a WooCommerce product into an object suitable for the response. * * @param \WC_Product $product Product instance. * @return array */ public function get_item_response( $product ) { return [ 'id' => $product->get_id(), 'name' => $this->prepare_html_response( $product->get_title() ), 'parent' => $product->get_parent_id(), 'type' => $product->get_type(), 'variation' => $this->prepare_html_response( $product->is_type( 'variation' ) ? wc_get_formatted_variation( $product, true, true, false ) : '' ), 'permalink' => $product->get_permalink(), 'sku' => $this->prepare_html_response( $product->get_sku() ), 'short_description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_short_description() ) ) ), 'description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_description() ) ) ), 'on_sale' => $product->is_on_sale(), 'prices' => (object) $this->prepare_product_price_response( $product ), 'price_html' => $this->prepare_html_response( $product->get_price_html() ), 'average_rating' => (string) $product->get_average_rating(), 'review_count' => $product->get_review_count(), 'images' => $this->get_images( $product ), 'categories' => $this->get_term_list( $product, 'product_cat' ), 'tags' => $this->get_term_list( $product, 'product_tag' ), 'attributes' => $this->get_attributes( $product ), 'variations' => $this->get_variations( $product ), 'has_options' => $product->has_options(), 'is_purchasable' => $product->is_purchasable(), 'is_in_stock' => $product->is_in_stock(), 'is_on_backorder' => 'onbackorder' === $product->get_stock_status(), 'low_stock_remaining' => $this->get_low_stock_remaining( $product ), 'sold_individually' => $product->is_sold_individually(), 'add_to_cart' => (object) array_merge( [ 'text' => $this->prepare_html_response( $product->add_to_cart_text() ), 'description' => $this->prepare_html_response( $product->add_to_cart_description() ), 'url' => $this->prepare_html_response( $product->add_to_cart_url() ), ], ( new QuantityLimits() )->get_add_to_cart_limits( $product ) ), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER, $product ), ]; } /** * Get list of product images. * * @param \WC_Product $product Product instance. * @return array */ protected function get_images( \WC_Product $product ) { $attachment_ids = array_merge( [ $product->get_image_id() ], $product->get_gallery_image_ids() ); return array_filter( array_map( [ $this->image_attachment_schema, 'get_item_response' ], $attachment_ids ) ); } /** * Gets remaining stock amount for a product. * * @param \WC_Product $product Product instance. * @return integer|null */ protected function get_remaining_stock( \WC_Product $product ) { if ( is_null( $product->get_stock_quantity() ) ) { return null; } return $product->get_stock_quantity(); } /** * If a product has low stock, return the remaining stock amount for display. * * @param \WC_Product $product Product instance. * @return integer|null */ protected function get_low_stock_remaining( \WC_Product $product ) { $remaining_stock = $this->get_remaining_stock( $product ); $stock_format = get_option( 'woocommerce_stock_format' ); // Don't show the low stock badge if the settings doesn't allow it. if ( 'no_amount' === $stock_format ) { return null; } // Show the low stock badge if the remaining stock is below or equal to the threshold. if ( ! is_null( $remaining_stock ) && $remaining_stock <= wc_get_low_stock_amount( $product ) ) { return max( $remaining_stock, 0 ); } return null; } /** * Returns true if the given attribute is valid. * * @param mixed $attribute Object or variable to check. * @return boolean */ protected function filter_valid_attribute( $attribute ) { return is_a( $attribute, '\WC_Product_Attribute' ); } /** * Returns true if the given attribute is valid and used for variations. * * @param mixed $attribute Object or variable to check. * @return boolean */ protected function filter_variation_attribute( $attribute ) { return $this->filter_valid_attribute( $attribute ) && $attribute->get_variation(); } /** * Get variation IDs and attributes from the DB. * * @param \WC_Product $product Product instance. * @returns array */ protected function get_variations( \WC_Product $product ) { $variation_ids = $product->is_type( 'variable' ) ? $product->get_visible_children() : []; if ( ! count( $variation_ids ) ) { return []; } /** * Gets default variation data which applies to all of this products variations. */ $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_variation_attribute' ] ); $default_variation_meta_data = array_reduce( $attributes, function( $defaults, $attribute ) use ( $product ) { $meta_key = wc_variation_attribute_name( $attribute->get_name() ); $defaults[ $meta_key ] = [ 'name' => wc_attribute_label( $attribute->get_name(), $product ), 'value' => null, ]; return $defaults; }, [] ); $default_variation_meta_keys = array_keys( $default_variation_meta_data ); /** * Gets individual variation data from the database, using cache where possible. */ $cache_group = 'product_variation_meta_data'; $cache_value = wp_cache_get( $product->get_id(), $cache_group ); $last_modified = get_the_modified_date( 'U', $product->get_id() ); if ( false === $cache_value || $last_modified !== $cache_value['last_modified'] ) { global $wpdb; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $variation_meta_data = $wpdb->get_results( " SELECT post_id as variation_id, meta_key as attribute_key, meta_value as attribute_value FROM {$wpdb->postmeta} WHERE post_id IN (" . implode( ',', array_map( 'esc_sql', $variation_ids ) ) . ") AND meta_key IN ('" . implode( "','", array_map( 'esc_sql', $default_variation_meta_keys ) ) . "') " ); // phpcs:enable wp_cache_set( $product->get_id(), [ 'last_modified' => $last_modified, 'data' => $variation_meta_data, ], $cache_group ); } else { $variation_meta_data = $cache_value['data']; } /** * Merges and formats default variation data with individual variation data. */ $attributes_by_variation = array_reduce( $variation_meta_data, function( $values, $data ) use ( $default_variation_meta_keys ) { // The query above only includes the keys of $default_variation_meta_data so we know all of the attributes // being processed here apply to this product. However, we need an additional check here because the // cache may have been primed elsewhere and include keys from other products. // @see AbstractProductGrid::prime_product_variations. if ( in_array( $data->attribute_key, $default_variation_meta_keys, true ) ) { $values[ $data->variation_id ][ $data->attribute_key ] = $data->attribute_value; } return $values; }, array_fill_keys( $variation_ids, [] ) ); $variations = []; foreach ( $variation_ids as $variation_id ) { $attribute_data = $default_variation_meta_data; foreach ( $attributes_by_variation[ $variation_id ] as $meta_key => $meta_value ) { if ( '' !== $meta_value ) { $attribute_data[ $meta_key ]['value'] = $meta_value; } } $variations[] = (object) [ 'id' => $variation_id, 'attributes' => array_values( $attribute_data ), ]; } return $variations; } /** * Get list of product attributes and attribute terms. * * @param \WC_Product $product Product instance. * @return array */ protected function get_attributes( \WC_Product $product ) { $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_valid_attribute' ] ); $default_attributes = $product->get_default_attributes(); $return = []; foreach ( $attributes as $attribute_slug => $attribute ) { // Only visible or variation attributes will be exposed by this API. if ( ! $attribute->get_visible() && ! $attribute->get_variation() ) { continue; } $terms = $attribute->is_taxonomy() ? array_map( [ $this, 'prepare_product_attribute_taxonomy_value' ], $attribute->get_terms() ) : array_map( [ $this, 'prepare_product_attribute_value' ], $attribute->get_options() ); // Custom attribute names are sanitized to be the array keys. // So when we do the array_key_exists check below we also need to sanitize the attribute names. $sanitized_attribute_name = sanitize_key( $attribute->get_name() ); if ( array_key_exists( $sanitized_attribute_name, $default_attributes ) ) { foreach ( $terms as $term ) { $term->default = $term->slug === $default_attributes[ $sanitized_attribute_name ]; } } $return[] = (object) [ 'id' => $attribute->get_id(), 'name' => wc_attribute_label( $attribute->get_name(), $product ), 'taxonomy' => $attribute->is_taxonomy() ? $attribute->get_name() : null, 'has_variations' => true === $attribute->get_variation(), 'terms' => $terms, ]; } return $return; } /** * Prepare an attribute term for the response. * * @param \WP_Term $term Term object. * @return object */ protected function prepare_product_attribute_taxonomy_value( \WP_Term $term ) { return $this->prepare_product_attribute_value( $term->name, $term->term_id, $term->slug ); } /** * Prepare an attribute term for the response. * * @param string $name Attribute term name. * @param int $id Attribute term ID. * @param string $slug Attribute term slug. * @return object */ protected function prepare_product_attribute_value( $name, $id = 0, $slug = '' ) { return (object) [ 'id' => (int) $id, 'name' => $name, 'slug' => $slug ? $slug : $name, ]; } /** * Get an array of pricing data. * * @param \WC_Product $product Product instance. * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return array */ protected function prepare_product_price_response( \WC_Product $product, $tax_display_mode = '' ) { $prices = []; $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode ); $price_function = $this->get_price_function_from_tax_display_mode( $tax_display_mode ); // If we have a variable product, get the price from the variations (this will use the min value). if ( $product->is_type( 'variable' ) ) { $regular_price = $product->get_variation_regular_price(); $sale_price = $product->get_variation_sale_price(); } else { $regular_price = $product->get_regular_price(); $sale_price = $product->get_sale_price(); } $prices['price'] = $this->prepare_money_response( $price_function( $product ), wc_get_price_decimals() ); $prices['regular_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $regular_price ] ), wc_get_price_decimals() ); $prices['sale_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $sale_price ] ), wc_get_price_decimals() ); $prices['price_range'] = $this->get_price_range( $product, $tax_display_mode ); return $this->prepare_currency_response( $prices ); } /** * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode. * * @param string $tax_display_mode Provided tax display mode. * @return string Valid tax display mode. */ protected function get_tax_display_mode( $tax_display_mode = '' ) { return in_array( $tax_display_mode, [ 'incl', 'excl' ], true ) ? $tax_display_mode : get_option( 'woocommerce_tax_display_shop' ); } /** * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode. * * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return string Function name. */ protected function get_price_function_from_tax_display_mode( $tax_display_mode ) { return 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax'; } /** * Get price range from certain product types. * * @param \WC_Product $product Product instance. * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return object|null */ protected function get_price_range( \WC_Product $product, $tax_display_mode = '' ) { $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode ); if ( $product->is_type( 'variable' ) ) { $prices = $product->get_variation_prices( true ); if ( ! empty( $prices['price'] ) && ( min( $prices['price'] ) !== max( $prices['price'] ) ) ) { return (object) [ 'min_amount' => $this->prepare_money_response( min( $prices['price'] ), wc_get_price_decimals() ), 'max_amount' => $this->prepare_money_response( max( $prices['price'] ), wc_get_price_decimals() ), ]; } } if ( $product->is_type( 'grouped' ) ) { $children = array_filter( array_map( 'wc_get_product', $product->get_children() ), 'wc_products_array_filter_visible_grouped' ); $price_function = 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax'; foreach ( $children as $child ) { if ( '' !== $child->get_price() ) { $child_prices[] = $price_function( $child ); } } if ( ! empty( $child_prices ) ) { return (object) [ 'min_amount' => $this->prepare_money_response( min( $child_prices ), wc_get_price_decimals() ), 'max_amount' => $this->prepare_money_response( max( $child_prices ), wc_get_price_decimals() ), ]; } } return null; } /** * Returns a list of terms assigned to the product. * * @param \WC_Product $product Product object. * @param string $taxonomy Taxonomy name. * @return array Array of terms (id, name, slug). */ protected function get_term_list( \WC_Product $product, $taxonomy = '' ) { if ( ! $taxonomy ) { return []; } $terms = get_the_terms( $product->get_id(), $taxonomy ); if ( ! $terms || is_wp_error( $terms ) ) { return []; } $return = []; $default_category = (int) get_option( 'default_product_cat', 0 ); foreach ( $terms as $term ) { $link = get_term_link( $term, $taxonomy ); if ( is_wp_error( $link ) ) { continue; } if ( $term->term_id === $default_category ) { continue; } $return[] = (object) [ 'id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, 'link' => $link, ]; } return $return; } }