From fc0ba518945102b836794fc539ea6bd42af60cc1 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 21 Dec 2023 16:15:56 +0100 Subject: [PATCH 01/64] fix: Disappearing letterhead header in pdf - fix: Make sure 'header-html' is extracted from document after it is rendered as html and loaded in wkhtmltopdf options, else it goes missing --- frappe/utils/pdf.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 24b6896620..b7a8557b48 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -227,7 +227,7 @@ def read_options_from_html(html): return str(soup), options -def prepare_header_footer(soup): +def prepare_header_footer(soup: BeautifulSoup): options = {} head = soup.find("head").contents @@ -240,10 +240,6 @@ def prepare_header_footer(soup): for html_id in ("header-html", "footer-html"): content = soup.find(id=html_id) if content: - # there could be multiple instances of header-html/footer-html - for tag in soup.find_all(id=html_id): - tag.extract() - toggle_visible_pdf(content) id_map = {"header-html": "pdf_header_html", "footer-html": "pdf_footer_html"} hook_func = frappe.get_hooks(id_map.get(html_id)) @@ -256,6 +252,10 @@ def prepare_header_footer(soup): css=css, ) + # there could be multiple instances of header-html/footer-html + for tag in soup.find_all(id=html_id): + tag.extract() + # create temp file fname = os.path.join("/tmp", f"frappe-pdf-{frappe.generate_hash()}.html") with open(fname, "wb") as f: From 29e761671dc73edcc3b0a9745d825b8851a34e34 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 21 Dec 2023 17:25:19 +0100 Subject: [PATCH 02/64] feat: Letterhead scripts - feat: Allow script injection into header/footer.html to allow manipulation of styles using page numbers/args received by wkhtmltopdf - misc: also validate letterhead scripts - Include scripts in printview as well as pdf - Add helper instructions & re-arrange fields --- frappe/__init__.py | 6 ++- .../doctype/letter_head/letter_head.js | 51 +++++++++++++++++++ .../doctype/letter_head/letter_head.json | 35 ++++++++++++- .../print_formats/pdf_header_footer.html | 16 +++++- frappe/utils/pdf.py | 26 ++++++---- frappe/www/printview.html | 16 +++--- frappe/www/printview.py | 33 ++++++++++-- 7 files changed, 157 insertions(+), 26 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 3d31adbcfa..7c41c21b51 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -2116,7 +2116,9 @@ def get_print( pdf_options["password"] = password html = get_response_content("printview") - return get_pdf(html, options=pdf_options, output=output) if as_pdf else html + return ( + get_pdf(html, options=pdf_options, output=output, letterhead=letterhead) if as_pdf else html + ) def attach_print( @@ -2155,7 +2157,7 @@ def attach_print( ext = ".pdf" kwargs["as_pdf"] = True content = ( - get_pdf(html, options={"password": password} if password else None) + get_pdf(html, options={"password": password} if password else None, letterhead=letterhead) if html else get_print(doctype, name, **kwargs) ) diff --git a/frappe/printing/doctype/letter_head/letter_head.js b/frappe/printing/doctype/letter_head/letter_head.js index 55d97cf37f..a1ef08bce9 100644 --- a/frappe/printing/doctype/letter_head/letter_head.js +++ b/frappe/printing/doctype/letter_head/letter_head.js @@ -2,7 +2,58 @@ // For license information, please see license.txt frappe.ui.form.on("Letter Head", { + setup(frm) { + frm.get_field("instructions").html(INSTRUCTIONS); + }, + refresh: function (frm) { frm.flag_public_attachments = true; }, + + validate: (frm) => { + ["header_script", "footer_script"].forEach((field) => { + if (!frm.doc[field]) return; + + try { + eval(frm.doc[field]); + } catch (e) { + frappe.throw({ + title: __("Error in Header/Footer Script"), + indicator: "orange", + message: '
' + e.stack + "
", + }); + } + }); + }, }); + +const INSTRUCTIONS = `

Letter Head Scripts

+

Header/Footer scripts can be used to add dynamic behaviours.

+
+
+// The following Header Script will add the current date to an element in 'Header HTML' with class "header-content"
+var el = document.getElementsByClassName("header-content");
+if (el.length > 0) {
+	el[0].textContent += " " + new Date().toGMTString();
+}
+
+
+

You can also access wkhtmltopdf variables (valid only in PDF print):

+
+
+// Get Header and Footer wkhtmltopdf variables
+// Snippet and more variables: https://wkhtmltopdf.org/usage/wkhtmltopdf.txt
+var vars = {};
+var query_strings_from_url = document.location.search.substring(1).split('&');
+for (var query_string in query_strings_from_url) {
+	if (query_strings_from_url.hasOwnProperty(query_string)) {
+		var temp_var = query_strings_from_url[query_string].split('=', 2);
+		vars[temp_var[0]] = decodeURI(temp_var[1]);
+	}
+}
+var el = document.getElementsByClassName("header-content");
+if (el.length > 0 && vars["page"] == 1) {
+	el[0].textContent += " : " + vars["date"];
+}
+
+
`; diff --git a/frappe/printing/doctype/letter_head/letter_head.json b/frappe/printing/doctype/letter_head/letter_head.json index 021f79ca93..4ffca134f2 100644 --- a/frappe/printing/doctype/letter_head/letter_head.json +++ b/frappe/printing/doctype/letter_head/letter_head.json @@ -26,7 +26,11 @@ "footer_image", "footer_image_height", "footer_image_width", - "footer_align" + "footer_align", + "scripts_section", + "header_script", + "footer_script", + "instructions" ], "fields": [ { @@ -162,13 +166,40 @@ "fieldtype": "Select", "label": "Footer Based On", "options": "Image\nHTML" + }, + { + "depends_on": "eval:!doc.__islocal && doc.source==='HTML'", + "fieldname": "header_script", + "fieldtype": "Code", + "label": "Header Script", + "options": "Javascript" + }, + { + "depends_on": "eval:!doc.__islocal && doc.footer_source==='HTML'", + "fieldname": "footer_script", + "fieldtype": "Code", + "label": "Footer Script", + "options": "Javascript" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval: doc.header_script || doc.footer_script", + "fieldname": "scripts_section", + "fieldtype": "Section Break", + "label": "Scripts" + }, + { + "fieldname": "instructions", + "fieldtype": "HTML", + "label": "Instructions", + "read_only": 1 } ], "icon": "fa fa-font", "idx": 1, "links": [], "max_attachments": 3, - "modified": "2023-12-08 15:52:37.525003", + "modified": "2023-12-21 16:19:37.525003", "modified_by": "Administrator", "module": "Printing", "name": "Letter Head", diff --git a/frappe/templates/print_formats/pdf_header_footer.html b/frappe/templates/print_formats/pdf_header_footer.html index 189cf43cd9..91973efb70 100644 --- a/frappe/templates/print_formats/pdf_header_footer.html +++ b/frappe/templates/print_formats/pdf_header_footer.html @@ -56,14 +56,28 @@ } } } + + function custom_load() { + if (window.custom_header_footer_script) { + window.custom_header_footer_script(); + } + } + {% if custom_header_footer_script -%} + + {%- endif %} + {% for tag in styles -%} {{ tag | string }} {%- endfor %} - +