\n"; echo "\n"; echo "\n"; self::output_tag( 'title', $site_name ); self::output_tag( 'link', $site_url ); self::output_tag( 'description', 'Google Shopping Feed' ); $page_size = (int) apply_filters( 'livebetter_google_feed_page_size', self::DEFAULT_PAGE_SIZE ); $page_size = max( 1, $page_size ); $page = 1; do { $query_args = array( 'status' => 'publish', 'type' => array( 'simple', 'variation' ), 'visibility' => array( 'visible', 'catalog', 'search' ), 'limit' => $page_size, 'page' => $page, ); $query_args = apply_filters( 'livebetter_google_feed_query_args', $query_args, $page, $page_size ); $products = wc_get_products( $query_args ); foreach ( $products as $product ) { self::render_item( $product ); } $page++; } while ( count( $products ) === $page_size ); echo "\n"; echo "\n"; } private static function render_item( WC_Product $product ) { if ( 'publish' !== $product->get_status() ) { return; } $parent = null; if ( $product->is_type( 'variation' ) ) { $parent_id = $product->get_parent_id(); $parent = $parent_id ? wc_get_product( $parent_id ) : null; if ( $parent instanceof WC_Product && 'publish' !== $parent->get_status() ) { return; } } $image_link = self::get_image_link( $product, $parent ); if ( empty( $image_link ) ) { return; } if ( '' === $product->get_price() ) { return; } $price_data = self::get_price_data( $product ); if ( empty( $price_data['price'] ) ) { return; } $availability = self::get_availability( $product ); $brand = self::get_brand( $product, $parent ); $gtin = self::get_gtin( $product, $parent ); $mpn = self::get_mpn( $product, $parent ); $gender = self::get_gender( $product, $parent ); $age_group = self::get_age_group( $product, $parent ); $size = self::get_size( $product, $parent ); $size_system = $size ? self::get_size_system() : ''; $size_type = $size ? self::get_size_type() : ''; $color = self::get_color( $product, $parent ); $material = self::get_material( $product, $parent ); $identifier_exists = ( empty( $brand ) && empty( $gtin ) && empty( $mpn ) ) ? 'no' : 'yes'; $item_data = array( 'id' => (string) $product->get_id(), 'item_group_id' => $parent ? (string) $parent->get_id() : '', 'title' => self::get_title( $product, $parent ), 'description' => self::get_description( $product, $parent ), 'link' => $product->get_permalink(), 'image_link' => $image_link, 'availability' => $availability, 'price' => $price_data['price'], 'sale_price' => $price_data['sale_price'], 'condition' => 'new', 'brand' => $brand, 'gtin' => $gtin, 'mpn' => $mpn, 'gender' => $gender, 'age_group' => $age_group, 'size' => $size, 'size_system' => $size_system, 'size_type' => $size_type, 'color' => $color, 'material' => $material, 'identifier_exists' => $identifier_exists, ); $item_data = apply_filters( 'livebetter_google_feed_item_data', $item_data, $product, $parent ); echo "\n"; self::output_tag( 'g:id', $item_data['id'] ); if ( ! empty( $item_data['item_group_id'] ) ) { self::output_tag( 'g:item_group_id', $item_data['item_group_id'] ); } self::output_tag( 'title', $item_data['title'] ); self::output_tag( 'description', $item_data['description'] ); self::output_tag( 'link', $item_data['link'] ); self::output_tag( 'g:image_link', $item_data['image_link'] ); self::output_tag( 'g:availability', $item_data['availability'] ); self::output_tag( 'g:price', $item_data['price'] ); if ( ! empty( $item_data['sale_price'] ) ) { self::output_tag( 'g:sale_price', $item_data['sale_price'] ); } self::output_tag( 'g:condition', $item_data['condition'] ); if ( ! empty( $item_data['brand'] ) ) { self::output_tag( 'g:brand', $item_data['brand'] ); } if ( ! empty( $item_data['gender'] ) ) { self::output_tag( 'g:gender', $item_data['gender'] ); } if ( ! empty( $item_data['age_group'] ) ) { self::output_tag( 'g:age_group', $item_data['age_group'] ); } if ( ! empty( $item_data['size'] ) ) { self::output_tag( 'g:size', $item_data['size'] ); } if ( ! empty( $item_data['size_system'] ) ) { self::output_tag( 'g:size_system', $item_data['size_system'] ); } if ( ! empty( $item_data['size_type'] ) ) { self::output_tag( 'g:size_type', $item_data['size_type'] ); } if ( ! empty( $item_data['color'] ) ) { self::output_tag( 'g:color', $item_data['color'] ); } if ( ! empty( $item_data['material'] ) ) { self::output_tag( 'g:material', $item_data['material'] ); } if ( ! empty( $item_data['gtin'] ) ) { self::output_tag( 'g:gtin', $item_data['gtin'] ); } if ( ! empty( $item_data['mpn'] ) ) { self::output_tag( 'g:mpn', $item_data['mpn'] ); } if ( 'no' === $item_data['identifier_exists'] ) { self::output_tag( 'g:identifier_exists', 'no' ); } echo "\n"; } private static function get_title( WC_Product $product, $parent ) { $title = $product->get_name(); if ( empty( $title ) && $parent instanceof WC_Product ) { $title = $parent->get_name(); } return $title; } private static function get_description( WC_Product $product, $parent ) { $description = $product->get_description(); if ( empty( $description ) ) { $description = $product->get_short_description(); } if ( empty( $description ) && $parent instanceof WC_Product ) { $description = $parent->get_description(); } if ( empty( $description ) && $parent instanceof WC_Product ) { $description = $parent->get_short_description(); } $description = strip_shortcodes( (string) $description ); $description = wp_strip_all_tags( $description ); $description = preg_replace( '/\\s+/', ' ', $description ); $description = trim( $description ); if ( function_exists( 'mb_substr' ) ) { $description = mb_substr( $description, 0, 5000 ); } else { $description = substr( $description, 0, 5000 ); } if ( '' === $description ) { $description = self::get_title( $product, $parent ); } return $description; } private static function get_image_link( WC_Product $product, $parent ) { $image_id = $product->get_image_id(); if ( ! $image_id && $parent instanceof WC_Product ) { $image_id = $parent->get_image_id(); } if ( ! $image_id ) { $gallery_ids = $product->get_gallery_image_ids(); if ( empty( $gallery_ids ) && $parent instanceof WC_Product ) { $gallery_ids = $parent->get_gallery_image_ids(); } $image_id = $gallery_ids ? (int) $gallery_ids[0] : 0; } if ( ! $image_id ) { return ''; } $image_link = wp_get_attachment_image_url( $image_id, 'full' ); return $image_link ? $image_link : ''; } private static function get_price_data( WC_Product $product ) { $currency = get_woocommerce_currency(); $decimals = wc_get_price_decimals(); $price = wc_get_price_to_display( $product ); $price = wc_format_decimal( $price, $decimals ) . ' ' . $currency; $sale_price = ''; if ( $product->is_on_sale() && '' !== $product->get_sale_price() ) { $regular_price_value = wc_get_price_to_display( $product, array( 'price' => $product->get_regular_price() ) ); $sale_price_value = wc_get_price_to_display( $product, array( 'price' => $product->get_sale_price() ) ); $price = wc_format_decimal( $regular_price_value, $decimals ) . ' ' . $currency; $sale_price = wc_format_decimal( $sale_price_value, $decimals ) . ' ' . $currency; } return array( 'price' => $price, 'sale_price' => $sale_price, ); } private static function get_availability( WC_Product $product ) { $stock_holder = self::get_stock_holder( $product ); if ( $stock_holder->managing_stock() ) { $combined_stock = self::get_combined_stock( $stock_holder ); if ( $combined_stock > 0 ) { $availability = 'in_stock'; } else { $availability = 'out_of_stock'; if ( $stock_holder->backorders_allowed() ) { $availability = apply_filters( 'livebetter_google_feed_backorder_availability', $availability, $product, $stock_holder ); } } } else { $availability = $stock_holder->is_in_stock() ? 'in_stock' : 'out_of_stock'; } return apply_filters( 'livebetter_google_feed_availability', $availability, $product, $stock_holder ); } private static function get_stock_holder( WC_Product $product ) { $managed_id = $product->get_stock_managed_by_id(); if ( $managed_id && $managed_id !== $product->get_id() ) { $holder = wc_get_product( $managed_id ); if ( $holder instanceof WC_Product ) { return $holder; } } return $product; } private static function get_combined_stock( WC_Product $product ) { $external_key = self::EXTERNAL_STOCK_META_KEY_FALLBACK; if ( class_exists( 'Siti_Stock_Plugin' ) ) { $external_key = Siti_Stock_Plugin::EXTERNAL_STOCK_META_KEY; } $raw_stock = $product->get_stock_quantity( 'edit' ); $base_stock = function_exists( 'wc_stock_amount' ) ? wc_stock_amount( $raw_stock ) : (int) $raw_stock; $external_stock = (int) $product->get_meta( $external_key, true ); return max( 0, (int) $base_stock ) + max( 0, $external_stock ); } private static function get_brand( WC_Product $product, $parent ) { $brand = self::get_taxonomy_terms_value( $product, $parent, array( 'product_brand', 'product_brands', 'brands', 'brand' ) ); if ( empty( $brand ) ) { $brand = $product->get_meta( '_wc_gla_brand', true ); } if ( empty( $brand ) && $parent instanceof WC_Product ) { $brand = $parent->get_meta( '_wc_gla_brand', true ); } if ( empty( $brand ) ) { $brand = self::get_attribute_value( $product, $parent, array( 'pa_merk', 'pa_brand', 'brand' ) ); } return apply_filters( 'livebetter_google_feed_brand', $brand, $product, $parent ); } private static function get_gtin( WC_Product $product, $parent ) { $gtin = ''; if ( method_exists( $product, 'get_global_unique_id' ) ) { $gtin = preg_replace( '/[^0-9]/', '', (string) $product->get_global_unique_id() ); } if ( empty( $gtin ) && $parent instanceof WC_Product && method_exists( $parent, 'get_global_unique_id' ) ) { $gtin = preg_replace( '/[^0-9]/', '', (string) $parent->get_global_unique_id() ); } return apply_filters( 'livebetter_google_feed_gtin', $gtin, $product, $parent ); } private static function get_mpn( WC_Product $product, $parent ) { $mpn = $product->get_sku(); if ( empty( $mpn ) && $parent instanceof WC_Product ) { $mpn = $parent->get_sku(); } return apply_filters( 'livebetter_google_feed_mpn', $mpn, $product, $parent ); } private static function get_gender( WC_Product $product, $parent ) { $gender = (string) $product->get_meta( self::GENDER_META_KEY, true ); if ( empty( $gender ) && $parent instanceof WC_Product ) { $gender = (string) $parent->get_meta( self::GENDER_META_KEY, true ); } return apply_filters( 'livebetter_google_feed_gender', $gender, $product, $parent ); } private static function get_age_group( WC_Product $product, $parent ) { $age_group = (string) $product->get_meta( self::AGE_GROUP_META_KEY, true ); if ( empty( $age_group ) && $parent instanceof WC_Product ) { $age_group = (string) $parent->get_meta( self::AGE_GROUP_META_KEY, true ); } if ( empty( $age_group ) ) { $age_group = 'adult'; } return apply_filters( 'livebetter_google_feed_age_group', $age_group, $product, $parent ); } private static function get_size( WC_Product $product, $parent ) { $size = self::get_attribute_value( $product, $parent, array( 'pa_maat', 'maat' ) ); return apply_filters( 'livebetter_google_feed_size', $size, $product, $parent ); } private static function get_size_system() { return apply_filters( 'livebetter_google_feed_size_system', 'EU' ); } private static function get_size_type() { return apply_filters( 'livebetter_google_feed_size_type', 'regular' ); } private static function get_color( WC_Product $product, $parent ) { $color = self::get_attribute_value( $product, $parent, array( 'pa_kleur', 'kleur', 'color' ) ); return apply_filters( 'livebetter_google_feed_color', $color, $product, $parent ); } private static function get_material( WC_Product $product, $parent ) { $material = self::get_attribute_value( $product, $parent, array( 'pa_materiaal', 'materiaal', 'material' ) ); return apply_filters( 'livebetter_google_feed_material', $material, $product, $parent ); } private static function get_attribute_value( WC_Product $product, $parent, array $keys ) { foreach ( $keys as $key ) { $value = trim( (string) $product->get_attribute( $key ) ); if ( '' !== $value ) { return $value; } } if ( $parent instanceof WC_Product ) { foreach ( $keys as $key ) { $value = trim( (string) $parent->get_attribute( $key ) ); if ( '' !== $value ) { return $value; } } } return ''; } private static function get_taxonomy_terms_value( WC_Product $product, $parent, array $taxonomies ) { foreach ( $taxonomies as $taxonomy ) { if ( ! taxonomy_exists( $taxonomy ) ) { continue; } $terms = get_the_terms( $product->get_id(), $taxonomy ); if ( empty( $terms ) && $parent instanceof WC_Product ) { $terms = get_the_terms( $parent->get_id(), $taxonomy ); } if ( $terms && ! is_wp_error( $terms ) ) { return implode( ', ', wp_list_pluck( $terms, 'name' ) ); } } return ''; } public static function register_product_data_tab( $tabs ) { if ( ! is_array( $tabs ) ) { return $tabs; } $tabs['livebetter_google_shopping'] = array( 'label' => __( 'Google Shopping', 'livebetter-google-shopping-feed' ), 'target' => 'livebetter_google_shopping_data', 'class' => array( 'show_if_simple', 'show_if_variable' ), 'priority' => 80, ); return $tabs; } public static function render_product_data_panel() { if ( ! function_exists( 'woocommerce_wp_select' ) ) { return; } global $post; if ( ! $post ) { return; } $product = wc_get_product( $post->ID ); if ( ! $product instanceof WC_Product ) { return; } $gender = (string) $product->get_meta( self::GENDER_META_KEY, true ); $age_group = (string) $product->get_meta( self::AGE_GROUP_META_KEY, true ); if ( '' === $age_group ) { $age_group = 'adult'; } echo '
'; woocommerce_wp_select( array( 'id' => self::GENDER_META_KEY, 'label' => __( 'Geslacht', 'livebetter-google-shopping-feed' ), 'value' => $gender, 'options' => array( '' => __( 'Niet ingesteld', 'livebetter-google-shopping-feed' ), 'male' => __( 'Male (M)', 'livebetter-google-shopping-feed' ), 'female' => __( 'Female (F)', 'livebetter-google-shopping-feed' ), 'unisex' => __( 'Unisex (U)', 'livebetter-google-shopping-feed' ), ), ) ); woocommerce_wp_select( array( 'id' => self::AGE_GROUP_META_KEY, 'label' => __( 'Doelgroep', 'livebetter-google-shopping-feed' ), 'value' => $age_group, 'options' => array( 'adult' => __( '18+ (Adult)', 'livebetter-google-shopping-feed' ), 'kids' => __( 'Kids', 'livebetter-google-shopping-feed' ), 'toddler' => __( 'Toddler', 'livebetter-google-shopping-feed' ), 'infant' => __( 'Infant', 'livebetter-google-shopping-feed' ), 'newborn' => __( 'Newborn', 'livebetter-google-shopping-feed' ), ), ) ); echo '
'; } public static function save_product_data_fields( $post_id ) { if ( ! isset( $_POST[ self::GENDER_META_KEY ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing return; } $gender = sanitize_text_field( wp_unslash( $_POST[ self::GENDER_META_KEY ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $age_group = sanitize_text_field( wp_unslash( $_POST[ self::AGE_GROUP_META_KEY ] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing update_post_meta( $post_id, self::GENDER_META_KEY, $gender ); update_post_meta( $post_id, self::AGE_GROUP_META_KEY, $age_group ); } private static function output_tag( $tag, $value ) { $value = self::escape_xml( $value ); echo '<' . $tag . '>' . $value . '\n"; } private static function escape_xml( $value ) { return htmlspecialchars( (string) $value, ENT_XML1 | ENT_COMPAT, 'UTF-8' ); } }