- Nov 8, 2009
- 8,887
- 9,192
WooCommerce sells their version for $79: https://woocommerce.com/products/google-product-feed/
But this plugin will do it for free.
Save as wp-content/plugins/gmc-csv-feed/gmc-csv-feed.php and activate. your feed will be at /wp-json/gmc/v1/csv
But this plugin will do it for free.
Save as wp-content/plugins/gmc-csv-feed/gmc-csv-feed.php and activate. your feed will be at /wp-json/gmc/v1/csv
Code:
<?php
/**
* Plugin Name: GMC CSV Feed for WooCommerce
* Description: Public CSV feed for Google Merchant Center at /wp-json/gmc/v1/csv
* Version: 1.0.0
* Author: You
*/
// Ensure WooCommerce
add_action('plugins_loaded', function () {
if (!class_exists('WooCommerce')) return;
add_action('rest_api_init', function () {
register_rest_route('gmc/v1', '/csv', [
'methods' => 'GET',
'callback' => 'gmc_csv_feed_output',
'permission_callback' => '__return_true', // public
]);
});
});
function gmc_csv_feed_output(\WP_REST_Request $req) {
// Headers
header('Content-Type: text/csv; charset=UTF-8');
header('Cache-Control: max-age=600, public');
$out = fopen('php://output', 'w');
$currency = get_woocommerce_currency();
// Google Merchant minimal headers
$headers = [
'id',
'title',
'description',
'link',
'image_link',
'availability',
'price',
'brand',
'condition',
// add more fields if you want: google_product_category, gtin, mpn, sale_price, etc.
];
fputcsv($out, $headers);
// Query all published products including variations
$paged = 1;
$perPage = 200;
while (true) {
$args = [
'status' => 'publish',
'limit' => $perPage,
'page' => $paged,
'return' => 'objects',
'type' => ['simple', 'variable'],
];
$products = wc_get_products($args);
if (empty($products)) break;
foreach ($products as $product) {
if ($product->is_type('variable')) {
foreach ($product->get_children() as $child_id) {
$variation = wc_get_product($child_id);
if ($variation && $variation->get_status() === 'publish') {
gmc_put_product_row($out, $variation, $currency, $product);
}
}
} else {
gmc_put_product_row($out, $product, $currency, null);
}
}
$paged++;
}
fclose($out);
exit;
}
function gmc_put_product_row($out, WC_Product $p, string $currency, ?WC_Product $parent) {
// ID prefers SKU
$id = $p->get_sku();
if (!$id) $id = (string)$p->get_id();
// Title
$title = $p->get_name();
// Description - plain text
$desc = $p->get_description();
if (!$desc) $desc = $p->get_short_description();
$desc = trim(preg_replace('/\s+/', ' ', wp_strip_all_tags($desc)));
// Link
$link = get_permalink($parent ? $parent->get_id() : $p->get_id());
// Add variation query args so GMC can land on selected options
if ($p->is_type('variation')) {
$attrs = $p->get_attributes();
if (!empty($attrs)) {
$link = add_query_arg(array_map('wc_clean', $attrs), $link);
}
}
// Image
$image_id = $p->get_image_id();
if (!$image_id && $parent) $image_id = $parent->get_image_id();
$image = $image_id ? wp_get_attachment_url($image_id) : '';
// Availability
$availability = $p->is_in_stock() ? 'in stock' : 'out of stock';
// Price with currency, tax-inclusive retail price preferred
$price_val = wc_get_price_including_tax($p, ['price' => $p->get_price()]);
if ($price_val === '') $price_val = 0;
$price = wc_format_decimal($price_val, wc_get_price_decimals()) . ' ' . $currency;
// Brand - try common meta or fallback to site name
$brand = get_post_meta($p->get_id(), 'brand', true);
if (!$brand) $brand = get_post_meta($p->get_id(), '_brand', true);
if (!$brand && $parent) $brand = get_post_meta($parent->get_id(), 'brand', true);
if (!$brand) $brand = get_bloginfo('name');
// Condition
$condition = 'new';
$row = [
$id,
gmc_trim_to($title, 150),
gmc_trim_to($desc, 5000),
$link,
$image,
$availability,
$price,
$brand,
$condition,
];
fputcsv($out, $row);
}
function gmc_trim_to($text, $max) {
if (mb_strlen($text) <= $max) return $text;
return rtrim(mb_substr($text, 0, $max - 1)) . '…';
}