PluginProbe ʕ •ᴥ•ʔ
Meta for WooCommerce / 1.11.3
Meta for WooCommerce v1.11.3
3.7.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.11.1 1.11.2 1.11.3 1.11.4 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.2.0 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.4.0 2.4.1 2.5.0 2.5.1 2.6.0 2.6.1 2.6.10 2.6.11 2.6.12 2.6.13 2.6.14 2.6.15 2.6.16 2.6.17 2.6.18 2.6.19 2.6.2 2.6.20 2.6.21 2.6.22 2.6.23 2.6.24 2.6.25 2.6.26 2.6.27 2.6.28 2.6.29 2.6.3 2.6.30 2.6.4 2.6.5 2.6.6 2.6.7 2.6.8 2.6.9 3.0.0 3.0.1 3.0.10 3.0.11 3.0.12 3.0.13 3.0.14 3.0.15 3.0.16 3.0.17 3.0.18 3.0.19 3.0.2 3.0.20 3.0.21 3.0.22 3.0.23 3.0.24 3.0.25 3.0.26 3.0.27 3.0.28 3.0.29 3.0.3 3.0.30 3.0.31 3.0.32 3.0.33 3.0.34 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.10 3.1.11 3.1.12 3.1.13 3.1.14 3.1.15 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.1 3.2.10 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.4.0 3.4.1 3.4.10 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.4.7 3.4.8 3.4.9 3.5.10 3.5.11 3.5.12 3.5.13 3.5.14 3.5.15 3.5.16 3.5.17 3.5.18 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 3.5.9 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0
facebook-for-woocommerce / includes / fbproduct.php
facebook-for-woocommerce / includes Last commit date
Integrations 6 years ago Products 6 years ago Utilities 6 years ago test 6 years ago AJAX.php 6 years ago Admin.php 6 years ago Lifecycle.php 6 years ago Products.php 6 years ago fbasync.php 6 years ago fbbackground.php 6 years ago fbgraph.php 6 years ago fbinfobanner.php 6 years ago fbproduct.php 6 years ago fbproductfeed.php 6 years ago fbutils.php 6 years ago fbwpml.php 6 years ago
fbproduct.php
828 lines
1 <?php
2 /**
3 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4 *
5 * This source code is licensed under the license found in the
6 * LICENSE file in the root directory of this source tree.
7 *
8 * @package FacebookCommerce
9 */
10
11 use SkyVerge\WooCommerce\Facebook\Products;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
18 include_once 'includes/fbutils.php';
19 }
20
21 if ( ! class_exists( 'WC_Facebook_Product' ) ) :
22
23 /**
24 * Custom FB Product proxy class
25 */
26 class WC_Facebook_Product {
27
28 // Should match facebook-commerce.php while we migrate that code over
29 // to this object.
30 const FB_PRODUCT_DESCRIPTION = 'fb_product_description';
31 const FB_PRODUCT_PRICE = 'fb_product_price';
32 const FB_PRODUCT_IMAGE = 'fb_product_image';
33 const FB_VARIANT_IMAGE = 'fb_image';
34 const FB_VISIBILITY = 'fb_visibility';
35
36 const MIN_DATE_1 = '1970-01-29';
37 const MIN_DATE_2 = '1970-01-30';
38 const MAX_DATE = '2038-01-17';
39 const MAX_TIME = 'T23:59+00:00';
40 const MIN_TIME = 'T00:00+00:00';
41
42 static $use_checkout_url = array(
43 'simple' => 1,
44 'variable' => 1,
45 'variation' => 1,
46 );
47
48 public function __construct( $wpid, $parent_product = null ) {
49
50 $this->id = $wpid;
51 $this->fb_description = '';
52 $this->woo_product = wc_get_product( $wpid );
53 $this->gallery_urls = null;
54 $this->fb_use_parent_image = null;
55 $this->fb_price = 0;
56 $this->main_description = '';
57 $this->sync_short_description = \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_SHORT === facebook_for_woocommerce()->get_integration()->get_product_description_mode();
58
59 if ( $meta = get_post_meta( $wpid, self::FB_VISIBILITY, true ) ) {
60 $this->fb_visibility = wc_string_to_bool( $meta );
61 } else {
62 $this->fb_visibility = ''; // for products that haven't synced yet
63 }
64
65 // Variable products should use some data from the parent_product
66 // For performance reasons, that data shouldn't be regenerated every time.
67 if ( $parent_product ) {
68 $this->gallery_urls = $parent_product->get_gallery_urls();
69 $this->fb_use_parent_image = $parent_product->get_use_parent_image();
70 $this->main_description = $parent_product->get_fb_description();
71 }
72 }
73
74 public function exists() {
75 return ( $this->woo_product !== null && $this->woo_product !== false );
76 }
77
78 // Fall back to calling method on $woo_product
79 public function __call( $function, $args ) {
80 if ( $this->woo_product ) {
81 return call_user_func_array( array( $this->woo_product, $function ), $args );
82 } else {
83 $e = new Exception();
84 $backtrace = var_export( $e->getTraceAsString(), true );
85 WC_Facebookcommerce_Utils::fblog(
86 "Calling $function on Null Woo Object. Trace:\n" . $backtrace,
87 array(),
88 true
89 );
90 return null;
91 }
92 }
93
94 public function get_gallery_urls() {
95 if ( $this->gallery_urls === null ) {
96 if ( is_callable( array( $this, 'get_gallery_image_ids' ) ) ) {
97 $image_ids = $this->get_gallery_image_ids();
98 } else {
99 $image_ids = $this->get_gallery_attachment_ids();
100 }
101 $gallery_urls = array();
102 foreach ( $image_ids as $image_id ) {
103 $image_url = wp_get_attachment_url( $image_id );
104 if ( ! empty( $image_url ) ) {
105 array_push(
106 $gallery_urls,
107 WC_Facebookcommerce_Utils::make_url( $image_url )
108 );
109 }
110 }
111 $this->gallery_urls = array_filter( $gallery_urls );
112 }
113
114 return $this->gallery_urls;
115 }
116
117 public function get_post_data() {
118 if ( is_callable( 'get_post' ) ) {
119 return get_post( $this->id );
120 } else {
121 return $this->get_post_data();
122 }
123 }
124
125 public function get_fb_price() {
126 // Cache the price in this object in case of multiple calls.
127 if ( $this->fb_price ) {
128 return $this->fb_price;
129 }
130
131 $price = get_post_meta(
132 $this->id,
133 self::FB_PRODUCT_PRICE,
134 true
135 );
136
137 if ( is_numeric( $price ) ) {
138 return intval( round( $price * 100 ) );
139 }
140
141 // If product is composite product, we rely on their pricing.
142 if ( class_exists( 'WC_Product_Composite' )
143 && $this->woo_product->get_type() === 'composite' ) {
144 $price = get_option( 'woocommerce_tax_display_shop' ) === 'incl'
145 ? $this->woo_product->get_composite_price_including_tax()
146 : $this->woo_product->get_composite_price();
147 $this->fb_price = intval( round( $price * 100 ) );
148 return $this->fb_price;
149 }
150
151 // Get regular price: regular price doesn't include sales
152 $regular_price = floatval( $this->get_regular_price() );
153
154 // If it's a bookable product, the normal price is null/0.
155 if ( ! $regular_price &&
156 class_exists( 'WC_Product_Booking' ) &&
157 is_wc_booking_product( $this ) ) {
158 $product = new WC_Product_Booking( $this->woo_product );
159 $regular_price = $product->get_display_cost();
160 }
161
162 // Get regular price plus tax, if it's set to display and taxable
163 // whether price includes tax is based on 'woocommerce_tax_display_shop'
164 $price = $this->get_price_plus_tax( $regular_price );
165 $this->fb_price = intval( round( $price * 100 ) );
166 return $this->fb_price;
167 }
168
169
170 /**
171 * Gets a list of image URLs to use for this product in Facebook sync.
172 *
173 * @return array
174 */
175 public function get_all_image_urls() {
176
177 $image_urls = [];
178
179 $product_image_url = wp_get_attachment_url( $this->woo_product->get_image_id() );
180 $parent_product_image_url = null;
181 $custom_image_url = $this->woo_product->get_meta( self::FB_PRODUCT_IMAGE );
182
183 if ( $this->woo_product->is_type( 'variation' ) ) {
184
185 if ( $parent_product = wc_get_product( $this->woo_product->get_parent_id() ) ) {
186
187 $parent_product_image_url = wp_get_attachment_url( $parent_product->get_image_id() );
188 }
189 }
190
191 switch ( $this->woo_product->get_meta( Products::PRODUCT_IMAGE_SOURCE_META_KEY ) ) {
192
193 case Products::PRODUCT_IMAGE_SOURCE_CUSTOM:
194 $image_urls = [ $custom_image_url, $product_image_url, $parent_product_image_url ];
195 break;
196
197 case Products::PRODUCT_IMAGE_SOURCE_PARENT_PRODUCT:
198 $image_urls = [ $parent_product_image_url, $product_image_url ];
199 break;
200
201 case Products::PRODUCT_IMAGE_SOURCE_PRODUCT:
202 default:
203 $image_urls = [ $product_image_url, $parent_product_image_url ];
204 break;
205 }
206
207 $image_urls = array_merge( $image_urls, $this->get_gallery_urls() );
208 $image_urls = array_filter( array_unique( $image_urls ) );
209
210 if ( empty( $image_urls ) ) {
211 // TODO: replace or remove this placeholder - placeholdit.imgix.net is no longer available {WV 2020-01-21}
212 $image_urls[] = sprintf( 'https://placeholdit.imgix.net/~text?txtsize=33&name=%s&w=530&h=530', rawurlencode( strip_tags( $this->woo_product->get_title() ) ) );
213 }
214
215 return $image_urls;
216 }
217
218 // Returns the parent image id for variable products only.
219 public function get_parent_image_id() {
220 if ( WC_Facebookcommerce_Utils::is_variation_type( $this->woo_product->get_type() ) ) {
221 $parent_data = $this->get_parent_data();
222 return $parent_data['image_id'];
223 }
224 return null;
225 }
226
227 public function set_description( $description ) {
228 $description = stripslashes(
229 WC_Facebookcommerce_Utils::clean_string( $description )
230 );
231 $this->fb_description = $description;
232 update_post_meta(
233 $this->id,
234 self::FB_PRODUCT_DESCRIPTION,
235 $description
236 );
237 }
238
239 public function set_product_image( $image ) {
240 if ( $image !== null && strlen( $image ) !== 0 ) {
241 $image = WC_Facebookcommerce_Utils::clean_string( $image );
242 $image = WC_Facebookcommerce_Utils::make_url( $image );
243 update_post_meta(
244 $this->id,
245 self::FB_PRODUCT_IMAGE,
246 $image
247 );
248 }
249 }
250
251 public function set_price( $price ) {
252 if ( is_numeric( $price ) ) {
253 $this->fb_price = intval( round( $price * 100 ) );
254 update_post_meta(
255 $this->id,
256 self::FB_PRODUCT_PRICE,
257 $price
258 );
259 } else {
260 delete_post_meta(
261 $this->id,
262 self::FB_PRODUCT_PRICE
263 );
264 }
265 }
266
267 public function get_use_parent_image() {
268 if ( $this->fb_use_parent_image === null ) {
269 $variant_image_setting =
270 get_post_meta( $this->id, self::FB_VARIANT_IMAGE, true );
271 $this->fb_use_parent_image = ( $variant_image_setting ) ? true : false;
272 }
273 return $this->fb_use_parent_image;
274 }
275
276 public function set_use_parent_image( $setting ) {
277 $this->fb_use_parent_image = ( $setting == 'yes' );
278 update_post_meta(
279 $this->id,
280 self::FB_VARIANT_IMAGE,
281 $this->fb_use_parent_image
282 );
283 }
284
285 public function get_fb_description() {
286 if ( $this->fb_description ) {
287 return $this->fb_description;
288 }
289
290 $description = get_post_meta(
291 $this->id,
292 self::FB_PRODUCT_DESCRIPTION,
293 true
294 );
295
296 if ( $description ) {
297 return $description;
298 }
299
300 if ( WC_Facebookcommerce_Utils::is_variation_type( $this->woo_product->get_type() ) ) {
301
302 $description = WC_Facebookcommerce_Utils::clean_string( $this->woo_product->get_description() );
303
304 if ( $description ) {
305 return $description;
306 }
307 if ( $this->main_description ) {
308 return $this->main_description;
309 }
310 }
311
312 $post = $this->get_post_data();
313
314 $post_content = WC_Facebookcommerce_Utils::clean_string(
315 $post->post_content
316 );
317 $post_excerpt = WC_Facebookcommerce_Utils::clean_string(
318 $post->post_excerpt
319 );
320 $post_title = WC_Facebookcommerce_Utils::clean_string(
321 $post->post_title
322 );
323
324 // Sanitize description
325 if ( $post_content ) {
326 $description = $post_content;
327 }
328 if ( $this->sync_short_description || ( $description == '' && $post_excerpt ) ) {
329 $description = $post_excerpt;
330 }
331 if ( $description == '' ) {
332 $description = $post_title;
333 }
334
335 return $description;
336 }
337
338 public function add_sale_price( $product_data ) {
339 // initialize sale date and sale_price
340 $product_data['sale_price_start_date'] = self::MIN_DATE_1 . self::MIN_TIME;
341 $product_data['sale_price_end_date'] = self::MIN_DATE_2 . self::MAX_TIME;
342 $product_data['sale_price'] = $product_data['price'];
343
344 $sale_price = $this->woo_product->get_sale_price();
345 // check if sale exist
346 if ( ! is_numeric( $sale_price ) ) {
347 return $product_data;
348 }
349 $sale_price =
350 intval( round( $this->get_price_plus_tax( $sale_price ) * 100 ) );
351
352 $sale_start =
353 ( $date = get_post_meta( $this->id, '_sale_price_dates_from', true ) )
354 ? date_i18n( 'Y-m-d', $date ) . self::MIN_TIME
355 : self::MIN_DATE_1 . self::MIN_TIME;
356
357 $sale_end =
358 ( $date = get_post_meta( $this->id, '_sale_price_dates_to', true ) )
359 ? date_i18n( 'Y-m-d', $date ) . self::MAX_TIME
360 : self::MAX_DATE . self::MAX_TIME;
361
362 // check if sale is expired and sale time range is valid
363 $product_data['sale_price_start_date'] = $sale_start;
364 $product_data['sale_price_end_date'] = $sale_end;
365 $product_data['sale_price'] = $sale_price;
366 return $product_data;
367 }
368
369 public function is_hidden() {
370 $wpid = $this->id;
371 if ( WC_Facebookcommerce_Utils::is_variation_type( $this->get_type() ) ) {
372 $wpid = $this->get_parent_id();
373 }
374 $hidden_from_catalog = has_term(
375 'exclude-from-catalog',
376 'product_visibility',
377 $wpid
378 );
379 $hidden_from_search = has_term(
380 'exclude-from-search',
381 'product_visibility',
382 $wpid
383 );
384 // fb_visibility === '': after initial sync by feed
385 // fb_visibility === false: set hidden on FB metadata
386 // Explicitly check whether flip 'hide' before.
387 return ( $hidden_from_catalog && $hidden_from_search ) ||
388 $this->fb_visibility === false || ! $this->get_fb_price();
389 }
390
391 public function get_price_plus_tax( $price ) {
392 $woo_product = $this->woo_product;
393 // // wc_get_price_including_tax exist for Woo > 2.7
394 if ( function_exists( 'wc_get_price_including_tax' ) ) {
395 $args = array(
396 'qty' => 1,
397 'price' => $price,
398 );
399 return get_option( 'woocommerce_tax_display_shop' ) === 'incl'
400 ? wc_get_price_including_tax( $woo_product, $args )
401 : wc_get_price_excluding_tax( $woo_product, $args );
402 } else {
403 return get_option( 'woocommerce_tax_display_shop' ) === 'incl'
404 ? $woo_product->get_price_including_tax( 1, $price )
405 : $woo_product->get_price_excluding_tax( 1, $price );
406 }
407 }
408
409 public function get_grouped_product_option_names( $key, $option_values ) {
410 // Convert all slug_names in $option_values into the visible names that
411 // advertisers have set to be the display names for a given attribute value
412 $terms = get_the_terms( $this->id, $key );
413 return array_map(
414 function ( $slug_name ) use ( $terms ) {
415 foreach ( $terms as $term ) {
416 if ( $term->slug === $slug_name ) {
417 return $term->name;
418 }
419 }
420 return $slug_name;
421 },
422 $option_values
423 );
424 }
425
426 public function get_variant_option_name( $label, $default_value ) {
427 // For the given label, get the Visible name rather than the slug
428 $meta = get_post_meta( $this->id, $label, true );
429 $attribute_name = str_replace( 'attribute_', '', $label );
430 $term = get_term_by( 'slug', $meta, $attribute_name );
431 return $term && $term->name ? $term->name : $default_value;
432 }
433
434 public function update_visibility( $is_product_page, $visible_box_checked ) {
435 $visibility = get_post_meta( $this->id, self::FB_VISIBILITY, true );
436 if ( $visibility && ! $is_product_page ) {
437 // If the product was previously set to visible, keep it as visible
438 // (unless we're on the product page)
439 $this->fb_visibility = $visibility;
440 } else {
441 // If the product is not visible OR we're on the product page,
442 // then update the visibility as needed.
443 $this->fb_visibility = $visible_box_checked ? true : false;
444 update_post_meta( $this->id, self::FB_VISIBILITY, $this->fb_visibility );
445 }
446 }
447
448 // wrapper function to find item_id for default variation
449 function find_matching_product_variation() {
450 if ( is_callable( array( $this, 'get_default_attributes' ) ) ) {
451 $default_attributes = $this->get_default_attributes();
452 } else {
453 $default_attributes = $this->get_variation_default_attributes();
454 }
455
456 if ( ! $default_attributes ) {
457 return;
458 }
459 foreach ( $default_attributes as $key => $value ) {
460 if ( strncmp( $key, 'attribute_', strlen( 'attribute_' ) ) === 0 ) {
461 continue;
462 }
463 unset( $default_attributes[ $key ] );
464 $default_attributes[ sprintf( 'attribute_%s', $key ) ] = $value;
465 }
466 if ( class_exists( 'WC_Data_Store' ) ) {
467 // for >= woo 3.0.0
468 $data_store = WC_Data_Store::load( 'product' );
469 return $data_store->find_matching_product_variation(
470 $this,
471 $default_attributes
472 );
473 } else {
474 return $this->get_matching_variation( $default_attributes );
475 }
476 }
477
478 /**
479 * Gets product data to send to Facebook.
480 *
481 * @param string $retailer_id the retailer ID of the product
482 * @param bool $prepare_for_product_feed whether the data is going to be used in a feed upload
483 * @return array
484 */
485 public function prepare_product( $retailer_id = null, $prepare_for_product_feed = false ) {
486
487 if ( ! $retailer_id ) {
488 $retailer_id =
489 WC_Facebookcommerce_Utils::get_fb_retailer_id( $this );
490 }
491 $image_urls = $this->get_all_image_urls();
492
493 // Replace WordPress sanitization's ampersand with a real ampersand.
494 $product_url = str_replace(
495 '&amp%3B',
496 '&',
497 html_entity_decode( $this->get_permalink() )
498 );
499
500 // Use product_url for external/bundle product setting.
501 $product_type = $this->get_type();
502 if ( ! $product_type || ! isset( self::$use_checkout_url[ $product_type ] ) ) {
503 $checkout_url = $product_url;
504 } elseif ( wc_get_cart_url() ) {
505 $char = '?';
506 // Some merchant cart pages are actually a querystring
507 if ( strpos( wc_get_cart_url(), '?' ) !== false ) {
508 $char = '&';
509 }
510
511 $checkout_url = WC_Facebookcommerce_Utils::make_url(
512 wc_get_cart_url() . $char
513 );
514
515 if ( WC_Facebookcommerce_Utils::is_variation_type( $this->get_type() ) ) {
516 $query_data = array(
517 'add-to-cart' => $this->get_parent_id(),
518 'variation_id' => $this->get_id(),
519 );
520
521 $query_data = array_merge(
522 $query_data,
523 $this->get_variation_attributes()
524 );
525
526 } else {
527 $query_data = array(
528 'add-to-cart' => $this->get_id(),
529 );
530 }
531
532 $checkout_url = $checkout_url . http_build_query( $query_data );
533
534 } else {
535 $checkout_url = null;
536 }
537
538 $id = $this->get_id();
539 if ( WC_Facebookcommerce_Utils::is_variation_type( $this->get_type() ) ) {
540 $id = $this->get_parent_id();
541 }
542 $categories =
543 WC_Facebookcommerce_Utils::get_product_categories( $id );
544 $brand = get_the_term_list( $id, 'product_brand', '', ', ' );
545 $brand = is_wp_error( $brand ) || ! $brand
546 ? WC_Facebookcommerce_Utils::get_store_name()
547 : WC_Facebookcommerce_Utils::clean_string( $brand );
548
549 $product_data = array(
550 'name' => WC_Facebookcommerce_Utils::clean_string(
551 $this->get_title()
552 ),
553 'description' => $this->get_fb_description(),
554 'image_url' => $image_urls[0], // The array can't be empty.
555 'additional_image_urls' => array_slice( $image_urls, 1 ),
556 'url' => $product_url,
557 'category' => $categories['categories'],
558 'brand' => $brand,
559 'retailer_id' => $retailer_id,
560 'price' => $this->get_fb_price(),
561 'currency' => get_woocommerce_currency(),
562 'availability' => $this->is_in_stock() ? 'in stock' :
563 'out of stock',
564 'visibility' => ! $this->is_hidden()
565 ? \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_VISIBLE
566 : \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN,
567 );
568
569 // Only use checkout URLs if they exist.
570 if ( $checkout_url ) {
571 $product_data['checkout_url'] = $checkout_url;
572 }
573
574 $product_data = $this->add_sale_price( $product_data );
575
576 // IF using WPML, set the product to staging unless it is in the
577 // default language. WPML >= 3.2 Supported.
578 if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
579 if ( class_exists( 'WC_Facebook_WPML_Injector' ) && WC_Facebook_WPML_Injector::should_hide( $id ) ) {
580 $product_data['visibility'] = \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN;
581 }
582 }
583
584 // Exclude variations that are "virtual" products from export to Facebook &&
585 // No Visibility Option for Variations
586 if ( true === $this->get_virtual() ) {
587 $product_data['visibility'] = \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN;
588 }
589
590 if ( ! $prepare_for_product_feed ) {
591 $this->prepare_variants_for_item( $product_data );
592 } elseif (
593 WC_Facebookcommerce_Utils::is_all_caps( $product_data['description'] )
594 ) {
595 $product_data['description'] =
596 mb_strtolower( $product_data['description'] );
597 }
598
599 /**
600 * Filters the generated product data.
601 *
602 * @param int $id Woocommerce product id
603 * @param array $product_data An array of product data
604 */
605 return apply_filters(
606 'facebook_for_woocommerce_integration_prepare_product',
607 $product_data,
608 $id
609 );
610 }
611
612
613 /**
614 * Normalizes variant data for Facebook.
615 *
616 * @param array $product_data variation product data
617 * @return array
618 */
619 public function prepare_variants_for_item( &$product_data ) {
620
621 /** @var \WC_Product_Variation $product */
622 $product = $this;
623
624 if ( ! $product->is_type( 'variation' ) ) {
625 return [];
626 }
627
628 $attributes = $product->get_variation_attributes();
629
630 if ( ! $attributes ) {
631 return [];
632 }
633
634 $variant_names = array_keys( $attributes );
635 $variant_data = [];
636
637 // Loop through variants (size, color, etc) if they exist
638 // For each product field type, pull the single variant
639 foreach ( $variant_names as $original_variant_name ) {
640
641 // Retrieve label name for attribute
642 $label = wc_attribute_label( $original_variant_name, $product );
643
644 // Clean up variant name (e.g. pa_color should be color)
645 // Replace "custom_data:foo" with just "foo" so we can use the key
646 // Product item API expects "custom_data" instead of "custom_data:foo"
647 $new_name = str_replace( 'custom_data:', '', \WC_Facebookcommerce_Utils::sanitize_variant_name( $original_variant_name ) );
648
649 // Sometimes WC returns an array, sometimes it's an assoc array, depending
650 // on what type of taxonomy it's using. array_values will guarantee we
651 // only get a flat array of values.
652 if ( $options = $this->get_variant_option_name( $label, $attributes[ $original_variant_name ] ) ) {
653
654 if ( is_array( $options ) ) {
655
656 $option_values = array_values( $options );
657
658 } else {
659
660 $option_values = [ $options ];
661
662 // If this attribute has value 'any', options will be empty strings
663 // Redirect to product page to select variants.
664 // Reset checkout url since checkout_url (build from query data will
665 // be invalid in this case.
666 if ( count( $option_values ) === 1 && empty( $option_values[0] ) ) {
667 $option_values[0] = 'any';
668 $product_data['checkout_url'] = $product_data['url'];
669 }
670 }
671
672 if ( \WC_Facebookcommerce_Utils::FB_VARIANT_GENDER === $new_name ) {
673
674 // If we can't validate the gender, this will be null.
675 $product_data[ $new_name ] = \WC_Facebookcommerce_Utils::validateGender( $option_values[0] );
676 }
677
678 switch ( $new_name ) {
679
680 case \WC_Facebookcommerce_Utils::FB_VARIANT_SIZE:
681 case \WC_Facebookcommerce_Utils::FB_VARIANT_COLOR:
682 case \WC_Facebookcommerce_Utils::FB_VARIANT_PATTERN:
683
684 $variant_data[] = [
685 'product_field' => $new_name,
686 'label' => $label,
687 'options' => $option_values,
688 ];
689
690 $product_data[ $new_name ] = $option_values[0];
691
692 break;
693
694 case \WC_Facebookcommerce_Utils::FB_VARIANT_GENDER:
695
696 // If we can't validate the GENDER field, we'll fall through to the
697 // default case and set the gender into custom data.
698 if ( $product_data[ $new_name ] ) {
699
700 $variant_data[] = [
701 'product_field' => $new_name,
702 'label' => $label,
703 'options' => $option_values,
704 ];
705 }
706
707 break;
708
709 default:
710
711 // This is for any custom_data.
712 if ( ! isset( $product_data['custom_data'] ) ) {
713 $product_data['custom_data'] = [];
714 }
715
716 $product_data['custom_data'][ $new_name ] = urldecode( $option_values[0] );
717
718 break;
719 }
720
721 } else {
722
723 \WC_Facebookcommerce_Utils::log( $product->get_id() . ': No options for ' . $original_variant_name );
724 continue;
725 }
726 }
727
728 return $variant_data;
729 }
730
731
732 /**
733 * Normalizes variable product variations data for Facebook.
734 *
735 * @param bool $feed_data whether this is used for feed data
736 * @return array
737 */
738 public function prepare_variants_for_group( $feed_data = false ) {
739
740 /** @var \WC_Product_Variable $product */
741 $product = $this;
742 $final_variants = [];
743
744 try {
745
746 if ( ! $product->is_type( 'variable' ) ) {
747 throw new \Exception( 'prepare_variants_for_group called on non-variable product' );
748 }
749
750 $variation_attributes = $product->get_variation_attributes();
751
752 if ( ! $variation_attributes ) {
753 return [];
754 }
755
756 foreach ( array_keys( $product->get_attributes() ) as $name ) {
757
758 $label = wc_attribute_label( $name, $product );
759
760 if ( taxonomy_is_product_attribute( $name ) ) {
761 $key = $name;
762 } else {
763 // variation_attributes keys are labels for custom attrs for some reason
764 $key = $label;
765 }
766
767 if ( ! $key ) {
768 throw new \Exception( "Critical error: can't get attribute name or label!" );
769 }
770
771 if ( isset( $variation_attributes[ $key ] ) ) {
772 // array of the options (e.g. small, medium, large)
773 $option_values = $variation_attributes[ $key ];
774 } else {
775 // skip variations without valid attribute options
776 \WC_Facebookcommerce_Utils::log( $product->get_id() . ': No options for ' . $name );
777 continue;
778 }
779
780 // If this is a variable product, check default attribute.
781 // If it's being used, show it as the first option on Facebook.
782 if ( $first_option = $product->get_variation_default_attribute( $key ) ) {
783
784 $index = array_search( $first_option, $option_values, false );
785
786 unset( $option_values[ $index ] );
787
788 array_unshift( $option_values, $first_option );
789 }
790
791 if ( function_exists( 'taxonomy_is_product_attribute' ) && taxonomy_is_product_attribute( $name ) ) {
792 $option_values = $this->get_grouped_product_option_names( $key, $option_values );
793 }
794
795 /**
796 * For API approach, product_field need to start with 'custom_data:'
797 * @link https://developers.facebook.com/docs/marketing-api/reference/product-variant/
798 * Clean up variant name (e.g. pa_color should be color):
799 */
800 $name = \WC_Facebookcommerce_Utils::sanitize_variant_name( $name );
801
802 // for feed uploading, product field should remove prefix 'custom_data:'
803 if ( $feed_data ) {
804 $name = str_replace( 'custom_data:', '', $name );
805 }
806
807 $final_variants[] = [
808 'product_field' => $name,
809 'label' => $label,
810 'options' => $option_values,
811 ];
812 }
813
814 } catch ( \Exception $e ) {
815
816 \WC_Facebookcommerce_Utils::fblog( $e->getMessage() );
817
818 return [];
819 }
820
821 return $final_variants;
822 }
823
824
825 }
826
827 endif;
828