芝麻web文件管理V1.00
编辑当前文件:/home/ezdajrnh/www/wp-content/plugins/facebook-for-woocommerce/includes/Feed/FeedUploadUtils.php
get_connection_handler()->get_commerce_merchant_settings_id(); $store_urls = [ wc_get_page_permalink( 'shop' ) ]; foreach ( $comments as $comment ) { try { $post_type = get_post_type( $comment->comment_post_ID ); if ( 'product' !== $post_type ) { continue; } $rating = get_comment_meta( $comment->comment_ID, 'rating', true ); if ( ! is_numeric( $rating ) ) { continue; } $reviewer_id = $comment->user_id; // If reviewer_id is 0 then the reviewer is a logged-out user $reviewer_is_anonymous = '0' === $reviewer_id ? 'true' : 'false'; $product = wc_get_product( $comment->comment_post_ID ); if ( null === $product ) { continue; } $product_name = $product->get_name(); $product_url = $product->get_permalink(); $product_skus = [ $product->get_sku() ]; $reviews_data[] = array( 'aggregator' => 'woocommerce', 'store.name' => $store_name, 'store.id' => $store_id, 'store.storeUrls' => "['" . implode( "','", $store_urls ) . "']", 'review_id' => $comment->comment_ID, 'rating' => intval( $rating ), 'title' => null, 'content' => $comment->comment_content, 'created_at' => $comment->comment_date, 'updated_at' => null, 'review_image_urls' => null, 'incentivized' => 'false', 'has_verified_purchase' => 'false', 'reviewer.name' => $comment->comment_author, 'reviewer.reviewerID' => $reviewer_id, 'reviewer.isAnonymous' => $reviewer_is_anonymous, 'product.name' => $product_name, 'product.url' => $product_url, 'product.productIdentifiers.skus' => "['" . implode( "','", $product_skus ) . "']", ); } catch ( \Exception $e ) { Logger::log( 'Exception while trying to map product review data for feed', array( 'flow_name' => self::RATINGS_AND_REVIEWS_SYNC_LOGGING_FLOW_NAME, 'flow_step' => 'map_ratings_and_reviews_data', 'extra_data' => [ 'exception_message' => $e->getMessage(), ], ), array( 'should_send_log_to_meta' => true, 'should_save_log_in_woocommerce' => true, 'woocommerce_log_level' => \WC_Log_Levels::ERROR, ) ); continue; } } return $reviews_data; } catch ( \Exception $exception ) { Logger::log( 'Error while fetching ratings and reviews data.', array( 'event' => self::RATINGS_AND_REVIEWS_SYNC_LOGGING_FLOW_NAME, 'event_type' => 'get_ratings_and_reviews_data', 'extra_data' => [ 'query_args' => wp_json_encode( $query_args ), ], ), array( 'should_send_log_to_meta' => true, 'should_save_log_in_woocommerce' => false, 'woocommerce_log_level' => \WC_Log_Levels::DEBUG, ), $exception, ); throw $exception; } } /** * Query for coupons and map them to Meta format. * * @param array $query_args arguments for the get_posts() call * * @throws \Exception If an error occurs during fetching coupons. */ public static function get_coupons_data( array $query_args ): array { try { $coupon_posts = get_posts( $query_args ); $coupons_data = array(); // Loop through each coupon post and map the necessary fields. foreach ( $coupon_posts as $coupon_post ) { // Create a coupon object using the coupon code. $coupon = new WC_Coupon( $coupon_post->post_title ); if ( ! self::is_valid_coupon( $coupon ) ) { continue; } try { // Map discount type and amount $woo_discount_type = $coupon->get_discount_type(); $percent_off = ''; $fixed_amount_off = ''; if ( 'percent' === $woo_discount_type ) { $value_type = self::VALUE_TYPE_PERCENTAGE; $percent_off = $coupon->get_amount(); } elseif ( in_array( $woo_discount_type, array( 'fixed_cart', 'fixed_product' ), true ) ) { $value_type = self::VALUE_TYPE_FIXED_AMOUNT; $fixed_amount_off = $coupon->get_amount(); // TODO we may want to pass in optional currency code for multinational support } else { Logger::log( 'Unknown discount type encountered during feed processing', array( 'promotion_id' => $coupon_post->ID, 'extra_data' => [ 'discount_type' => $woo_discount_type ], 'flow_name' => self::PROMO_SYNC_LOGGING_FLOW_NAME, 'flow_step' => 'map_discount_type', ), array( 'should_send_log_to_meta' => true, 'should_save_log_in_woocommerce' => true, 'woocommerce_log_level' => \WC_Log_Levels::WARNING, ) ); continue; } // Map start and end dates (if available) $start_date_time = $coupon->get_date_created() ? (string) $coupon->get_date_created()->getTimestamp() : $coupon_post->post_date; $end_date_time = $coupon->get_date_expires() ? (string) $coupon->get_date_expires()->getTimestamp() : ''; // Map target type. Coupons that apply both a discount and free shipping are already // filtered out in is_valid_coupon $is_free_shipping = $coupon->get_free_shipping(); $target_shipping_options = ''; if ( $is_free_shipping ) { $target_type = self::TARGET_TYPE_SHIPPING; $value_type = self::VALUE_TYPE_PERCENTAGE; $percent_off = '100'; // 100% off shipping $target_shipping_options = [ 'STANDARD' ]; // options are STANDARD, RUSH, EXPEDITED, TWO_DAY } else { $target_type = self::TARGET_TYPE_LINE_ITEM; } // Map target granularity if ( $is_free_shipping || 'fixed_cart' === $woo_discount_type ) { $target_granularity = self::TARGET_GRANULARITY_ORDER_LEVEL; } else { $target_granularity = self::TARGET_GRANULARITY_ITEM_LEVEL; } // Map target selection if ( empty( $coupon->get_product_ids() ) && empty( $coupon->get_product_categories() ) && empty( $coupon->get_excluded_product_ids() ) && empty( $coupon->get_excluded_product_categories() ) ) { // Coupon applies to all products. $target_selection = self::TARGET_SELECTION_ENTIRE_CATALOG; } else { $target_selection = self::TARGET_SELECTION_SPECIFIC_PRODUCTS; } // Determine target product mapping $target_product_set_retailer_ids = ''; $target_product_retailer_ids = ''; $target_filter = ''; if ( self::TARGET_SELECTION_SPECIFIC_PRODUCTS === $target_selection ) { $target_filter = self::get_target_filter( $coupon->get_product_ids(), $coupon->get_excluded_product_ids(), $coupon->get_product_categories(), $coupon->get_excluded_product_categories() ); } // Build the mapped coupon data array. $data = array( 'offer_id' => $coupon->get_id(), 'title' => $coupon->get_code(), 'value_type' => $value_type, 'percent_off' => $percent_off, 'fixed_amount_off' => $fixed_amount_off, 'application_type' => self::APPLICATION_TYPE_BUYER_APPLIED, 'target_type' => $target_type, 'target_shipping_option_types' => $target_shipping_options, 'target_granularity' => $target_granularity, 'target_selection' => $target_selection, 'start_date_time' => $start_date_time, 'end_date_time' => $end_date_time, 'coupon_codes' => array( $coupon->get_code() ), 'public_coupon_code' => '', // TODO allow public coupons 'target_filter' => $target_filter, 'target_product_retailer_ids' => $target_product_retailer_ids, 'target_product_group_retailer_ids' => '', // Concept does not exist in Woo 'target_product_set_retailer_ids' => $target_product_set_retailer_ids, 'redeem_limit_per_user' => $coupon->get_usage_limit_per_user(), 'min_subtotal' => $coupon->get_minimum_amount(), // TODO we may want to pass in optional currency code for multinational support 'min_quantity' => '', // Concept does not exist in Woo 'offer_terms' => '', // TODO link to T&C page? 'target_quantity' => '', // Concept does not exist in Woo 'prerequisite_filter' => '', // Concept does not exist in Woo 'prerequisite_product_retailer_ids' => '', // Concept does not exist in Woo 'prerequisite_product_group_retailer_ids' => '', // Concept does not exist in Woo 'prerequisite_product_set_retailer_ids' => '', // Concept does not exist in Woo 'exclude_sale_priced_products' => $coupon->get_exclude_sale_items() ? 'YES' : 'NO', 'usage_count' => $coupon->get_usage_count(), 'usage_limit' => $coupon->get_usage_limit(), ); $coupons_data[] = $data; } catch ( \Exception $e ) { Logger::log( 'Exception while trying to get coupon data for feed', array( 'promotion_id' => $coupon_post->ID, 'extra_data' => [ 'exception_message' => $e->getMessage(), 'query_args' => wp_json_encode( $query_args ), ], 'flow_name' => self::PROMO_SYNC_LOGGING_FLOW_NAME, 'flow_step' => 'map_coupon_data', ), array( 'should_send_log_to_meta' => true, 'should_save_log_in_woocommerce' => true, 'woocommerce_log_level' => \WC_Log_Levels::ERROR, ) ); continue; } } return $coupons_data; } catch ( \Exception $e ) { Logger::log( 'Error while fetching coupons data.', array( 'event' => self::PROMO_SYNC_LOGGING_FLOW_NAME, 'event_type' => 'get_coupon_data', 'extra_data' => [ 'query_args' => wp_json_encode( $query_args ) ], ), array( 'should_send_log_to_meta' => true, 'should_save_log_in_woocommerce' => false, 'woocommerce_log_level' => \WC_Log_Levels::DEBUG, ), $e, ); throw $e; } } private static function is_valid_coupon( WC_Coupon $coupon ): bool { /** * Fields not supported by Meta: * - coupon gives both a discount and free shipping * - Maximum Spend is set * - Allowed Emails are set * - limit_usage_to_x_items is set * - missing coupon code * - coupon uses brand targeting */ if ( empty( $coupon->get_code() ) ) { return false; } if ( $coupon->get_free_shipping() && $coupon->get_amount() > 0 ) { return false; } if ( $coupon->get_maximum_amount() > 0 ) { return false; } if ( count( $coupon->get_email_restrictions() ) > 0 ) { return false; } if ( ( $coupon->get_limit_usage_to_x_items() ?? 0 ) > 0 ) { return false; } $brands = $coupon->get_meta( 'product_brands' ); $brands_count = is_countable( $brands ) ? count( $brands ) : ( ! empty( $brands ) ? 1 : 0 ); if ( $brands_count > 0 ) { return false; } $exclude_brands = $coupon->get_meta( 'exclude_product_brands' ); $exclude_brands_count = is_countable( $exclude_brands ) ? count( $exclude_brands ) : ( ! empty( $exclude_brands ) ? 1 : 0 ); if ( $exclude_brands_count > 0 ) { return false; } return true; } private static function get_target_filter( array $included_product_ids, array $excluded_product_ids, array $included_product_category_ids, array $excluded_product_category_ids ): string { $filter_parts = []; $included_products = self::get_products( $included_product_ids, $included_product_category_ids ); $excluded_products = self::get_products( $excluded_product_ids, $excluded_product_category_ids ); if ( ! empty( $included_products ) ) { // "is product x or is product y" $included = self::build_retailer_id_filter( $included_products, 'eq' ); $filter_parts[] = [ 'or' => $included ]; } if ( ! empty( $excluded_products ) ) { // "is not product x and is not product y" $excluded = self::build_retailer_id_filter( $excluded_products, 'neq' ); $filter_parts[] = [ 'and' => $excluded ]; } // Combine the filter parts: // - If both parts are present, wrap them in an "and" clause. // - If only one part exists, use it directly. if ( count( $filter_parts ) > 1 ) { $final_filter = [ 'and' => $filter_parts ]; } elseif ( count( $filter_parts ) === 1 ) { $final_filter = $filter_parts[0]; } else { return ''; } /** * Return the JSON representation. It should look something like: * {"and":[ * {"or":[{"retailer_id":{"eq":"retailer_id_1"}},{"retailer_id":{"eq":"retailer_id_2"}}]}, * {"and":[{"retailer_id":{"neq":"retailer_id_3"}},{"retailer_id":{"neq":"retailer_id_4"}}]} * ]} */ return wp_json_encode( $final_filter ); } private static function build_retailer_id_filter( array $products, string $operator ): array { return array_map( function ( $product ) use ( $operator ) { $fb_retailer_id = \WC_Facebookcommerce_Utils::get_fb_retailer_id( $product ); return [ 'retailer_id' => [ $operator => $fb_retailer_id ] ]; }, $products ); } private static function get_products( array $product_ids, array $product_category_ids ): array { $products = []; if ( ! empty( $product_ids ) ) { $products = wc_get_products( array( 'include' => $product_ids, 'orderby' => 'ID', 'order' => 'ASC', ) ); } // TODO when confident in category syncing, we can use target_product_set_retailer_ids instead of // extracting products from the categories to use in the target filter. This current logic // may result in the target filter field being too large for Meta to ingest. if ( ! empty( $product_category_ids ) ) { $products_from_categories = wc_get_products( array( 'product_category_id' => $product_category_ids, 'orderby' => 'ID', 'order' => 'ASC', ) ); $products = array_unique( array_merge( $products, $products_from_categories ) ); } return $products; } public static function get_navigation_menu_data(): array { try { // Fetch all product categories $args = array( 'taxonomy' => 'product_cat', 'orderby' => 'name', 'order' => 'ASC', 'hide_empty' => false, // Show all categories, even if they are empty ); $categories = get_terms( $args ); $category_tree = self::build_category_tree( $categories ); return array( 'navigation' => array( array( 'items' => $category_tree, 'title' => 'Product Categories', 'partner_menu_handle' => 'product_categories_menu', 'partner_menu_id' => '1', ), ), ); } catch ( \Exception $e ) { Logger::log( 'Error while fetching navigations menu data.', array( 'event' => self::NAVIGATION_MENU_SYNC_LOGGING_FLOW_NAME, 'event_type' => 'get_navigation_menu_data', ), array( 'should_send_log_to_meta' => true, 'should_save_log_in_woocommerce' => false, 'woocommerce_log_level' => \WC_Log_Levels::DEBUG, ), $e, ); throw $e; } } private static function build_category_tree( array $categories, int $parent_id = 0, array &$memo = [] ): array { if ( isset( $memo[ $parent_id ] ) ) { return $memo[ $parent_id ]; } $branch = []; foreach ( $categories as $category ) { if ( $category->parent === $parent_id ) { $children = self::build_category_tree( $categories, $category->term_taxonomy_id, $memo ); $category_data = array( 'title' => $category->name, 'resourceType' => 'collection', 'retailerID' => $category->term_taxonomy_id, ); if ( ! empty( $children ) ) { $category_data['items'] = $children; } $branch[] = $category_data; } } $memo[ $parent_id ] = $branch; return $branch; } }