From bb73d77ceca069b559d776ab88be24f033705384 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 8 Dec 2023 19:08:34 +0000 Subject: [PATCH 001/187] fix: grid row default values with not using model --- frappe/public/js/frappe/form/grid.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index b5942c7f46..0d97ce165d 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -834,7 +834,13 @@ export default class Grid { if (!this.df.data) { this.df.data = this.get_data() || []; } - this.df.data.push({ idx: this.df.data.length + 1, __islocal: true }); + const defaults = this.docfields.reduce((acc, d) => { + acc[d.fieldname] = d.default; + return acc; + }, {}); + this.df.data.push( + $.extend({ idx: this.df.data.length + 1, __islocal: true }, defaults) + ); this.refresh(); } From d7026b8a262e4e2360313ff7d123db938fd97c02 Mon Sep 17 00:00:00 2001 From: Corentin Flr <10946971+cogk@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:40:12 +0100 Subject: [PATCH 002/187] fix(meta)!: Allow level 0 fields when a doc has been shared with user --- frappe/model/meta.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 0e80a0957c..99e6d2f740 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -595,6 +595,10 @@ class Meta(Document): self.get_permlevel_access(permission_type=permission_type, parenttype=parenttype, user=user) ) + if 0 not in permlevel_access and permission_type in ("read", "select"): + if frappe.share.get_shared(self.name, user, rights=[permission_type], limit=1): + permlevel_access.add(0) + permitted_fieldnames.extend( df.fieldname for df in self.get_fieldnames_with_value( From 06bd8b9e9c436697d98324cf1c30ed13608dce4b Mon Sep 17 00:00:00 2001 From: Corentin Flr <10946971+cogk@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:06:54 +0100 Subject: [PATCH 003/187] test: Add test for get_list of shared document --- frappe/core/doctype/docshare/test_docshare.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frappe/core/doctype/docshare/test_docshare.py b/frappe/core/doctype/docshare/test_docshare.py index 125e829d9b..3b6ec0c396 100644 --- a/frappe/core/doctype/docshare/test_docshare.py +++ b/frappe/core/doctype/docshare/test_docshare.py @@ -56,6 +56,24 @@ class TestDocShare(FrappeTestCase): with self.assertRowsRead(1): self.assertTrue(self.event.has_permission()) + def test_list_permission(self): + frappe.set_user(self.user) + with self.assertRaises(frappe.PermissionError): + frappe.get_list("Web Page") + + frappe.set_user("Administrator") + doc = frappe.new_doc("Web Page") + doc.update({"title": "test document for docshare permissions"}) + doc.insert() + frappe.share.add("Web Page", doc.name, self.user) + + frappe.set_user(self.user) + self.assertEqual(len(frappe.get_list("Web Page")), 1) + + doc.delete(ignore_permissions=True) + with self.assertRaises(frappe.PermissionError): + frappe.get_list("Web Page") + def test_share_permission(self): frappe.share.add("Event", self.event.name, self.user, write=1, share=1) From fc0ba518945102b836794fc539ea6bd42af60cc1 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 21 Dec 2023 16:15:56 +0100 Subject: [PATCH 004/187] 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 005/187] 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 %} - +