Mini cart blank fix

User
we have created custom woocommerce product type probo_configuraor but when open product page of this type then woocommerce default min cart icon block modal shoqwing blank for other roduct type it works fine
Assistant
Got it. The Mini Cart **(Blocks modal)** going blank on your custom product type usually means the Store API response for the cart is erroring *only* on that product page (JS sees a 500 → renders nothing). Here’s a tight checklist with fixes that solve this 99% of the time for custom types.

# 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.

User
we have this
if ( ! class_exists( 'Probo_Product_Configurable' ) ) {
class Probo_Product_Configurable extends WC_Product {
public function get_type() {
return 'probo_configurable';
}
}
}
Assistant
Thanks—your class is too minimal for WooCommerce Blocks. Make these **surgical additions** so the mini‑cart modal stops going blank on this product page.

# 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.

User
yes this js for probo api
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
Assistant
Great — that pinpoints it: the **probo-product-configurator.js** is throwing a JS error on that page and stopping Woo Blocks’ Mini‑Cart render. Here’s how to fix it quickly and safely.

## 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.

User
we have aleardy
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);
Assistant
Got it — your enqueue scoping is fine. The mini‑cart goes blank because `probo-product-configurator.js` throws a runtime/syntax error on that page, which stops Woo Blocks from rendering. Apply the hardening below (no guessing needed).

# 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.

User
we have return from php {success: true, status: null}
in jquery when status is null then show progress instead of null
Assistant
Got it ✅
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?

User
Assistant
Here’s a tightened, safer version of your script that:

– 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.

User
Assistant
👌 If you just want to **show the generated XML in the browser** (instead of forcing download), you don’t need the `Content-Disposition: attachment` headers. Just set the `Content-Type` to `application/xml` (or `text/xml`) and echo the XML.

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?

Leave a Comment

Your email address will not be published. Required fields are marked *