From 697978c8f3aa2ad192a518dc4e9d394eaf4dcb27 Mon Sep 17 00:00:00 2001 From: "stravo1@mac" Date: Fri, 13 Feb 2026 21:37:57 +0530 Subject: [PATCH 01/19] fix(website_404): cache website 404 results wrt to users fixes #37007 --- frappe/website/page_renderers/not_found_page.py | 2 +- frappe/website/path_resolver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/page_renderers/not_found_page.py b/frappe/website/page_renderers/not_found_page.py index 704dca77d1..1898d7dece 100644 --- a/frappe/website/page_renderers/not_found_page.py +++ b/frappe/website/page_renderers/not_found_page.py @@ -21,7 +21,7 @@ class NotFoundPage(TemplatePage): def render(self): if self.can_cache_404(): - frappe.cache.hset("website_404", self.request_url, True) + frappe.cache.hset("website_404", f"{frappe.session.user}|{self.request_url}", True) return super().render() def can_cache_404(self): diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 0a2e302118..929438e055 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -35,7 +35,7 @@ class PathResolver: return "desk", TemplatePage("desk", self.http_status_code) # check if the request url is in 404 list - if request.url and can_cache() and frappe.cache.hget("website_404", request.url): + if request.url and can_cache() and frappe.cache.hget("website_404", f"{frappe.session.user}|{request.url}"): return self.path, NotFoundPage(self.path) try: From a534936726adaaaa5d6b44dfacd036659b2c67df Mon Sep 17 00:00:00 2001 From: "stravo1@mac" Date: Mon, 16 Feb 2026 22:15:01 +0530 Subject: [PATCH 02/19] Revert "fix(website_404): cache website 404 results wrt to users" This reverts commit 697978c8f3aa2ad192a518dc4e9d394eaf4dcb27. --- frappe/website/page_renderers/not_found_page.py | 2 +- frappe/website/path_resolver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/page_renderers/not_found_page.py b/frappe/website/page_renderers/not_found_page.py index 1898d7dece..704dca77d1 100644 --- a/frappe/website/page_renderers/not_found_page.py +++ b/frappe/website/page_renderers/not_found_page.py @@ -21,7 +21,7 @@ class NotFoundPage(TemplatePage): def render(self): if self.can_cache_404(): - frappe.cache.hset("website_404", f"{frappe.session.user}|{self.request_url}", True) + frappe.cache.hset("website_404", self.request_url, True) return super().render() def can_cache_404(self): diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 929438e055..0a2e302118 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -35,7 +35,7 @@ class PathResolver: return "desk", TemplatePage("desk", self.http_status_code) # check if the request url is in 404 list - if request.url and can_cache() and frappe.cache.hget("website_404", f"{frappe.session.user}|{request.url}"): + if request.url and can_cache() and frappe.cache.hget("website_404", request.url): return self.path, NotFoundPage(self.path) try: From 9dcaab96eef44c54aa3e7d53ae9afc30f21357cb Mon Sep 17 00:00:00 2001 From: "stravo1@mac" Date: Mon, 16 Feb 2026 23:41:32 +0530 Subject: [PATCH 03/19] fix(website_404): skip caching 404 for pages with permission checks --- .../website/page_renderers/not_found_page.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/frappe/website/page_renderers/not_found_page.py b/frappe/website/page_renderers/not_found_page.py index 704dca77d1..9b980f5d51 100644 --- a/frappe/website/page_renderers/not_found_page.py +++ b/frappe/website/page_renderers/not_found_page.py @@ -2,6 +2,7 @@ import os from urllib.parse import urlparse import frappe +from frappe.website.page_renderers.document_page import _find_matching_document_webview from frappe.website.page_renderers.template_page import TemplatePage from frappe.website.utils import can_cache @@ -26,10 +27,26 @@ class NotFoundPage(TemplatePage): def can_cache_404(self): # do not cache 404 for custom homepages - return can_cache() and self.request_url and not self.is_custom_home_page() + # also skip caching docs with website permission checks (access is dynamic) + return ( + can_cache() + and self.request_url + and not self.is_custom_home_page() + and not self.has_website_permission_check() + ) def is_custom_home_page(self): url_parts = urlparse(self.request_url) request_url = os.path.splitext(url_parts.path)[0] request_path = os.path.splitext(self.request_path)[0] return request_url in HOMEPAGE_PATHS and request_path not in HOMEPAGE_PATHS + + def has_website_permission_check(self): + request_path = os.path.splitext(self.request_path)[0] + if not (document := _find_matching_document_webview(request_path)): + return False + doctype, docname = document + doc = frappe.get_cached_doc(doctype, docname) + return hasattr(doc, "has_website_permission") or bool( + frappe.get_hooks("has_website_permission", {}).get(doctype) + ) From b98396f4f407e1a2e5d66b6c8b230f3ec7de1903 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Mon, 23 Feb 2026 11:54:09 +0530 Subject: [PATCH 04/19] fix: fix: add doc button for long doctype name --- frappe/public/js/frappe/list/list_view.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index d9c6514556..6473db896e 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -291,8 +291,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { set_primary_action() { if (this.can_create && !frappe.boot.read_only) { const doctype_name = __(frappe.router.doctype_layout) || __(this.doctype); + const full_label = __("Add {0}", [doctype_name], "Primary action in list view"); const create_button = this.page.set_primary_action( - __("Add {0}", [doctype_name], "Primary action in list view"), + full_label, () => { if (this.settings.primary_action) { this.settings.primary_action(); @@ -304,12 +305,32 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { ); if (frappe.is_mobile()) { create_button.append(__("Add")); + } else { + this._trim_primary_action_if_overflow(create_button, full_label); } } else { this.page.clear_primary_action(); } } + _trim_primary_action_if_overflow(btn, full_label) { + const container = this.page.wrapper.find(".page-head-content")[0]; + if (!container || !btn[0]) return; + const containerRect = container.getBoundingClientRect(); + const btnRect = btn[0].getBoundingClientRect(); + if (btnRect.right > containerRect.right) { + const short_label = __("Add"); + btn.attr("title", full_label) + .tooltip({ delay: { show: 600, hide: 100 }, trigger: "hover" }) + .html( + `${frappe.utils.icon( + "add", + "xs" + )} ` + ); + } + } + make_new_doc() { const doctype = this.doctype; const options = {}; From f1dd3da3f022d12320848ca61bb9ab84d7211d78 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Tue, 24 Feb 2026 12:23:41 +0530 Subject: [PATCH 05/19] refactor: remove add icon for add doc button with large doctype name --- frappe/public/js/frappe/list/list_view.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 6473db896e..375cee1d0a 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -291,9 +291,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { set_primary_action() { if (this.can_create && !frappe.boot.read_only) { const doctype_name = __(frappe.router.doctype_layout) || __(this.doctype); - const full_label = __("Add {0}", [doctype_name], "Primary action in list view"); + const add_button_label = __("Add {0}", [doctype_name], "Primary action in list view"); const create_button = this.page.set_primary_action( - full_label, + add_button_label, () => { if (this.settings.primary_action) { this.settings.primary_action(); @@ -306,28 +306,23 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if (frappe.is_mobile()) { create_button.append(__("Add")); } else { - this._trim_primary_action_if_overflow(create_button, full_label); + this._trim_primary_action_if_overflow(create_button, add_button_label); } } else { this.page.clear_primary_action(); } } - _trim_primary_action_if_overflow(btn, full_label) { + _trim_primary_action_if_overflow(btn, add_button_label) { const container = this.page.wrapper.find(".page-head-content")[0]; if (!container || !btn[0]) return; const containerRect = container.getBoundingClientRect(); const btnRect = btn[0].getBoundingClientRect(); if (btnRect.right > containerRect.right) { const short_label = __("Add"); - btn.attr("title", full_label) - .tooltip({ delay: { show: 600, hide: 100 }, trigger: "hover" }) - .html( - `${frappe.utils.icon( - "add", - "xs" - )} ` - ); + btn.attr("title", add_button_label) + .tooltip({ delay: { show: 100, hide: 100 }, trigger: "hover" }) + .html(``); } } From bb23efd4e71a0691fc9af443426eec406b983a31 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Tue, 24 Feb 2026 12:52:15 +0530 Subject: [PATCH 06/19] refactor: button inner text change without html manipulation --- frappe/public/js/frappe/list/list_view.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 375cee1d0a..d671ed20b0 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -320,9 +320,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { const btnRect = btn[0].getBoundingClientRect(); if (btnRect.right > containerRect.right) { const short_label = __("Add"); - btn.attr("title", add_button_label) - .tooltip({ delay: { show: 100, hide: 100 }, trigger: "hover" }) - .html(``); + btn.attr("title", add_button_label).tooltip({ + delay: { show: 100, hide: 100 }, + trigger: "hover", + }); + btn.find("span").text(short_label); } } From 0c80d82b41ba20d7962864004d1edf43146eea36 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Tue, 24 Feb 2026 15:19:55 +0530 Subject: [PATCH 07/19] refactor: remove unnecessary tooltip delay and trigger --- frappe/public/js/frappe/list/list_view.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index d671ed20b0..2c28a24063 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -320,10 +320,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { const btnRect = btn[0].getBoundingClientRect(); if (btnRect.right > containerRect.right) { const short_label = __("Add"); - btn.attr("title", add_button_label).tooltip({ - delay: { show: 100, hide: 100 }, - trigger: "hover", - }); + btn.attr("title", add_button_label).tooltip(); btn.find("span").text(short_label); } } From 7f90bc817fb63f5efbc8b208147b6acedf16e90a Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Tue, 24 Feb 2026 16:16:43 +0530 Subject: [PATCH 08/19] fix: use format's pdf generator in full page --- frappe/printing/page/print/print.js | 4 +--- frappe/www/printview.html | 2 +- frappe/www/printview.py | 3 ++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js index f2f69e3aa3..b20e1481a6 100644 --- a/frappe/printing/page/print/print.js +++ b/frappe/printing/page/print/print.js @@ -738,9 +738,7 @@ frappe.ui.form.PrintView = class { encodeURIComponent(this.get_letterhead()) + "&settings=" + encodeURIComponent(JSON.stringify(this.additional_settings)) + - (this.lang_code ? "&_lang=" + this.lang_code : "") + - "&pdf_generator=" + - encodeURIComponent(pdf_generator) + (this.lang_code ? "&_lang=" + this.lang_code : "") ) ); if (!w) { diff --git a/frappe/www/printview.html b/frappe/www/printview.html index c3c557c8e4..17e6380baa 100644 --- a/frappe/www/printview.html +++ b/frappe/www/printview.html @@ -18,7 +18,7 @@ {{ _("Print") }} + href="/api/method/frappe.utils.print_format.download_pdf?doctype={{doctype|e}}&name={{name|e}}&format={{print_format|e}}&letterhead={{letterhead|e}}&no_letterhead={{no_letterhead|e}}&_lang={{lang|e}}&key={{key|e}}&pdf_generator={{pdf_generator|e}}"> {{ _('Get PDF') }} diff --git a/frappe/www/printview.py b/frappe/www/printview.py index b6b7d9aa8d..31d47e6d73 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -92,6 +92,7 @@ def get_context(context) -> PrintContext: # Include selected print format name in access log print_format_name = getattr(print_format, "name", "Standard") + pdf_generator = getattr(print_format, "pdf_generator", "wkhtmltopdf") make_access_log( doctype=frappe.form_dict.doctype, @@ -114,7 +115,7 @@ def get_context(context) -> PrintContext: "print_format": print_format_name, "letterhead": letterhead, "no_letterhead": frappe.form_dict.no_letterhead, - "pdf_generator": frappe.form_dict.get("pdf_generator", "wkhtmltopdf"), + "pdf_generator": frappe.form_dict.get("pdf_generator", pdf_generator), } From 4f305d7a0d67f4b6a677f3229815ab54866bd6be Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Tue, 24 Feb 2026 14:24:19 +0530 Subject: [PATCH 09/19] fix: allow one session per user should override simultaneous sessions (for non-admins) Update description to match Signed-off-by: Akhil Narang --- frappe/auth.py | 6 +++++- frappe/core/doctype/system_settings/system_settings.json | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/auth.py b/frappe/auth.py index 5a447a99af..1658930317 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -253,7 +253,11 @@ class LoginManager: ): return - clear_sessions(frappe.session.user, keep_current=True) + clear_sessions( + frappe.session.user, + keep_current=True, + force=frappe.session.user != "Administrator", + ) def authenticate(self, user: str | None = None, pwd: str | None = None): from frappe.core.doctype.user.user import User diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index c1aa3fb0d7..75d1bd978a 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -248,7 +248,6 @@ }, { "default": "0", - "description": "Note: Multiple sessions will be allowed in case of mobile device", "fieldname": "deny_multiple_sessions", "fieldtype": "Check", "label": "Allow only one session per user" @@ -790,7 +789,7 @@ "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2026-01-02 18:13:45.430712", + "modified": "2026-02-24 14:27:04.763075", "modified_by": "Administrator", "module": "Core", "name": "System Settings", From 047559c03d4640520d6c485678b40f333b9d605a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 24 Feb 2026 16:22:42 +0530 Subject: [PATCH 10/19] fix: mark complete for settings page view step --- frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue b/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue index 68150e9744..e34edbf9b5 100644 --- a/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue +++ b/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue @@ -170,6 +170,7 @@ function updateSettings(step) { }; frappe.set_route("Form", step.reference_document); + markComplete(step); } async function createEntry(step) { From 27e5d5341c8fe7d105edbbd6c2b982087d29435f Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 23 Feb 2026 19:30:55 +0530 Subject: [PATCH 11/19] fix: use `JSON.parse()` for filter processing Signed-off-by: Akhil Narang --- frappe/public/js/frappe/utils/utils.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index bfc1d0ad38..511d49123b 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1909,7 +1909,13 @@ Object.assign(frappe.utils, { process_filter_expression(filter) { let filters = []; - filters = filter ? new Function(`return ${filter}`)() : []; + if (filter) { + try { + filters = JSON.parse(filter); + } catch { + console.warn("Invalid JSON in filter expression", filter); + } + } return this.cleanup_filters(filters); }, cleanup_filters(filters) { From b439f9a215b1e40a7ae569ec8ac19c9e18331585 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 23 Feb 2026 20:32:58 +0530 Subject: [PATCH 12/19] fix(workspace): check before allowing user to edit Signed-off-by: Akhil Narang --- frappe/desk/doctype/workspace/workspace.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index 29d9cb6b0f..1424e2a3d8 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -76,6 +76,18 @@ class Workspace(Document): if self.public and not is_workspace_manager() and not disable_saving_as_public(): frappe.throw(_("You need to be Workspace Manager to edit this document")) + + if ( + not self.public + and self.for_user + and self.for_user != frappe.session.user + and not is_workspace_manager() + ): + frappe.throw( + _("You are not allowed to edit this workspace"), + frappe.PermissionError, + ) + if self.has_value_changed("title"): validate_route_conflict(self.doctype, self.title) else: From 7bac6f06d8fb83739ee17d46ef26ee1e793ade57 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 24 Feb 2026 20:57:48 +0530 Subject: [PATCH 13/19] fix: ignore xss filter for letterhead html field --- frappe/printing/doctype/letter_head/letter_head.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frappe/printing/doctype/letter_head/letter_head.json b/frappe/printing/doctype/letter_head/letter_head.json index 9855c0ff32..4ddafc47db 100644 --- a/frappe/printing/doctype/letter_head/letter_head.json +++ b/frappe/printing/doctype/letter_head/letter_head.json @@ -98,6 +98,7 @@ "description": "Letter Head in HTML", "fieldname": "content", "fieldtype": "HTML Editor", + "ignore_xss_filter": 1, "label": "Header HTML", "oldfieldname": "content", "oldfieldtype": "Text Editor" @@ -113,6 +114,7 @@ "description": "Footer will display correctly only in PDF", "fieldname": "footer", "fieldtype": "HTML Editor", + "ignore_xss_filter": 1, "label": "Footer HTML" }, { @@ -184,6 +186,7 @@ { "collapsible": 1, "collapsible_depends_on": "eval: doc.header_script || doc.footer_script", + "depends_on": "eval: !doc.__islocal", "fieldname": "scripts_section", "fieldtype": "Section Break", "label": "Scripts" @@ -200,7 +203,7 @@ "links": [], "make_attachments_public": 1, "max_attachments": 3, - "modified": "2024-04-12 10:30:25.793932", + "modified": "2026-02-24 20:53:14.297567", "modified_by": "Administrator", "module": "Printing", "name": "Letter Head", @@ -223,8 +226,9 @@ "role": "Desk User" } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From c6e3b46f7e163707b99d08e66f6b1f7e1b17f0da Mon Sep 17 00:00:00 2001 From: Kerolles Fathy Date: Tue, 24 Feb 2026 18:52:47 +0200 Subject: [PATCH 14/19] Revert "fix: routing for non-query reports (#36646)" This reverts commit a77799b5381b49d4d69c7289393d101eead63b29. --- frappe/public/js/frappe/utils/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index bfc1d0ad38..6d7cef63c3 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1575,7 +1575,8 @@ Object.assign(frappe.utils, { if (item.is_query_report) { route = "query-report/" + item.name; } else if (!item.is_query_report && item.report_ref_doctype) { - route = frappe.router.slug(item.report_ref_doctype) + "/view/report/"; + route = + frappe.router.slug(item.report_ref_doctype) + "/view/report/" + item.name; } else { route = "report/" + item.name; } From 6cfc920d960004846794a05af31c1b59b5b8a7ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 23:01:31 +0000 Subject: [PATCH 15/19] chore(deps): bump minimatch from 3.1.2 to 3.1.3 Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.3. - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.3) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9dd12ad0e0..689fa38fba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2055,9 +2055,9 @@ mime@^1.4.1: integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + version "3.1.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.3.tgz#6a5cba9b31f503887018f579c89f81f61162e624" + integrity sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA== dependencies: brace-expansion "^1.1.7" From 4beff9e1c6164f2f1ccee0ef914393b4f837465c Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Wed, 25 Feb 2026 09:59:59 +0530 Subject: [PATCH 16/19] fix: remove is_wkhtmltopdf_valid check when pdf generator is set to chrome --- frappe/printing/page/print/print.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js index f2f69e3aa3..65c06e34f1 100644 --- a/frappe/printing/page/print/print.js +++ b/frappe/printing/page/print/print.js @@ -706,7 +706,10 @@ frappe.ui.form.PrintView = class { return; } } else { - this.is_wkhtmltopdf_valid(); + let pdf_generator = this.get_pdf_generator(print_format?.pdf_generator); + if (pdf_generator === "wkhtmltopdf") { + this.is_wkhtmltopdf_valid(); + } this.render_page( "/api/method/frappe.utils.print_format.download_pdf?", false, From e725c414aced15e3a1ac565385ffa32513be16f1 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 25 Feb 2026 10:09:24 +0530 Subject: [PATCH 17/19] feat(email_queue): log suspend/resume actions in activity log (#37450) Signed-off-by: Akhil Narang --- frappe/email/doctype/email_queue/email_queue.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index b0576dfe38..ca05ec6278 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -514,7 +514,18 @@ def send_now(name: str | int, force_send: bool = False): @frappe.whitelist() def toggle_sending(enable: bool | int | str): frappe.only_for("System Manager") - frappe.db.set_default("suspend_email_queue", 0 if sbool(enable) else 1) + suspend_value = 0 if sbool(enable) else 1 + frappe.db.set_default("suspend_email_queue", suspend_value) + + action = "Resumed" if suspend_value == 0 else "Suspended" + frappe.get_doc( + { + "doctype": "Activity Log", + "user": frappe.session.user, + "status": "Success", + "subject": f"Email Queue sending {action.lower()}", + } + ).insert(ignore_permissions=True, ignore_links=True) def on_doctype_update(): From 9b161c95281746ea880fd7ef48ea4b5d58e71b63 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 25 Feb 2026 10:09:30 +0530 Subject: [PATCH 18/19] fix(oauth): cleanup code, use user from session (#37434) * fix(oauth): use user from session Signed-off-by: Akhil Narang * refactor(oauth): simplify code Signed-off-by: Akhil Narang --------- Signed-off-by: Akhil Narang --- frappe/oauth.py | 55 ++++++++++++++++--------------------------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/frappe/oauth.py b/frappe/oauth.py index 5e170db5d2..67595555f9 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -2,13 +2,14 @@ import base64 import datetime import hashlib import re -from http import cookies -from urllib.parse import unquote, urljoin, urlparse +from urllib.parse import urljoin, urlparse +from oauthlib.common import Request from oauthlib.openid import RequestValidator import frappe from frappe.auth import LoginManager +from frappe.integrations.doctype.oauth_client.oauth_client import OAuthClient from frappe.utils.data import cstr, get_system_timezone, now_datetime @@ -73,13 +74,11 @@ class OAuthWebRequestValidator(RequestValidator): # Post-authorization def save_authorization_code(self, client_id, code, request, *args, **kwargs): - cookie_dict = get_cookie_dict_from_headers(request) - oac = frappe.new_doc("OAuth Authorization Code") oac.scopes = get_url_delimiter().join(request.scopes) oac.redirect_uri_bound_to_authorization_code = request.redirect_uri oac.client = client_id - oac.user = unquote(cookie_dict["user_id"].value) + oac.user = frappe.session.user oac.authorization_code = code["code"] if request.nonce: @@ -92,43 +91,32 @@ class OAuthWebRequestValidator(RequestValidator): oac.save(ignore_permissions=True) frappe.db.commit() - def authenticate_client(self, request, *args, **kwargs): + def authenticate_client(self, request: Request, *args, **kwargs) -> bool | None: + """ + Loads the client based on request parameters and sets in oauth request. + Returns True on success, None on error. + """ # Get ClientID in URL if request.client_id: - oc = frappe.get_doc("OAuth Client", request.client_id) + client_name = request.client_id else: # Extract token, instantiate OAuth Bearer Token and use clientid from there. if "refresh_token" in frappe.form_dict: - oc = frappe.get_doc( - "OAuth Client", - frappe.db.get_value( - "OAuth Bearer Token", - {"refresh_token": frappe.form_dict["refresh_token"]}, - "client", - ), - ) + token_filters = {"refresh_token": frappe.form_dict["refresh_token"]} elif "token" in frappe.form_dict: - oc = frappe.get_doc( - "OAuth Client", - frappe.db.get_value("OAuth Bearer Token", frappe.form_dict["token"], "client"), - ) + token_filters = {"name": frappe.form_dict["token"]} else: - oc = frappe.get_doc( - "OAuth Client", - frappe.db.get_value( - "OAuth Bearer Token", - frappe.get_request_header("Authorization").split(" ")[1], - "client", - ), - ) + token_filters = {"name": frappe.get_request_header("Authorization").split(" ")[1]} + + client_name = frappe.db.get_value("OAuth Bearer Token", filters=token_filters, fieldname="client") + + oc: OAuthClient = frappe.get_doc("OAuth Client", client_name) try: request.client = request.client or oc.as_dict() except Exception as e: return generate_json_error_response(e) - cookie_dict = get_cookie_dict_from_headers(request) - user_id = unquote(cookie_dict.get("user_id").value) if "user_id" in cookie_dict else "Guest" - return frappe.session.user == user_id + return True def authenticate_client_id(self, client_id, request, *args, **kwargs): cli_id = frappe.db.get_value("OAuth Client", client_id, "name") @@ -506,13 +494,6 @@ class OAuthWebRequestValidator(RequestValidator): return True -def get_cookie_dict_from_headers(r): - cookie = cookies.BaseCookie() - if r.headers.get("Cookie"): - cookie.load(r.headers.get("Cookie")) - return cookie - - def calculate_at_hash(access_token, hash_alg): """Helper method for calculating an access token hash, as described in http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken From ef04bb4cab27e41201781286a31a114a5c399bfe Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 25 Feb 2026 10:45:00 +0530 Subject: [PATCH 19/19] fix: dark theme support for onboarding panel --- .../public/js/frappe/ui/sidebar/sidebar.html | 2 +- .../ui/user_onboarding/OnboardingPanel.vue | 4 +- .../user_onboarding/user_onboarding.bundle.js | 40 +++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.html b/frappe/public/js/frappe/ui/sidebar/sidebar.html index ef5e6d630a..72498d5327 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.html +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.html @@ -43,7 +43,7 @@

{%= frappe.utils.icon("user-check" , "sm", "", "", "text-ink-gray-7 current-color", true)%} - {%= __("Getting started") %} + {%= __("Getting Started") %}

diff --git a/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue b/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue index e34edbf9b5..17dde86cc6 100644 --- a/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue +++ b/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue @@ -264,10 +264,10 @@ function markReset(step) {
- {{ __("Reset all") }} + {{ __("Reset All") }}
- Skip all + {{ __("Skip All") }}
diff --git a/frappe/public/js/frappe/ui/user_onboarding/user_onboarding.bundle.js b/frappe/public/js/frappe/ui/user_onboarding/user_onboarding.bundle.js index 86a994519e..c75170d28b 100644 --- a/frappe/public/js/frappe/ui/user_onboarding/user_onboarding.bundle.js +++ b/frappe/public/js/frappe/ui/user_onboarding/user_onboarding.bundle.js @@ -215,6 +215,46 @@ function addStyles() { color: #6b7280; font-size: 14px; } + + [data-theme="dark"] .onb-panel { + background-color: #232323; + color: #e5e7eb; + box-shadow: 0 12px 40px rgba(0,0,0,0.6); + } + + [data-theme="dark"] .text-base { + color: #e5e7eb; + } + + [data-theme="dark"] .onb-skip { + color: #9ca3af; + } + + [data-theme="dark"] .onb-skip:hover { + color: #f3f4f6; + } + + [data-theme="dark"] .onb-title-steps, + [data-theme="dark"] .onb-progress-text { + color: #9ca3af; + } + + [data-theme="dark"] .onb-group:hover { + background: #374151; + color: #f3f4f6; + } + + [data-theme="dark"] .onb-progress-badge { + background: rgba(245,158,11,0.15); + color: #fbbf24; + } + + [data-theme="dark"] .onb-progress-badge-complete { + background: rgba(16,185,129,0.15); + color: #34d399; + } + + `; document.head.appendChild(style);