Skip to content

Commit

Permalink
Merge pull request #22317 from frappe/version-13-hotfix
Browse files Browse the repository at this point in the history
chore: release v13
  • Loading branch information
ankush committed Sep 6, 2023
2 parents 0acad30 + e6dabc7 commit bfbcf8a
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 98 deletions.
19 changes: 2 additions & 17 deletions frappe/__init__.py
Expand Up @@ -2085,24 +2085,9 @@ def bold(text):
def safe_eval(code, eval_globals=None, eval_locals=None):
"""A safer `eval`"""

from frappe.utils.safe_exec import UNSAFE_ATTRIBUTES
from frappe.utils.safe_exec import safe_eval

whitelisted_globals = {"int": int, "float": float, "long": int, "round": round}
code = unicodedata.normalize("NFKC", code)

for attribute in UNSAFE_ATTRIBUTES:
if attribute in code:
throw('Illegal rule {0}. Cannot use "{1}"'.format(bold(code), attribute))

if "__" in code:
throw('Illegal rule {0}. Cannot use "__"'.format(bold(code)))

if not eval_globals:
eval_globals = {}

eval_globals["__builtins__"] = {}
eval_globals.update(whitelisted_globals)
return eval(code, eval_globals, eval_locals)
return safe_eval(code, eval_globals, eval_locals)


def get_system_settings(key, ignore_if_not_exists=False):
Expand Down
5 changes: 2 additions & 3 deletions frappe/commands/utils.py
Expand Up @@ -244,9 +244,8 @@ def execute(context, method, args=None, kwargs=None, profile=False):
try:
ret = frappe.get_attr(method)(*args, **kwargs)
except Exception:
ret = frappe.safe_eval(
method + "(*args, **kwargs)", eval_globals=globals(), eval_locals=locals()
)
# eval is safe here because input is from console
ret = eval(method + "(*args, **kwargs)", globals(), locals()) # nosemgrep

if profile:
import pstats
Expand Down
4 changes: 2 additions & 2 deletions frappe/database/database.py
Expand Up @@ -254,9 +254,9 @@ def log_query(self, query, values, debug, explain):

# info
if (frappe.conf.get("logging") or False) == 2:
frappe.log("<<<< query")
frappe.log("#### query")
frappe.log(self.mogrify(query, values))
frappe.log(">>>>")
frappe.log("####")

def mogrify(self, query, values):
"""build the query string with values"""
Expand Down
24 changes: 15 additions & 9 deletions frappe/public/js/frappe/form/formatters.js
Expand Up @@ -135,16 +135,22 @@ frappe.form.formatters = {
if(value[0] == "'" && value[value.length -1] == "'") {
return value.substring(1, value.length - 1);
}
if(docfield && docfield.link_onclick) {
return repl('<a onclick="%(onclick)s">%(value)s</a>',
{onclick: docfield.link_onclick.replace(/"/g, '&quot;'), value:value});
} else if(docfield && doctype) {
if (docfield && docfield.link_onclick) {
return repl('<a onclick="%(onclick)s" href="#">%(value)s</a>', {
onclick: docfield.link_onclick.replace(/"/g, "&quot;") + "; return false;",
value: value,
});
} else if (docfield && doctype) {
if (frappe.model.can_read(doctype)) {
return `<a
href="/app/${encodeURIComponent(frappe.router.slug(doctype))}/${encodeURIComponent(original_value)}"
data-doctype="${doctype}"
data-name="${original_value}">
${__(options && options.label || value)}</a>`;
const a = document.createElement("a");
a.href = `/app/${encodeURIComponent(
frappe.router.slug(doctype)
)}/${encodeURIComponent(original_value)}`;
a.dataset.doctype = doctype;
a.dataset.name = original_value;
a.dataset.value = original_value;
a.innerText = __((options && options.label) || value);
return a.outerHTML;
} else {
return value;
}
Expand Down
65 changes: 39 additions & 26 deletions frappe/public/js/frappe/list/list_view.js
Expand Up @@ -899,7 +899,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
return this.settings.get_form_link(doc);
}

return `/app/${frappe.router.slug(frappe.router.doctype_layout || this.doctype)}/${encodeURIComponent(doc.name)}`;
return `/app/${encodeURIComponent(
frappe.router.slug(frappe.router.doctype_layout || this.doctype)
)}/${encodeURIComponent(cstr(doc.name))}`;
}

get_seen_class(doc) {
Expand All @@ -910,20 +912,28 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {

get_like_html(doc) {
const liked_by = JSON.parse(doc._liked_by || "[]");
let heart_class = liked_by.includes(frappe.session.user)
const heart_class = liked_by.includes(frappe.session.user)
? "liked-by liked"
: "not-liked";
const title = liked_by.map((u) => frappe.user_info(u).fullname).join(", ");

return `<span
class="like-action ${heart_class}"
data-name="${doc.name}" data-doctype="${this.doctype}"
data-liked-by="${encodeURI(doc._liked_by) || "[]"}"
title="${liked_by.map(u => frappe.user_info(u).fullname).join(', ')}">
${frappe.utils.icon('heart', 'sm', 'like-icon')}
</span>
<span class="likes-count">
${liked_by.length > 99 ? __("99") + "+" : __(liked_by.length || "")}
</span>`;
const div = document.createElement("div");
div.innerHTML = `
<span class="like-action ${heart_class}">
${frappe.utils.icon("heart", "sm", "like-icon")}
</span>
<span class="likes-count">
${liked_by.length > 99 ? __("99") + "+" : __(liked_by.length || "")}
</span>
`;

const like = div.querySelector(".like-action");
like.setAttribute("data-liked-by", doc._liked_by || "[]");
like.setAttribute("data-doctype", this.doctype);
like.setAttribute("data-name", doc.name);
like.setAttribute("title", title);

return div.innerHTML;
}

get_subject_html(doc) {
Expand All @@ -936,31 +946,34 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
if (!value) {
value = doc.name;
}
let subject = strip_html(value.toString());
let escaped_subject = frappe.utils.escape_html(subject);

const seen = this.get_seen_class(doc);

let subject_html = `
const div = document.createElement("div");
div.innerHTML = `
<span class="level-item select-like">
<input class="list-row-checkbox" type="checkbox"
data-name="${escape(doc.name)}">
<input class="list-row-checkbox" type="checkbox">
<span class="list-row-like hidden-xs style="margin-bottom: 1px;">
${this.get_like_html(doc)}
</span>
</span>
<span class="level-item ${seen} ellipsis" title="${escaped_subject}">
<a class="ellipsis"
href="${this.get_form_link(doc)}"
title="${escaped_subject}"
data-doctype="${this.doctype}"
data-name="${doc.name}">
${subject}
</a>
<span class="level-item ${seen} ellipsis">
<a class="ellipsis"></a>
</span>
`;

return subject_html;
const checkbox = div.querySelector(".list-row-checkbox");
checkbox.dataset.doctype = this.doctype;
checkbox.dataset.name = doc.name;

const link = div.querySelector(".level-item a");
link.dataset.doctype = this.doctype;
link.dataset.name = doc.name;
link.href = this.get_form_link(doc);
link.title = value.toString();
link.textContent = value.toString();

return div.innerHTML;
}

get_indicator_html(doc) {
Expand Down
15 changes: 6 additions & 9 deletions frappe/public/js/frappe/ui/like.js
Expand Up @@ -32,15 +32,12 @@ frappe.ui.toggle_like = function($btn, doctype, name, callback) {
// renable click
$btn.css('pointer-events', 'auto');

if(!r.exc) {
// update in all local-buttons
var action_buttons = $('.like-action[data-name="'+ name.replace(/"/g, '\"')
+'"][data-doctype="'+ doctype.replace(/"/g, '\"')+'"]');

if(add==="Yes") {
action_buttons.removeClass("not-liked").addClass("liked");
} else {
action_buttons.addClass("not-liked").removeClass("liked");
if (!r.exc) {
for (const like of document.querySelectorAll(".like-action")) {
if (like.dataset.name === name && like.dataset.doctype === doctype) {
like.classList.toggle("not-liked", add === "No");
like.classList.toggle("liked", add === "Yes");
}
}

// update in locals (form)
Expand Down
22 changes: 15 additions & 7 deletions frappe/public/js/frappe/ui/link_preview.js
Expand Up @@ -170,12 +170,13 @@ frappe.ui.LinkPreview = class {
this.href = this.href.replace(new RegExp(' ', 'g'), '%20');
}

let popover_content =`
const div = document.createElement("div");
div.innerHTML = `
<div class="preview-popover-header">
<div class="preview-header">
${this.get_image_html(preview_data)}
<div class="preview-name">
<a href=${this.href}>${__(preview_data.preview_title)}</a>
<a></a>
</div>
<div class="text-muted preview-title">${this.get_id_html(preview_data)}</div>
</div>
Expand All @@ -186,16 +187,23 @@ frappe.ui.LinkPreview = class {
</div>
`;

return popover_content;
const a = div.querySelector(".preview-name > a");
a.href = this.href;
a.innerText = __(preview_data.preview_title);

return div.innerHTML;

}

get_id_html(preview_data) {
let id_html = '';
if (preview_data.preview_title !== preview_data.name) {
id_html = `<a class="text-muted" href=${this.href}>${preview_data.name}</a>`;
const a = document.createElement("a");
a.href = this.href;
a.className = "text-muted";
a.innerText = preview_data.name;
return a.outerHTML;
}

return id_html;
return "";
}

get_image_html(preview_data) {
Expand Down
17 changes: 11 additions & 6 deletions frappe/public/js/frappe/utils/common.js
Expand Up @@ -35,12 +35,12 @@ frappe.get_avatar = function(css_class, title, image_url=null, remove_color, dat
if (!css_class) {
css_class = "avatar-small";
}
let el = document.createElement("div");

if (image_url) {
const image = (window.cordova && image_url.indexOf('http') === -1) ? frappe.base_url + image_url : image_url;
return `<span class="avatar ${css_class}" title="${title}" ${data_attributes}>
<span class="avatar-frame" style='background-image: url("${image}")'
title="${title}"></span>
el.innerHTML = `
<span class="avatar ${css_class}" ${data_attributes}>
<span class="avatar-frame" style='background-image: url("${image_url}")'</span>
</span>`;
} else {
let abbr = frappe.get_abbr(title);
Expand All @@ -54,13 +54,18 @@ frappe.get_avatar = function(css_class, title, image_url=null, remove_color, dat
abbr = abbr.substr(0, 1);
}

return `<span class="avatar ${css_class}" title="${title}" ${data_attributes}>
el.innerHTML = `<span class="avatar ${css_class}" ${data_attributes}>
<div class="avatar-frame standard-image"
style="${style}">
${abbr}
</div>
</span>`;
}

el.querySelector(".avatar").setAttribute("title", title);
el.querySelector(".avatar-frame").setAttribute("title", title);

return el.innerHTML;
};

frappe.avatar_group = function (users, limit=4, options = {}) {
Expand Down Expand Up @@ -379,4 +384,4 @@ frappe.utils.get_page_view_count = function (route) {
return frappe.call("frappe.website.doctype.web_page_view.web_page_view.get_page_view_count", {
path: route
});
};
};
37 changes: 26 additions & 11 deletions frappe/public/js/frappe/views/breadcrumbs.js
Expand Up @@ -80,8 +80,16 @@ frappe.breadcrumbs = {
},

set_custom_breadcrumbs(breadcrumbs) {
const html = `<li><a href="${breadcrumbs.route}">${breadcrumbs.label}</a></li>`;
this.$breadcrumbs.append(html);
this.append_breadcrumb_element(breadcrumbs.route, breadcrumbs.label);
},

append_breadcrumb_element(route, label) {
const el = document.createElement("li");
const a = document.createElement("a");
a.href = route;
a.innerText = label;
el.appendChild(a);
this.$breadcrumbs.append(el);
},

set_workspace_breadcrumb(breadcrumbs) {
Expand All @@ -91,13 +99,22 @@ frappe.breadcrumbs = {
this.set_workspace(breadcrumbs);
}

if (breadcrumbs.workspace) {
if (!breadcrumbs.module_info.blocked && frappe.visible_modules.includes(breadcrumbs.module_info.module)) {
$(`<li><a href="/app/${frappe.router.slug(breadcrumbs.workspace)}">${__(breadcrumbs.workspace)}</a></li>`)
.appendTo(this.$breadcrumbs);
}
if (!breadcrumbs.workspace) {
return;
}

if (
breadcrumbs.module_info &&
(breadcrumbs.module_info.blocked ||
!frappe.visible_modules.includes(breadcrumbs.module_info.module))
) {
return;
}

this.append_breadcrumb_element(
`/app/${frappe.router.slug(breadcrumbs.workspace)}`,
__(breadcrumbs.workspace)
);
},

set_workspace(breadcrumbs) {
Expand Down Expand Up @@ -143,17 +160,15 @@ frappe.breadcrumbs = {
} else {
route = doctype_route;
}
$(`<li><a href="/app/${route}">${__(doctype)}</a></li>`)
.appendTo(this.$breadcrumbs);
this.append_breadcrumb_element(`/app/${route}`, __(doctype));
}
},

set_form_breadcrumb(breadcrumbs, view) {
const doctype = breadcrumbs.doctype;
const docname = frappe.get_route().slice(2).join("/");
let form_route = `/app/${frappe.router.slug(doctype)}/${docname}`;
$(`<li><a href="${form_route}">${__(docname)}</a></li>`)
.appendTo(this.$breadcrumbs);
this.append_breadcrumb_element(form_route, __(docname));

if (view === "form") {
let last_crumb = this.$breadcrumbs.find('li').last();
Expand Down

0 comments on commit bfbcf8a

Please sign in to comment.