From 0ab6840d1d1df78fbccc2c07baecc8ccfa280b56 Mon Sep 17 00:00:00 2001 From: Shllokkk Date: Thu, 23 Apr 2026 16:17:50 +0530 Subject: [PATCH 1/4] feat(letterhead): introduce custom_css field to move styling out of html fields and to prevent scripts in html fields --- .../doctype/letter_head/letter_head.json | 67 ++++++++++++------- .../doctype/letter_head/letter_head.py | 6 +- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/frappe/printing/doctype/letter_head/letter_head.json b/frappe/printing/doctype/letter_head/letter_head.json index 119a59120f..f212ae2812 100644 --- a/frappe/printing/doctype/letter_head/letter_head.json +++ b/frappe/printing/doctype/letter_head/letter_head.json @@ -7,29 +7,33 @@ "document_type": "Setup", "engine": "InnoDB", "field_order": [ - "letter_head_for", "letter_head_name", "module", "source", "footer_source", "column_break_3", + "letter_head_for", "standard", "disabled", "is_default", "letter_head_image_section", + "align", "image", + "column_break_dpzk", "image_height", "image_width", - "align", + "footer_image_section", + "footer_align", + "footer_image", + "column_break_kfvf", + "footer_image_height", + "footer_image_width", "header_section", "content", "footer_section", "footer", - "footer_image_section", - "footer_image", - "footer_image_height", - "footer_image_width", - "footer_align", + "style_section", + "custom_css", "scripts_section", "header_script", "footer_script", @@ -47,7 +51,6 @@ "unique": 1 }, { - "depends_on": "letter_head_name", "fieldname": "source", "fieldtype": "Select", "label": "Letter Head Based On", @@ -59,7 +62,6 @@ }, { "default": "0", - "depends_on": "letter_head_name", "fieldname": "disabled", "fieldtype": "Check", "in_list_view": 1, @@ -69,7 +71,6 @@ }, { "default": "0", - "depends_on": "letter_head_name", "fieldname": "is_default", "fieldtype": "Check", "in_list_view": 1, @@ -79,25 +80,25 @@ "search_index": 1 }, { - "depends_on": "eval:doc.letter_head_name && doc.source === 'Image'", + "depends_on": "eval:doc.source === 'Image'", "fieldname": "letter_head_image_section", "fieldtype": "Section Break", "label": "Letter Head Image" }, { - "depends_on": "eval:doc.letter_head_name && doc.source === 'Image'", + "depends_on": "eval: doc.source === 'Image'", "fieldname": "image", "fieldtype": "Attach Image", "label": "Image" }, { - "depends_on": "eval:doc.source==='HTML' && doc.letter_head_name", + "depends_on": "eval:doc.source==='HTML'", "fieldname": "header_section", "fieldtype": "Section Break", "label": "Header" }, { - "depends_on": "eval:!doc.__islocal && doc.source==='HTML'", + "depends_on": "eval: doc.source==='HTML'", "description": "Letter Head in HTML", "fieldname": "content", "fieldtype": "HTML Editor", @@ -107,13 +108,13 @@ "oldfieldtype": "Text Editor" }, { - "depends_on": "eval:doc.footer_source==='HTML' && doc.letter_head_name", + "depends_on": "eval:doc.footer_source==='HTML'", "fieldname": "footer_section", "fieldtype": "Section Break", "label": "Footer" }, { - "depends_on": "eval:!doc.__islocal", + "depends_on": "eval: doc.footer_source==='HTML'", "description": "Footer will display correctly only in PDF", "fieldname": "footer", "fieldtype": "HTML Editor", @@ -138,7 +139,7 @@ "label": "Image Width (px)" }, { - "depends_on": "eval:doc.footer_source==='Image' && doc.letter_head_name", + "depends_on": "eval:doc.footer_source==='Image'", "fieldname": "footer_image_section", "fieldtype": "Section Break", "label": "Footer Image" @@ -165,22 +166,20 @@ "options": "Left\nRight\nCenter" }, { - "default": "HTML", - "depends_on": "letter_head_name", "fieldname": "footer_source", "fieldtype": "Select", "label": "Footer Based On", "options": "Image\nHTML" }, { - "depends_on": "eval:!doc.__islocal && doc.source==='HTML'", + "depends_on": "eval: doc.source==='HTML'", "fieldname": "header_script", "fieldtype": "Code", "label": "Header Script", "options": "Javascript" }, { - "depends_on": "eval:!doc.__islocal && doc.footer_source==='HTML'", + "depends_on": "eval: doc.footer_source==='HTML'", "fieldname": "footer_script", "fieldtype": "Code", "label": "Footer Script", @@ -189,7 +188,7 @@ { "collapsible": 1, "collapsible_depends_on": "eval: doc.header_script || doc.footer_script", - "depends_on": "eval: !doc.__islocal", + "depends_on": "eval: doc.source === 'HTML' || doc.footer_source === 'HTML'", "fieldname": "scripts_section", "fieldtype": "Section Break", "label": "Scripts" @@ -223,6 +222,28 @@ "label": "Module", "mandatory_depends_on": "eval: doc.standard == \"Yes\"", "options": "Module Def" + }, + { + "fieldname": "column_break_dpzk", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_kfvf", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.source === 'HTML' || doc.footer_source === 'HTML'", + "fieldname": "custom_css", + "fieldtype": "Code", + "label": "Custom CSS", + "options": "CSS" + }, + { + "collapsible": 1, + "depends_on": "eval: doc.source === 'HTML' || doc.footer_source === 'HTML'", + "fieldname": "style_section", + "fieldtype": "Section Break", + "label": "Style" } ], "icon": "fa fa-font", @@ -230,7 +251,7 @@ "links": [], "make_attachments_public": 1, "max_attachments": 3, - "modified": "2026-04-08 13:15:24.935222", + "modified": "2026-04-22 20:25:24.438817", "modified_by": "Administrator", "module": "Printing", "name": "Letter Head", diff --git a/frappe/printing/doctype/letter_head/letter_head.py b/frappe/printing/doctype/letter_head/letter_head.py index 220a09cac1..2582b40674 100644 --- a/frappe/printing/doctype/letter_head/letter_head.py +++ b/frappe/printing/doctype/letter_head/letter_head.py @@ -19,6 +19,7 @@ class LetterHead(Document): align: DF.Literal["Left", "Right", "Center"] content: DF.HTMLEditor | None + custom_css: DF.Code | None disabled: DF.Check footer: DF.HTMLEditor | None footer_align: DF.Literal["Left", "Right", "Center"] @@ -39,11 +40,6 @@ class LetterHead(Document): standard: DF.Literal["No", "Yes"] # end: auto-generated types - def before_insert(self): - # for better UX, let user set from attachment - if not frappe.flags.in_migrate and not frappe.flags.in_install: - self.source = "Image" - def on_trash(self): from frappe.defaults import clear_default From 193c2c200f68f001ae6a54f707dc3b5386072ee1 Mon Sep 17 00:00:00 2001 From: Shllokkk Date: Thu, 23 Apr 2026 16:20:45 +0530 Subject: [PATCH 2/4] fix: add css handling for letterheads for doctype printing --- frappe/www/printview.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frappe/www/printview.py b/frappe/www/printview.py index ff2b048305..f31c8af6e0 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -236,6 +236,12 @@ def get_rendered_template( {letter_head.header_script} """ + if letter_head.custom_css: + letter_head.content += f""" + + """ if letter_head.footer: letter_head.footer = frappe.utils.jinja.render_template(letter_head.footer, {"doc": doc.as_dict()}) @@ -427,7 +433,7 @@ def get_letter_head(doc: "Document", no_letterhead: bool, letterhead: str | None return frappe.db.get_value( "Letter Head", letterhead_name, - ["content", "footer", "header_script", "footer_script"], + ["content", "footer", "header_script", "footer_script", "custom_css"], as_dict=True, ) else: @@ -435,7 +441,7 @@ def get_letter_head(doc: "Document", no_letterhead: bool, letterhead: str | None frappe.db.get_value( "Letter Head", {"is_default": 1}, - ["content", "footer", "header_script", "footer_script"], + ["content", "footer", "header_script", "footer_script", "custom_css"], as_dict=True, ) or {} From 72cdae85e75af71cfcc2bbe006d3fdd892c6a317 Mon Sep 17 00:00:00 2001 From: Shllokkk Date: Thu, 23 Apr 2026 16:21:49 +0530 Subject: [PATCH 3/4] fix: add css handling for letterheads for report printing --- frappe/utils/print_format.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index 3a3770bce1..75645b915f 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -294,7 +294,7 @@ def render_letterhead_for_print(letterhead: str | None = None, doc: dict | str | frappe.db.get_value( "Letter Head", letterhead or {"is_default": 1}, - ["content", "footer", "header_script", "footer_script"], + ["content", "footer", "header_script", "footer_script", "custom_css"], as_dict=True, ) or {} @@ -307,6 +307,8 @@ def render_letterhead_for_print(letterhead: str | None = None, doc: dict | str | header = render_template(letter_head.content, {"doc": context_doc}) if letter_head.header_script: header += f"\n\n" + if letter_head.custom_css: + header += f"\n\n" rendered["header"] = header if letter_head.footer: From 006d0e1754421bd60b83956752d2d1c78a10039a Mon Sep 17 00:00:00 2001 From: Shllokkk Date: Fri, 24 Apr 2026 13:20:47 +0530 Subject: [PATCH 4/4] fix: minor suggested fixes --- .../doctype/letter_head/letter_head.json | 29 ++++++------------- frappe/utils/print_format.py | 4 +-- frappe/www/printview.py | 12 ++++---- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/frappe/printing/doctype/letter_head/letter_head.json b/frappe/printing/doctype/letter_head/letter_head.json index f212ae2812..f4aa731b86 100644 --- a/frappe/printing/doctype/letter_head/letter_head.json +++ b/frappe/printing/doctype/letter_head/letter_head.json @@ -30,10 +30,9 @@ "footer_image_width", "header_section", "content", + "custom_css", "footer_section", "footer", - "style_section", - "custom_css", "scripts_section", "header_script", "footer_script", @@ -86,7 +85,6 @@ "label": "Letter Head Image" }, { - "depends_on": "eval: doc.source === 'Image'", "fieldname": "image", "fieldtype": "Attach Image", "label": "Image" @@ -98,11 +96,10 @@ "label": "Header" }, { - "depends_on": "eval: doc.source==='HTML'", + "depends_on": "eval:doc.source==='HTML'", "description": "Letter Head in HTML", "fieldname": "content", "fieldtype": "HTML Editor", - "ignore_xss_filter": 1, "label": "Header HTML", "oldfieldname": "content", "oldfieldtype": "Text Editor" @@ -114,11 +111,10 @@ "label": "Footer" }, { - "depends_on": "eval: doc.footer_source==='HTML'", + "depends_on": "eval:doc.footer_source==='HTML'", "description": "Footer will display correctly only in PDF", "fieldname": "footer", "fieldtype": "HTML Editor", - "ignore_xss_filter": 1, "label": "Footer HTML" }, { @@ -172,14 +168,14 @@ "options": "Image\nHTML" }, { - "depends_on": "eval: doc.source==='HTML'", + "depends_on": "eval:doc.source==='HTML'", "fieldname": "header_script", "fieldtype": "Code", "label": "Header Script", "options": "Javascript" }, { - "depends_on": "eval: doc.footer_source==='HTML'", + "depends_on": "eval:doc.footer_source==='HTML'", "fieldname": "footer_script", "fieldtype": "Code", "label": "Footer Script", @@ -187,8 +183,8 @@ }, { "collapsible": 1, - "collapsible_depends_on": "eval: doc.header_script || doc.footer_script", - "depends_on": "eval: doc.source === 'HTML' || doc.footer_source === 'HTML'", + "collapsible_depends_on": "eval:doc.header_script || doc.footer_script", + "depends_on": "eval:doc.source === 'HTML' || doc.footer_source === 'HTML'", "fieldname": "scripts_section", "fieldtype": "Section Break", "label": "Scripts" @@ -232,18 +228,11 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval: doc.source === 'HTML' || doc.footer_source === 'HTML'", + "depends_on": "eval:doc.source === 'HTML'", "fieldname": "custom_css", "fieldtype": "Code", "label": "Custom CSS", "options": "CSS" - }, - { - "collapsible": 1, - "depends_on": "eval: doc.source === 'HTML' || doc.footer_source === 'HTML'", - "fieldname": "style_section", - "fieldtype": "Section Break", - "label": "Style" } ], "icon": "fa fa-font", @@ -251,7 +240,7 @@ "links": [], "make_attachments_public": 1, "max_attachments": 3, - "modified": "2026-04-22 20:25:24.438817", + "modified": "2026-04-24 13:17:34.972069", "modified_by": "Administrator", "module": "Printing", "name": "Letter Head", diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index 75645b915f..62440c3358 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -305,11 +305,11 @@ def render_letterhead_for_print(letterhead: str | None = None, doc: dict | str | if letter_head.content: header = render_template(letter_head.content, {"doc": context_doc}) - if letter_head.header_script: - header += f"\n\n" if letter_head.custom_css: header += f"\n\n" rendered["header"] = header + if letter_head.header_script: + header += f"\n\n" if letter_head.footer: footer = render_template(letter_head.footer, {"doc": context_doc}) diff --git a/frappe/www/printview.py b/frappe/www/printview.py index f31c8af6e0..15e9ea1854 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -230,18 +230,18 @@ def get_rendered_template( if letter_head.content: letter_head.content = frappe.utils.jinja.render_template(letter_head.content, {"doc": doc.as_dict()}) - if letter_head.header_script: - letter_head.content += f""" - - """ if letter_head.custom_css: letter_head.content += f""" """ + if letter_head.header_script: + letter_head.content += f""" + + """ if letter_head.footer: letter_head.footer = frappe.utils.jinja.render_template(letter_head.footer, {"doc": doc.as_dict()})