array(), // store feed definitions keyed by slug 'general' => array( 'include_out_of_stock' => 'no', 'enable_cache' => 'yes', 'cache_expiry' => 3600, 'max_additional_images' => 3, ), ); if ( false === get_option( ACFW_OPTION_KEY ) ) { update_option( ACFW_OPTION_KEY, $defaults ); } } public function enqueue_admin_assets( $hook ) { if ( strpos( $hook, 'acfw-settings' ) === false && $hook !== 'toplevel_page_acfw-settings' ) return; wp_enqueue_style( 'acfw-admin', ACFW_PLUGIN_URL . 'assets/admin.css', array(), ACFW_VERSION ); } public function register_menu() { add_menu_page( 'ACFW Feeds', 'ACFW Feeds', 'manage_options', 'acfw-settings', array( $this, 'settings_page' ), 'dashicons-alt' ); } private function get_options() { $opts = get_option( ACFW_OPTION_KEY, array() ); return wp_parse_args( $opts, array( 'feeds' => array(), 'general' => array( 'include_out_of_stock' => 'no', 'enable_cache' => 'yes', 'cache_expiry' => 3600, 'max_additional_images' => 3, ), ) ); } public function maybe_handle_actions() { if ( ! current_user_can( 'manage_options' ) ) return; // handle create/update feed if ( isset( $_POST['acfw_save_feed'] ) ) { check_admin_referer( 'acfw_save_feed_action', 'acfw_nonce' ); $this->save_feed_from_post(); wp_safe_redirect( admin_url( 'admin.php?page=acfw-settings&tab=feeds&saved=1' ) ); exit; } // handle generate feed if ( isset( $_POST['acfw_generate_feed'] ) ) { check_admin_referer( 'acfw_generate_feed_action', 'acfw_nonce' ); // feed_slug sanitized via sanitize_title ensuring safe key for file naming. $slug = isset( $_POST['feed_slug'] ) ? sanitize_title( wp_unslash( $_POST['feed_slug'] ) ) : ''; $format = isset( $_POST['feed_format'] ) ? sanitize_key( wp_unslash( $_POST['feed_format'] ) ) : 'xml'; if ( ! in_array( $format, array( 'xml','csv' ), true ) ) { $format = 'xml'; } if ( $slug ) { $this->generate_feed( $slug, $format ); } wp_safe_redirect( admin_url( 'admin.php?page=acfw-settings&tab=feeds&generated=' . rawurlencode( $slug ) . '&format=' . $format ) ); exit; } // handle clear cache if ( isset( $_POST['acfw_clear_cache'] ) ) { check_admin_referer( 'acfw_clear_cache_action', 'acfw_nonce' ); $slug = isset( $_POST['feed_slug'] ) ? sanitize_title( wp_unslash( $_POST['feed_slug'] ) ) : ''; if ( $slug ) { $this->clear_feed_cache( $slug ); } wp_safe_redirect( admin_url( 'admin.php?page=acfw-settings&tab=feeds&cleared=' . rawurlencode( $slug ) ) ); exit; } // handle general settings save if ( isset( $_POST['acfw_save_general'] ) ) { check_admin_referer( 'acfw_save_general_action', 'acfw_nonce' ); $opts = $this->get_options(); $g = $opts['general']; $g['include_out_of_stock'] = isset( $_POST['include_out_of_stock'] ) ? 'yes' : 'no'; $g['enable_cache'] = isset( $_POST['enable_cache'] ) ? 'yes' : 'no'; $g['cache_expiry'] = isset( $_POST['cache_expiry'] ) ? intval( wp_unslash( $_POST['cache_expiry'] ) ) : $g['cache_expiry']; $g['max_additional_images'] = isset( $_POST['max_additional_images'] ) ? intval( wp_unslash( $_POST['max_additional_images'] ) ) : $g['max_additional_images']; $opts['general'] = $g; update_option( ACFW_OPTION_KEY, $opts ); wp_safe_redirect( admin_url( 'admin.php?page=acfw-settings&tab=settings&saved=1' ) ); exit; } } private function save_feed_from_post() { $opts = $this->get_options(); $name = isset( $_POST['feed_name'] ) ? sanitize_text_field( wp_unslash( $_POST['feed_name'] ) ) : ''; $slug_raw = isset( $_POST['feed_slug'] ) ? wp_unslash( $_POST['feed_slug'] ) : ''; $slug = $slug_raw !== '' ? sanitize_title( $slug_raw ) : sanitize_title( $name ); $fmt_raw = isset( $_POST['feed_format'] ) ? sanitize_key( wp_unslash( $_POST['feed_format'] ) ) : 'xml'; $format = in_array( $fmt_raw, array( 'xml','csv' ), true ) ? $fmt_raw : 'xml'; if ( ! $name || ! $slug ) { return; // invalid } $opts['feeds'][ $slug ] = array( 'name' => $name, 'slug' => $slug, 'format' => $format, 'created' => time(), ); update_option( ACFW_OPTION_KEY, $opts ); } public function settings_page() { $opts = $this->get_options(); $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'feeds'; ?>

Advanced Custom Feeds for WooCommerce (ACFW)

get_feed_file_path( $slug, $format ); $url = $this->get_feed_file_url( $slug, $format ); if ( file_exists( $file ) ) { // translators: %s is the feed slug (already escaped and wrapped in tags). echo '

' . sprintf( esc_html__( 'Feed generated: %s', 'advanced-custom-feeds-for-woocommerce' ), '' . esc_html( $slug ) . '' ) . '

'; echo '

' . esc_html__( 'View feed', 'advanced-custom-feeds-for-woocommerce' ) . ' ' . esc_html__( 'Feed URL:', 'advanced-custom-feeds-for-woocommerce' ) . ' ' . esc_html( $url ) . '

';// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } else { echo '

' . esc_html__( 'Feed generation failed or file missing.', 'advanced-custom-feeds-for-woocommerce' ) . '

'; } endif; ?>

Create / Edit Feed


Existing Feeds

No feeds configured yet.

get_feed_file_path($slug,$format); $url = $this->get_feed_file_url($slug,$format); ?>
NameSlugFormatActions

General Settings

Include out of stock products >
Enable cache >
Cache expiry time (seconds)
Maximum additional images per product

FAQ & Plugin Info

What does this plugin do?

Generates XML or CSV product feeds for your WooCommerce store. Configure multiple feeds, generate them on demand and serve them via a URL.

Where are feeds stored?

Feeds are stored in wp-content/uploads/acfw-feeds/ and are accessible publicly unless your server blocks access.

Support

Author: Pranav MT

get_feed_dir() . $slug . '.' . $format; } private function get_feed_file_url( $slug, $format ) { $upload = wp_upload_dir(); return trailingslashit( $upload['baseurl'] ) . 'acfw-feeds/' . $slug . '.' . $format; } private function clear_feed_cache( $slug ) { foreach ( array( 'xml', 'csv' ) as $fmt ) { $file = $this->get_feed_file_path( $slug, $fmt ); if ( file_exists( $file ) ) { wp_delete_file( $file ); } } } private function generate_feed( $slug, $format = 'xml' ) { // generate and save feed file $opts = $this->get_options(); $g = $opts['general']; $include_oos = ( $g['include_out_of_stock'] === 'yes' ); $max_images = max( 0, intval( $g['max_additional_images'] ) ); // respect caching: if enabled and file recent, do nothing $file = $this->get_feed_file_path( $slug, $format ); $use_cache = ( $g['enable_cache'] === 'yes' ); if ( $use_cache && file_exists( $file ) ) { $age = time() - filemtime( $file ); if ( $age < intval( $g['cache_expiry'] ) ) return; // keep existing file } // prepare WP_Query for products if ( ! class_exists( 'WooCommerce' ) && ! post_type_exists( 'product' ) ) { // no products, write empty feed $content = $this->render_empty_feed( $format ); file_put_contents( $file, $content ); return; } // Potentially heavy queries: meta_query and tax_query can be filtered for optimization if needed. $meta_query = WC()->query ? WC()->query->get_meta_query() : array(); $tax_query = array(); $args = array( 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish', 'meta_query' => $meta_query, 'tax_query' => $tax_query, ); if ( ! $include_oos ) { // only in-stock products $args['meta_query'][] = array( 'key' => '_stock_status', 'value' => 'instock', ); } $q = new WP_Query( $args ); $rows = array(); while ( $q->have_posts() ) { $q->the_post(); $product = wc_get_product( get_the_ID() ); if ( ! $product ) continue; $row = array( 'id' => $product->get_id(), 'title' => $product->get_name(), 'sku' => $product->get_sku(), 'price' => $product->get_price(), 'url' => $product->get_permalink(), 'image' => wp_get_attachment_url( $product->get_image_id() ), 'stock_status' => $product->is_in_stock() ? 'instock' : 'outofstock', ); // additional images $gallery = $product->get_gallery_image_ids(); $extra = array(); if ( ! empty( $gallery ) && $max_images > 0 ) { $taken = array_slice( $gallery, 0, $max_images ); foreach ( $taken as $att_id ) { $extra[] = wp_get_attachment_url( $att_id ); } } $row['additional_images'] = $extra; $rows[] = $row; } wp_reset_postdata(); if ( $format === 'csv' ) { $content = $this->render_csv( $rows ); } else { $content = $this->render_xml( $rows ); } file_put_contents( $file, $content ); } private function render_empty_feed( $format ) { if ( $format === 'csv' ) return "id,title,sku,price,url,image,stock_status,additional_images\n"; $xml = new SimpleXMLElement( '' ); return $xml->asXML(); } private function render_csv( $rows ) { $headers = array( 'id','title','sku','price','url','image','stock_status','additional_images' ); $csv_lines = array(); $csv_lines[] = $this->csv_join( $headers ); foreach ( $rows as $r ) { $csv_lines[] = $this->csv_join( array( $r['id'], $r['title'], $r['sku'], $r['price'], $r['url'], $r['image'], $r['stock_status'], implode( '|', $r['additional_images'] ), ) ); } return implode( "\r\n", $csv_lines ) . "\r\n"; // final newline } private function csv_join( $fields ) { $escaped = array(); foreach ( $fields as $field ) { $field = (string) $field; $needs_quotes = ( strpos( $field, ',' ) !== false || strpos( $field, '"' ) !== false || strpos( $field, "\n" ) !== false || strpos( $field, "\r" ) !== false ); $field = str_replace( '"', '""', $field ); if ( $needs_quotes ) { $field = '"' . $field . '"'; } $escaped[] = $field; } return implode( ',', $escaped ); } private function render_xml( $rows ) { $xml = new SimpleXMLElement( '' ); foreach ( $rows as $r ) { $p = $xml->addChild( 'product' ); $p->addChild( 'id', esc_html( $r['id'] ) ); $p->addChild( 'title', htmlspecialchars( $r['title'] ) ); $p->addChild( 'sku', htmlspecialchars( $r['sku'] ) ); $p->addChild( 'price', htmlspecialchars( $r['price'] ) ); $p->addChild( 'url', htmlspecialchars( $r['url'] ) ); $p->addChild( 'image', htmlspecialchars( $r['image'] ) ); $p->addChild( 'stock_status', htmlspecialchars( $r['stock_status'] ) ); if ( ! empty( $r['additional_images'] ) ) { $imgs = $p->addChild( 'additional_images' ); foreach ( $r['additional_images'] as $im ) { $imgs->addChild( 'image', htmlspecialchars( $im ) ); } } } // force header and formatting will be handled by file return $xml->asXML(); } } new ACFW_Plugin(); // Note: This single-file plugin is intentionally minimal and focuses on the requested features: // - Admin settings with tabs (Feeds / Settings / FAQ & Info) // - Save feed definition (name, slug, format) // - Generate feed (XML/CSV) stored under uploads/acfw-feeds/ // - Cache control via settings and clear cache per-feed // - View feed button and feed URL after generation // // To use: save this file as "acfw-advanced-custom-feeds-for-woocommerce.php" inside a folder named // "advanced-custom-feeds-for-woocommerce" in wp-content/plugins/. Then activate the plugin and go to // the ACFW Feeds admin page.