Bug 40525: [24.05] Update spreadsheet format to escape formulas

Signed-off-by: Jesse Maseto <jesse@bywatersolutions.com>
This commit is contained in:
David Cook 2025-10-28 05:55:49 +00:00 committed by Jesse Maseto
parent 6ad5eeefea
commit 4dee52666c
No known key found for this signature in database
GPG Key ID: 4D16BFCF78FFFDEC
5 changed files with 124 additions and 4 deletions

View File

@ -1,5 +1,5 @@
[% USE TablesSettings %]
[% Asset.js("js/datatables_buttons_shim.js") | $raw %]
<script>
function KohaTable(selector, dt_parameters, columns_settings) {
var id = 0;
@ -40,6 +40,8 @@ function KohaTable(selector, dt_parameters, columns_settings) {
}
}
const export_format_spreadsheet = ButtonsShim.export_format_spreadsheet;
// Add a "Clear filter" button to table filter form field
dt_parameters[ "buttons" ] = [
{
@ -58,7 +60,7 @@ function KohaTable(selector, dt_parameters, columns_settings) {
text: _("CSV"),
exportOptions: {
columns: exportColumns,
format: export_format
format: export_format_spreadsheet
},
},
{

View File

@ -2,4 +2,5 @@
[% USE Asset %]
[% Asset.js("lib/jquery/plugins/jquery.dataTables.min.js") | $raw %]
[% Asset.js("lib/jquery/plugins/dataTables.responsive.min.js") | $raw %]
[% Asset.js("js/datatables_buttons_shim.js") | $raw %]
[% Asset.js("js/datatables.js") | $raw %]

View File

@ -1210,6 +1210,8 @@
);
});
const export_format_spreadsheet = ButtonsShim.export_format_spreadsheet;
var dTables = $("#checkoutst,#holdst,#overduest,#opac-user-relative-issues-table");
dTables.each(function(){
var thIndex = $(this).find("th.psort").index();
@ -1250,7 +1252,8 @@
extend: "csv",
exportOptions: {
/* CSV export should include all columns (even invisible ones) unless they are .noExport */
columns: ":not(.noExport)"
columns: ":not(.noExport)",
format: export_format_spreadsheet
}
}
]

View File

@ -240,6 +240,8 @@ $.fn.dataTable.ext.buttons.clearFilter = {
}
};
const export_format_spreadsheet = ButtonsShim.export_format_spreadsheet;
var export_buttons = [
{
extend: 'excelHtml5',
@ -252,7 +254,7 @@ $.fn.dataTable.ext.buttons.clearFilter = {
extend: 'csvHtml5',
exportOptions: {
columns: exportColumns,
format: export_format
format: export_format_spreadsheet
},
},
{

View File

@ -0,0 +1,112 @@
window.ButtonsShim = {};
ButtonsShim.export_format_spreadsheet = {
body: function (data, row, column, node) {
const newnode = node.cloneNode(true);
const no_export_nodes = newnode.querySelectorAll(".no-export");
no_export_nodes.forEach(child => {
child.parentNode.removeChild(child);
});
//Note: innerHTML is the same thing as the data variable,
//minus the ".no-export" nodes that we've removed
//Note: See dataTables.buttons.js for original function usage
const str = ButtonsShim.stripData(newnode.innerHTML, {
decodeEntities: false,
stripHtml: true,
stripNewlines: true,
trim: true,
escapeExcelFormula: true
});
return str;
},
};
ButtonsShim._stripHtml = function (input) {
var _max_str_len = Math.pow(2, 28);
var _re_html = /<([^>]*>)/g;
if (! input || typeof input !== 'string') {
return input;
}
// Irrelevant check to workaround CodeQL's false positive on the regex
if (input.length > _max_str_len) {
throw new Error('Exceeded max str len');
}
var previous;
input = input.replace(_re_html, ''); // Complete tags
// Safety for incomplete script tag - use do / while to ensure that
// we get all instances
do {
previous = input;
input = input.replace(/<script/i, '');
} while (input !== previous);
return previous;
};
ButtonsShim.stripHtml = function (mixed) {
var type = typeof mixed;
if (type === 'function') {
ButtonsShim._stripHtml = mixed;
return;
}
else if (type === 'string') {
return ButtonsShim._stripHtml(mixed);
}
return mixed;
};
ButtonsShim.stripHtmlScript = function (input) {
var previous;
do {
previous = input;
input = input.replace(/<script\b[^<]*(?:(?!<\/script[^>]*>)<[^<]*)*<\/script[^>]*>/gi, '');
} while (input !== previous);
return input;
};
ButtonsShim.stripHtmlComments = function (input) {
var previous;
do {
previous = input;
input = input.replace(/(<!--.*?--!?>)|(<!--[\S\s]+?--!?>)|(<!--[\S\s]*?$)/g, '');
} while (input !== previous);
return input;
};
ButtonsShim.stripData = function (str, config) {
// If the input is an HTML element, we can use the HTML from it (HTML might be stripped below).
if (str !== null && typeof str === 'object' && str.nodeName && str.nodeType) {
str = str.innerHTML;
}
if (typeof str !== 'string') {
return str;
}
// Always remove script tags
//str = Buttons.stripHtmlScript(str);
str = ButtonsShim.stripHtmlScript(str);
// Always remove comments
//str = Buttons.stripHtmlComments(str);
str = ButtonsShim.stripHtmlComments(str);
if (!config || config.stripHtml) {
//str = DataTable.util.stripHtml(str);
str = ButtonsShim.stripHtml(str);
}
if (!config || config.trim) {
str = str.trim();
}
if (!config || config.stripNewlines) {
str = str.replace(/\n/g, ' ');
}
// Prevent Excel from running a formula
if (!config || config.escapeExcelFormula) {
if (str.match(/^[=@\t\r]/)) {
str = "'" + str;
}
}
return str;
};