\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 . '' . $tag . ">\n";
}
private static function escape_xml( $value ) {
return htmlspecialchars( (string) $value, ENT_XML1 | ENT_COMPAT, 'UTF-8' );
}
}