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)
__( 'Feeds', 'advanced-custom-feeds-for-woocommerce' ),
'settings' => __( 'Settings', 'advanced-custom-feeds-for-woocommerce' ),
'info' => __( 'FAQ & Info', 'advanced-custom-feeds-for-woocommerce' ),
);
foreach ( $tabs as $t_slug => $label ) {
$url = esc_url( admin_url( 'admin.php?page=acfw-settings&tab=' . $t_slug ) );
$cls = $tab === $t_slug ? 'nav-tab nav-tab-active' : 'nav-tab';
echo '' . esc_html( $label ) . '';// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped above
}
?>
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.
| Name | Slug | Format | Actions |
get_feed_file_path($slug,$format);
$url = $this->get_feed_file_url($slug,$format);
?>
|
|
|
|
General Settings
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.