PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.3
WooCommerce v10.9.3
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / StoreApi / Schemas / V1 / ProductSchema.php
woocommerce / src / StoreApi / Schemas / V1 Last commit date
AI 1 year ago Agentic 7 months ago AbstractAddressSchema.php 1 year ago AbstractSchema.php 1 month ago BatchSchema.php 2 years ago BillingAddressSchema.php 2 years ago CartCouponSchema.php 1 year ago CartExtensionsSchema.php 1 year ago CartFeeSchema.php 2 years ago CartItemSchema.php 1 month ago CartSchema.php 2 months ago CartShippingRateSchema.php 1 month ago CheckoutOrderSchema.php 2 years ago CheckoutSchema.php 1 month ago ErrorSchema.php 2 years ago ImageAttachmentSchema.php 3 months ago ItemSchema.php 11 months ago OrderCouponSchema.php 2 years ago OrderFeeSchema.php 2 years ago OrderItemSchema.php 1 month ago OrderSchema.php 2 months ago PatternsSchema.php 1 year ago ProductAttributeSchema.php 2 years ago ProductAttributeTermSchema.php 1 month ago ProductBrandSchema.php 1 year ago ProductCategorySchema.php 2 years ago ProductCollectionDataSchema.php 11 months ago ProductReviewSchema.php 2 years ago ProductSchema.php 1 month ago ShippingAddressSchema.php 2 years ago ShopperListItemSchema.php 1 month ago ShopperListSchema.php 1 month ago TermSchema.php 2 years ago
ProductSchema.php
1048 lines
1 <?php
2 namespace Automattic\WooCommerce\StoreApi\Schemas\V1;
3
4 use Automattic\WooCommerce\Enums\ProductType;
5 use Automattic\WooCommerce\StoreApi\SchemaController;
6 use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
7 use Automattic\WooCommerce\StoreApi\Utilities\QuantityLimits;
8 use Automattic\WooCommerce\Blocks\Utils\ProductAvailabilityUtils;
9 use Automattic\WooCommerce\Enums\ProductStockStatus;
10
11 /**
12 * ProductSchema class.
13 */
14 class ProductSchema extends AbstractSchema {
15 /**
16 * The schema item name.
17 *
18 * @var string
19 */
20 protected $title = 'product';
21
22 /**
23 * The schema item identifier.
24 *
25 * @var string
26 */
27 const IDENTIFIER = 'product';
28
29 /**
30 * Image attachment schema instance.
31 *
32 * @var ImageAttachmentSchema
33 */
34 protected $image_attachment_schema;
35
36 /**
37 * Constructor.
38 *
39 * @param ExtendSchema $extend Rest Extending instance.
40 * @param SchemaController $controller Schema Controller instance.
41 */
42 public function __construct( ExtendSchema $extend, SchemaController $controller ) {
43 parent::__construct( $extend, $controller );
44 $this->image_attachment_schema = $this->controller->get( ImageAttachmentSchema::IDENTIFIER );
45 }
46
47 /**
48 * Product schema properties.
49 *
50 * @return array
51 */
52 public function get_properties() {
53 return [
54 'id' => [
55 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
56 'type' => 'integer',
57 'context' => [ 'view', 'edit', 'embed' ],
58 'readonly' => true,
59 ],
60 'name' => [
61 'description' => __( 'Product name.', 'woocommerce' ),
62 'type' => 'string',
63 'context' => [ 'view', 'edit', 'embed' ],
64 ],
65 'slug' => [
66 'description' => __( 'Product slug.', 'woocommerce' ),
67 'type' => 'string',
68 'context' => [ 'view', 'edit', 'embed' ],
69 ],
70 'parent' => [
71 'description' => __( 'ID of the parent product, if applicable.', 'woocommerce' ),
72 'type' => 'integer',
73 'context' => [ 'view', 'edit', 'embed' ],
74 'readonly' => true,
75 ],
76 'type' => [
77 'description' => __( 'Product type.', 'woocommerce' ),
78 'type' => 'string',
79 'context' => [ 'view', 'edit', 'embed' ],
80 'readonly' => true,
81 ],
82 'variation' => [
83 'description' => __( 'Product variation attributes, if applicable.', 'woocommerce' ),
84 'type' => 'string',
85 'context' => [ 'view', 'edit', 'embed' ],
86 ],
87 'permalink' => [
88 'description' => __( 'Product URL.', 'woocommerce' ),
89 'type' => 'string',
90 'format' => 'uri',
91 'context' => [ 'view', 'edit', 'embed' ],
92 'readonly' => true,
93 ],
94 'short_description' => [
95 'description' => __( 'Product short description in HTML format.', 'woocommerce' ),
96 'type' => 'string',
97 'context' => [ 'view', 'edit', 'embed' ],
98 ],
99 'description' => [
100 'description' => __( 'Product full description in HTML format.', 'woocommerce' ),
101 'type' => 'string',
102 'context' => [ 'view', 'edit', 'embed' ],
103 ],
104 'on_sale' => [
105 'description' => __( 'Is the product on sale?', 'woocommerce' ),
106 'type' => 'boolean',
107 'context' => [ 'view', 'edit', 'embed' ],
108 'readonly' => true,
109 ],
110 'sku' => [
111 'description' => __( 'Unique identifier.', 'woocommerce' ),
112 'type' => 'string',
113 'context' => [ 'view', 'edit', 'embed' ],
114 ],
115 'prices' => [
116 'description' => __( 'Price data provided using the smallest unit of the currency.', 'woocommerce' ),
117 'type' => 'object',
118 'context' => [ 'view', 'edit', 'embed' ],
119 'readonly' => true,
120 'properties' => array_merge(
121 $this->get_store_currency_properties(),
122 [
123 'price' => [
124 'description' => __( 'Current product price.', 'woocommerce' ),
125 'type' => 'string',
126 'context' => [ 'view', 'edit', 'embed' ],
127 'readonly' => true,
128 ],
129 'regular_price' => [
130 'description' => __( 'Regular product price.', 'woocommerce' ),
131 'type' => 'string',
132 'context' => [ 'view', 'edit', 'embed' ],
133 'readonly' => true,
134 ],
135 'sale_price' => [
136 'description' => __( 'Sale product price, if applicable.', 'woocommerce' ),
137 'type' => 'string',
138 'context' => [ 'view', 'edit', 'embed' ],
139 'readonly' => true,
140 ],
141 'price_range' => [
142 'description' => __( 'Price range, if applicable.', 'woocommerce' ),
143 'type' => [ 'object', 'null' ],
144 'context' => [ 'view', 'edit', 'embed' ],
145 'readonly' => true,
146 'properties' => [
147 'min_amount' => [
148 'description' => __( 'Price amount.', 'woocommerce' ),
149 'type' => 'string',
150 'context' => [ 'view', 'edit', 'embed' ],
151 'readonly' => true,
152 ],
153 'max_amount' => [
154 'description' => __( 'Price amount.', 'woocommerce' ),
155 'type' => 'string',
156 'context' => [ 'view', 'edit', 'embed' ],
157 'readonly' => true,
158 ],
159 ],
160 ],
161 ]
162 ),
163 ],
164 'price_html' => array(
165 'description' => __( 'Price string formatted as HTML.', 'woocommerce' ),
166 'type' => 'string',
167 'context' => array( 'view', 'edit', 'embed' ),
168 'readonly' => true,
169 ),
170 'average_rating' => [
171 'description' => __( 'Reviews average rating.', 'woocommerce' ),
172 'type' => 'string',
173 'context' => [ 'view', 'edit', 'embed' ],
174 'readonly' => true,
175 ],
176 'review_count' => [
177 'description' => __( 'Amount of reviews that the product has.', 'woocommerce' ),
178 'type' => 'integer',
179 'context' => [ 'view', 'edit', 'embed' ],
180 'readonly' => true,
181 ],
182 'images' => [
183 'description' => __( 'List of images.', 'woocommerce' ),
184 'type' => 'array',
185 'context' => [ 'view', 'edit', 'embed' ],
186 'items' => [
187 'type' => 'object',
188 'properties' => $this->image_attachment_schema->get_properties(),
189 ],
190 ],
191 'categories' => [
192 'description' => __( 'List of categories, if applicable.', 'woocommerce' ),
193 'type' => 'array',
194 'context' => [ 'view', 'edit', 'embed' ],
195 'items' => [
196 'type' => 'object',
197 'properties' => [
198 'id' => [
199 'description' => __( 'Category ID', 'woocommerce' ),
200 'type' => 'number',
201 'context' => [ 'view', 'edit', 'embed' ],
202 'readonly' => true,
203 ],
204 'name' => [
205 'description' => __( 'Category name', 'woocommerce' ),
206 'type' => 'string',
207 'context' => [ 'view', 'edit', 'embed' ],
208 'readonly' => true,
209 ],
210 'slug' => [
211 'description' => __( 'Category slug', 'woocommerce' ),
212 'type' => 'string',
213 'context' => [ 'view', 'edit', 'embed' ],
214 'readonly' => true,
215 ],
216 'link' => [
217 'description' => __( 'Category link', 'woocommerce' ),
218 'type' => 'string',
219 'context' => [ 'view', 'edit', 'embed' ],
220 'readonly' => true,
221 ],
222 ],
223 ],
224 ],
225 'tags' => [
226 'description' => __( 'List of tags, if applicable.', 'woocommerce' ),
227 'type' => 'array',
228 'context' => [ 'view', 'edit', 'embed' ],
229 'items' => [
230 'type' => 'object',
231 'properties' => [
232 'id' => [
233 'description' => __( 'Tag ID', 'woocommerce' ),
234 'type' => 'number',
235 'context' => [ 'view', 'edit', 'embed' ],
236 'readonly' => true,
237 ],
238 'name' => [
239 'description' => __( 'Tag name', 'woocommerce' ),
240 'type' => 'string',
241 'context' => [ 'view', 'edit', 'embed' ],
242 'readonly' => true,
243 ],
244 'slug' => [
245 'description' => __( 'Tag slug', 'woocommerce' ),
246 'type' => 'string',
247 'context' => [ 'view', 'edit', 'embed' ],
248 'readonly' => true,
249 ],
250 'link' => [
251 'description' => __( 'Tag link.', 'woocommerce' ),
252 'type' => 'string',
253 'context' => [ 'view', 'edit', 'embed' ],
254 'readonly' => true,
255 ],
256 ],
257 ],
258 ],
259 'brands' => [
260 'description' => __( 'List of brands, if applicable.', 'woocommerce' ),
261 'type' => 'array',
262 'context' => [ 'view', 'edit', 'embed' ],
263 'items' => [
264 'type' => 'object',
265 'properties' => [
266 'id' => [
267 'description' => __( 'Brand ID', 'woocommerce' ),
268 'type' => 'number',
269 'context' => [ 'view', 'edit', 'embed' ],
270 'readonly' => true,
271 ],
272 'name' => [
273 'description' => __( 'Brand name', 'woocommerce' ),
274 'type' => 'string',
275 'context' => [ 'view', 'edit', 'embed' ],
276 'readonly' => true,
277 ],
278 'slug' => [
279 'description' => __( 'Brand slug', 'woocommerce' ),
280 'type' => 'string',
281 'context' => [ 'view', 'edit', 'embed' ],
282 'readonly' => true,
283 ],
284 'link' => [
285 'description' => __( 'Brand link', 'woocommerce' ),
286 'type' => 'string',
287 'context' => [ 'view', 'edit', 'embed' ],
288 'readonly' => true,
289 ],
290 ],
291 ],
292 ],
293 'attributes' => [
294 'description' => __( 'List of attributes (taxonomy terms) assigned to the product. For variable products, these are mapped to variations (see the `variations` field).', 'woocommerce' ),
295 'type' => 'array',
296 'context' => [ 'view', 'edit', 'embed' ],
297 'items' => [
298 'type' => 'object',
299 'properties' => [
300 'id' => [
301 'description' => __( 'The attribute ID, or 0 if the attribute is not taxonomy based.', 'woocommerce' ),
302 'type' => 'integer',
303 'context' => [ 'view', 'edit', 'embed' ],
304 'readonly' => true,
305 ],
306 'name' => [
307 'description' => __( 'The attribute name.', 'woocommerce' ),
308 'type' => 'string',
309 'context' => [ 'view', 'edit', 'embed' ],
310 'readonly' => true,
311 ],
312 'taxonomy' => [
313 'description' => __( 'The attribute taxonomy, or null if the attribute is not taxonomy based.', 'woocommerce' ),
314 'type' => 'string',
315 'context' => [ 'view', 'edit', 'embed' ],
316 'readonly' => true,
317 ],
318 'has_variations' => [
319 'description' => __( 'True if this attribute is used by product variations.', 'woocommerce' ),
320 'type' => 'boolean',
321 'context' => [ 'view', 'edit', 'embed' ],
322 'readonly' => true,
323 ],
324 'terms' => [
325 'description' => __( 'List of assigned attribute terms.', 'woocommerce' ),
326 'type' => 'array',
327 'context' => [ 'view', 'edit', 'embed' ],
328 'items' => [
329 'type' => 'object',
330 'properties' => [
331 'id' => [
332 'description' => __( 'The term ID, or 0 if the attribute is not a global attribute.', 'woocommerce' ),
333 'type' => 'integer',
334 'context' => [ 'view', 'edit', 'embed' ],
335 'readonly' => true,
336 ],
337 'name' => [
338 'description' => __( 'The term name.', 'woocommerce' ),
339 'type' => 'string',
340 'context' => [ 'view', 'edit', 'embed' ],
341 'readonly' => true,
342 ],
343 'slug' => [
344 'description' => __( 'The term slug.', 'woocommerce' ),
345 'type' => 'string',
346 'context' => [ 'view', 'edit', 'embed' ],
347 'readonly' => true,
348 ],
349 'default' => [
350 'description' => __( 'If this is a default attribute', 'woocommerce' ),
351 'type' => 'boolean',
352 'context' => [ 'view', 'edit', 'embed' ],
353 'readonly' => true,
354 ],
355 ],
356 ],
357 ],
358 ],
359 ],
360 ],
361 'variations' => [
362 'description' => __( 'List of variation IDs, if applicable.', 'woocommerce' ),
363 'type' => 'array',
364 'context' => [ 'view', 'edit', 'embed' ],
365 'items' => [
366 'type' => 'object',
367 'properties' => [
368 'id' => [
369 'description' => __( 'The attribute ID, or 0 if the attribute is not taxonomy based.', 'woocommerce' ),
370 'type' => 'integer',
371 'context' => [ 'view', 'edit', 'embed' ],
372 'readonly' => true,
373 ],
374 'attributes' => [
375 'description' => __( 'List of variation attributes.', 'woocommerce' ),
376 'type' => 'array',
377 'context' => [ 'view', 'edit', 'embed' ],
378 'items' => [
379 'type' => 'object',
380 'properties' => [
381 'name' => [
382 'description' => __( 'The attribute name.', 'woocommerce' ),
383 'type' => 'string',
384 'context' => [ 'view', 'edit', 'embed' ],
385 'readonly' => true,
386 ],
387 'value' => [
388 'description' => __( 'The assigned attribute.', 'woocommerce' ),
389 'type' => 'string',
390 'context' => [ 'view', 'edit', 'embed' ],
391 'readonly' => true,
392 ],
393 ],
394 ],
395 ],
396 ],
397 ],
398 ],
399 'grouped_products' => [
400 'description' => __( 'List of grouped product IDs, if applicable.', 'woocommerce' ),
401 'type' => 'array',
402 'context' => [ 'view', 'edit', 'embed' ],
403 'items' => [
404 'description' => __( 'List of grouped product ids.', 'woocommerce' ),
405 'type' => 'integer',
406 'context' => [ 'view', 'edit', 'embed' ],
407 'readonly' => true,
408 ],
409 ],
410 'has_options' => [
411 'description' => __( 'Does the product have additional options before it can be added to the cart?', 'woocommerce' ),
412 'type' => 'boolean',
413 'context' => [ 'view', 'edit', 'embed' ],
414 'readonly' => true,
415 ],
416 'is_purchasable' => [
417 'description' => __( 'Is the product purchasable?', 'woocommerce' ),
418 'type' => 'boolean',
419 'context' => [ 'view', 'edit', 'embed' ],
420 'readonly' => true,
421 ],
422 'is_in_stock' => [
423 'description' => __( 'Is the product in stock?', 'woocommerce' ),
424 'type' => 'boolean',
425 'context' => [ 'view', 'edit', 'embed' ],
426 'readonly' => true,
427 ],
428 'is_on_backorder' => [
429 'description' => __( 'Is the product stock backordered? This will also return false if backorder notifications are turned off.', 'woocommerce' ),
430 'type' => 'boolean',
431 'context' => [ 'view', 'edit', 'embed' ],
432 'readonly' => true,
433 ],
434 'stock_availability' => [
435 'description' => __( 'Information about the product\'s availability.', 'woocommerce' ),
436 'type' => 'object',
437 'context' => [ 'view', 'edit', 'embed' ],
438 'readonly' => true,
439 'properties' => [
440 'text' => [
441 'description' => __( 'Stock availability text.', 'woocommerce' ),
442 'type' => 'string',
443 'context' => [ 'view', 'edit', 'embed' ],
444 'readonly' => true,
445 ],
446 'class' => [
447 'description' => __( 'Stock availability class.', 'woocommerce' ),
448 'type' => 'string',
449 'context' => [ 'view', 'edit', 'embed' ],
450 'readonly' => true,
451 ],
452 ],
453 ],
454 'low_stock_remaining' => [
455 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woocommerce' ),
456 'type' => [ 'number', 'null' ],
457 'context' => [ 'view', 'edit', 'embed' ],
458 'readonly' => true,
459 ],
460 'sold_individually' => [
461 'description' => __( 'If true, only one item of this product is allowed for purchase in a single order.', 'woocommerce' ),
462 'type' => 'boolean',
463 'context' => [ 'view', 'edit', 'embed' ],
464 'readonly' => true,
465 ],
466 'weight' => [
467 'description' => sprintf(
468 /* translators: %s: weight unit */
469 __( 'Product weight (%s).', 'woocommerce' ),
470 get_option( 'woocommerce_weight_unit' )
471 ),
472 'type' => 'string',
473 'context' => [ 'view', 'edit' ],
474 'readonly' => true,
475 ],
476 'dimensions' => [
477 'description' => __( 'Product dimensions.', 'woocommerce' ),
478 'type' => 'object',
479 'context' => [ 'view', 'edit' ],
480 'readonly' => true,
481 'properties' => [
482 'length' => [
483 'description' => sprintf(
484 /* translators: %s: dimension unit */
485 __( 'Product length (%s).', 'woocommerce' ),
486 get_option( 'woocommerce_dimension_unit' )
487 ),
488 'type' => 'string',
489 'context' => [ 'view', 'edit' ],
490 'readonly' => true,
491 ],
492 'width' => [
493 'description' => sprintf(
494 /* translators: %s: dimension unit */
495 __( 'Product width (%s).', 'woocommerce' ),
496 get_option( 'woocommerce_dimension_unit' )
497 ),
498 'type' => 'string',
499 'context' => [ 'view', 'edit' ],
500 'readonly' => true,
501 ],
502 'height' => [
503 'description' => sprintf(
504 /* translators: %s: dimension unit */
505 __( 'Product height (%s).', 'woocommerce' ),
506 get_option( 'woocommerce_dimension_unit' )
507 ),
508 'type' => 'string',
509 'context' => [ 'view', 'edit' ],
510 'readonly' => true,
511 ],
512 ],
513 ],
514 'formatted_weight' => [
515 'description' => __( 'Product weight formatted for display (e.g. "2.5 kg").', 'woocommerce' ),
516 'type' => 'string',
517 'context' => [ 'view', 'edit' ],
518 'readonly' => true,
519 ],
520 'formatted_dimensions' => [
521 'description' => __( 'Product dimensions formatted for display (e.g. "10 × 5 × 3 cm").', 'woocommerce' ),
522 'type' => 'string',
523 'context' => [ 'view', 'edit' ],
524 'readonly' => true,
525 ],
526 'add_to_cart' => [
527 'description' => __( 'Add to cart button parameters.', 'woocommerce' ),
528 'type' => 'object',
529 'context' => [ 'view', 'edit', 'embed' ],
530 'readonly' => true,
531 'properties' => [
532 'text' => [
533 'description' => __( 'Button text.', 'woocommerce' ),
534 'type' => 'string',
535 'context' => [ 'view', 'edit', 'embed' ],
536 'readonly' => true,
537 ],
538 'description' => [
539 'description' => __( 'Button description.', 'woocommerce' ),
540 'type' => 'string',
541 'context' => [ 'view', 'edit', 'embed' ],
542 'readonly' => true,
543 ],
544 'url' => [
545 'description' => __( 'Add to cart URL.', 'woocommerce' ),
546 'type' => 'string',
547 'context' => [ 'view', 'edit', 'embed' ],
548 'readonly' => true,
549 ],
550 'minimum' => [
551 'description' => __( 'The minimum quantity that can be added to the cart.', 'woocommerce' ),
552 'type' => 'number',
553 'context' => [ 'view', 'edit', 'embed' ],
554 'readonly' => true,
555 ],
556 'maximum' => [
557 'description' => __( 'The maximum quantity that can be added to the cart.', 'woocommerce' ),
558 'type' => 'number',
559 'context' => [ 'view', 'edit', 'embed' ],
560 'readonly' => true,
561 ],
562 'multiple_of' => [
563 'description' => __( 'The amount that quantities increment by. Quantity must be an multiple of this value.', 'woocommerce' ),
564 'type' => 'number',
565 'context' => [ 'view', 'edit', 'embed' ],
566 'readonly' => true,
567 'default' => 1,
568 ],
569 'single_text' => [
570 'description' => __( 'Button text in the single product page.', 'woocommerce' ),
571 'type' => 'string',
572 'context' => [ 'view', 'edit', 'embed' ],
573 'readonly' => true,
574 ],
575 ],
576 ],
577 'is_password_protected' => [
578 'description' => __( 'Whether the product requires a password to access its content.', 'woocommerce' ),
579 'type' => 'boolean',
580 'context' => [ 'view', 'edit', 'embed' ],
581 'readonly' => true,
582 ],
583 self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ),
584 ];
585 }
586
587 /**
588 * Convert a WooCommerce product into an object suitable for the response.
589 *
590 * @param \WC_Product $product Product instance.
591 * @return array
592 */
593 public function get_item_response( $product ) {
594 $availability = ProductAvailabilityUtils::get_product_availability( $product );
595 $password_required = post_password_required( $product->get_id() );
596 $short_description = $password_required ? '' : $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_short_description() ) ) );
597 $description = $password_required ? '' : $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_description() ) ) );
598
599 return [
600 'id' => $product->get_id(),
601 'name' => $this->prepare_html_response( $product->get_title() ),
602 'slug' => $product->get_slug(),
603 'parent' => $product->get_parent_id(),
604 'type' => $product->get_type(),
605 'variation' => $this->prepare_html_response( $product->is_type( ProductType::VARIATION ) ? wc_get_formatted_variation( $product, true, true, false ) : '' ),
606 'permalink' => $product->get_permalink(),
607 'sku' => $this->prepare_html_response( $product->get_sku() ),
608 'short_description' => $short_description,
609 'description' => $description,
610 'on_sale' => $product->is_on_sale(),
611 'prices' => (object) $this->prepare_product_price_response( $product ),
612 'price_html' => $this->prepare_html_response( $product->get_price_html() ),
613 'average_rating' => (string) $product->get_average_rating(),
614 'review_count' => $product->get_review_count(),
615 'images' => $this->get_images( $product ),
616 'categories' => $this->get_term_list( $product, 'product_cat' ),
617 'tags' => $this->get_term_list( $product, 'product_tag' ),
618 'brands' => $this->get_term_list( $product, 'product_brand' ),
619 'attributes' => $this->get_attributes( $product ),
620 'variations' => $this->get_variations( $product ),
621 'grouped_products' => $this->get_grouped_products( $product ),
622 'has_options' => $product->has_options(),
623 'is_purchasable' => $product->is_purchasable(),
624 'is_in_stock' => $product->is_in_stock(),
625 'is_on_backorder' => ProductStockStatus::ON_BACKORDER === $product->get_stock_status(),
626 'low_stock_remaining' => $this->get_low_stock_remaining( $product ),
627 'stock_availability' => (object) array(
628 'text' => $availability['availability'] ?? '',
629 'class' => $availability['class'] ?? '',
630 ),
631 'sold_individually' => $product->is_sold_individually(),
632 'weight' => $product->get_weight(),
633 'dimensions' => (object) [
634 'length' => $product->get_length(),
635 'width' => $product->get_width(),
636 'height' => $product->get_height(),
637 ],
638 'formatted_weight' => wc_format_weight( (float) $product->get_weight() ),
639 'formatted_dimensions' => html_entity_decode( wc_format_dimensions( (array) $product->get_dimensions( false ) ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
640 'add_to_cart' => (object) array_merge(
641 [
642 'text' => $this->prepare_html_response( $product->add_to_cart_text() ),
643 'description' => $this->prepare_html_response( $product->add_to_cart_description() ),
644 'url' => $this->prepare_html_response( $product->add_to_cart_url() ),
645 'single_text' => $this->prepare_html_response( $product->single_add_to_cart_text() ),
646 ],
647 ( new QuantityLimits() )->get_add_to_cart_limits( $product )
648 ),
649 'is_password_protected' => '' !== $product->get_post_password(),
650 self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER, $product ),
651
652 ];
653 }
654
655 /**
656 * Get list of product images.
657 *
658 * @param \WC_Product $product Product instance.
659 * @return array
660 */
661 protected function get_images( \WC_Product $product ) {
662 $attachment_ids = array_filter( array_merge( [ $product->get_image_id() ], $product->get_gallery_image_ids() ) );
663 if ( ! empty( $attachment_ids ) ) {
664 // Prime caches to reduce future queries.
665 _prime_post_caches( $attachment_ids );
666 }
667
668 return array_values( array_filter( array_map( [ $this->image_attachment_schema, 'get_item_response' ], $attachment_ids ) ) );
669 }
670
671 /**
672 * Gets remaining stock amount for a product.
673 *
674 * @param \WC_Product $product Product instance.
675 * @return int|float|null
676 */
677 protected function get_remaining_stock( \WC_Product $product ) {
678 if ( is_null( $product->get_stock_quantity() ) ) {
679 return null;
680 }
681 return $product->get_stock_quantity();
682 }
683
684 /**
685 * If a product has low stock, return the remaining stock amount for display.
686 *
687 * @param \WC_Product $product Product instance.
688 * @return int|float|null
689 */
690 protected function get_low_stock_remaining( \WC_Product $product ) {
691 $remaining_stock = $this->get_remaining_stock( $product );
692 $stock_format = get_option( 'woocommerce_stock_format' );
693
694 // Don't show the low stock badge if the settings doesn't allow it.
695 if ( 'no_amount' === $stock_format ) {
696 return null;
697 }
698
699 // Show the low stock badge if the remaining stock is below or equal to the threshold.
700 if ( ! is_null( $remaining_stock ) && $remaining_stock <= wc_get_low_stock_amount( $product ) ) {
701 return max( $remaining_stock, 0 );
702 }
703
704 return null;
705 }
706
707 /**
708 * Returns true if the given attribute is valid.
709 *
710 * @param mixed $attribute Object or variable to check.
711 * @return boolean
712 */
713 protected function filter_valid_attribute( $attribute ) {
714 return is_a( $attribute, '\WC_Product_Attribute' );
715 }
716
717 /**
718 * Returns true if the given attribute is valid and used for variations.
719 *
720 * @param mixed $attribute Object or variable to check.
721 * @return boolean
722 */
723 protected function filter_variation_attribute( $attribute ) {
724 return $this->filter_valid_attribute( $attribute ) && $attribute->get_variation();
725 }
726
727 /**
728 * Get variation IDs and attributes from the DB.
729 *
730 * @param \WC_Product $product Product instance.
731 * @returns array
732 */
733 protected function get_variations( \WC_Product $product ) {
734 $variation_ids = $product->is_type( ProductType::VARIABLE ) ? $product->get_visible_children() : [];
735
736 if ( ! count( $variation_ids ) ) {
737 return [];
738 }
739
740 /**
741 * Gets default variation data which applies to all of this products variations.
742 */
743 $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_variation_attribute' ] );
744 $default_variation_meta_data = array_reduce(
745 $attributes,
746 function ( $defaults, $attribute ) use ( $product ) {
747 $meta_key = wc_variation_attribute_name( $attribute->get_name() );
748 $defaults[ $meta_key ] = [
749 'name' => wc_attribute_label( $attribute->get_name(), $product ),
750 'value' => null,
751 ];
752 return $defaults;
753 },
754 []
755 );
756 $default_variation_meta_keys = array_keys( $default_variation_meta_data );
757
758 /**
759 * Gets individual variation data from the database, using cache where possible.
760 */
761 $cache_group = 'product_variation_meta_data';
762 $cache_value = wp_cache_get( $product->get_id(), $cache_group );
763 $last_modified = get_the_modified_date( 'U', $product->get_id() );
764
765 if ( false === $cache_value || $last_modified !== $cache_value['last_modified'] ) {
766 global $wpdb;
767 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
768 $variation_meta_data = $wpdb->get_results(
769 "
770 SELECT post_id as variation_id, meta_key as attribute_key, meta_value as attribute_value
771 FROM {$wpdb->postmeta}
772 WHERE post_id IN (" . implode( ',', array_map( 'esc_sql', $variation_ids ) ) . ")
773 AND meta_key IN ('" . implode( "','", array_map( 'esc_sql', $default_variation_meta_keys ) ) . "')
774 "
775 );
776 // phpcs:enable
777
778 wp_cache_set(
779 $product->get_id(),
780 [
781 'last_modified' => $last_modified,
782 'data' => $variation_meta_data,
783 ],
784 $cache_group
785 );
786 } else {
787 $variation_meta_data = $cache_value['data'];
788 }
789
790 /**
791 * Merges and formats default variation data with individual variation data.
792 */
793 $attributes_by_variation = array_reduce(
794 $variation_meta_data,
795 function ( $values, $data ) use ( $default_variation_meta_keys ) {
796 // The query above only includes the keys of $default_variation_meta_data so we know all of the attributes
797 // being processed here apply to this product. However, we need an additional check here because the
798 // cache may have been primed elsewhere and include keys from other products.
799 // @see AbstractProductGrid::prime_product_variations.
800 if ( in_array( $data->attribute_key, $default_variation_meta_keys, true ) ) {
801 $values[ $data->variation_id ][ $data->attribute_key ] = $data->attribute_value;
802 }
803 return $values;
804 },
805 array_fill_keys( $variation_ids, [] )
806 );
807
808 $variations = [];
809
810 foreach ( $variation_ids as $variation_id ) {
811 $attribute_data = $default_variation_meta_data;
812
813 foreach ( $attributes_by_variation[ $variation_id ] as $meta_key => $meta_value ) {
814 if ( '' !== $meta_value ) {
815 $attribute_data[ $meta_key ]['value'] = $meta_value;
816 }
817 }
818
819 $variations[] = (object) [
820 'id' => $variation_id,
821 'attributes' => array_values( $attribute_data ),
822 ];
823 }
824
825 return $variations;
826 }
827
828 /**
829 * Get grouped product IDs.
830 *
831 * @param \WC_Product $product Product instance.
832 * @return array
833 */
834 protected function get_grouped_products( \WC_Product $product ) {
835 if ( $product->is_type( ProductType::GROUPED ) ) {
836 return array_map(
837 function ( $child ) {
838 return $child->get_id();
839 },
840 $product->get_visible_children(),
841 );
842 }
843 return [];
844 }
845
846 /**
847 * Get list of product attributes and attribute terms.
848 *
849 * @param \WC_Product $product Product instance.
850 * @return array
851 */
852 protected function get_attributes( \WC_Product $product ) {
853 $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_valid_attribute' ] );
854 $default_attributes = $product->get_default_attributes();
855 $return = [];
856
857 foreach ( $attributes as $attribute_slug => $attribute ) {
858 // Only visible or variation attributes will be exposed by this API.
859 if ( ! $attribute->get_visible() && ! $attribute->get_variation() ) {
860 continue;
861 }
862
863 $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() );
864 // Custom attribute names are sanitized to be the array keys.
865 // So when we do the array_key_exists check below we also need to sanitize the attribute names.
866
867 $sanitized_attribute_name = sanitize_key( $attribute->get_name() );
868
869 if ( array_key_exists( $sanitized_attribute_name, $default_attributes ) ) {
870 foreach ( $terms as $term ) {
871 $term->default = $term->slug === $default_attributes[ $sanitized_attribute_name ];
872 }
873 }
874
875 $return[] = (object) [
876 'id' => $attribute->get_id(),
877 'name' => wc_attribute_label( $attribute->get_name(), $product ),
878 'taxonomy' => $attribute->is_taxonomy() ? $attribute->get_name() : null,
879 'has_variations' => true === $attribute->get_variation(),
880 'terms' => $terms,
881 ];
882 }
883
884 return $return;
885 }
886
887 /**
888 * Prepare an attribute term for the response.
889 *
890 * @param \WP_Term $term Term object.
891 * @return object
892 */
893 protected function prepare_product_attribute_taxonomy_value( \WP_Term $term ) {
894 return $this->prepare_product_attribute_value( $term->name, $term->term_id, $term->slug );
895 }
896
897 /**
898 * Prepare an attribute term for the response.
899 *
900 * @param string $name Attribute term name.
901 * @param int $id Attribute term ID.
902 * @param string $slug Attribute term slug.
903 * @return object
904 */
905 protected function prepare_product_attribute_value( $name, $id = 0, $slug = '' ) {
906 return (object) [
907 'id' => (int) $id,
908 'name' => $name,
909 'slug' => $slug ? $slug : $name,
910 ];
911 }
912
913 /**
914 * Get an array of pricing data.
915 *
916 * @param \WC_Product $product Product instance.
917 * @param string $tax_display_mode If returned prices are incl or excl of tax.
918 * @return array
919 */
920 protected function prepare_product_price_response( \WC_Product $product, $tax_display_mode = '' ) {
921 $prices = [];
922 $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode );
923 $price_function = $this->get_price_function_from_tax_display_mode( $tax_display_mode );
924 $price_decimals = wc_get_price_decimals();
925
926 // If we have a variable product, get the price from the variations (this will use the min value).
927 if ( $product->is_type( ProductType::VARIABLE ) ) {
928 $regular_price = $product->get_variation_regular_price();
929 $sale_price = $product->get_variation_sale_price();
930 } else {
931 $regular_price = $product->get_regular_price();
932 $sale_price = $product->get_sale_price();
933 }
934
935 $prices['price'] = $this->prepare_money_response( $price_function( $product ), $price_decimals );
936 $prices['regular_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $regular_price ] ), $price_decimals );
937 $prices['sale_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $sale_price ] ), $price_decimals );
938 $prices['price_range'] = $this->get_price_range( $product, $tax_display_mode );
939
940 return $this->prepare_currency_response( $prices );
941 }
942
943 /**
944 * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode.
945 *
946 * @param string $tax_display_mode Provided tax display mode.
947 * @return string Valid tax display mode.
948 */
949 protected function get_tax_display_mode( $tax_display_mode = '' ) {
950 return in_array( $tax_display_mode, [ 'incl', 'excl' ], true ) ? $tax_display_mode : get_option( 'woocommerce_tax_display_shop' );
951 }
952
953 /**
954 * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode.
955 *
956 * @param string $tax_display_mode If returned prices are incl or excl of tax.
957 * @return string Function name.
958 */
959 protected function get_price_function_from_tax_display_mode( $tax_display_mode ) {
960 return 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax';
961 }
962
963 /**
964 * Get price range from certain product types.
965 *
966 * @param \WC_Product $product Product instance.
967 * @param string $tax_display_mode If returned prices are incl or excl of tax.
968 * @return object|null
969 */
970 protected function get_price_range( \WC_Product $product, $tax_display_mode = '' ) {
971 $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode );
972
973 if ( $product->is_type( ProductType::VARIABLE ) ) {
974 $prices = $product->get_variation_prices( true );
975
976 if ( ! empty( $prices['price'] ) && ( min( $prices['price'] ) !== max( $prices['price'] ) ) ) {
977 return (object) [
978 'min_amount' => $this->prepare_money_response( min( $prices['price'] ), wc_get_price_decimals() ),
979 'max_amount' => $this->prepare_money_response( max( $prices['price'] ), wc_get_price_decimals() ),
980 ];
981 }
982 }
983
984 if ( $product->is_type( ProductType::GROUPED ) ) {
985 $children = $product->get_visible_children();
986 $price_function = 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax';
987
988 foreach ( $children as $child ) {
989 if ( '' !== $child->get_price() ) {
990 $child_prices[] = $price_function( $child );
991 }
992 }
993
994 if ( ! empty( $child_prices ) ) {
995 return (object) [
996 'min_amount' => $this->prepare_money_response( min( $child_prices ), wc_get_price_decimals() ),
997 'max_amount' => $this->prepare_money_response( max( $child_prices ), wc_get_price_decimals() ),
998 ];
999 }
1000 }
1001
1002 return null;
1003 }
1004
1005 /**
1006 * Returns a list of terms assigned to the product.
1007 *
1008 * @param \WC_Product $product Product object.
1009 * @param string $taxonomy Taxonomy name.
1010 * @return array Array of terms (id, name, slug).
1011 */
1012 protected function get_term_list( \WC_Product $product, $taxonomy = '' ) {
1013 if ( ! $taxonomy ) {
1014 return [];
1015 }
1016
1017 $terms = get_the_terms( $product->get_id(), $taxonomy );
1018
1019 if ( ! $terms || is_wp_error( $terms ) ) {
1020 return [];
1021 }
1022
1023 $return = [];
1024 $default_category = (int) get_option( 'default_product_cat', 0 );
1025
1026 foreach ( $terms as $term ) {
1027 $link = get_term_link( $term, $taxonomy );
1028
1029 if ( is_wp_error( $link ) ) {
1030 continue;
1031 }
1032
1033 if ( $term->term_id === $default_category ) {
1034 continue;
1035 }
1036
1037 $return[] = (object) [
1038 'id' => $term->term_id,
1039 'name' => $term->name,
1040 'slug' => $term->slug,
1041 'link' => $link,
1042 ];
1043 }
1044
1045 return $return;
1046 }
1047 }
1048