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 | '&%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 |