PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 11.1.0
WooCommerce v11.1.0
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 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 9 months ago AbstractAddressSchema.php 1 month ago AbstractSchema.php 2 months ago BatchSchema.php 2 years ago BillingAddressSchema.php 1 month ago CartCouponSchema.php 1 year ago CartExtensionsSchema.php 2 years ago CartFeeSchema.php 2 years ago CartItemSchema.php 2 months ago CartSchema.php 1 month ago CartShippingRateSchema.php 1 month ago CheckoutOrderSchema.php 2 years ago CheckoutSchema.php 1 month ago ErrorSchema.php 2 years ago ImageAttachmentSchema.php 5 months ago ItemSchema.php 1 year ago OrderCouponSchema.php 2 years ago OrderFeeSchema.php 2 years ago OrderItemSchema.php 2 months ago OrderSchema.php 3 months ago PatternsSchema.php 1 year ago ProductAttributeSchema.php 2 years ago ProductAttributeTermSchema.php 2 months ago ProductBrandSchema.php 1 year ago ProductCategorySchema.php 2 years ago ProductCollectionDataSchema.php 1 year ago ProductReviewSchema.php 2 years ago ProductSchema.php 2 weeks ago ShippingAddressSchema.php 2 years ago ShopperListItemSchema.php 2 months ago ShopperListSchema.php 2 months ago TermSchema.php 2 years ago
ProductSchema.php
1050 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 use Automattic\WooCommerce\Enums\StockDisplayFormat;
11 use Automattic\WooCommerce\Enums\TaxDisplayMode;
12
13 /**
14 * ProductSchema class.
15 */
16 class ProductSchema extends AbstractSchema {
17 /**
18 * The schema item name.
19 *
20 * @var string
21 */
22 protected $title = 'product';
23
24 /**
25 * The schema item identifier.
26 *
27 * @var string
28 */
29 const IDENTIFIER = 'product';
30
31 /**
32 * Image attachment schema instance.
33 *
34 * @var ImageAttachmentSchema
35 */
36 protected $image_attachment_schema;
37
38 /**
39 * Constructor.
40 *
41 * @param ExtendSchema $extend Rest Extending instance.
42 * @param SchemaController $controller Schema Controller instance.
43 */
44 public function __construct( ExtendSchema $extend, SchemaController $controller ) {
45 parent::__construct( $extend, $controller );
46 $this->image_attachment_schema = $this->controller->get( ImageAttachmentSchema::IDENTIFIER );
47 }
48
49 /**
50 * Product schema properties.
51 *
52 * @return array
53 */
54 public function get_properties() {
55 return [
56 'id' => [
57 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
58 'type' => 'integer',
59 'context' => [ 'view', 'edit', 'embed' ],
60 'readonly' => true,
61 ],
62 'name' => [
63 'description' => __( 'Product name.', 'woocommerce' ),
64 'type' => 'string',
65 'context' => [ 'view', 'edit', 'embed' ],
66 ],
67 'slug' => [
68 'description' => __( 'Product slug.', 'woocommerce' ),
69 'type' => 'string',
70 'context' => [ 'view', 'edit', 'embed' ],
71 ],
72 'parent' => [
73 'description' => __( 'ID of the parent product, if applicable.', 'woocommerce' ),
74 'type' => 'integer',
75 'context' => [ 'view', 'edit', 'embed' ],
76 'readonly' => true,
77 ],
78 'type' => [
79 'description' => __( 'Product type.', 'woocommerce' ),
80 'type' => 'string',
81 'context' => [ 'view', 'edit', 'embed' ],
82 'readonly' => true,
83 ],
84 'variation' => [
85 'description' => __( 'Product variation attributes, if applicable.', 'woocommerce' ),
86 'type' => 'string',
87 'context' => [ 'view', 'edit', 'embed' ],
88 ],
89 'permalink' => [
90 'description' => __( 'Product URL.', 'woocommerce' ),
91 'type' => 'string',
92 'format' => 'uri',
93 'context' => [ 'view', 'edit', 'embed' ],
94 'readonly' => true,
95 ],
96 'short_description' => [
97 'description' => __( 'Product short description in HTML format.', 'woocommerce' ),
98 'type' => 'string',
99 'context' => [ 'view', 'edit', 'embed' ],
100 ],
101 'description' => [
102 'description' => __( 'Product full description in HTML format.', 'woocommerce' ),
103 'type' => 'string',
104 'context' => [ 'view', 'edit', 'embed' ],
105 ],
106 'on_sale' => [
107 'description' => __( 'Is the product on sale?', 'woocommerce' ),
108 'type' => 'boolean',
109 'context' => [ 'view', 'edit', 'embed' ],
110 'readonly' => true,
111 ],
112 'sku' => [
113 'description' => __( 'Unique identifier.', 'woocommerce' ),
114 'type' => 'string',
115 'context' => [ 'view', 'edit', 'embed' ],
116 ],
117 'prices' => [
118 'description' => __( 'Price data provided using the smallest unit of the currency.', 'woocommerce' ),
119 'type' => 'object',
120 'context' => [ 'view', 'edit', 'embed' ],
121 'readonly' => true,
122 'properties' => array_merge(
123 $this->get_store_currency_properties(),
124 [
125 'price' => [
126 'description' => __( 'Current product price.', 'woocommerce' ),
127 'type' => 'string',
128 'context' => [ 'view', 'edit', 'embed' ],
129 'readonly' => true,
130 ],
131 'regular_price' => [
132 'description' => __( 'Regular product price.', 'woocommerce' ),
133 'type' => 'string',
134 'context' => [ 'view', 'edit', 'embed' ],
135 'readonly' => true,
136 ],
137 'sale_price' => [
138 'description' => __( 'Sale product price, if applicable.', 'woocommerce' ),
139 'type' => 'string',
140 'context' => [ 'view', 'edit', 'embed' ],
141 'readonly' => true,
142 ],
143 'price_range' => [
144 'description' => __( 'Price range, if applicable.', 'woocommerce' ),
145 'type' => [ 'object', 'null' ],
146 'context' => [ 'view', 'edit', 'embed' ],
147 'readonly' => true,
148 'properties' => [
149 'min_amount' => [
150 'description' => __( 'Price amount.', 'woocommerce' ),
151 'type' => 'string',
152 'context' => [ 'view', 'edit', 'embed' ],
153 'readonly' => true,
154 ],
155 'max_amount' => [
156 'description' => __( 'Price amount.', 'woocommerce' ),
157 'type' => 'string',
158 'context' => [ 'view', 'edit', 'embed' ],
159 'readonly' => true,
160 ],
161 ],
162 ],
163 ]
164 ),
165 ],
166 'price_html' => array(
167 'description' => __( 'Price string formatted as HTML.', 'woocommerce' ),
168 'type' => 'string',
169 'context' => array( 'view', 'edit', 'embed' ),
170 'readonly' => true,
171 ),
172 'average_rating' => [
173 'description' => __( 'Reviews average rating.', 'woocommerce' ),
174 'type' => 'string',
175 'context' => [ 'view', 'edit', 'embed' ],
176 'readonly' => true,
177 ],
178 'review_count' => [
179 'description' => __( 'Amount of reviews that the product has.', 'woocommerce' ),
180 'type' => 'integer',
181 'context' => [ 'view', 'edit', 'embed' ],
182 'readonly' => true,
183 ],
184 'images' => [
185 'description' => __( 'List of images.', 'woocommerce' ),
186 'type' => 'array',
187 'context' => [ 'view', 'edit', 'embed' ],
188 'items' => [
189 'type' => 'object',
190 'properties' => $this->image_attachment_schema->get_properties(),
191 ],
192 ],
193 'categories' => [
194 'description' => __( 'List of categories, if applicable.', 'woocommerce' ),
195 'type' => 'array',
196 'context' => [ 'view', 'edit', 'embed' ],
197 'items' => [
198 'type' => 'object',
199 'properties' => [
200 'id' => [
201 'description' => __( 'Category ID', 'woocommerce' ),
202 'type' => 'number',
203 'context' => [ 'view', 'edit', 'embed' ],
204 'readonly' => true,
205 ],
206 'name' => [
207 'description' => __( 'Category name', 'woocommerce' ),
208 'type' => 'string',
209 'context' => [ 'view', 'edit', 'embed' ],
210 'readonly' => true,
211 ],
212 'slug' => [
213 'description' => __( 'Category slug', 'woocommerce' ),
214 'type' => 'string',
215 'context' => [ 'view', 'edit', 'embed' ],
216 'readonly' => true,
217 ],
218 'link' => [
219 'description' => __( 'Category link', 'woocommerce' ),
220 'type' => 'string',
221 'context' => [ 'view', 'edit', 'embed' ],
222 'readonly' => true,
223 ],
224 ],
225 ],
226 ],
227 'tags' => [
228 'description' => __( 'List of tags, if applicable.', 'woocommerce' ),
229 'type' => 'array',
230 'context' => [ 'view', 'edit', 'embed' ],
231 'items' => [
232 'type' => 'object',
233 'properties' => [
234 'id' => [
235 'description' => __( 'Tag ID', 'woocommerce' ),
236 'type' => 'number',
237 'context' => [ 'view', 'edit', 'embed' ],
238 'readonly' => true,
239 ],
240 'name' => [
241 'description' => __( 'Tag name', 'woocommerce' ),
242 'type' => 'string',
243 'context' => [ 'view', 'edit', 'embed' ],
244 'readonly' => true,
245 ],
246 'slug' => [
247 'description' => __( 'Tag slug', 'woocommerce' ),
248 'type' => 'string',
249 'context' => [ 'view', 'edit', 'embed' ],
250 'readonly' => true,
251 ],
252 'link' => [
253 'description' => __( 'Tag link.', 'woocommerce' ),
254 'type' => 'string',
255 'context' => [ 'view', 'edit', 'embed' ],
256 'readonly' => true,
257 ],
258 ],
259 ],
260 ],
261 'brands' => [
262 'description' => __( 'List of brands, if applicable.', 'woocommerce' ),
263 'type' => 'array',
264 'context' => [ 'view', 'edit', 'embed' ],
265 'items' => [
266 'type' => 'object',
267 'properties' => [
268 'id' => [
269 'description' => __( 'Brand ID', 'woocommerce' ),
270 'type' => 'number',
271 'context' => [ 'view', 'edit', 'embed' ],
272 'readonly' => true,
273 ],
274 'name' => [
275 'description' => __( 'Brand name', 'woocommerce' ),
276 'type' => 'string',
277 'context' => [ 'view', 'edit', 'embed' ],
278 'readonly' => true,
279 ],
280 'slug' => [
281 'description' => __( 'Brand slug', 'woocommerce' ),
282 'type' => 'string',
283 'context' => [ 'view', 'edit', 'embed' ],
284 'readonly' => true,
285 ],
286 'link' => [
287 'description' => __( 'Brand link', 'woocommerce' ),
288 'type' => 'string',
289 'context' => [ 'view', 'edit', 'embed' ],
290 'readonly' => true,
291 ],
292 ],
293 ],
294 ],
295 'attributes' => [
296 'description' => __( 'List of attributes (taxonomy terms) assigned to the product. For variable products, these are mapped to variations (see the `variations` field).', 'woocommerce' ),
297 'type' => 'array',
298 'context' => [ 'view', 'edit', 'embed' ],
299 'items' => [
300 'type' => 'object',
301 'properties' => [
302 'id' => [
303 'description' => __( 'The attribute ID, or 0 if the attribute is not taxonomy based.', 'woocommerce' ),
304 'type' => 'integer',
305 'context' => [ 'view', 'edit', 'embed' ],
306 'readonly' => true,
307 ],
308 'name' => [
309 'description' => __( 'The attribute name.', 'woocommerce' ),
310 'type' => 'string',
311 'context' => [ 'view', 'edit', 'embed' ],
312 'readonly' => true,
313 ],
314 'taxonomy' => [
315 'description' => __( 'The attribute taxonomy, or null if the attribute is not taxonomy based.', 'woocommerce' ),
316 'type' => 'string',
317 'context' => [ 'view', 'edit', 'embed' ],
318 'readonly' => true,
319 ],
320 'has_variations' => [
321 'description' => __( 'True if this attribute is used by product variations.', 'woocommerce' ),
322 'type' => 'boolean',
323 'context' => [ 'view', 'edit', 'embed' ],
324 'readonly' => true,
325 ],
326 'terms' => [
327 'description' => __( 'List of assigned attribute terms.', 'woocommerce' ),
328 'type' => 'array',
329 'context' => [ 'view', 'edit', 'embed' ],
330 'items' => [
331 'type' => 'object',
332 'properties' => [
333 'id' => [
334 'description' => __( 'The term ID, or 0 if the attribute is not a global attribute.', 'woocommerce' ),
335 'type' => 'integer',
336 'context' => [ 'view', 'edit', 'embed' ],
337 'readonly' => true,
338 ],
339 'name' => [
340 'description' => __( 'The term name.', 'woocommerce' ),
341 'type' => 'string',
342 'context' => [ 'view', 'edit', 'embed' ],
343 'readonly' => true,
344 ],
345 'slug' => [
346 'description' => __( 'The term slug.', 'woocommerce' ),
347 'type' => 'string',
348 'context' => [ 'view', 'edit', 'embed' ],
349 'readonly' => true,
350 ],
351 'default' => [
352 'description' => __( 'If this is a default attribute', 'woocommerce' ),
353 'type' => 'boolean',
354 'context' => [ 'view', 'edit', 'embed' ],
355 'readonly' => true,
356 ],
357 ],
358 ],
359 ],
360 ],
361 ],
362 ],
363 'variations' => [
364 'description' => __( 'List of variation IDs, if applicable.', 'woocommerce' ),
365 'type' => 'array',
366 'context' => [ 'view', 'edit', 'embed' ],
367 'items' => [
368 'type' => 'object',
369 'properties' => [
370 'id' => [
371 'description' => __( 'The attribute ID, or 0 if the attribute is not taxonomy based.', 'woocommerce' ),
372 'type' => 'integer',
373 'context' => [ 'view', 'edit', 'embed' ],
374 'readonly' => true,
375 ],
376 'attributes' => [
377 'description' => __( 'List of variation attributes.', 'woocommerce' ),
378 'type' => 'array',
379 'context' => [ 'view', 'edit', 'embed' ],
380 'items' => [
381 'type' => 'object',
382 'properties' => [
383 'name' => [
384 'description' => __( 'The attribute name.', 'woocommerce' ),
385 'type' => 'string',
386 'context' => [ 'view', 'edit', 'embed' ],
387 'readonly' => true,
388 ],
389 'value' => [
390 'description' => __( 'The assigned attribute.', 'woocommerce' ),
391 'type' => 'string',
392 'context' => [ 'view', 'edit', 'embed' ],
393 'readonly' => true,
394 ],
395 ],
396 ],
397 ],
398 ],
399 ],
400 ],
401 'grouped_products' => [
402 'description' => __( 'List of grouped product IDs, if applicable.', 'woocommerce' ),
403 'type' => 'array',
404 'context' => [ 'view', 'edit', 'embed' ],
405 'items' => [
406 'description' => __( 'List of grouped product ids.', 'woocommerce' ),
407 'type' => 'integer',
408 'context' => [ 'view', 'edit', 'embed' ],
409 'readonly' => true,
410 ],
411 ],
412 'has_options' => [
413 'description' => __( 'Does the product have additional options before it can be added to the cart?', 'woocommerce' ),
414 'type' => 'boolean',
415 'context' => [ 'view', 'edit', 'embed' ],
416 'readonly' => true,
417 ],
418 'is_purchasable' => [
419 'description' => __( 'Is the product purchasable?', 'woocommerce' ),
420 'type' => 'boolean',
421 'context' => [ 'view', 'edit', 'embed' ],
422 'readonly' => true,
423 ],
424 'is_in_stock' => [
425 'description' => __( 'Is the product in stock?', 'woocommerce' ),
426 'type' => 'boolean',
427 'context' => [ 'view', 'edit', 'embed' ],
428 'readonly' => true,
429 ],
430 'is_on_backorder' => [
431 'description' => __( 'Is the product stock backordered? This will also return false if backorder notifications are turned off.', 'woocommerce' ),
432 'type' => 'boolean',
433 'context' => [ 'view', 'edit', 'embed' ],
434 'readonly' => true,
435 ],
436 'stock_availability' => [
437 'description' => __( 'Information about the product\'s availability.', 'woocommerce' ),
438 'type' => 'object',
439 'context' => [ 'view', 'edit', 'embed' ],
440 'readonly' => true,
441 'properties' => [
442 'text' => [
443 'description' => __( 'Stock availability text.', 'woocommerce' ),
444 'type' => 'string',
445 'context' => [ 'view', 'edit', 'embed' ],
446 'readonly' => true,
447 ],
448 'class' => [
449 'description' => __( 'Stock availability class.', 'woocommerce' ),
450 'type' => 'string',
451 'context' => [ 'view', 'edit', 'embed' ],
452 'readonly' => true,
453 ],
454 ],
455 ],
456 'low_stock_remaining' => [
457 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woocommerce' ),
458 'type' => [ 'number', 'null' ],
459 'context' => [ 'view', 'edit', 'embed' ],
460 'readonly' => true,
461 ],
462 'sold_individually' => [
463 'description' => __( 'If true, only one item of this product is allowed for purchase in a single order.', 'woocommerce' ),
464 'type' => 'boolean',
465 'context' => [ 'view', 'edit', 'embed' ],
466 'readonly' => true,
467 ],
468 'weight' => [
469 'description' => sprintf(
470 /* translators: %s: weight unit */
471 __( 'Product weight (%s).', 'woocommerce' ),
472 get_option( 'woocommerce_weight_unit' )
473 ),
474 'type' => 'string',
475 'context' => [ 'view', 'edit' ],
476 'readonly' => true,
477 ],
478 'dimensions' => [
479 'description' => __( 'Product dimensions.', 'woocommerce' ),
480 'type' => 'object',
481 'context' => [ 'view', 'edit' ],
482 'readonly' => true,
483 'properties' => [
484 'length' => [
485 'description' => sprintf(
486 /* translators: %s: dimension unit */
487 __( 'Product length (%s).', 'woocommerce' ),
488 get_option( 'woocommerce_dimension_unit' )
489 ),
490 'type' => 'string',
491 'context' => [ 'view', 'edit' ],
492 'readonly' => true,
493 ],
494 'width' => [
495 'description' => sprintf(
496 /* translators: %s: dimension unit */
497 __( 'Product width (%s).', 'woocommerce' ),
498 get_option( 'woocommerce_dimension_unit' )
499 ),
500 'type' => 'string',
501 'context' => [ 'view', 'edit' ],
502 'readonly' => true,
503 ],
504 'height' => [
505 'description' => sprintf(
506 /* translators: %s: dimension unit */
507 __( 'Product height (%s).', 'woocommerce' ),
508 get_option( 'woocommerce_dimension_unit' )
509 ),
510 'type' => 'string',
511 'context' => [ 'view', 'edit' ],
512 'readonly' => true,
513 ],
514 ],
515 ],
516 'formatted_weight' => [
517 'description' => __( 'Product weight formatted for display (e.g. "2.5 kg").', 'woocommerce' ),
518 'type' => 'string',
519 'context' => [ 'view', 'edit' ],
520 'readonly' => true,
521 ],
522 'formatted_dimensions' => [
523 'description' => __( 'Product dimensions formatted for display (e.g. "10 × 5 × 3 cm").', 'woocommerce' ),
524 'type' => 'string',
525 'context' => [ 'view', 'edit' ],
526 'readonly' => true,
527 ],
528 'add_to_cart' => [
529 'description' => __( 'Add to cart button parameters.', 'woocommerce' ),
530 'type' => 'object',
531 'context' => [ 'view', 'edit', 'embed' ],
532 'readonly' => true,
533 'properties' => [
534 'text' => [
535 'description' => __( 'Button text.', 'woocommerce' ),
536 'type' => 'string',
537 'context' => [ 'view', 'edit', 'embed' ],
538 'readonly' => true,
539 ],
540 'description' => [
541 'description' => __( 'Button description.', 'woocommerce' ),
542 'type' => 'string',
543 'context' => [ 'view', 'edit', 'embed' ],
544 'readonly' => true,
545 ],
546 'url' => [
547 'description' => __( 'Add to cart URL.', 'woocommerce' ),
548 'type' => 'string',
549 'context' => [ 'view', 'edit', 'embed' ],
550 'readonly' => true,
551 ],
552 'minimum' => [
553 'description' => __( 'The minimum quantity that can be added to the cart.', 'woocommerce' ),
554 'type' => 'number',
555 'context' => [ 'view', 'edit', 'embed' ],
556 'readonly' => true,
557 ],
558 'maximum' => [
559 'description' => __( 'The maximum quantity that can be added to the cart.', 'woocommerce' ),
560 'type' => 'number',
561 'context' => [ 'view', 'edit', 'embed' ],
562 'readonly' => true,
563 ],
564 'multiple_of' => [
565 'description' => __( 'The amount that quantities increment by. Quantity must be an multiple of this value.', 'woocommerce' ),
566 'type' => 'number',
567 'context' => [ 'view', 'edit', 'embed' ],
568 'readonly' => true,
569 'default' => 1,
570 ],
571 'single_text' => [
572 'description' => __( 'Button text in the single product page.', 'woocommerce' ),
573 'type' => 'string',
574 'context' => [ 'view', 'edit', 'embed' ],
575 'readonly' => true,
576 ],
577 ],
578 ],
579 'is_password_protected' => [
580 'description' => __( 'Whether the product requires a password to access its content.', 'woocommerce' ),
581 'type' => 'boolean',
582 'context' => [ 'view', 'edit', 'embed' ],
583 'readonly' => true,
584 ],
585 self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ),
586 ];
587 }
588
589 /**
590 * Convert a WooCommerce product into an object suitable for the response.
591 *
592 * @param \WC_Product $product Product instance.
593 * @return array
594 */
595 public function get_item_response( $product ) {
596 $availability = ProductAvailabilityUtils::get_product_availability( $product );
597 $password_required = post_password_required( $product->get_id() );
598 $short_description = $password_required ? '' : $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_short_description() ) ) );
599 $description = $password_required ? '' : $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_description() ) ) );
600
601 return [
602 'id' => $product->get_id(),
603 'name' => $this->prepare_html_response( $product->get_title() ),
604 'slug' => $product->get_slug(),
605 'parent' => $product->get_parent_id(),
606 'type' => $product->get_type(),
607 'variation' => $this->prepare_html_response( $product->is_type( ProductType::VARIATION ) ? wc_get_formatted_variation( $product, true, true, false ) : '' ),
608 'permalink' => $product->get_permalink(),
609 'sku' => $this->prepare_html_response( $product->get_sku() ),
610 'short_description' => $short_description,
611 'description' => $description,
612 'on_sale' => $product->is_on_sale(),
613 'prices' => (object) $this->prepare_product_price_response( $product ),
614 'price_html' => $this->prepare_html_response( $product->get_price_html() ),
615 'average_rating' => (string) $product->get_average_rating(),
616 'review_count' => $product->get_review_count(),
617 'images' => $this->get_images( $product ),
618 'categories' => $this->get_term_list( $product, 'product_cat' ),
619 'tags' => $this->get_term_list( $product, 'product_tag' ),
620 'brands' => $this->get_term_list( $product, 'product_brand' ),
621 'attributes' => $this->get_attributes( $product ),
622 'variations' => $this->get_variations( $product ),
623 'grouped_products' => $this->get_grouped_products( $product ),
624 'has_options' => $product->has_options(),
625 'is_purchasable' => $product->is_purchasable(),
626 'is_in_stock' => $product->is_in_stock(),
627 'is_on_backorder' => ProductStockStatus::ON_BACKORDER === $product->get_stock_status(),
628 'low_stock_remaining' => $this->get_low_stock_remaining( $product ),
629 'stock_availability' => (object) array(
630 'text' => $availability['availability'] ?? '',
631 'class' => $availability['class'] ?? '',
632 ),
633 'sold_individually' => $product->is_sold_individually(),
634 'weight' => $product->get_weight(),
635 'dimensions' => (object) [
636 'length' => $product->get_length(),
637 'width' => $product->get_width(),
638 'height' => $product->get_height(),
639 ],
640 'formatted_weight' => wc_format_weight( (float) $product->get_weight() ),
641 'formatted_dimensions' => html_entity_decode( wc_format_dimensions( (array) $product->get_dimensions( false ) ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
642 'add_to_cart' => (object) array_merge(
643 [
644 'text' => $this->prepare_html_response( $product->add_to_cart_text() ),
645 'description' => $this->prepare_html_response( $product->add_to_cart_description() ),
646 'url' => $this->prepare_html_response( $product->add_to_cart_url() ),
647 'single_text' => $this->prepare_html_response( $product->single_add_to_cart_text() ),
648 ],
649 ( new QuantityLimits() )->get_add_to_cart_limits( $product )
650 ),
651 'is_password_protected' => '' !== $product->get_post_password(),
652 self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER, $product ),
653
654 ];
655 }
656
657 /**
658 * Get list of product images.
659 *
660 * @param \WC_Product $product Product instance.
661 * @return array
662 */
663 protected function get_images( \WC_Product $product ) {
664 $attachment_ids = array_filter( array_merge( [ $product->get_image_id() ], $product->get_gallery_image_ids() ) );
665 if ( ! empty( $attachment_ids ) ) {
666 // Prime caches to reduce future queries.
667 _prime_post_caches( $attachment_ids );
668 }
669
670 return array_values( array_filter( array_map( [ $this->image_attachment_schema, 'get_item_response' ], $attachment_ids ) ) );
671 }
672
673 /**
674 * Gets remaining stock amount for a product.
675 *
676 * @param \WC_Product $product Product instance.
677 * @return int|float|null
678 */
679 protected function get_remaining_stock( \WC_Product $product ) {
680 if ( is_null( $product->get_stock_quantity() ) ) {
681 return null;
682 }
683 return $product->get_stock_quantity();
684 }
685
686 /**
687 * If a product has low stock, return the remaining stock amount for display.
688 *
689 * @param \WC_Product $product Product instance.
690 * @return int|float|null
691 */
692 protected function get_low_stock_remaining( \WC_Product $product ) {
693 $remaining_stock = $this->get_remaining_stock( $product );
694 $stock_format = get_option( 'woocommerce_stock_format' );
695
696 // Don't show the low stock badge if the settings doesn't allow it.
697 if ( StockDisplayFormat::NEVER === $stock_format ) {
698 return null;
699 }
700
701 // Show the low stock badge if the remaining stock is below or equal to the threshold.
702 if ( ! is_null( $remaining_stock ) && $remaining_stock <= wc_get_low_stock_amount( $product ) ) {
703 return max( $remaining_stock, 0 );
704 }
705
706 return null;
707 }
708
709 /**
710 * Returns true if the given attribute is valid.
711 *
712 * @param mixed $attribute Object or variable to check.
713 * @return boolean
714 */
715 protected function filter_valid_attribute( $attribute ) {
716 return is_a( $attribute, '\WC_Product_Attribute' );
717 }
718
719 /**
720 * Returns true if the given attribute is valid and used for variations.
721 *
722 * @param mixed $attribute Object or variable to check.
723 * @return boolean
724 */
725 protected function filter_variation_attribute( $attribute ) {
726 return $this->filter_valid_attribute( $attribute ) && $attribute->get_variation();
727 }
728
729 /**
730 * Get variation IDs and attributes from the DB.
731 *
732 * @param \WC_Product $product Product instance.
733 * @returns array
734 */
735 protected function get_variations( \WC_Product $product ) {
736 $variation_ids = $product->is_type( ProductType::VARIABLE ) ? $product->get_visible_children() : [];
737
738 if ( ! count( $variation_ids ) ) {
739 return [];
740 }
741
742 /**
743 * Gets default variation data which applies to all of this products variations.
744 */
745 $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_variation_attribute' ] );
746 $default_variation_meta_data = array_reduce(
747 $attributes,
748 function ( $defaults, $attribute ) use ( $product ) {
749 $meta_key = wc_variation_attribute_name( $attribute->get_name() );
750 $defaults[ $meta_key ] = [
751 'name' => wc_attribute_label( $attribute->get_name(), $product ),
752 'value' => null,
753 ];
754 return $defaults;
755 },
756 []
757 );
758 $default_variation_meta_keys = array_keys( $default_variation_meta_data );
759
760 /**
761 * Gets individual variation data from the database, using cache where possible.
762 */
763 $cache_group = 'product_variation_meta_data';
764 $cache_value = wp_cache_get( $product->get_id(), $cache_group );
765 $last_modified = get_the_modified_date( 'U', $product->get_id() );
766
767 if ( false === $cache_value || $last_modified !== $cache_value['last_modified'] ) {
768 global $wpdb;
769 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
770 $variation_meta_data = $wpdb->get_results(
771 "
772 SELECT post_id as variation_id, meta_key as attribute_key, meta_value as attribute_value
773 FROM {$wpdb->postmeta}
774 WHERE post_id IN (" . implode( ',', array_map( 'esc_sql', $variation_ids ) ) . ")
775 AND meta_key IN ('" . implode( "','", array_map( 'esc_sql', $default_variation_meta_keys ) ) . "')
776 "
777 );
778 // phpcs:enable
779
780 wp_cache_set(
781 $product->get_id(),
782 [
783 'last_modified' => $last_modified,
784 'data' => $variation_meta_data,
785 ],
786 $cache_group
787 );
788 } else {
789 $variation_meta_data = $cache_value['data'];
790 }
791
792 /**
793 * Merges and formats default variation data with individual variation data.
794 */
795 $attributes_by_variation = array_reduce(
796 $variation_meta_data,
797 function ( $values, $data ) use ( $default_variation_meta_keys ) {
798 // The query above only includes the keys of $default_variation_meta_data so we know all of the attributes
799 // being processed here apply to this product. However, we need an additional check here because the
800 // cache may have been primed elsewhere and include keys from other products.
801 // @see AbstractProductGrid::prime_product_variations.
802 if ( in_array( $data->attribute_key, $default_variation_meta_keys, true ) ) {
803 $values[ $data->variation_id ][ $data->attribute_key ] = $data->attribute_value;
804 }
805 return $values;
806 },
807 array_fill_keys( $variation_ids, [] )
808 );
809
810 $variations = [];
811
812 foreach ( $variation_ids as $variation_id ) {
813 $attribute_data = $default_variation_meta_data;
814
815 foreach ( $attributes_by_variation[ $variation_id ] as $meta_key => $meta_value ) {
816 if ( '' !== $meta_value ) {
817 $attribute_data[ $meta_key ]['value'] = $meta_value;
818 }
819 }
820
821 $variations[] = (object) [
822 'id' => $variation_id,
823 'attributes' => array_values( $attribute_data ),
824 ];
825 }
826
827 return $variations;
828 }
829
830 /**
831 * Get grouped product IDs.
832 *
833 * @param \WC_Product $product Product instance.
834 * @return array
835 */
836 protected function get_grouped_products( \WC_Product $product ) {
837 if ( $product->is_type( ProductType::GROUPED ) ) {
838 return array_map(
839 function ( $child ) {
840 return $child->get_id();
841 },
842 $product->get_visible_children(),
843 );
844 }
845 return [];
846 }
847
848 /**
849 * Get list of product attributes and attribute terms.
850 *
851 * @param \WC_Product $product Product instance.
852 * @return array
853 */
854 protected function get_attributes( \WC_Product $product ) {
855 $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_valid_attribute' ] );
856 $default_attributes = $product->get_default_attributes();
857 $return = [];
858
859 foreach ( $attributes as $attribute_slug => $attribute ) {
860 // Only visible or variation attributes will be exposed by this API.
861 if ( ! $attribute->get_visible() && ! $attribute->get_variation() ) {
862 continue;
863 }
864
865 $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() );
866 // Custom attribute names are sanitized to be the array keys.
867 // So when we do the array_key_exists check below we also need to sanitize the attribute names.
868
869 $sanitized_attribute_name = sanitize_key( $attribute->get_name() );
870
871 if ( array_key_exists( $sanitized_attribute_name, $default_attributes ) ) {
872 foreach ( $terms as $term ) {
873 $term->default = $term->slug === $default_attributes[ $sanitized_attribute_name ];
874 }
875 }
876
877 $return[] = (object) [
878 'id' => $attribute->get_id(),
879 'name' => wc_attribute_label( $attribute->get_name(), $product ),
880 'taxonomy' => $attribute->is_taxonomy() ? $attribute->get_name() : null,
881 'has_variations' => true === $attribute->get_variation(),
882 'terms' => $terms,
883 ];
884 }
885
886 return $return;
887 }
888
889 /**
890 * Prepare an attribute term for the response.
891 *
892 * @param \WP_Term $term Term object.
893 * @return object
894 */
895 protected function prepare_product_attribute_taxonomy_value( \WP_Term $term ) {
896 return $this->prepare_product_attribute_value( $term->name, $term->term_id, $term->slug );
897 }
898
899 /**
900 * Prepare an attribute term for the response.
901 *
902 * @param string $name Attribute term name.
903 * @param int $id Attribute term ID.
904 * @param string $slug Attribute term slug.
905 * @return object
906 */
907 protected function prepare_product_attribute_value( $name, $id = 0, $slug = '' ) {
908 return (object) [
909 'id' => (int) $id,
910 'name' => $name,
911 'slug' => $slug ? $slug : $name,
912 ];
913 }
914
915 /**
916 * Get an array of pricing data.
917 *
918 * @param \WC_Product $product Product instance.
919 * @param string $tax_display_mode If returned prices are incl or excl of tax.
920 * @return array
921 */
922 protected function prepare_product_price_response( \WC_Product $product, $tax_display_mode = '' ) {
923 $prices = [];
924 $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode );
925 $price_function = $this->get_price_function_from_tax_display_mode( $tax_display_mode );
926 $price_decimals = wc_get_price_decimals();
927
928 // If we have a variable product, get the price from the variations (this will use the min value).
929 if ( $product->is_type( ProductType::VARIABLE ) ) {
930 $regular_price = $product->get_variation_regular_price();
931 $sale_price = $product->get_variation_sale_price();
932 } else {
933 $regular_price = $product->get_regular_price();
934 $sale_price = $product->get_sale_price();
935 }
936
937 $prices['price'] = $this->prepare_money_response( $price_function( $product ), $price_decimals );
938 $prices['regular_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $regular_price ] ), $price_decimals );
939 $prices['sale_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $sale_price ] ), $price_decimals );
940 $prices['price_range'] = $this->get_price_range( $product, $tax_display_mode );
941
942 return $this->prepare_currency_response( $prices );
943 }
944
945 /**
946 * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode.
947 *
948 * @param string $tax_display_mode Provided tax display mode.
949 * @return string Valid tax display mode.
950 */
951 protected function get_tax_display_mode( $tax_display_mode = '' ) {
952 return in_array( $tax_display_mode, [ TaxDisplayMode::INCLUSIVE, TaxDisplayMode::EXCLUSIVE ], true ) ? $tax_display_mode : get_option( 'woocommerce_tax_display_shop' );
953 }
954
955 /**
956 * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode.
957 *
958 * @param string $tax_display_mode If returned prices are incl or excl of tax.
959 * @return string Function name.
960 */
961 protected function get_price_function_from_tax_display_mode( $tax_display_mode ) {
962 return TaxDisplayMode::INCLUSIVE === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax';
963 }
964
965 /**
966 * Get price range from certain product types.
967 *
968 * @param \WC_Product $product Product instance.
969 * @param string $tax_display_mode If returned prices are incl or excl of tax.
970 * @return object|null
971 */
972 protected function get_price_range( \WC_Product $product, $tax_display_mode = '' ) {
973 $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode );
974
975 if ( $product->is_type( ProductType::VARIABLE ) ) {
976 $prices = $product->get_variation_prices( true );
977
978 if ( ! empty( $prices['price'] ) && ( min( $prices['price'] ) !== max( $prices['price'] ) ) ) {
979 return (object) [
980 'min_amount' => $this->prepare_money_response( min( $prices['price'] ), wc_get_price_decimals() ),
981 'max_amount' => $this->prepare_money_response( max( $prices['price'] ), wc_get_price_decimals() ),
982 ];
983 }
984 }
985
986 if ( $product->is_type( ProductType::GROUPED ) ) {
987 $children = $product->get_visible_children();
988 $price_function = TaxDisplayMode::INCLUSIVE === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax';
989
990 foreach ( $children as $child ) {
991 if ( '' !== $child->get_price() ) {
992 $child_prices[] = $price_function( $child );
993 }
994 }
995
996 if ( ! empty( $child_prices ) ) {
997 return (object) [
998 'min_amount' => $this->prepare_money_response( min( $child_prices ), wc_get_price_decimals() ),
999 'max_amount' => $this->prepare_money_response( max( $child_prices ), wc_get_price_decimals() ),
1000 ];
1001 }
1002 }
1003
1004 return null;
1005 }
1006
1007 /**
1008 * Returns a list of terms assigned to the product.
1009 *
1010 * @param \WC_Product $product Product object.
1011 * @param string $taxonomy Taxonomy name.
1012 * @return array Array of terms (id, name, slug).
1013 */
1014 protected function get_term_list( \WC_Product $product, $taxonomy = '' ) {
1015 if ( ! $taxonomy ) {
1016 return [];
1017 }
1018
1019 $terms = get_the_terms( $product->get_id(), $taxonomy );
1020
1021 if ( ! $terms || is_wp_error( $terms ) ) {
1022 return [];
1023 }
1024
1025 $return = [];
1026 $default_category = (int) get_option( 'default_product_cat', 0 );
1027
1028 foreach ( $terms as $term ) {
1029 $link = get_term_link( $term, $taxonomy );
1030
1031 if ( is_wp_error( $link ) ) {
1032 continue;
1033 }
1034
1035 if ( $term->term_id === $default_category ) {
1036 continue;
1037 }
1038
1039 $return[] = (object) [
1040 'id' => $term->term_id,
1041 'name' => $term->name,
1042 'slug' => $term->slug,
1043 'link' => $link,
1044 ];
1045 }
1046
1047 return $return;
1048 }
1049 }
1050