From 5393c936751ceeebe203b887ee07ebd2f326e7ef Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 18:27:30 +0530 Subject: [PATCH] fix(pos): escape item data on pos item selector (backport #55503) (#55508) Co-authored-by: Diptanil Saha fix(pos): escape item data on pos item selector (#55503) --- .../page/point_of_sale/pos_item_selector.js | 62 ++++++++++++------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 69ec1e56934..1da8e1e5d65 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -112,17 +112,37 @@ erpnext.PointOfSale.ItemSelector = class { render_item_list_column_header() { return `
-
Name
-
Price
-
UOM
-
Quantity Available
+
${__("Name")}
+
${__("Price")}
+
${__("UOM")}
+
${__("Quantity Available")}
`; } get_item_html(item) { const me = this; // eslint-disable-next-line no-unused-vars - const { item_image, serial_no, batch_no, barcode, actual_qty, uom, price_list_rate } = item; + function sanitize_item_data(item) { + return Object.fromEntries( + Object.entries(item).map(([key, value]) => [ + key, + typeof value === "string" ? frappe.utils.escape_html(value) : value, + ]) + ); + } + const sanitize_item = sanitize_item_data(item); + const { + item_code, + stock_uom, + item_name, + item_image, + serial_no, + batch_no, + barcode, + actual_qty, + uom, + price_list_rate, + } = sanitize_item; const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0; let indicator_color; let qty_to_display = actual_qty; @@ -149,29 +169,29 @@ erpnext.PointOfSale.ItemSelector = class { ${item.item_name} `; } else { return `
${qty_to_display}
-
${frappe.get_abbr(item.item_name)}
`; +
${frappe.get_abbr(item_name)}
`; } } return `
+ data-item-code="${item_code}" data-serial-no="${serial_no}" + data-batch-no="${batch_no}" data-uom="${uom}" + data-rate="${price_list_rate || 0}" + data-stock-uom="${stock_uom}" + title="${item_name}"> ${get_item_image_html()}
- ${!me.hide_images ? frappe.ellipsis(item.item_name, 18) : item.item_name} + ${!me.hide_images ? frappe.ellipsis(item_name, 18) : item_name}
${ !me.hide_images @@ -189,7 +209,7 @@ erpnext.PointOfSale.ItemSelector = class { } handle_broken_image($img) { - const item_abbr = $($img).attr("alt"); + const item_abbr = frappe.utils.escape_html($($img).attr("alt")); $($img).parent().replaceWith(`
${item_abbr}
`); } @@ -244,7 +264,7 @@ erpnext.PointOfSale.ItemSelector = class { set_item_selector_filter_label(value) { const $filter_label = this.$component.find(".label"); - $filter_label.html(value ? __(value) : __("All Items")); + $filter_label.html(value ? frappe.utils.escape_html(__(value)) : __("All Items")); } hide_open_link_btn() { @@ -329,12 +349,12 @@ erpnext.PointOfSale.ItemSelector = class { this.$component.on("click", ".item-wrapper", function () { const $item = $(this); - const item_code = unescape($item.attr("data-item-code")); - let batch_no = unescape($item.attr("data-batch-no")); - let serial_no = unescape($item.attr("data-serial-no")); - let uom = unescape($item.attr("data-uom")); - let rate = unescape($item.attr("data-rate")); - let stock_uom = unescape($item.attr("data-stock-uom")); + const item_code = $item.attr("data-item-code"); + let batch_no = $item.attr("data-batch-no"); + let serial_no = $item.attr("data-serial-no"); + let uom = $item.attr("data-uom"); + let rate = $item.attr("data-rate"); + let stock_uom = $item.attr("data-stock-uom"); // escape(undefined) returns "undefined" then unescape returns "undefined" batch_no = batch_no === "undefined" ? undefined : batch_no;