Mini cart blank fix
August 21, 2025 · AI Conversations
# Quick checks (2 mins)
1. Open the product page (your `probo_configurator` type) → DevTools → **Console** + **Network**.
2. In **Network**, filter by **`/wp-json/wc/store/v1/cart`** (and `/items`).
– If you see **500**, click it → “Response” to confirm a PHP error. That’s why the modal is blank.
3. If status is 200 but still blank, check **Console** for a JS error (often from scripts enqueued only for this product type).
# Common causes & concrete fixes
## A) Product type isn’t wired to core add-to-cart flow
Blocks don’t need `get_refreshed_fragments`, but your type must behave like a real Woo product.
**1) Map the custom type to a class**
add_filter('woocommerce_product_class', function ($classname, $product_type) {
if ($product_type === 'probo_configurator') {
return 'WC_Product_Probo_Configurator';
}
return $classname;
}, 10, 2);
**2) Minimal product class**
class WC_Product_Probo_Configurator extends WC_Product {
protected $product_type = 'probo_configurator';
public function get_type() { return 'probo_configurator'; }
public function is_purchasable() { return true; }
public function is_sold_individually() { return false; } // or true as needed
public function supports( $feature ) {
if ( $feature === 'ajax_add_to_cart' ) return true;
return parent::supports( $feature );
}
public function add_to_cart_text() { return __('Add to cart', 'your-textdomain'); }
public function single_add_to_cart_text() { return __('Configure & add', 'your-textdomain'); }
}
**3) Hook the add-to-cart template for your type**
If you don’t have a button, Blocks cart can still open, but many themes/scripts assume this hook exists:
add_action('woocommerce_probo_configurator_add_to_cart', 'woocommerce_simple_add_to_cart', 30);
Then create `woocommerce/single-product/add-to-cart/probo_configurator.php` (can reuse simple product template to start).
## B) Store API cart item data contains non-serializable fields
If you add custom data via `woocommerce_add_cart_item_data`, **ensure only scalars/arrays** (no objects/resources/closures). Bad shapes cause `/wc/store/v1/cart` to 500.
**Safe add-to-cart data**
add_filter('woocommerce_add_cart_item_data', function($cart_item_data, $product_id, $variation_id) {
// Only scalars/arrays
if (!empty($_POST['probo_payload'])) {
$payload = json_decode(stripslashes($_POST['probo_payload']), true);
$cart_item_data['probo_config'] = is_array($payload) ? $payload : [];
}
return $cart_item_data;
}, 10, 3);
**Expose only serializable data to Store API (Blocks)**
// Define schema for your cart item extension so Blocks knows how to render it
add_action('woocommerce_store_api_register_endpoint_data', function($endpoints) {
$endpoints->register_endpoint_data([
'endpoint' => AutomatticWooCommerceStoreApiRoutesV1CartItems::IDENTIFIER,
'namespace' => 'probo',
'schema_callback' => function() {
return [
'probo_config' => [
'description' => __('Configurator data', 'td'),
'type' => 'object',
'context' => ['view', 'edit'],
'readonly' => true,
],
];
},
'data_callback' => function($cart_item) {
$data = $cart_item->get_data();
return [
'probo_config' => isset($data['probo_config']) ? $data['probo_config'] : null,
];
},
'schema_type' => 'cart-item',
]);
}, 10);
> Tip: If you’re not using any custom cart data, comment out your `woocommerce_add_cart_item_data`/`woocommerce_get_item_data` hooks and reload. If the modal starts working, the issue is your cart item payload.
## C) PHP fatal only on this product template
A small fatal (e.g., using `$product->get_price_html()` on a product with no price yet) can kill the page footer and break Blocks hydration.
– Check your **single-product template for `probo_configurator`**.
– Make sure you don’t `exit`/`wp_die()` in any AJAX handler used on that page.
– Ensure `wp_footer()` is present (Blocks modal scripts need it).
## D) Scripts/styles enqueued for the custom type are breaking the modal
A JS error anywhere can stop the Mini Cart modal from rendering.
– Only enqueue your configurator scripts on that product type:
add_action('wp_enqueue_scripts', function () {
if (is_product()) {
global $product;
if ($product && $product->get_type() === 'probo_configurator') {
wp_enqueue_script('probo-configurator', plugin_dir_url(__FILE__).'assets/probo.js', ['wp-element'], '1.0', true);
}
}
});
– Avoid redefining `window.wp`, `React`, etc. Don’t mess with `wcBlocks*` globals.
## E) Product has no price/tax class → Store API rejects line items
Even if you compute price later, give Woo a valid price so the Store API can render cart lines.
add_filter('woocommerce_product_get_price', function($price, $product){
if ($product->get_type() === 'probo_configurator' && $price === '') {
return '0'; // or a base price
}
return $price;
}, 10, 2);
# What to do right now
1. Hit the broken page → check `/wp-json/wc/store/v1/cart` status.
2. If **500**, check `wp-content/debug.log`. Likely a cart-item data shape or template fatal.
3. Implement A(1–3) to ensure your type is first-class.
4. If you add custom cart data, implement **B** exactly as above (only serializable data + schema).
5. Confirm **wp_footer()** and remove any `wp_die()`/premature exits.
6. Re-test the modal.
If you paste your `WC_Product_Probo_Configurator` class and your `add_to_cart`/cart-data filters here, I’ll pinpoint the exact line to fix.
if ( ! class_exists( 'Probo_Product_Configurable' ) ) {
class Probo_Product_Configurable extends WC_Product {
public function get_type() {
return 'probo_configurable';
}
}
}
# 1) Map your product type to the class
// Tell Woo which class to use for your custom type
add_filter('woocommerce_product_class', function ($classname, $product_type) {
if ($product_type === 'probo_configurable') {
return 'Probo_Product_Configurable';
}
return $classname;
}, 10, 2);
# 2) Register the type in admin (optional but nice)
add_filter('product_type_selector', function($types){
$types['probo_configurable'] = __('Probo Configurable', 'td');
return $types;
});
# 3) Flesh out the product class (purchasable + ajax + price)
if ( ! class_exists( 'Probo_Product_Configurable' ) ) {
class Probo_Product_Configurable extends WC_Product {
protected $product_type = 'probo_configurable';
public function get_type() { return 'probo_configurable'; }
// Blocks expect a normal purchasable product
public function is_purchasable() { return true; }
public function is_sold_individually() { return false; } // change if needed
// Let AJAX add-to-cart work like simple products
public function supports( $feature ) {
if ( $feature === 'ajax_add_to_cart' ) return true;
return parent::supports( $feature );
}
// Texts (optional)
public function add_to_cart_text() { return __('Add to cart', 'td'); }
public function single_add_to_cart_text() { return __('Configure & add', 'td'); }
}
}
### Safety net: ensure a numeric price exists
Blocks’ Store API may reject line items with empty price.
add_filter('woocommerce_product_get_price', function($price, $product){
if ($product instanceof Probo_Product_Configurable && ($price === '' || $price === null)) {
return '0'; // base price; replace with your logic
}
return $price;
}, 10, 2);
# 4) Add the add‑to‑cart template for your type
Create: `yourtheme/woocommerce/single-product/add-to-cart/probo_configurable.php`
Start simple by reusing the simple product form:
<?php
defined('ABSPATH') || exit;
// You can copy Woo's simple.php and adjust labels,
// or just include it directly if you don’t need special fields:
wc_get_template( 'single-product/add-to-cart/simple.php' );
And make sure your single product template ultimately outputs `wp_footer()` (many themes do; just don’t remove it).
# 5) (If you attach custom config to cart) keep cart item data serializable
A common reason the **Blocks mini‑cart modal shows blank** is a 500 on `/wp-json/wc/store/v1/cart` caused by **objects/closures** stored in cart item data.
add_filter('woocommerce_add_cart_item_data', function($cart_item_data, $product_id, $variation_id){
if (!empty($_POST['probo_payload'])) {
$payload = json_decode(stripslashes($_POST['probo_payload']), true);
$cart_item_data['probo_config'] = is_array($payload) ? $payload : [];
}
return $cart_item_data;
}, 10, 3);
(Only arrays/scalars—no objects/resources.)
Optional (lets Store API expose your extra data cleanly):
add_action('woocommerce_store_api_register_endpoint_data', function($endpoints){
$endpoints->register_endpoint_data([
'endpoint' => AutomatticWooCommerceStoreApiRoutesV1CartItems::IDENTIFIER,
'namespace' => 'probo',
'schema_callback' => function() {
return [
'probo_config' => [
'description' => __('Configurator data','td'),
'type' => 'object',
'context' => ['view'],
'readonly' => true,
],
];
},
'data_callback' => function($cart_item){
$data = $cart_item->get_data();
return ['probo_config' => $data['probo_config'] ?? null];
},
'schema_type' => 'cart-item',
]);
});
# 6) Targeted script enqueue (avoid JS collisions)
add_action('wp_enqueue_scripts', function(){
if (is_product()) {
global $product;
if ($product && $product->get_type() === 'probo_configurable') {
wp_enqueue_script('probo-configurator', plugin_dir_url(__FILE__).'assets/probo.js', ['wp-element'], '1.0', true);
}
}
});
# 7) Quick diagnostic (do once)
– Open the broken product page → DevTools → **Network** → check `/wp-json/wc/store/v1/cart`.
– If it’s **500**, your PHP error log will point to either the class/price or cart item data.
– Also check **Console** for JS errors from configurator scripts.
—
Implement the 1–4 steps first (mapping, registration, class features, template). If it still blanks, add 5 (cart data shape).
If you share any error from `/wp-json/wc/store/v1/cart` “Response” tab or `debug.log`, I’ll spot the exact line to change.
wp_enqueue_script('probo-configurator', PROBO_PLUGIN_URL . 'assets/src/probo-product-configurator.js', array('jquery'), PROBO_PLUGIN_VERSION, true);
Causign the error when I comment this mini cart working fine
## 1) Enqueue only on your custom type (prevent collateral damage)
add_action('wp_enqueue_scripts', function () {
if (is_product()) {
global $product;
if ($product && $product->get_type() === 'probo_configurable') {
wp_enqueue_script(
'probo-configurator',
PROBO_PLUGIN_URL . 'assets/src/probo-product-configurator.js',
['jquery'], // add more deps only if you actually use them
PROBO_PLUGIN_VERSION,
true
);
}
}
}, 20);
## 2) If your JS has `import`/`export` or top‑level `await`
Your file is an ES Module but WP enqueues it as a classic script → syntax error → Mini‑Cart dies.
**Option A (fast):** mark it as a module at render time.
add_filter('script_loader_tag', function ($tag, $handle, $src) {
if ('probo-configurator' === $handle) {
return "<script type='module' src='" . esc_url($src) . "'></script>";
}
return $tag;
}, 10, 3);
**Option B (preferred):** rebuild to an IIFE/UMD bundle (no import/exports), then you don’t need `type="module"`.
## 3) Wrap the JS in a safe, noConflict IIFE + hard guards
Most Mini‑Cart breakages are from uncaught errors or leaking globals.
// probo-product-configurator.js
(function (window, document, $, undefined) {
'use strict';
function safeInit() {
// Only run on our product type page
var root = document.querySelector('[data-probo-configurator]');
if (!root) return;
// Example: guard DOM lookups & network calls
try {
// your init code here...
// DO NOT override window.fetch/Promise or wc/wcBlocks globals
} catch (e) {
console.error('[Probo] init failed:', e);
}
}
// jQuery noConflict safe
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', safeInit);
} else {
safeInit();
}
})(window, document, jQuery);
**Important do-nots (common culprits):**
– Don’t redefine globals like `window.React`, `wp`, `wc`, `wcSettings`, `wcBlocks*`.
– Don’t polyfill/replace `window.fetch`/`Promise`/`URL` on this page.
– Don’t attach catch‑all handlers like `$(document).on('click', '*', …)` with `stopImmediatePropagation()`; scope to your UI.
– If you add listeners on `.add_to_cart_button`, avoid blocking default events unless you must (no global `preventDefault`).
– Avoid `document.write`, and avoid syntax that requires a bundler (top‑level `import`/`export`) unless you set `type="module"`.
## 4) Add dependencies only if you use them
If your code calls any WP packages, declare them as dependencies so load order is correct:
// Example only if you truly use them:
$deps = ['jquery']; // add 'wp-element', 'wp-hooks', 'wp-i18n' etc. as needed
wp_enqueue_script('probo-configurator', PROBO_PLUGIN_URL . 'assets/src/probo-product-configurator.js', $deps, PROBO_PLUGIN_VERSION, true);
## 5) Quick triage checklist (do once)
– Open Console on the broken page → fix the **first** error shown (usually a syntax/runtime error from your file).
– If you see “Unexpected token ‘export’ / ‘import’ / ‘await’” → apply **#2**.
– If you see “$ is not a function” → you didn’t use noConflict wrapper → apply **#3**.
– If errors mention `wc`, `wp`, `React`, you’re colliding with Blocks → remove those redefinitions.
—
If you paste the **first console error line** (message + file:line), I can give you the exact one‑line fix.
add_action('wp_enqueue_scripts', function () {
if (is_product()) {
global $product;
if ($product && $product->get_type() === 'probo_configurable') {
wp_enqueue_script(
'probo-configurator',
PROBO_PLUGIN_URL . 'assets/src/probo-product-configurator.js',
['jquery'], // add more deps only if you actually use them
PROBO_PLUGIN_VERSION,
true
);
}
}
}, 20);
# Do these 4 fixes
## 1) If the file has `import`/`export` (ESM), load it as a module
Otherwise you’ll get “Unexpected token ‘export/import’” and the mini‑cart dies.
// Keep your existing enqueue...
// Add this filter once:
add_filter('script_loader_tag', function ($tag, $handle, $src) {
if ($handle === 'probo-configurator') {
// Load as ES module
return "<script type='module' src='" . esc_url($src) . "'></script>";
}
return $tag;
}, 10, 3);
**OR (preferred)**: bundle to a classic IIFE/UMD build and enqueue the **built** file instead of `/src/`:
wp_enqueue_script(
'probo-configurator',
PROBO_PLUGIN_URL . 'assets/dist/probo-product-configurator.bundle.js',
['jquery'], PROBO_PLUGIN_VERSION, true
);
## 2) Wrap your JS so errors don’t bubble and only run on your page
Edit `probo-product-configurator.js`:
// probo-product-configurator.js
(function (window, document, $, undefined) {
'use strict';
// run only on our product type page
function isProboPage() {
// add this attribute in your PHP single-product template for probo type
return document.body.hasAttribute('data-probo-configurable');
}
function init() {
if (!isProboPage()) return;
try {
// === your configurator code here ===
// Example: never hijack global add-to-cart clicks for other products
$(document)
.off('click.probo') // prevent duplicates
.on('click.probo', '.single_add_to_cart_button', function (e) {
const $form = $(this).closest('form.cart');
const isProbo = $form.length && $form.data('product_type') === 'probo_configurable';
if (!isProbo) return; // don't touch other product types
// ...your logic...
});
} catch (err) {
console.error('[Probo] init failed:', err);
// don’t rethrow; let Woo Blocks continue
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})(window, document, jQuery);
In your PHP for this product type, add the marker and form data:
add_action('wp', function () {
if (is_product()) {
global $product;
if ($product && $product->get_type() === 'probo_configurable') {
add_filter('body_class', fn($c) => array_merge($c, ['probo-configurable']));
add_action('woocommerce_before_add_to_cart_form', function () use ($product) {
echo '<script>document.body.setAttribute("data-probo-configurable","1");</script>';
}, 5);
}
}
});
(Or output `data-product_type="probo_configurable"` on your “.)
## 3) Don’t collide with Woo/Blocks globals
Common silent breakers:
– Do **not** redefine `window.wp`, `window.wc`, `wcSettings`, `wcBlocks*`, `React`, or polyfill/replace `window.fetch` or `Promise`.
– Don’t attach global “catch‑all” handlers that call `stopImmediatePropagation()` everywhere.
– Only `preventDefault()` for **your** product’s add‑to‑cart, not globally.
If you currently do something like:
$(document).on('click', '.single_add_to_cart_button', e => { e.preventDefault(); ... });
replace with the **scoped** version shown in #2 that checks the form’s product type.
## 4) Declare real dependencies (load order)
If your JS actually uses any WP packages, declare them so they’re loaded first:
$deps = ['jquery']; // add only if used: 'wp-i18n','wp-hooks','wp-element','wp-data'
wp_enqueue_script('probo-configurator', PROBO_PLUGIN_URL . 'assets/dist/probo-product-configurator.bundle.js', $deps, PROBO_PLUGIN_VERSION, true);
—
### Quick test
1) Open Console on the Probo product page and reload.
2) If you still see a syntax error about `import/export`, use Fix #1 (module) or switch to the built bundle.
3) If errors mention `wp`, `wc`, or `React`, remove any redefinitions.
4) Once the console is clean, the mini‑cart modal will render normally.
If you drop the **first console error message (text + line)** here, I’ll give an exact one‑line fix.
in jquery when status is null then show progress instead of null
You just need a small condition in your jQuery callback:
$.ajax({
url: ajaxurl,
type: "POST",
data: { action: "your_action" },
dataType: "json",
success: function (res) {
if (res.success) {
var status = res.status;
if (status === null || status === "") {
$("#statusBox").text("Progress...");
} else {
$("#statusBox").text(status);
}
} else {
$("#statusBox").text("Error");
}
},
error: function () {
$("#statusBox").text("Ajax failed");
}
});
👉 Replace `#statusBox` with your target element.
Now whenever PHP returns `status: null`, the UI will show **“Progress…”** instead of a blank/null.
Do you want me to also make it **auto-updating** (keep polling until status is no longer null), or just a one-time “Progress…” message?
– builds the XML
– saves it under `wp-content/uploads/orderxmls/`
– then immediately forces a **download** (`Content-Disposition: attachment`)
I also:
– sanitized the incoming IDs
– used a prepared `IN` query
– ensured the target folder exists
– avoided fatal header issues by exiting right after download
<?php
define('WP_USE_THEMES', false);
require_once 'wp-load.php';
/**
* Create XML and return its full file path.
*/
function createOrderXMLfileroot(array $ids, string $ponumber, string $xmlFileName) : string {
global $wpdb;
// 1) Safe upload path
$upload_dir = wp_upload_dir();
$dir = trailingslashit($upload_dir['basedir']) . 'orderxmls/';
if ( ! file_exists($dir) ) {
wp_mkdir_p($dir);
}
$filePath = $dir . $xmlFileName;
// 2) Sanitize IDs and build a prepared IN() query
$ids = array_values(array_filter(array_map('absint', $ids)));
if (empty($ids)) {
throw new RuntimeException('No valid IDs provided.');
}
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
// One row (first id) for header dates
$userDetails = $wpdb->get_row(
$wpdb->prepare("SELECT * FROM klGjgz_inventories WHERE id = %d", $ids[0])
);
// Your fixed user (69). Replace if you need dynamic user.
$user_id = 69;
$customer = get_userdata($user_id);
if (!$customer) {
throw new RuntimeException('Customer not found.');
}
$account_number = get_user_meta($user_id, 'account_number', true );
$InvoiceToOrg = get_user_meta($user_id, 'customer_invtoorg', true );
$ShipFromOrg = get_user_meta($user_id, 'customer_shipfromorg', true );
$ShipToOrg = get_user_meta($user_id, 'customer_shiptoorg', true );
$cname = trim(implode(' ', array_filter([$customer->first_name, $customer->last_name])));
// 3) Build XML
$dom = new DOMDocument('1.0', 'utf-8');
$dom->formatOutput = true;
$root = $dom->createElement('SalesOrder');
$header = $dom->createElement('Header');
$header->appendChild($dom->createElement('CustomerAccount', (string)$account_number));
$header->appendChild($dom->createElement('CustomerName', $cname));
$header->appendChild($dom->createElement('OrderSourceReference', $ponumber . '_' . date('His')));
$header->appendChild($dom->createElement('ShipFromOrg', (string)$ShipFromOrg));
$header->appendChild($dom->createElement('ShipToOrg', (string)$ShipToOrg));
$header->appendChild($dom->createElement('InvoiceToOrg', (string)$InvoiceToOrg));
$header->appendChild($dom->createElement('CreationDate', date('Y-m-d')));
$header->appendChild($dom->createElement('CreationTime', date('His')));
// Fallback if created_at is missing
$created_at_ts = $userDetails && !empty($userDetails->created_at) ? strtotime($userDetails->created_at) : time();
$header->appendChild($dom->createElement('ScanDate', date('Y-m-d', $created_at_ts)));
$header->appendChild($dom->createElement('ScanTime', date('His', $created_at_ts)));
$root->appendChild($header);
$lineItemsXml = $dom->createElement('LineItems');
// 4) Fetch all line items safely
$sql = "SELECT * FROM klGjgz_inventories WHERE id IN ($placeholders)";
$lineItems = $wpdb->get_results($wpdb->prepare($sql, $ids));
foreach ($lineItems as $item) {
$BinID = !empty($item->orderid) ? $item->orderid : 'NA';
$itemXml = $dom->createElement('LineItem');
$itemXml->appendChild($dom->createElement('OrigSysLineRef', (string)$item->id));
$itemXml->appendChild($dom->createElement('LineNumber', (string)$item->id));
$itemXml->appendChild($dom->createElement('InventoryItem', (string)$item->sku));
$itemXml->appendChild($dom->createElement('OrderedQuantity', (string)$item->quantity));
$itemXml->appendChild($dom->createElement('UnitListPrice', 'NA'));
// Strip backslashes in description safely
$desc = isset($item->description) ? str_replace("\", "", (string)$item->description) : '';
$itemXml->appendChild($dom->createElement('ItemDescription', $desc));
$itemXml->appendChild($dom->createElement('Notes', (string)$BinID));
$lineItemsXml->appendChild($itemXml);
}
$root->appendChild($lineItemsXml);
$dom->appendChild($root);
// 5) Save
if ($dom->save($filePath) === false) {
throw new RuntimeException('Failed to save XML file.');
}
return $filePath;
}
/**
* Force the browser to download the XML file.
*/
function force_download_xml(string $filePath, string $downloadName = null) : void {
if (!file_exists($filePath)) {
status_header(404);
wp_die('File not found.');
}
if ($downloadName === null) {
$downloadName = basename($filePath);
}
// Clean output buffering to avoid corrupting the download
if (ob_get_length()) { @ob_end_clean(); }
header('Content-Description: File Transfer');
header('Content-Type: application/xml; charset=utf-8');
header('Content-Disposition: attachment; filename="' . rawurldecode($downloadName) . '"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($filePath));
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: public');
header('Expires: 0');
readfile($filePath);
exit;
}
/* ==== Run it ==== */
// Example inputs
$poname = str_replace(' ', '_', sanitize_text_field('Craig 20/8'));
$xmlFileName = "eVMI_evmi_5933_69_{$poname}_20250820011234.xml";
$posts = [5933,5935,5936,5937,5939,5940,5945,5946,5963]; // IDs
try {
$path = createOrderXMLfileroot($posts, 'Craig 20/8', $xmlFileName);
force_download_xml($path, $xmlFileName);
} catch (Throwable $e) {
status_header(500);
wp_die('Error: ' . esc_html($e->getMessage()));
}
**Notes**
– This script is meant to be hit directly (like `yourdomain.com/path/to/generate-xml.php`). Don’t echo anything before the headers.
– If you need this behind a WP page/endpoint, wrap the “Run it” part in a permission check (e.g., only logged‑in users) and call it from a custom route or admin action.
Here’s how you can adapt your code at the bottom:
<?php
define( 'WP_USE_THEMES', false );
require_once( 'wp-load.php' );
function createOrderXMLfileroot($ids,$ponumber,$xmlFileName){
global $wpdb;
$userDetails = $wpdb->get_row( "SELECT * FROM klGjgz_inventories WHERE id='".intval($ids[0])."'" );
$customer = get_userdata(69);
$account_number = get_user_meta(69, 'account_number', true );
$InvoiceToOrg = get_user_meta(69, 'customer_invtoorg', true );
$ShipFromOrg = get_user_meta(69, 'customer_shipfromorg', true );
$ShipToOrg = get_user_meta(69, 'customer_shiptoorg', true );
$cname = trim($customer->first_name . ' ' . $customer->last_name);
$dom = new DOMDocument('1.0', 'utf-8');
$dom->formatOutput = true;
$root = $dom->createElement('SalesOrder');
$header = $dom->createElement('Header');
$header->appendChild($dom->createElement('CustomerAccount', $account_number));
$header->appendChild($dom->createElement('CustomerName', $cname));
$header->appendChild($dom->createElement('OrderSourceReference', $ponumber.'_'. date('His')));
$header->appendChild($dom->createElement('ShipFromOrg', $ShipFromOrg));
$header->appendChild($dom->createElement('ShipToOrg', $ShipToOrg));
$header->appendChild($dom->createElement('InvoiceToOrg', $InvoiceToOrg));
$header->appendChild($dom->createElement('CreationDate', date('Y-m-d')));
$header->appendChild($dom->createElement('CreationTime', date('His')));
$header->appendChild($dom->createElement('ScanDate', date('Y-m-d', strtotime($userDetails->created_at))));
$header->appendChild($dom->createElement('ScanTime', date('His', strtotime($userDetails->created_at))));
$root->appendChild($header);
$lineItemsXml = $dom->createElement('LineItems');
$lineItems = $wpdb->get_results("SELECT * FROM klGjgz_inventories WHERE id in (".implode(",",$ids).")");
foreach ($lineItems as $item) {
$BinID = $item->orderid ?: 'NA';
$itemXml = $dom->createElement('LineItem');
$itemXml->appendChild($dom->createElement('OrigSysLineRef', $item->id));
$itemXml->appendChild($dom->createElement('LineNumber', $item->id));
$itemXml->appendChild($dom->createElement('InventoryItem', $item->sku));
$itemXml->appendChild($dom->createElement('OrderedQuantity', $item->quantity));
$itemXml->appendChild($dom->createElement('UnitListPrice', 'NA'));
$itemXml->appendChild($dom->createElement('ItemDescription', str_replace("\","",$item->description)));
$itemXml->appendChild($dom->createElement('Notes', $BinID));
$lineItemsXml->appendChild($itemXml);
}
$root->appendChild($lineItemsXml);
$dom->appendChild($root);
// Instead of saving to file, return XML string
return $dom->saveXML();
}
// ==== Run ====
$poname = str_replace(" ","_",sanitize_text_field('Craig 20/8'));
$xmlFileName = "eVMI_evmi_5933_69_".$poname."_20250820011234.xml";
$posts = [5933,5935,5936,5937,5939,5940,5945,5946,5963];
// Set content type to XML
header('Content-Type: application/xml; charset=utf-8');
// Output XML directly
echo createOrderXMLfileroot($posts,'Craig 20/8',$xmlFileName);
exit;
### 🔑 Key changes:
– Removed file saving (`$dom->save($filePath)`)
– Used `$dom->saveXML()` to return XML as string
– Added `header('Content-Type: application/xml')`
– Echoed the XML so it displays nicely in browser
👉 Now when you load this PHP file in the browser, the raw XML will be displayed instead of being downloaded.
Do you want me to also pretty-print it in the browser (with syntax highlighting), or just raw XML output?