Merge branch 'develop' into auto_repeat_weekdays

This commit is contained in:
Rucha Mahabal 2020-12-11 11:39:42 +05:30 committed by GitHub
commit 6350291dc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 1260 additions and 680 deletions

14
.editorconfig Normal file
View file

@ -0,0 +1,14 @@
# Root editor config file
root = true
# Common settings
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
# python, js indentation settings
[{*.py,*.js}]
indent_style = tab
indent_size = 4

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Community Forum
url: https://discuss.erpnext.com/
about: For general QnA, discussions and community help.

View file

@ -21,8 +21,8 @@ def docs_link_exists(body):
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
if parsed_url.netloc == "github.com":
_, org, repo, _type, ref = parsed_url.path.split('/')
if org == "frappe" and repo in docs_repos:
parts = parsed_url.path.split('/')
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True

View file

@ -44,6 +44,20 @@ frappe.ui.form.on('Auto Repeat', {
// auto repeat schedule
frappe.auto_repeat.render_schedule(frm);
frm.trigger('toggle_submit_on_creation');
},
reference_doctype: function(frm) {
frm.trigger('toggle_submit_on_creation');
},
toggle_submit_on_creation: function(frm) {
// submit on creation checkbox
frappe.model.with_doctype(frm.doc.reference_doctype, () => {
let meta = frappe.get_meta(frm.doc.reference_doctype);
frm.toggle_display('submit_on_creation', meta.is_submittable);
});
},
template: function(frm) {

View file

@ -13,6 +13,7 @@
"section_break_3",
"reference_doctype",
"reference_document",
"submit_on_creation",
"column_break_5",
"start_date",
"end_date",
@ -201,10 +202,16 @@
"depends_on": "eval:doc.frequency==='Weekly';",
"fieldname": "section_break_12",
"fieldtype": "Section Break"
},
{
"default": "0",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"label": "Submit on Creation"
}
],
"links": [],
"modified": "2020-11-10 22:44:51.815740",
"modified": "2020-12-10 10:43:13.449172",
"modified_by": "Administrator",
"module": "Automation",
"name": "Auto Repeat",

View file

@ -24,6 +24,7 @@ class AutoRepeat(Document):
def validate(self):
self.update_status()
self.validate_reference_doctype()
self.validate_submit_on_creation()
self.validate_dates()
self.validate_email_id()
self.validate_auto_repeat_days()
@ -64,6 +65,11 @@ class AutoRepeat(Document):
if not frappe.get_meta(self.reference_doctype).allow_auto_repeat:
frappe.throw(_("Enable Allow Auto Repeat for the doctype {0} in Customize Form").format(self.reference_doctype))
def validate_submit_on_creation(self):
if self.submit_on_creation and not frappe.get_meta(self.reference_doctype).is_submittable:
frappe.throw(_('Cannot enable {0} for a non-submittable doctype').format(
frappe.bold('Submit on Creation')))
def validate_dates(self):
if frappe.flags.in_patch:
return
@ -160,6 +166,9 @@ class AutoRepeat(Document):
self.update_doc(new_doc, reference_doc)
new_doc.insert(ignore_permissions = True)
if self.submit_on_creation:
new_doc.submit()
return new_doc
def update_doc(self, new_doc, reference_doc):
@ -170,7 +179,7 @@ class AutoRepeat(Document):
if new_doc.meta.get_field('auto_repeat'):
new_doc.set('auto_repeat', self.name)
for fieldname in ['naming_series', 'ignore_pricing_rule', 'posting_time', 'select_print_heading', 'remarks', 'owner']:
for fieldname in ['naming_series', 'ignore_pricing_rule', 'posting_time', 'select_print_heading', 'user_remark', 'remarks', 'owner']:
if new_doc.meta.get_field(fieldname):
new_doc.set(fieldname, reference_doc.get(fieldname))

View file

@ -158,6 +158,25 @@ class TestAutoRepeat(unittest.TestCase):
doc = make_auto_repeat(frequency='Daily', reference_document=todo.name, start_date=add_days(today(), -2))
self.assertEqual(getdate(doc.next_schedule_date), current_date)
def test_submit_on_creation(self):
doctype = 'Test Submittable DocType'
create_submittable_doctype(doctype)
current_date = getdate()
submittable_doc = frappe.get_doc(dict(doctype=doctype, test='test submit on creation')).insert()
submittable_doc.submit()
doc = make_auto_repeat(frequency='Daily', reference_doctype=doctype, reference_document=submittable_doc.name,
start_date=add_days(current_date, -1), submit_on_creation=1)
data = get_auto_repeat_entries(current_date)
create_repeated_entries(data)
docnames = frappe.db.get_all(doc.reference_doctype,
filters={'auto_repeat': doc.name},
fields=['docstatus'],
limit=1
)
self.assertEquals(docnames[0].docstatus, 1)
def make_auto_repeat(**args):
args = frappe._dict(args)
@ -165,6 +184,7 @@ def make_auto_repeat(**args):
'doctype': 'Auto Repeat',
'reference_doctype': args.reference_doctype or 'ToDo',
'reference_document': args.reference_document or frappe.db.get_value('ToDo', 'name'),
'submit_on_creation': args.submit_on_creation or 0,
'frequency': args.frequency or 'Daily',
'start_date': args.start_date or add_days(today(), -1),
'end_date': args.end_date or "",
@ -176,3 +196,34 @@ def make_auto_repeat(**args):
}).insert(ignore_permissions=True)
return doc
def create_submittable_doctype(doctype):
if frappe.db.exists('DocType', doctype):
return
else:
doc = frappe.get_doc({
'doctype': 'DocType',
'__newname': doctype,
'module': 'Custom',
'custom': 1,
'is_submittable': 1,
'fields': [{
'fieldname': 'test',
'label': 'Test',
'fieldtype': 'Data'
}],
'permissions': [{
'role': 'System Manager',
'read': 1,
'write': 1,
'create': 1,
'delete': 1,
'submit': 1,
'cancel': 1,
'amend': 1
}]
}).insert()
doc.allow_auto_repeat = 1
doc.save()

View file

@ -1,27 +1,21 @@
from __future__ import unicode_literals
# imports - standard imports
import json
from collections.abc import MutableMapping, MutableSequence, Sequence
# imports - third-party imports
import requests
# imports - compatibility imports
import six
# imports - standard imports
from collections import Sequence, MutableSequence, Mapping, MutableMapping
if six.PY2:
from urlparse import urlparse # PY2
else:
from urllib.parse import urlparse # PY3
import json
from urllib.parse import urlparse
# imports - module imports
from frappe.model.document import Document
from frappe.exceptions import DuplicateEntryError
from frappe import _dict
import frappe
from frappe.exceptions import DuplicateEntryError
from frappe.model.document import Document
session = frappe.session
def get_user_doc(user = None):
if isinstance(user, Document):
return user
@ -38,12 +32,12 @@ def squashify(what):
return what
def safe_json_loads(*args):
results = [ ]
results = []
for arg in args:
try:
arg = json.loads(arg)
except Exception as e:
except Exception:
pass
results.append(arg)
@ -81,7 +75,7 @@ def dictify(arg):
for i, a in enumerate(arg):
arg[i] = dictify(a)
elif isinstance(arg, MutableMapping):
arg = _dict(arg)
arg = frappe._dict(arg)
return arg
@ -113,4 +107,4 @@ def get_emojis():
emojis = resp.json()
redis.hset('frappe_emojis', 'emojis', emojis)
return dictify(emojis)
return dictify(emojis)

View file

@ -100,13 +100,11 @@ def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_pas
# Extract public and/or private files to the restored site, if user has given the path
if with_public_files:
with_public_files = os.path.join(base_path, with_public_files)
public = extract_files(site, with_public_files, 'public')
public = extract_files(site, with_public_files)
os.remove(public)
if with_private_files:
with_private_files = os.path.join(base_path, with_private_files)
private = extract_files(site, with_private_files, 'private')
private = extract_files(site, with_private_files)
os.remove(private)
# Removing temporarily created file

View file

@ -394,7 +394,10 @@ class DocType(Document):
frappe.db.sql("""update tabSingles set value=%s
where doctype=%s and field='name' and value = %s""", (new, new, old))
else:
frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new))
frappe.db.multisql({
"mariadb": f"RENAME TABLE `tab{old}` TO `tab{new}`",
"postgres": f"ALTER TABLE `tab{old}` RENAME TO `tab{new}`"
})
def rename_files_and_folders(self, old, new):
# move files

View file

@ -18,6 +18,9 @@ frappe.ui.form.on('Domain Settings', {
checked: active_domains.includes(domain)
};
});
},
on_change: () => {
frm.dirty();
}
},
render_input: true

View file

@ -30,7 +30,7 @@ import frappe
from frappe import _, conf
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, cstr, encode, get_files_path, get_hook_method, random_string, strip
from frappe.utils.image import strip_exif_data
class MaxFileSizeReachedError(frappe.ValidationError):
pass
@ -456,6 +456,7 @@ class File(Document):
def save_file(self, content=None, decode=False, ignore_existing_file_check=False):
file_exists = False
self.content = content
if decode:
if isinstance(content, text_type):
self.content = content.encode("utf-8")
@ -466,10 +467,19 @@ class File(Document):
if not self.is_private:
self.is_private = 0
self.file_size = self.check_max_file_size()
self.content_hash = get_content_hash(self.content)
self.content_type = mimetypes.guess_type(self.file_name)[0]
self.file_size = self.check_max_file_size()
if (
self.content_type and "image" in self.content_type
and frappe.get_system_settings("strip_exif_metadata_from_uploaded_images")
):
self.content = strip_exif_data(self.content, self.content_type)
self.content_hash = get_content_hash(self.content)
duplicate_file = None
# check if a file exists with the same content hash and is also in the same folder (public or private)
@ -633,7 +643,12 @@ def get_extension(filename, extn, content):
return extn
def get_local_image(file_url):
file_path = frappe.get_site_path("public", file_url.lstrip("/"))
if file_url.startswith("/private"):
file_url_path = (file_url.lstrip("/"), )
else:
file_url_path = ("public", file_url.lstrip("/"))
file_path = frappe.get_site_path(*file_url_path)
try:
image = Image.open(file_path)

View file

@ -48,29 +48,33 @@ frappe.ui.form.on('Server Script', {
setup_help(frm) {
frm.get_field('help_html').html(`
<h3>Examples</h3>
<h4>DocType Event</h4>
<pre><code>
<p>Add logic for standard doctype events like Before Insert, After Submit, etc.</p>
<pre>
<code>
# set property
if "test" in doc.description:
doc.status = 'Closed'
doc.status = 'Closed'
# validate
if "validate" in doc.description:
raise frappe.ValidationError
raise frappe.ValidationError
# auto create another document
if doc.allocted_to:
frappe.get_doc(dict(
doctype = 'ToDo'
owner = doc.allocated_to,
description = doc.subject
)).insert()
</code></pre>
if doc.allocated_to:
frappe.get_doc(dict(
doctype = 'ToDo'
owner = doc.allocated_to,
description = doc.subject
)).insert()
</code>
</pre>
<hr>
<h4>API Call</h4>
<p>Respond to <code>/api/method/&lt;method-name&gt;</code> calls, just like whitelisted methods</p>
<pre><code>
# respond to API
@ -79,6 +83,21 @@ if frappe.form_dict.message == "ping":
else:
frappe.response['message'] = "ok"
</code></pre>
<hr>
<h4>Permission Query</h4>
<p>Add conditions to the where clause of list queries.</p>
<pre><code>
# generate dynamic conditions and set it in the conditions variable
tenant_id = frappe.db.get_value(...)
conditions = 'tenant_id = {}'.format(tenant_id)
# resulting select query
select name from \`tabPerson\`
where tenant_id = 2
order by creation desc
</code></pre>
`);
}

View file

@ -24,7 +24,7 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Script Type",
"options": "DocType Event\nScheduler Event\nAPI",
"options": "DocType Event\nScheduler Event\nPermission Query\nAPI",
"reqd": 1
},
{
@ -35,7 +35,7 @@
"reqd": 1
},
{
"depends_on": "eval:doc.script_type==='DocType Event'",
"depends_on": "eval:['DocType Event', 'Permission Query'].includes(doc.script_type)",
"fieldname": "reference_doctype",
"fieldtype": "Link",
"in_list_view": 1,
@ -88,7 +88,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-11-11 12:39:41.391052",
"modified": "2020-12-03 22:42:02.708148",
"modified_by": "Administrator",
"module": "Core",
"name": "Server Script",

View file

@ -4,6 +4,8 @@
from __future__ import unicode_literals
import ast
import frappe
from frappe.model.document import Document
from frappe.utils.safe_exec import safe_exec
@ -11,9 +13,9 @@ from frappe import _
class ServerScript(Document):
@staticmethod
def validate():
def validate(self):
frappe.only_for('Script Manager', True)
ast.parse(self.script)
@staticmethod
def on_update():
@ -41,6 +43,12 @@ class ServerScript(Document):
# wrong report type!
raise frappe.DoesNotExistError
def get_permission_query_conditions(self, user):
locals = {"user": user, "conditions": ""}
safe_exec(self.script, None, locals)
if locals["conditions"]:
return locals["conditions"]
@frappe.whitelist()
def setup_scheduler_events(script_name, frequency):
method = frappe.scrub('{0}-{1}'.format(script_name, frequency))

View file

@ -50,6 +50,9 @@ def get_server_script_map():
# },
# '_api': {
# '[path]': '[server script]'
# },
# 'permission_query': {
# 'DocType': '[server script]'
# }
# }
if frappe.flags.in_patch and not frappe.db.table_exists('Server Script'):
@ -57,16 +60,20 @@ def get_server_script_map():
script_map = frappe.cache().get_value('server_script_map')
if script_map is None:
script_map = {}
script_map = {
'permission_query': {}
}
enabled_server_scripts = frappe.get_all('Server Script',
fields=('name', 'reference_doctype', 'doctype_event','api_method', 'script_type'),
filters={'disabled': 0})
for script in enabled_server_scripts:
if script.script_type == 'DocType Event':
script_map.setdefault(script.reference_doctype, {}).setdefault(script.doctype_event, []).append(script.name)
elif script.script_type == 'Permission Query':
script_map['permission_query'][script.reference_doctype] = script.name
else:
script_map.setdefault('_api', {})[script.api_method] = script.name
frappe.cache().set_value('server_script_map', script_map)
return script_map
return script_map

View file

@ -45,6 +45,22 @@ frappe.response['message'] = 'hello'
allow_guest = 1,
script = '''
frappe.flags = 'hello'
'''
),
dict(
name='test_permission_query',
script_type = 'Permission Query',
reference_doctype = 'ToDo',
script = '''
conditions = '1 = 1'
'''),
dict(
name='test_invalid_namespace_method',
script_type = 'DocType Event',
doctype_event = 'Before Insert',
reference_doctype = 'Note',
script = '''
frappe.method_that_doesnt_exist("do some magic")
'''
)
]
@ -85,3 +101,12 @@ class TestServerScript(unittest.TestCase):
def test_api_return(self):
self.assertEqual(frappe.get_doc('Server Script', 'test_return_value').execute_method(), 'hello')
def test_permission_query(self):
self.assertTrue('where (1 = 1)' in frappe.db.get_list('ToDo', return_query=1))
self.assertTrue(isinstance(frappe.db.get_list('ToDo'), list))
def test_attribute_error(self):
"""Raise AttributeError if method not found in Namespace"""
note = frappe.get_doc({"doctype": "Note", "title": "Test Note: Server Script"})
self.assertRaises(AttributeError, note.insert)

View file

@ -1,37 +1,36 @@
frappe.ui.form.on("System Settings", "refresh", function(frm) {
frappe.call({
method: "frappe.core.doctype.system_settings.system_settings.load",
callback: function(data) {
frappe.all_timezones = data.message.timezones;
frm.set_df_property("time_zone", "options", frappe.all_timezones);
frappe.ui.form.on("System Settings", {
refresh: function(frm) {
frappe.call({
method: "frappe.core.doctype.system_settings.system_settings.load",
callback: function(data) {
frappe.all_timezones = data.message.timezones;
frm.set_df_property("time_zone", "options", frappe.all_timezones);
$.each(data.message.defaults, function(key, val) {
frm.set_value(key, val);
frappe.sys_defaults[key] = val;
})
$.each(data.message.defaults, function(key, val) {
frm.set_value(key, val);
frappe.sys_defaults[key] = val;
});
}
});
},
enable_password_policy: function(frm) {
if (frm.doc.enable_password_policy == 0) {
frm.set_value("minimum_password_score", "");
} else {
frm.set_value("minimum_password_score", "2");
}
});
});
frappe.ui.form.on("System Settings", "enable_password_policy", function(frm) {
if(frm.doc.enable_password_policy == 0){
frm.set_value("minimum_password_score", "");
} else {
frm.set_value("minimum_password_score", "2");
}
});
frappe.ui.form.on("System Settings", "enable_two_factor_auth", function(frm) {
if(frm.doc.enable_two_factor_auth == 0){
frm.set_value("bypass_2fa_for_retricted_ip_users", 0);
frm.set_value("bypass_restrict_ip_check_if_2fa_enabled", 0);
}
});
frappe.ui.form.on("System Settings", "enable_prepared_report_auto_deletion", function(frm) {
if (frm.doc.enable_prepared_report_auto_deletion) {
if (!frm.doc.prepared_report_expiry_period) {
frm.set_value('prepared_report_expiry_period', 7);
},
enable_two_factor_auth: function(frm) {
if (frm.doc.enable_two_factor_auth == 0) {
frm.set_value("bypass_2fa_for_retricted_ip_users", 0);
frm.set_value("bypass_restrict_ip_check_if_2fa_enabled", 0);
}
},
enable_prepared_report_auto_deletion: function(frm) {
if (frm.doc.enable_prepared_report_auto_deletion) {
if (!frm.doc.prepared_report_expiry_period) {
frm.set_value('prepared_report_expiry_period', 7);
}
}
}
});

View file

@ -37,6 +37,7 @@
"allow_login_using_mobile_number",
"allow_login_using_user_name",
"allow_error_traceback",
"strip_exif_metadata_from_uploaded_images",
"password_settings",
"logout_on_password_reset",
"force_user_to_reset_password",
@ -460,12 +461,18 @@
"fieldname": "prepared_report_section",
"fieldtype": "Section Break",
"label": "Prepared Report"
},
{
"default": "1",
"fieldname": "strip_exif_metadata_from_uploaded_images",
"fieldtype": "Check",
"label": "Strip EXIF tags from uploaded images"
}
],
"icon": "fa fa-cog",
"issingle": 1,
"links": [],
"modified": "2020-08-12 14:35:45.214327",
"modified": "2020-11-30 18:52:22.161391",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",

View file

@ -81,6 +81,11 @@ frappe.ui.form.on("Customize Form", {
} else {
f._sortable = false;
}
if (f.fieldtype == "Table") {
frm.add_custom_button(f.options, function() {
frm.set_value('doc_type', f.options);
}, __('Customize Child Table'));
}
});
frm.fields_dict.fields.grid.refresh();
},

View file

@ -39,7 +39,7 @@ class CustomizeForm(Document):
translation = self.get_name_translation()
self.label = translation.translated_text if translation else ''
self.create_auto_repeat_custom_field_if_requried(meta)
self.create_auto_repeat_custom_field_if_required(meta)
# NOTE doc (self) is sent to clientside by run_method
@ -74,19 +74,25 @@ class CustomizeForm(Document):
for d in meta.get(fieldname):
self.append(fieldname, d)
def create_auto_repeat_custom_field_if_requried(self, meta):
def create_auto_repeat_custom_field_if_required(self, meta):
'''
Create auto repeat custom field if it's not already present
'''
if self.allow_auto_repeat:
if not frappe.db.exists('Custom Field', {'fieldname': 'auto_repeat', 'dt': self.doc_type}) and \
not frappe.db.exists('DocField', {'fieldname': 'auto_repeat', 'parent': self.name}):
insert_after = self.fields[len(self.fields) - 1].fieldname
df = dict(
fieldname='auto_repeat',
label='Auto Repeat',
fieldtype='Link',
options='Auto Repeat',
insert_after=insert_after,
read_only=1, no_copy=1, print_hide=1)
create_custom_field(self.doc_type, df)
all_fields = [df.fieldname for df in meta.fields]
if "auto_repeat" in all_fields:
return
insert_after = self.fields[len(self.fields) - 1].fieldname
create_custom_field(self.doc_type, dict(
fieldname='auto_repeat',
label='Auto Repeat',
fieldtype='Link',
options='Auto Repeat',
insert_after=insert_after,
read_only=1, no_copy=1, print_hide=1
))
def get_name_translation(self):

View file

@ -233,7 +233,7 @@ CREATE TABLE `tabDocType` (
DROP TABLE IF EXISTS `tabSeries`;
CREATE TABLE `tabSeries` (
`name` varchar(100) DEFAULT NULL,
`name` varchar(100),
`current` int(10) NOT NULL DEFAULT 0,
PRIMARY KEY(`name`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View file

@ -5,6 +5,7 @@
from __future__ import unicode_literals
from frappe.model.document import Document
from frappe.modules.export_file import export_to_files
from frappe.config import get_modules_from_all_apps_for_user
import frappe
from frappe import _
import json
@ -42,6 +43,24 @@ class Dashboard(Document):
except ValueError as error:
frappe.throw(_("Invalid json added in the custom options: {0}").format(error))
def get_permission_query_conditions(user):
if not user:
user = frappe.session.user
if user == 'Administrator':
return
roles = frappe.get_roles(user)
if "System Manager" in roles:
return None
allowed_modules = [frappe.db.escape(module.get('module_name')) for module in get_modules_from_all_apps_for_user()]
module_condition = '`tabDashboard`.`module` in ({allowed_modules}) or `tabDashboard`.`module` is NULL'.format(
allowed_modules=','.join(allowed_modules))
return module_condition
@frappe.whitelist()
def get_permitted_charts(dashboard_name):
permitted_charts = []

View file

@ -7,17 +7,18 @@ import frappe
from frappe import _
import datetime
import json
from frappe.utils.dashboard import cache_source, get_from_date_from_timespan
from frappe.utils import nowdate, add_to_date, getdate, get_last_day, formatdate,\
get_datetime, cint, now_datetime
from frappe.utils.dashboard import cache_source
from frappe.utils import nowdate, getdate, get_datetime, cint, now_datetime
from frappe.utils.dateutils import\
get_period, get_period_beginning, get_from_date_from_timespan, get_dates_from_timegrain
from frappe.model.naming import append_number_if_name_exists
from frappe.boot import get_allowed_reports
from frappe.config import get_modules_from_all_apps_for_user
from frappe.model.document import Document
from frappe.modules.export_file import export_to_files
def get_permission_query_conditions(user):
if not user:
user = frappe.session.user
@ -30,9 +31,11 @@ def get_permission_query_conditions(user):
doctype_condition = False
report_condition = False
module_condition = False
allowed_doctypes = [frappe.db.escape(doctype) for doctype in frappe.permissions.get_doctypes_with_read()]
allowed_reports = [frappe.db.escape(key) if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()]
allowed_modules = [frappe.db.escape(module.get('module_name')) for module in get_modules_from_all_apps_for_user()]
if allowed_doctypes:
doctype_condition = '`tabDashboard Chart`.`document_type` in ({allowed_doctypes})'.format(
@ -40,18 +43,24 @@ def get_permission_query_conditions(user):
if allowed_reports:
report_condition = '`tabDashboard Chart`.`report_name` in ({allowed_reports})'.format(
allowed_reports=','.join(allowed_reports))
if allowed_modules:
module_condition = '''`tabDashboard Chart`.`module` in ({allowed_modules})
or `tabDashboard Chart`.`module` is NULL'''.format(
allowed_modules=','.join(allowed_modules))
return '''
(`tabDashboard Chart`.`chart_type` in ('Count', 'Sum', 'Average')
and {doctype_condition})
or
(`tabDashboard Chart`.`chart_type` = 'Report'
and {report_condition})
'''.format(
doctype_condition=doctype_condition,
report_condition=report_condition
)
((`tabDashboard Chart`.`chart_type` in ('Count', 'Sum', 'Average')
and {doctype_condition})
or
(`tabDashboard Chart`.`chart_type` = 'Report'
and {report_condition}))
and
({module_condition})
'''.format(
doctype_condition=doctype_condition,
report_condition=report_condition,
module_condition=module_condition
)
def has_permission(doc, ptype, user):
roles = frappe.get_roles(user)
@ -156,6 +165,7 @@ def add_chart_to_dashboard(args):
def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date):
if not from_date:
from_date = get_from_date_from_timespan(to_date, timespan)
from_date = get_period_beginning(from_date, timegrain)
if not to_date:
to_date = now_datetime()
@ -185,7 +195,7 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date):
result = get_result(data, timegrain, from_date, to_date)
chart_config = {
"labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result],
"labels": [get_period(r[0], timegrain) for r in result],
"datasets": [{
"name": chart.name,
"values": [r[1] for r in result]
@ -279,16 +289,8 @@ def get_aggregate_function(chart_type):
def get_result(data, timegrain, from_date, to_date):
start_date = getdate(from_date)
end_date = getdate(to_date)
result = [[start_date, 0.0]]
while start_date < end_date:
next_date = get_next_expected_date(start_date, timegrain)
result.append([next_date, 0.0])
start_date = next_date
dates = get_dates_from_timegrain(from_date, to_date, timegrain)
result = [[date, 0] for date in dates]
data_index = 0
if data:
for i, d in enumerate(result):
@ -298,65 +300,6 @@ def get_result(data, timegrain, from_date, to_date):
return result
def get_next_expected_date(date, timegrain):
next_date = None
# given date is always assumed to be the period ending date
next_date = get_period_ending(add_to_date(date, days=1), timegrain)
return getdate(next_date)
def get_period_ending(date, timegrain):
date = getdate(date)
if timegrain == 'Daily':
pass
elif timegrain == 'Weekly':
date = get_week_ending(date)
elif timegrain == 'Monthly':
date = get_month_ending(date)
elif timegrain == 'Quarterly':
date = get_quarter_ending(date)
elif timegrain == 'Yearly':
date = get_year_ending(date)
return getdate(date)
def get_week_ending(date):
# week starts on monday
from datetime import timedelta
start = date - timedelta(days = date.weekday())
end = start + timedelta(days=6)
return end
def get_month_ending(date):
month_of_the_year = int(date.strftime('%m'))
# first day of next month (note month starts from 1)
date = add_to_date('{}-01-01'.format(date.year), months = month_of_the_year)
# last day of this month
return add_to_date(date, days=-1)
def get_quarter_ending(date):
date = getdate(date)
# find the earliest quarter ending date that is after
# the given date
for month in (3, 6, 9, 12):
quarter_end_month = getdate('{}-{}-01'.format(date.year, month))
quarter_end_date = getdate(get_last_day(quarter_end_month))
if date <= quarter_end_date:
date = quarter_end_date
break
return date
def get_year_ending(date):
''' returns year ending of the given date '''
# first day of next year (note year starts from 1)
date = add_to_date('{}-01-01'.format(date.year), months = 12)
# last day of this month
return add_to_date(date, days=-1)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_charts_for_user(doctype, txt, searchfield, start, page_len, filters):

View file

@ -5,8 +5,8 @@ from __future__ import unicode_literals
import unittest, frappe
from frappe.utils import getdate, formatdate, get_last_day
from frappe.desk.doctype.dashboard_chart.dashboard_chart import (get,
get_period_ending)
from frappe.utils.dateutils import get_period_ending, get_period
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get
from datetime import datetime
from dateutil.relativedelta import relativedelta
@ -53,15 +53,11 @@ class TestDashboardChart(unittest.TestCase):
cur_date = datetime.now() - relativedelta(years=1)
result = get(chart_name='Test Dashboard Chart', refresh=1)
self.assertEqual(result.get('labels')[0], formatdate(cur_date.strftime('%Y-%m-%d')))
if formatdate(cur_date.strftime('%Y-%m-%d')) == formatdate(get_last_day(cur_date).strftime('%Y-%m-%d')):
cur_date += relativedelta(months=1)
for idx in range(1, 13):
for idx in range(13):
month = get_last_day(cur_date)
month = formatdate(month.strftime('%Y-%m-%d'))
self.assertEqual(result.get('labels')[idx], month)
self.assertEqual(result.get('labels')[idx], get_period(month))
cur_date += relativedelta(months=1)
frappe.db.rollback()
@ -87,15 +83,11 @@ class TestDashboardChart(unittest.TestCase):
cur_date = datetime.now() - relativedelta(years=1)
result = get(chart_name ='Test Empty Dashboard Chart', refresh=1)
self.assertEqual(result.get('labels')[0], formatdate(cur_date.strftime('%Y-%m-%d')))
if formatdate(cur_date.strftime('%Y-%m-%d')) == formatdate(get_last_day(cur_date).strftime('%Y-%m-%d')):
cur_date += relativedelta(months=1)
for idx in range(1, 13):
for idx in range(13):
month = get_last_day(cur_date)
month = formatdate(month.strftime('%Y-%m-%d'))
self.assertEqual(result.get('labels')[idx], month)
self.assertEqual(result.get('labels')[idx], get_period(month))
cur_date += relativedelta(months=1)
frappe.db.rollback()
@ -124,15 +116,11 @@ class TestDashboardChart(unittest.TestCase):
cur_date = datetime.now() - relativedelta(years=1)
result = get(chart_name ='Test Empty Dashboard Chart 2', refresh = 1)
self.assertEqual(result.get('labels')[0], formatdate(cur_date.strftime('%Y-%m-%d')))
if formatdate(cur_date.strftime('%Y-%m-%d')) == formatdate(get_last_day(cur_date).strftime('%Y-%m-%d')):
cur_date += relativedelta(months=1)
for idx in range(1, 13):
for idx in range(13):
month = get_last_day(cur_date)
month = formatdate(month.strftime('%Y-%m-%d'))
self.assertEqual(result.get('labels')[idx], month)
self.assertEqual(result.get('labels')[idx], get_period(month))
cur_date += relativedelta(months=1)
# only 1 data point with value
@ -183,13 +171,12 @@ class TestDashboardChart(unittest.TestCase):
timeseries = 1
)).insert()
result = get(chart_name ='Test Daily Dashboard Chart', refresh = 1)
result = get(chart_name = 'Test Daily Dashboard Chart', refresh = 1)
self.assertEqual(result.get('datasets')[0].get('values'), [200.0, 400.0, 300.0, 0.0, 100.0, 0.0])
self.assertEqual(
result.get('labels'),
[formatdate('2019-01-06'), formatdate('2019-01-07'), formatdate('2019-01-08'),\
formatdate('2019-01-09'), formatdate('2019-01-10'), formatdate('2019-01-11')]
['06-01-19', '07-01-19', '08-01-19', '09-01-19', '10-01-19', '11-01-19']
)
frappe.db.rollback()
@ -218,7 +205,10 @@ class TestDashboardChart(unittest.TestCase):
result = get(chart_name ='Test Weekly Dashboard Chart', refresh = 1)
self.assertEqual(result.get('datasets')[0].get('values'), [50.0, 300.0, 800.0, 0.0])
self.assertEqual(result.get('labels'), [formatdate('2018-12-30'), formatdate('2019-01-06'), formatdate('2019-01-13'), formatdate('2019-01-20')])
self.assertEqual(
result.get('labels'),
['30-12-18', '06-01-19', '13-01-19', '20-01-19']
)
frappe.db.rollback()

View file

@ -8,6 +8,7 @@ from frappe.model.document import Document
from frappe.utils import cint
from frappe.model.naming import append_number_if_name_exists
from frappe.modules.export_file import export_to_files
from frappe.config import get_modules_from_all_apps_for_user
class NumberCard(Document):
def autoname(self):
@ -33,16 +34,24 @@ def get_permission_query_conditions(user=None):
return None
doctype_condition = False
module_condition = False
allowed_doctypes = [frappe.db.escape(doctype) for doctype in frappe.permissions.get_doctypes_with_read()]
allowed_modules = [frappe.db.escape(module.get('module_name')) for module in get_modules_from_all_apps_for_user()]
if allowed_doctypes:
doctype_condition = '`tabNumber Card`.`document_type` in ({allowed_doctypes})'.format(
allowed_doctypes=','.join(allowed_doctypes))
if allowed_modules:
module_condition = '''`tabNumber Card`.`module` in ({allowed_modules})
or `tabNumber Card`.`module` is NULL'''.format(
allowed_modules=','.join(allowed_modules))
return '''
{doctype_condition}
'''.format(doctype_condition=doctype_condition)
{doctype_condition}
and
{module_condition}
'''.format(doctype_condition=doctype_condition, module_condition=module_condition)
def has_permission(doc, ptype, user):
roles = frappe.get_roles(user)

View file

@ -21,7 +21,7 @@ def follow_document(doctype, doc_name, user, force=False):
avoided for some doctype
follow only if track changes are set to 1
'''
if (doctype in ("Communication", "ToDo", "Email Unsubscribe", "File", "Comment")
if (doctype in ("Communication", "ToDo", "Email Unsubscribe", "File", "Comment", "Email Account", "Email Domain")
or doctype in log_types):
return

View file

@ -1,4 +1,5 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "Prompt",
@ -8,6 +9,8 @@
"engine": "InnoDB",
"field_order": [
"subject",
"use_html",
"response_html",
"response",
"owner",
"section_break_4",
@ -22,11 +25,12 @@
"reqd": 1
},
{
"depends_on": "eval:!doc.use_html",
"fieldname": "response",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Response",
"reqd": 1
"mandatory_depends_on": "eval:!doc.use_html"
},
{
"default": "user",
@ -45,10 +49,24 @@
"fieldtype": "HTML",
"label": "Email Reply Help",
"options": "<h4>Email Reply Example</h4>\n\n<pre>Order Overdue\n\nTransaction {{ name }} has exceeded Due Date. Please take necessary action.\n\nDetails\n\n- Customer: {{ customer }}\n- Amount: {{ grand_total }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup &gt; Customize Form View and selecting the document type (e.g. Sales Invoice)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>\n"
},
{
"default": "0",
"fieldname": "use_html",
"fieldtype": "Check",
"label": "Use HTML"
},
{
"depends_on": "eval:doc.use_html",
"fieldname": "response_html",
"fieldtype": "Code",
"label": "Response ",
"options": "HTML"
}
],
"icon": "fa fa-comment",
"modified": "2019-10-30 14:15:00.956347",
"links": [],
"modified": "2020-11-30 14:12:50.321633",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Template",

View file

@ -9,7 +9,29 @@ from six import string_types
class EmailTemplate(Document):
def validate(self):
validate_template(self.response)
if self.use_html:
validate_template(self.response_html)
else:
validate_template(self.response)
def get_formatted_subject(self, doc):
return frappe.render_template(self.subject, doc)
def get_formatted_response(self, doc):
if self.use_html:
return frappe.render_template(self.response_html, doc)
return frappe.render_template(self.response, doc)
def get_formatted_email(self, doc):
if isinstance(doc, string_types):
doc = json.loads(doc)
return {
"subject" : self.get_formatted_subject(doc),
"message" : self.get_formatted_response(doc)
}
@frappe.whitelist()
def get_email_template(template_name, doc):
@ -18,5 +40,4 @@ def get_email_template(template_name, doc):
doc = json.loads(doc)
email_template = frappe.get_doc("Email Template", template_name)
return {"subject" : frappe.render_template(email_template.subject, doc),
"message" : frappe.render_template(email_template.response, doc)}
return email_template.get_formatted_email(doc)

View file

@ -85,11 +85,11 @@ class Newsletter(WebsiteGenerator):
self.db_set("scheduled_to_send", len(self.recipients))
def get_message(self):
if self.content_type == "HTML":
return frappe.render_template(self.message_html, {"doc": self.as_dict()})
return {
'Rich Text': self.message,
'Markdown': markdown(self.message_md),
'HTML': self.message_html
'Markdown': markdown(self.message_md)
}[self.content_type or 'Rich Text']
def get_recipients(self):

View file

@ -207,7 +207,7 @@
"label": "Value To Be Set"
},
{
"depends_on": "eval:in_list(['Email', 'SMS'], doc.channel)",
"depends_on": "eval:doc.channel !=\"Slack\"",
"fieldname": "column_break_5",
"fieldtype": "Section Break",
"label": "Recipients"
@ -281,7 +281,7 @@
"icon": "fa fa-envelope",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-10-28 11:04:54.955567",
"modified": "2020-11-24 14:25:43.245677",
"modified_by": "Administrator",
"module": "Email",
"name": "Notification",

View file

@ -181,6 +181,7 @@ def get_context(context):
'document_type': doc.doctype,
'document_name': doc.name,
'subject': subject,
'from_user': doc.modified_by or doc.owner,
'email_content': frappe.render_template(self.message, context),
'attached_file': attachments and json.dumps(attachments[0])
}

View file

@ -210,10 +210,9 @@ class SMTPServer:
try:
if self.use_ssl:
if not self.port:
self.smtp_port = 465
self.port = 465
self._sess = smtplib.SMTP_SSL((self.server or "").encode('utf-8'),
cint(self.port) or None)
self._sess = smtplib.SMTP_SSL((self.server or ""), cint(self.port))
else:
if self.use_tls and not self.port:
self.port = 587

25
frappe/email/test_smtp.py Normal file
View file

@ -0,0 +1,25 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: The MIT License
import unittest
from frappe.email.smtp import SMTPServer
class TestSMTP(unittest.TestCase):
def test_smtp_ssl_session(self):
for port in [None, 0, 465, "465"]:
make_server(port, 1, 0)
def test_smtp_tls_session(self):
for port in [None, 0, 587, "587"]:
make_server(port, 0, 1)
def make_server(port, ssl, tls):
server = SMTPServer(
server = "smtp.gmail.com",
port = port,
use_ssl = ssl,
use_tls = tls
)
server.sess

View file

@ -94,6 +94,7 @@ permission_query_conditions = {
"User": "frappe.core.doctype.user.user.get_permission_query_conditions",
"Dashboard Settings": "frappe.desk.doctype.dashboard_settings.dashboard_settings.get_permission_query_conditions",
"Notification Log": "frappe.desk.doctype.notification_log.notification_log.get_permission_query_conditions",
"Dashboard": "frappe.desk.doctype.dashboard.dashboard.get_permission_query_conditions",
"Dashboard Chart": "frappe.desk.doctype.dashboard_chart.dashboard_chart.get_permission_query_conditions",
"Number Card": "frappe.desk.doctype.number_card.number_card.get_permission_query_conditions",
"Notification Settings": "frappe.desk.doctype.notification_settings.notification_settings.get_permission_query_conditions",

View file

@ -262,11 +262,11 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False)
for record in frappe.get_all(doctype, filters={"module": module_name}, pluck="name"):
print(f"* removing {doctype} '{record}'...")
if not dry_run:
frappe.delete_doc(doctype, record, ignore_on_trash=True)
frappe.delete_doc(doctype, record, ignore_on_trash=True, force=True)
print(f"* removing Module Def '{module_name}'...")
if not dry_run:
frappe.delete_doc("Module Def", module_name, ignore_on_trash=True)
frappe.delete_doc("Module Def", module_name, ignore_on_trash=True, force=True)
for doctype in set(drop_doctypes):
print(f"* dropping Table for '{doctype}'...")
@ -440,20 +440,11 @@ def extract_sql_from_archive(sql_file_path):
Returns:
str: Path of the decompressed SQL file
"""
from frappe.utils import get_bench_relative_path
sql_file_path = get_bench_relative_path(sql_file_path)
# Extract the gzip file if user has passed *.sql.gz file instead of *.sql file
if not os.path.exists(sql_file_path):
base_path = '..'
sql_file_path = os.path.join(base_path, sql_file_path)
if not os.path.exists(sql_file_path):
print('Invalid path {0}'.format(sql_file_path[3:]))
sys.exit(1)
elif sql_file_path.startswith(os.sep):
base_path = os.sep
else:
base_path = '.'
if sql_file_path.endswith('sql.gz'):
decompressed_file_name = extract_sql_gzip(os.path.abspath(sql_file_path))
decompressed_file_name = extract_sql_gzip(sql_file_path)
else:
decompressed_file_name = sql_file_path
@ -475,9 +466,12 @@ def extract_sql_gzip(sql_gz_path):
return decompressed_file
def extract_files(site_name, file_path, folder_name):
def extract_files(site_name, file_path):
import shutil
import subprocess
from frappe.utils import get_bench_relative_path
file_path = get_bench_relative_path(file_path)
# Need to do frappe.init to maintain the site locals
frappe.init(site=site_name)

View file

@ -1,41 +1,50 @@
from __future__ import unicode_literals
import frappe, json
from frappe.oauth import OAuthWebRequestValidator, WebApplicationServer
import hashlib
import json
from urllib.parse import quote, urlencode, urlparse
import jwt
from oauthlib.oauth2 import FatalClientError, OAuth2Error
from werkzeug import url_fix
from six.moves.urllib.parse import quote, urlencode, urlparse
from frappe.integrations.doctype.oauth_provider_settings.oauth_provider_settings import get_oauth_settings
import frappe
from frappe import _
from frappe.oauth import OAuthWebRequestValidator, WebApplicationServer
from frappe.integrations.doctype.oauth_provider_settings.oauth_provider_settings import get_oauth_settings
def get_oauth_server():
if not getattr(frappe.local, 'oauth_server', None):
oauth_validator = OAuthWebRequestValidator()
frappe.local.oauth_server = WebApplicationServer(oauth_validator)
frappe.local.oauth_server = WebApplicationServer(oauth_validator)
return frappe.local.oauth_server
def get_urlparams_from_kwargs(param_kwargs):
def sanitize_kwargs(param_kwargs):
arguments = param_kwargs
if arguments.get("data"):
arguments.pop("data")
if arguments.get("cmd"):
arguments.pop("cmd")
arguments.pop('data', None)
arguments.pop('cmd', None)
return urlencode(arguments)
return arguments
@frappe.whitelist()
def approve(*args, **kwargs):
r = frappe.request
uri = url_fix(r.url.replace("+"," "))
http_method = r.method
body = r.get_data()
headers = r.headers
try:
scopes, frappe.flags.oauth_credentials = get_oauth_server().validate_authorization_request(uri, http_method, body, headers)
scopes, frappe.flags.oauth_credentials = get_oauth_server().validate_authorization_request(
r.url,
r.method,
r.get_data(),
r.headers
)
headers, body, status = get_oauth_server().create_authorization_response(uri=frappe.flags.oauth_credentials['redirect_uri'], \
body=body, headers=headers, scopes=scopes, credentials=frappe.flags.oauth_credentials)
headers, body, status = get_oauth_server().create_authorization_response(
uri=frappe.flags.oauth_credentials['redirect_uri'],
body=r.get_data(),
headers=r.headers,
scopes=scopes,
credentials=frappe.flags.oauth_credentials
)
uri = headers.get('Location', None)
frappe.local.response["type"] = "redirect"
@ -47,34 +56,28 @@ def approve(*args, **kwargs):
return e
@frappe.whitelist(allow_guest=True)
def authorize(*args, **kwargs):
#Fetch provider URL from settings
oauth_settings = get_oauth_settings()
params = get_urlparams_from_kwargs(kwargs)
request_url = urlparse(frappe.request.url)
success_url = request_url.scheme + "://" + request_url.netloc + "/api/method/frappe.integrations.oauth2.approve?" + params
def authorize(**kwargs):
success_url = "/api/method/frappe.integrations.oauth2.approve?" + encode_params(sanitize_kwargs(kwargs))
failure_url = frappe.form_dict["redirect_uri"] + "?error=access_denied"
if frappe.session['user']=='Guest':
if frappe.session.user == 'Guest':
#Force login, redirect to preauth again.
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = "/login?redirect-to=/api/method/frappe.integrations.oauth2.authorize?" + quote(params.replace("+"," "))
elif frappe.session['user']!='Guest':
frappe.local.response["location"] = "/login?" + encode_params({'redirect-to': frappe.request.url})
else:
try:
r = frappe.request
uri = url_fix(r.url)
http_method = r.method
body = r.get_data()
headers = r.headers
scopes, frappe.flags.oauth_credentials = get_oauth_server().validate_authorization_request(uri, http_method, body, headers)
scopes, frappe.flags.oauth_credentials = get_oauth_server().validate_authorization_request(
r.url,
r.method,
r.get_data(),
r.headers
)
skip_auth = frappe.db.get_value("OAuth Client", frappe.flags.oauth_credentials['client_id'], "skip_authorization")
unrevoked_tokens = frappe.get_all("OAuth Bearer Token", filters={"status":"Active"})
if skip_auth or (oauth_settings["skip_authorization"] == "Auto" and len(unrevoked_tokens)):
if skip_auth or (get_oauth_settings().skip_authorization == "Auto" and unrevoked_tokens):
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = success_url
else:
@ -87,7 +90,6 @@ def authorize(*args, **kwargs):
})
resp_html = frappe.render_template("templates/includes/oauth_confirmation.html", response_html_params)
frappe.respond_as_web_page("Confirm Access", resp_html)
except FatalClientError as e:
return e
except OAuth2Error as e:
@ -95,20 +97,20 @@ def authorize(*args, **kwargs):
@frappe.whitelist(allow_guest=True)
def get_token(*args, **kwargs):
r = frappe.request
uri = url_fix(r.url)
http_method = r.method
body = r.form
headers = r.headers
#Check whether frappe server URL is set
frappe_server_url = frappe.db.get_value("Social Login Key", "frappe", "base_url") or None
if not frappe_server_url:
frappe.throw(_("Please set Base URL in Social Login Key for Frappe"))
try:
headers, body, status = get_oauth_server().create_token_response(uri, http_method, body, headers, frappe.flags.oauth_credentials)
r = frappe.request
headers, body, status = get_oauth_server().create_token_response(
r.url,
r.method,
r.form,
r.headers,
frappe.flags.oauth_credentials
)
out = frappe._dict(json.loads(body))
if not out.error and "openid" in out.scope:
token_user = frappe.db.get_value("OAuth Bearer Token", out.access_token, "user")
@ -116,7 +118,7 @@ def get_token(*args, **kwargs):
client_secret = frappe.db.get_value("OAuth Client", token_client, "client_secret")
if token_user in ["Guest", "Administrator"]:
frappe.throw(_("Logged in as Guest or Administrator"))
import hashlib
id_token_header = {
"typ":"jwt",
"alg":"HS256"
@ -128,9 +130,10 @@ def get_token(*args, **kwargs):
"iss": frappe_server_url,
"at_hash": frappe.oauth.calculate_at_hash(out.access_token, hashlib.sha256)
}
import jwt
id_token_encoded = jwt.encode(id_token, client_secret, algorithm='HS256', headers=id_token_header)
out.update({"id_token":str(id_token_encoded)})
out.update({"id_token": str(id_token_encoded)})
frappe.local.response = out
except FatalClientError as e:
@ -140,12 +143,12 @@ def get_token(*args, **kwargs):
@frappe.whitelist(allow_guest=True)
def revoke_token(*args, **kwargs):
r = frappe.request
uri = url_fix(r.url)
http_method = r.method
body = r.form
headers = r.headers
headers, body, status = get_oauth_server().create_revocation_response(uri, headers=headers, body=body, http_method=http_method)
headers, body, status = get_oauth_server().create_revocation_response(
r.url,
headers=r.headers,
body=r.form,
http_method=r.method
)
frappe.local.response['http_status_code'] = status
if status == 200:
@ -174,15 +177,22 @@ def openid_profile(*args, **kwargs):
"email": name,
"picture": picture
})
frappe.local.response = user_profile
def validate_url(url_string):
try:
result = urlparse(url_string)
if result.scheme and result.scheme in ["http", "https", "ftp", "ftps"]:
return True
else:
return False
return result.scheme and result.scheme in ["http", "https", "ftp", "ftps"]
except:
return False
return False
def encode_params(params):
"""
Encode a dict of params into a query string.
Use `quote_via=urllib.parse.quote` so that whitespaces will be encoded as
`%20` instead of as `+`. This is needed because oauthlib cannot handle `+`
as a whitespace.
"""
return urlencode(params, quote_via=quote)

View file

@ -802,12 +802,12 @@ class BaseDocument(object):
if translated:
val = _(val)
if absolute_value and isinstance(val, (int, float)):
val = abs(self.get(fieldname))
if not doc:
doc = getattr(self, "parent_doc", None) or self
if (absolute_value or doc.get('absolute_value')) and isinstance(val, (int, float)):
val = abs(self.get(fieldname))
return format_value(val, df=df, doc=doc, currency=currency)
def is_print_hide(self, fieldname, df=None, for_print=True):

View file

@ -18,6 +18,7 @@ from frappe.client import check_parent_permission
from frappe.model.utils.user_settings import get_user_settings, update_user_settings
from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr, get_timespan_date_range
from frappe.model.meta import get_table_columns
from frappe.core.doctype.server_script.server_script_utils import get_server_script_map
class DatabaseQuery(object):
def __init__(self, doctype, user=None):
@ -683,15 +684,23 @@ class DatabaseQuery(object):
self.match_filters.append(match_filters)
def get_permission_query_conditions(self):
conditions = []
condition_methods = frappe.get_hooks("permission_query_conditions", {}).get(self.doctype, [])
if condition_methods:
conditions = []
for method in condition_methods:
c = frappe.call(frappe.get_attr(method), self.user)
if c:
conditions.append(c)
return " and ".join(conditions) if conditions else None
permision_script_name = get_server_script_map().get("permission_query").get(self.doctype)
if permision_script_name:
script = frappe.get_doc("Server Script", permision_script_name)
condition = script.get_permission_query_conditions(self.user)
if condition:
conditions.append(condition)
return " and ".join(conditions) if conditions else ""
def run_custom_query(self, query):
if '%(key)s' in query:

View file

@ -209,7 +209,8 @@ class Meta(Document):
'owner': _('Created By'),
'modified_by': _('Modified By'),
'creation': _('Created On'),
'modified': _('Last Modified On')
'modified': _('Last Modified On'),
'_assign': _('Assigned To')
}.get(fieldname) or _('No Label')
return label

View file

@ -1,14 +1,15 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals, print_function
from __future__ import print_function, unicode_literals
import frappe
from frappe import _, bold
from frappe.utils import cint
from frappe.model.naming import validate_name
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.utils.password import rename_password
from frappe.model.naming import validate_name
from frappe.model.utils.user_settings import sync_user_settings, update_user_settings_data
from frappe.utils import cint
from frappe.utils.password import rename_password
@frappe.whitelist()
@ -42,7 +43,6 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F
force = cint(force)
merge = cint(merge)
meta = frappe.get_meta(doctype)
# call before_rename
@ -249,8 +249,17 @@ def update_link_field_values(link_fields, old, new, doctype):
# or no longer exists
pass
else:
# because the table hasn't been renamed yet!
parent = field['parent'] if field['parent']!=new else old
parent = field['parent']
# Handles the case where one of the link fields belongs to
# the DocType being renamed.
# Here this field could have the current DocType as its value too.
# In this case while updating link field value, the field's parent
# or the current DocType table name hasn't been renamed yet,
# so consider it's old name.
if parent == new and doctype == "DocType":
parent = old
frappe.db.sql("""
update `tab{table_name}` set `{fieldname}`=%s
@ -306,8 +315,7 @@ def get_link_fields(doctype):
def update_options_for_fieldtype(fieldtype, old, new):
if frappe.conf.developer_mode:
for name in frappe.db.sql_list("""select parent from
tabDocField where options=%s""", old):
for name in frappe.get_all("DocField", filters={"options": old}, pluck="parent"):
doctype = frappe.get_doc("DocType", name)
save = False
for f in doctype.fields:
@ -413,20 +421,21 @@ def update_parenttype_values(old, new):
child_doctypes += custom_child_doctypes
fields = [d['fieldname'] for d in child_doctypes]
property_setter_child_doctypes = frappe.db.sql("""\
select value as options from `tabProperty Setter`
where doc_type=%s and property='options' and
field_name in ("%s")""" % ('%s', '", "'.join(fields)),
(new,))
property_setter_child_doctypes = frappe.get_all(
"Property Setter",
filters={
"doc_type": new,
"property": "options",
"field_name": ("in", fields)
},
pluck="value"
)
child_doctypes = list(d['options'] for d in child_doctypes)
child_doctypes += property_setter_child_doctypes
child_doctypes = (d['options'] for d in child_doctypes)
for doctype in child_doctypes:
frappe.db.sql("""\
update `tab%s` set parenttype=%s
where parenttype=%s""" % (doctype, '%s', '%s'),
(new, old))
frappe.db.sql(f"update `tab{doctype}` set parenttype=%s where parenttype=%s", (new, old))
def rename_dynamic_links(doctype, old, new):
for df in get_dynamic_link_map().get(doctype, []):
@ -482,60 +491,30 @@ def bulk_rename(doctype, rows=None, via_console = False):
return rename_log
def update_linked_doctypes(doctype, docname, linked_to, value, ignore_doctypes=None):
"""
linked_doctype_info_list = list formed by get_fetch_fields() function
docname = Master DocType's name in which modification are made
value = Value for the field thats set in other DocType's by fetching from Master DocType
"""
linked_doctype_info_list = get_fetch_fields(doctype, linked_to, ignore_doctypes)
from frappe.model.utils.rename_doc import update_linked_doctypes
show_deprecation_warning("update_linked_doctypes")
return update_linked_doctypes(
doctype=doctype,
docname=docname,
linked_to=linked_to,
value=value,
ignore_doctypes=ignore_doctypes,
)
for d in linked_doctype_info_list:
frappe.db.sql("""
update
`tab{doctype}`
set
{linked_to_fieldname} = "{value}"
where
{master_fieldname} = {docname}
and {linked_to_fieldname} != "{value}"
""".format(
doctype = d['doctype'],
linked_to_fieldname = d['linked_to_fieldname'],
value = value,
master_fieldname = d['master_fieldname'],
docname = frappe.db.escape(docname)
))
def get_fetch_fields(doctype, linked_to, ignore_doctypes=None):
"""
doctype = Master DocType in which the changes are being made
linked_to = DocType name of the field thats being updated in Master
from frappe.model.utils.rename_doc import get_fetch_fields
show_deprecation_warning("get_fetch_fields")
This function fetches list of all DocType where both doctype and linked_to is found
as link fields.
Forms a list of dict in the form -
[{doctype: , master_fieldname: , linked_to_fieldname: ]
where
doctype = DocType where changes need to be made
master_fieldname = Fieldname where options = doctype
linked_to_fieldname = Fieldname where options = linked_to
"""
return get_fetch_fields(
doctype=doctype, linked_to=linked_to, ignore_doctypes=ignore_doctypes
)
master_list = get_link_fields(doctype)
linked_to_list = get_link_fields(linked_to)
out = []
from itertools import product
product_list = product(master_list, linked_to_list)
for d in product_list:
linked_doctype_info = frappe._dict()
if d[0]['parent'] == d[1]['parent'] \
and (not ignore_doctypes or d[0]['parent'] not in ignore_doctypes) \
and not d[1]['issingle']:
linked_doctype_info['doctype'] = d[0]['parent']
linked_doctype_info['master_fieldname'] = d[0]['fieldname']
linked_doctype_info['linked_to_fieldname'] = d[1]['fieldname']
out.append(linked_doctype_info)
return out
def show_deprecation_warning(funct):
from click import secho
message = (
f"Function frappe.model.rename_doc.{funct} has been deprecated and "
"moved to the frappe.model.utils.rename_doc"
)
secho(message, fg="yellow")

View file

@ -0,0 +1,58 @@
from itertools import product
import frappe
from frappe.model.rename_doc import get_link_fields
def update_linked_doctypes(doctype, docname, linked_to, value, ignore_doctypes=None):
"""
linked_doctype_info_list = list formed by get_fetch_fields() function
docname = Master DocType's name in which modification are made
value = Value for the field thats set in other DocType's by fetching from Master DocType
"""
linked_doctype_info_list = get_fetch_fields(doctype, linked_to, ignore_doctypes)
for d in linked_doctype_info_list:
frappe.db.set_value(
d.doctype,
{
d.master_fieldname : docname,
d.linked_to_fieldname : ("!=", value),
},
d.linked_to_fieldname,
value,
)
def get_fetch_fields(doctype, linked_to, ignore_doctypes=None):
"""
doctype = Master DocType in which the changes are being made
linked_to = DocType name of the field thats being updated in Master
This function fetches list of all DocType where both doctype and linked_to is found
as link fields.
Forms a list of dict in the form -
[{doctype: , master_fieldname: , linked_to_fieldname: ]
where
doctype = DocType where changes need to be made
master_fieldname = Fieldname where options = doctype
linked_to_fieldname = Fieldname where options = linked_to
"""
out = []
master_list = get_link_fields(doctype)
linked_to_list = get_link_fields(linked_to)
product_list = product(master_list, linked_to_list)
for d in product_list:
linked_doctype_info = frappe._dict()
if (
d[0]["parent"] == d[1]["parent"]
and (not ignore_doctypes or d[0]["parent"] not in ignore_doctypes)
and not d[1]["issingle"]
):
linked_doctype_info.doctype = d[0]["parent"]
linked_doctype_info.master_fieldname = d[0]["fieldname"]
linked_doctype_info.linked_to_fieldname = d[1]["fieldname"]
out.append(linked_doctype_info)
return out

View file

@ -148,7 +148,7 @@ class OAuthWebRequestValidator(RequestValidator):
print("Failed body authentication: Application %s does not exist".format(cid=request.client_id))
cookie_dict = get_cookie_dict_from_headers(request)
user_id = unquote(cookie_dict['user_id']) if 'user_id' in cookie_dict else "Guest"
user_id = unquote(cookie_dict.get('user_id').value) if 'user_id' in cookie_dict else "Guest"
return frappe.session.user == user_id
def authenticate_client_id(self, client_id, request, *args, **kwargs):

View file

@ -19,6 +19,7 @@ frappe.ui.form.on("Print Format", {
}
frm.trigger('render_buttons');
frm.toggle_display('standard', frappe.boot.developer_mode);
frm.trigger('hide_absolute_value_field');
},
render_buttons: function (frm) {
frm.page.clear_inner_toolbar();
@ -58,5 +59,20 @@ frappe.ui.form.on("Print Format", {
frm.set_value('show_section_headings', value);
frm.set_value('line_breaks', value);
frm.trigger('render_buttons');
},
doc_type: function (frm) {
frm.trigger('hide_absolute_value_field');
},
hide_absolute_value_field: function (frm) {
// TODO: make it work with frm.doc.doc_type
// Problem: frm isn't updated in some random cases
const doctype = locals[frm.doc.doctype][frm.doc.name];
if (doctype) {
frappe.model.with_doctype(doctype, () => {
const meta = frappe.get_meta(doctype);
const has_int_float_currency_field = meta.fields.filter(df => in_list(['Int', 'Float', 'Currency'], df.fieldtype));
frm.toggle_display('absolute_value', has_int_float_currency_field.length);
});
}
}
})
});

View file

@ -22,6 +22,7 @@
"align_labels_right",
"show_section_headings",
"line_breaks",
"absolute_value",
"column_break_11",
"font",
"css_section",
@ -196,13 +197,21 @@
"fieldtype": "Check",
"hidden": 1,
"label": "Print Format Builder"
},
{
"default": "0",
"depends_on": "doc_type",
"description": "If checked, negative numberic values of Currency, Quantity or Count would be shown as positive",
"fieldname": "absolute_value",
"fieldtype": "Check",
"label": "Show absolute values"
}
],
"icon": "fa fa-print",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-10-27 18:27:58.307070",
"modified": "2020-12-10 18:58:55.598269",
"modified_by": "Administrator",
"module": "Printing",
"name": "Print Format",

View file

@ -101,6 +101,7 @@ frappe.data_import.ImportPreview = class ImportPreview {
.replace('%H', 'HH')
.replace('%M', 'mm')
.replace('%S', 'ss')
.replace('%b', 'Mon')
: null;
let column_title = `<span class="indicator green">

View file

@ -148,7 +148,6 @@ frappe.Application = Class.extend({
user: frappe.session.user
},
callback: function(r) {
console.log(r);
if(r.message.show_alert){
frappe.show_alert({
indicator: 'red',

View file

@ -60,7 +60,7 @@ frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({
update_state() {
const value = this.get_value();
if (strip_html(value).trim() != "") {
if (strip_html(value).trim() != "" || value.includes('img')) {
this.button.removeClass('btn-default').addClass('btn-primary');
} else {
this.button.addClass('btn-default').removeClass('btn-primary');

View file

@ -22,27 +22,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({
this.has_input = true;
this.bind_change_event();
this.setup_autoname_check();
if (this.df.options == 'Phone') {
this.setup_phone();
}
// somehow this event does not bubble up to document
// after v7, if you can debug, remove this
},
setup_phone() {
if (frappe.phone_call.handler) {
this.$wrapper.find('.control-input')
.append(`
<span class="phone-btn">
<a class="btn-open no-decoration" title="${__('Make a call')}">
<i class="fa fa-phone"></i></a>
</span>
`)
.find('.phone-btn')
.click(() => {
frappe.phone_call.handler(this.get_value(), this.frm);
});
}
},
setup_autoname_check: function() {
if (!this.df.parent) return;
this.meta = frappe.get_meta(this.df.parent);

View file

@ -30,7 +30,7 @@ frappe.ui.form.Timeline = class Timeline {
render_input: true,
only_input: true,
on_submit: (val) => {
if(strip_html(val).trim() != "") {
if (strip_html(val).trim() != "" || val.includes('img')) {
this.insert_comment(val, this.comment_area.button);
}
}
@ -547,10 +547,7 @@ frappe.ui.form.Timeline = class Timeline {
log.color = 'dark';
log.sender = log.owner;
log.comment_type = 'Milestone';
log.content = __('{0} changed {1} to {2}', [
frappe.user.full_name(log.owner).bold(),
frappe.meta.get_label(this.frm.doctype, log.track_field),
log.value.bold()]);
log.content = __('{0} changed {1} to {2}', [ frappe.user.full_name(log.owner).bold(), frappe.meta.get_label(this.frm.doctype, log.track_field), log.value.bold()]);
return log;
});
return milestones;
@ -613,11 +610,7 @@ frappe.ui.form.Timeline = class Timeline {
const field_display_status = frappe.perm.get_field_display_status(df, null,
me.frm.perm);
if (field_display_status === 'Read' || field_display_status === 'Write') {
parts.push(__('{0} from {1} to {2}', [
__(df.label),
me.format_content_for_timeline(p[1]),
me.format_content_for_timeline(p[2])
]));
parts.push(__('{0} from {1} to {2}', [ __(df.label), me.format_content_for_timeline(p[1]), me.format_content_for_timeline(p[2])]));
}
}
}
@ -648,13 +641,7 @@ frappe.ui.form.Timeline = class Timeline {
null, me.frm.perm);
if (field_display_status === 'Read' || field_display_status === 'Write') {
parts.push(__('{0} from {1} to {2} in row #{3}', [
frappe.meta.get_label(me.frm.fields_dict[row[0]].grid.doctype,
p[0]),
me.format_content_for_timeline(p[1]),
me.format_content_for_timeline(p[2]),
row[1]
]));
parts.push(__('{0} from {1} to {2} in row #{3}', [ frappe.meta.get_label( me.frm.fields_dict[row[0]].grid.doctype, p[0]), me.format_content_for_timeline(p[1]), me.format_content_for_timeline(p[2]), row[1] ]));
}
}
return parts.length < 3;
@ -691,8 +678,7 @@ frappe.ui.form.Timeline = class Timeline {
return p;
});
if (parts.length) {
out.push(me.get_version_comment(version, __("{0} rows for {1}",
[__(key), parts.join(', ')])));
out.push(me.get_version_comment(version, __("{0} rows for {1}", [__(key), parts.join(', ')])));
}
}
});

View file

@ -373,6 +373,7 @@ export default class GridRow {
// no text editor in grid
if (df.fieldtype=='Text Editor') {
df = Object.assign({}, df);
df.fieldtype = 'Text';
}

View file

@ -306,6 +306,7 @@ $.extend(frappe.model, {
selected_children: opts.frm ? opts.frm.get_selected() : null
},
freeze: true,
freeze_message: opts.freeze_message || '',
callback: function(r) {
if(!r.exc) {
frappe.model.sync(r.message);

View file

@ -103,6 +103,31 @@ $.extend(frappe.model, {
return docfield[0];
},
get_from_localstorage: function(doctype) {
if (localStorage["_doctype:" + doctype]) {
return JSON.parse(localStorage["_doctype:" + doctype]);
}
},
set_in_localstorage: function(doctype, docs) {
try {
localStorage["_doctype:" + doctype] = JSON.stringify(docs);
} catch(e) {
// if quota is exceeded, clear local storage and set item
console.warn("localStorage quota exceeded, clearing doctype cache")
frappe.model.clear_local_storage();
localStorage["_doctype:" + doctype] = JSON.stringify(docs);
}
},
clear_local_storage: function() {
for(var key in localStorage) {
if (key.startsWith("_doctype:")) {
localStorage.removeItem(key);
}
}
},
with_doctype: function(doctype, callback, async) {
if(locals.DocType[doctype]) {
callback && callback();
@ -110,13 +135,15 @@ $.extend(frappe.model, {
let cached_timestamp = null;
let cached_doc = null;
if(localStorage["_doctype:" + doctype]) {
let cached_docs = JSON.parse(localStorage["_doctype:" + doctype]);
let cached_docs = frappe.model.get_from_localstorage(doctype)
if (cached_docs) {
cached_doc = cached_docs.filter(doc => doc.name === doctype)[0];
if(cached_doc) {
cached_timestamp = cached_doc.modified;
}
}
return frappe.call({
method:'frappe.desk.form.load.getdoctype',
type: "GET",
@ -134,7 +161,7 @@ $.extend(frappe.model, {
if(r.message=="use_cache") {
frappe.model.sync(cached_doc);
} else {
localStorage["_doctype:" + doctype] = JSON.stringify(r.docs);
frappe.model.set_in_localstorage(doctype, r.docs)
}
frappe.model.init_doctype(doctype);

View file

@ -234,11 +234,11 @@ frappe.utils.xss_sanitise = function (string, options) {
strategies: ['html', 'js'] // use all strategies.
}
const HTML_ESCAPE_MAP = {
'<': '&lt',
'>': '&gt',
'"': '&quot',
"'": '&#x27',
'/': '&#x2F'
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;'
};
const REGEX_SCRIPT = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi; // used in jQuery 1.7.2 src/ajax.js Line 14
options = Object.assign({ }, DEFAULT_OPTIONS, options); // don't deep copy, immutable beauty.

View file

@ -979,17 +979,42 @@ Object.assign(frappe.utils, {
return route;
},
shorten_number: function (number, country) {
country = (country == 'India') ? country : '';
shorten_number: function (number, country, min_length=4, max_no_of_decimals=2) {
/* returns the number as an abbreviated string
* PARAMS
* number - number to be shortened
* country - country that determines the numnber system to be used
* min_length - length below which the number will not be shortened
* max_no_of_decimals - max number of decimals of the shortened number
*/
// return number if total digits is lesser than min_length
const len = String(number).match(/\d/g).length;
if (len < min_length) return number.toString();
const number_system = this.get_number_system(country);
let x = Math.abs(Math.round(number));
for (const map of number_system) {
const condition = map.condition ? map.condition(x) : x >= map.divisor;
if (condition) {
return (number/map.divisor).toFixed(2) + ' ' + map.symbol;
if (x >= map.divisor) {
let result = number/map.divisor;
const no_of_decimals = this.get_number_of_decimals(result);
/*
If no_of_decimals is greater than max_no_of_decimals,
round the number to max_no_of_decimals
*/
result = no_of_decimals > max_no_of_decimals
? result.toFixed(max_no_of_decimals)
: result;
return result + ' ' + map.symbol;
}
}
return number.toFixed();
return number.toFixed(max_no_of_decimals);
},
get_number_of_decimals: function (number) {
if (Math.floor(number) === number) return 0;
return number.toString().split(".")[1].length || 0;
},
get_number_system: function (country) {
@ -1019,9 +1044,11 @@ Object.assign(frappe.utils, {
{
divisor: 1.0e+3,
symbol: 'K',
condition: (num) => num.toFixed().length > 5
}]
};
if (!Object.keys(number_system_map).includes(country)) country = '';
return number_system_map[country];
},
});

View file

@ -30,7 +30,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
return super.setup_defaults()
.then(() => {
this.board_name = frappe.get_route()[3];
this.page_title = this.board_name;
this.page_title = __(this.board_name);
this.card_meta = this.get_card_meta();
this.menu_items.push({

View file

@ -15,7 +15,9 @@ frappe.report_utils = {
if (raw_data.add_total_row) {
labels = labels.slice(0, -1);
datasets[0].values = datasets[0].values.slice(0, -1);
datasets.forEach(dataset => {
dataset.values = dataset.values.slice(0, -1);
});
}
return {

View file

@ -204,7 +204,7 @@ export default class NumberCardWidget extends Widget {
get_formatted_number(df) {
const default_country = frappe.sys_defaults.country;
const shortened_number = frappe.utils.shorten_number(this.number, default_country);
const shortened_number = frappe.utils.shorten_number(this.number, default_country, 5);
let number_parts = shortened_number.split(' ');
const symbol = number_parts[1] || '';
@ -269,7 +269,7 @@ export default class NumberCardWidget extends Widget {
result: this.number
}).then(res => {
if (res !== undefined) {
this.percentage_stat = shorten_number(res);
this.percentage_stat = frappe.utils.shorten_number(res);
}
});
}

View file

@ -244,9 +244,18 @@ h5.modal-title {
white-space: nowrap;
text-overflow: ellipsis;
}
.about-section {
padding-top: 1rem;
}
.about-footer {
padding-top: 1rem;
}
}
.logged-in > .nav-link {
max-width: 200px;
@extend .ellipsis;
max-width: 100%;
vertical-align: middle;
}

View file

@ -1,7 +1,7 @@
{% if not error %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ client_id }} wants to access the following details from your account</h3>
<h3 class="panel-title">{{ _("{} wants to access the following details from your account").format(client_id) }}</h3>
</div>
<div class="panel-body">
<ul class="list-group">
@ -11,10 +11,10 @@
</ul>
<ul class="list-inline">
<li>
<button id="allow" class="btn btn-sm btn-primary">Allow</button>
<button id="allow" class="btn btn-sm btn-primary">{{ _("Allow") }}</button>
</li>
<li>
<button id="deny" class="btn btn-sm btn-light">Deny</button>
<button id="deny" class="btn btn-sm btn-light">{{ _("Deny") }}</button>
</li>
</ul>
</div>
@ -22,24 +22,24 @@
<script type="text/javascript">
frappe.ready(function() {
$('#allow').on('click', function(event) {
window.location.replace("{{ success_url|string }}");
window.location.replace("{{ success_url | string }}");
});
$('#deny').on('click', function(event) {
window.location.replace("{{ failure_url|string }}");
window.location.replace("{{ failure_url | string }}");
});
});
</script>
{% else %}
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">Authorization error for {{ client_id }}</h3>
<h3 class="panel-title">{{ _("Authorization error for {}.").format(client_id) }}</h3>
</div>
<div class="panel-body">
<p>An unexpected error occurred while authorizing {{ client_id }}.</p>
<p>{{ _("An unexpected error occurred while authorizing {}.").format(client_id) }}</p>
<h4>{{ error }}</h4>
<ul class="list-inline">
<li>
<button class="btn btn-sm btn-light">OK</button>
<button class="btn btn-sm btn-light">{{ _("OK") }}</button>
</li>
</ul>
</div>

View file

@ -136,10 +136,9 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
{% elif df.fieldtype=="HTML" %}
{{ frappe.render_template(df.options, {"doc":doc}) }}
{% elif df.fieldtype=="Currency" %}
{{ doc.get_formatted(df.fieldname, doc, translated=df.translatable) }}
{% else %}
{{ doc.get_formatted(df.fieldname, parent_doc or doc, translated=df.translatable) }}
{%- set parent = parent_doc or doc -%}
{{ doc.get_formatted(df.fieldname, parent, translated=df.translatable, absolute_value=parent.absolute_value) }}
{% endif %}
{%- endmacro %}

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View file

@ -14,7 +14,7 @@ import glob
import frappe
import frappe.recorder
from frappe.installer import add_to_installed_apps
from frappe.utils import add_to_date, now
from frappe.utils import add_to_date, get_bench_relative_path, now
from frappe.utils.backups import fetch_latest_backups
@ -364,3 +364,21 @@ class TestCommands(BaseTestCommands):
else:
installed_apps = set(frappe.get_installed_apps())
self.assertSetEqual(list_apps, installed_apps)
def test_get_bench_relative_path(self):
bench_path = frappe.utils.get_bench_path()
test1_path = os.path.join(bench_path, "test1.txt")
test2_path = os.path.join(bench_path, "sites", "test2.txt")
with open(test1_path, "w+") as test1:
test1.write("asdf")
with open(test2_path, "w+") as test2:
test2.write("asdf")
self.assertTrue("test1.txt" in get_bench_relative_path("test1.txt"))
self.assertTrue("sites/test2.txt" in get_bench_relative_path("test2.txt"))
with self.assertRaises(SystemExit):
get_bench_relative_path("test3.txt")
os.remove(test1_path)
os.remove(test2_path)

View file

@ -290,6 +290,41 @@ class TestDocument(unittest.TestCase):
for docname in available_documents:
frappe.delete_doc(doctype, docname)
def test_rename_doctype(self):
from frappe.core.doctype.doctype.test_doctype import new_doctype
fields =[{
"label": "Linked To",
"fieldname": "linked_to_doctype",
"fieldtype": "Link",
"options": "DocType",
"unique": 0
}]
if not frappe.db.exists("DocType", "Rename This"):
new_doctype("Rename This", unique=0, fields=fields).insert()
to_rename_record = frappe.get_doc({
"doctype": "Rename This",
"linked_to_doctype": "Rename This"
})
to_rename_record.insert()
# Rename doctype
self.assertEqual("Renamed Doc", frappe.rename_doc("DocType", "Rename This", "Renamed Doc", force=True))
# Test if Doctype value has changed in Link field
renamed_doctype_record = frappe.get_doc("Renamed Doc", to_rename_record.name)
self.assertEqual(renamed_doctype_record.linked_to_doctype, "Renamed Doc")
# Test if there are conflicts between a record and a DocType
# having the same name
old_name = to_rename_record.name
new_name = "ToDo"
self.assertEqual(new_name, frappe.rename_doc("Renamed Doc", old_name, new_name, force=True))
frappe.delete_doc_if_exists("Renamed Doc", "ToDo")
frappe.delete_doc_if_exists("DocType", "Renamed Doc")
def test_non_negative_check(self):
frappe.delete_doc_if_exists("Currency", "Frappe Coin", 1)
@ -305,4 +340,4 @@ class TestDocument(unittest.TestCase):
d.insert()
self.assertEqual(frappe.db.get_value("Currency", d.name), d.name)
frappe.delete_doc_if_exists("Currency", "Frappe Coin", 1)
frappe.delete_doc_if_exists("Currency", "Frappe Coin", 1)

View file

@ -6,6 +6,7 @@ import unittest, frappe, requests, time
from frappe.test_runner import make_test_records
from six.moves.urllib.parse import urlparse, parse_qs, urljoin
from urllib.parse import urlencode, quote
from frappe.integrations.oauth2 import encode_params
class TestOAuth20(unittest.TestCase):
@ -232,13 +233,3 @@ def login(session):
def get_full_url(endpoint):
"""Turn '/endpoint' into 'http://127.0.0.1:8000/endpoint'."""
return urljoin(frappe.utils.get_url(), endpoint)
def encode_params(params):
"""
Encode a dict of params into a query string.
Use `quote_via=urllib.parse.quote` so that whitespaces will be encoded as
`%20` instead of as `+`. This is needed because oauthlib cannot handle `+`
as a whitespace.
"""
return urlencode(params, quote_via=quote)

View file

@ -7,6 +7,10 @@ import unittest
from frappe.utils import evaluate_filters, money_in_words, scrub_urls, get_url
from frappe.utils import ceil, floor
from PIL import Image
from frappe.utils.image import strip_exif_data
import io
class TestFilters(unittest.TestCase):
def test_simple_dict(self):
self.assertTrue(evaluate_filters({'doctype': 'User', 'status': 'Open'}, {'status': 'Open'}))
@ -122,3 +126,14 @@ class TestHTMLUtils(unittest.TestCase):
clean = clean_email_html(sample)
self.assertTrue('<h1>Hello</h1>' in clean)
self.assertTrue('<a href="http://test.com">text</a>' in clean)
class TestImage(unittest.TestCase):
def test_strip_exif_data(self):
original_image = Image.open("../apps/frappe/frappe/tests/data/exif_sample_image.jpg")
original_image_content = io.open("../apps/frappe/frappe/tests/data/exif_sample_image.jpg", mode='rb').read()
new_image_content = strip_exif_data(original_image_content, "image/jpeg")
new_image = Image.open(io.BytesIO(new_image_content))
self.assertEqual(new_image._getexif(), None)
self.assertNotEqual(original_image._getexif(), new_image._getexif())

View file

@ -66,9 +66,14 @@ def get_email_address(user=None):
def get_formatted_email(user, mail=None):
"""get Email Address of user formatted as: `John Doe <johndoe@example.com>`"""
fullname = get_fullname(user)
if not mail:
mail = get_email_address(user)
return cstr(make_header(decode_header(formataddr((fullname, mail)))))
mail = get_email_address(user) or validate_email_address(user)
if not mail:
return ''
else:
return cstr(make_header(decode_header(formataddr((fullname, mail)))))
def extract_email_id(email):
"""fetch only the email part of the Email Address"""
@ -729,3 +734,27 @@ def get_build_version():
# .build can sometimes not exist
# this is not a major problem so send fallback
return frappe.utils.random_string(8)
def get_bench_relative_path(file_path):
"""Fixes paths relative to the bench root directory if exists and returns the absolute path
Args:
file_path (str, Path): Path of a file that exists on the file system
Returns:
str: Absolute path of the file_path
"""
if not os.path.exists(file_path):
base_path = '..'
elif file_path.startswith(os.sep):
base_path = os.sep
else:
base_path = '.'
file_path = os.path.join(base_path, file_path)
if not os.path.exists(file_path):
print('Invalid path {0}'.format(file_path[3:]))
sys.exit(1)
return os.path.abspath(file_path)

View file

@ -1,15 +1,17 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
from six.moves import range
import json, os
from semantic_version import Version
import frappe
import json
import os
import subprocess # nosec
import requests
import subprocess # nosec
from frappe.utils import cstr
from semantic_version import Version
from six.moves import range
import frappe
from frappe import _, safe_decode
from frappe.utils import cstr
def get_change_log(user=None):
@ -165,9 +167,10 @@ def check_for_update():
add_message_to_redis(updates)
def parse_latest_non_beta_release(response):
"""
Pasrses the response JSON for all the releases and returns the latest non prerelease
Parses the response JSON for all the releases and returns the latest non prerelease
Parameters
response (list): response object returned by github
@ -182,32 +185,51 @@ def parse_latest_non_beta_release(response):
return None
def check_release_on_github(app):
# Check if repo remote is on github
from subprocess import CalledProcessError
def check_release_on_github(app: str):
"""
Check the latest release for a given Frappe application hosted on Github.
Args:
app (str): The name of the Frappe application.
Returns:
tuple(Version, str): The semantic version object of the latest release and the
organization name, if the application exists, otherwise None.
"""
from giturlparse import parse
from giturlparse.parser import ParserError
try:
remote_url = subprocess.check_output("cd ../apps/{} && git ls-remote --get-url".format(app), shell=True).decode()
except CalledProcessError:
# Passing this since some apps may not have git initializaed in them
return None
# Check if repo remote is on github
remote_url = subprocess.check_output("cd ../apps/{} && git ls-remote --get-url".format(app), shell=True)
except subprocess.CalledProcessError:
# Passing this since some apps may not have git initialized in them
return
if isinstance(remote_url, bytes):
remote_url = remote_url.decode()
if "github.com" not in remote_url:
return None
try:
parsed_url = parse(remote_url)
except ParserError:
# Invalid URL
return
# Get latest version from github
if 'https' not in remote_url:
return None
if parsed_url.resource != "github.com":
return
org_name = remote_url.split('/')[3]
r = requests.get('https://api.github.com/repos/{}/{}/releases'.format(org_name, app))
owner = parsed_url.owner
repo = parsed_url.name
# Get latest version from GitHub
r = requests.get(f"https://api.github.com/repos/{owner}/{repo}/releases")
if r.ok:
lastest_non_beta_release = parse_latest_non_beta_release(r.json())
return Version(lastest_non_beta_release), org_name
# In case of an improper response or if there are no releases
return None
latest_non_beta_release = parse_latest_non_beta_release(r.json())
if latest_non_beta_release:
return Version(latest_non_beta_release), owner
def add_message_to_redis(update_json):
# "update-message" will store the update message string

View file

@ -61,21 +61,6 @@ def generate_and_cache_results(args, function, cache_key, chart):
frappe.db.set_value("Dashboard Chart", args.chart_name, "last_synced_on", frappe.utils.now(), update_modified = False)
return results
def get_from_date_from_timespan(to_date, timespan):
days = months = years = 0
if timespan == "Last Week":
days = -7
if timespan == "Last Month":
months = -1
elif timespan == "Last Quarter":
months = -3
elif timespan == "Last Year":
years = -1
elif timespan == "All Time":
years = -50
return add_to_date(to_date, years=years, months=months, days=days,
as_datetime=True)
def get_dashboards_with_link(docname, doctype):
dashboards = []
links = []

View file

@ -221,6 +221,27 @@ def get_last_day(dt):
"""
return get_first_day(dt, 0, 1) + datetime.timedelta(-1)
def get_quarter_ending(date):
date = getdate(date)
# find the earliest quarter ending date that is after
# the given date
for month in (3, 6, 9, 12):
quarter_end_month = getdate('{}-{}-01'.format(date.year, month))
quarter_end_date = getdate(get_last_day(quarter_end_month))
if date <= quarter_end_date:
date = quarter_end_date
break
return date
def get_year_ending(date):
''' returns year ending of the given date '''
# first day of next year (note year starts from 1)
date = add_to_date('{}-01-01'.format(date.year), months = 12)
# last day of this month
return add_to_date(date, days=-1)
def get_time(time_str):
if isinstance(time_str, datetime.datetime):
@ -348,6 +369,8 @@ def format_duration(seconds, hide_days=False):
example: converts 12885 to '3h 34m 45s' where 12885 = seconds in float
"""
seconds = cint(seconds)
total_duration = {
'days': math.floor(seconds / (3600 * 24)),
@ -1300,12 +1323,14 @@ def generate_hash(*args, **kwargs):
def guess_date_format(date_string):
DATE_FORMATS = [
r"%d/%b/%y",
r"%d-%m-%Y",
r"%m-%d-%Y",
r"%Y-%m-%d",
r"%d-%m-%y",
r"%m-%d-%y",
r"%y-%m-%d",
r"%y-%b-%d",
r"%d/%m/%Y",
r"%m/%d/%Y",
r"%Y/%m/%d",

View file

@ -5,10 +5,9 @@ from __future__ import unicode_literals
import frappe
import frappe.defaults
import datetime
from frappe.utils import get_datetime
from frappe.utils import add_to_date, getdate
from frappe.utils.data import get_last_day_of_week
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
from frappe.utils import get_datetime, add_to_date, getdate
from frappe.utils.data import get_first_day, get_first_day_of_week, get_quarter_start, get_year_start,\
get_last_day, get_last_day_of_week, get_quarter_ending, get_year_ending
from six import string_types
# global values -- used for caching
@ -102,4 +101,52 @@ def get_dates_from_timegrain(from_date, to_date, timegrain="Daily"):
else:
date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain)
dates.append(date)
return dates
return dates
def get_from_date_from_timespan(to_date, timespan):
days = months = years = 0
if timespan == "Last Week":
days = -7
if timespan == "Last Month":
months = -1
elif timespan == "Last Quarter":
months = -3
elif timespan == "Last Year":
years = -1
elif timespan == "All Time":
years = -50
return add_to_date(to_date, years=years, months=months, days=days,
as_datetime=True)
def get_period(date, interval='Monthly'):
date = getdate(date)
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
return {
'Daily': date.strftime('%d-%m-%y'),
'Weekly': date.strftime('%d-%m-%y'),
'Monthly': str(months[date.month - 1]) + ' ' + str(date.year),
'Quarterly': 'Quarter ' + str(((date.month-1)//3)+1) + ' ' + str(date.year),
'Yearly': str(date.year)
}[interval]
def get_period_beginning(date, timegrain, as_str=True):
return getdate({
'Daily': date,
'Weekly': get_first_day_of_week(date),
'Monthly': get_first_day(date),
'Quarterly': get_quarter_start(date),
'Yearly': get_year_start(date)
}[timegrain])
def get_period_ending(date, timegrain):
date = getdate(date)
if timegrain == 'Daily':
return date
else:
return getdate({
'Daily': date,
'Weekly': get_last_day_of_week(date),
'Monthly': get_last_day(date),
'Quarterly': get_quarter_ending(date),
'Yearly': get_year_ending(date)
}[timegrain])

View file

@ -10,6 +10,7 @@ import json
import os
from bs4 import BeautifulSoup
from frappe.utils import cint, strip_html_tags
from frappe.utils.html_utils import unescape_html
from frappe.model.base_document import get_controller
from six import text_type
@ -345,11 +346,8 @@ def get_formatted_value(value, field):
:return:
"""
from six.moves.html_parser import HTMLParser
if getattr(field, 'fieldtype', None) in ["Text", "Text Editor"]:
h = HTMLParser()
value = h.unescape(frappe.safe_decode(value))
value = unescape_html(frappe.safe_decode(value))
value = (re.subn(r'<[\s]*(script|style).*?</\1>(?s)', '', text_type(value))[0])
value = ' '.join(value.split())
return field.label + " : " + strip_html_tags(text_type(value))

View file

@ -34,7 +34,7 @@ def clean_email_html(html):
'margin', 'margin-top', 'margin-bottom', 'margin-left', 'margin-right',
'padding', 'padding-top', 'padding-bottom', 'padding-left', 'padding-right',
'font-size', 'font-weight', 'font-family', 'text-decoration',
'line-height', 'text-align', 'vertical-align'
'line-height', 'text-align', 'vertical-align', 'display'
],
protocols=['cid', 'http', 'https', 'mailto', 'data'],
strip=True, strip_comments=True)
@ -106,9 +106,8 @@ def get_icon_html(icon, small=False):
return "<i class='{icon}'></i>".format(icon=icon)
def unescape_html(value):
from six.moves.html_parser import HTMLParser
h = HTMLParser()
return h.unescape(value)
from html import unescape
return unescape(value)
# adapted from https://raw.githubusercontent.com/html5lib/html5lib-python/4aa79f113e7486c7ec5d15a6e1777bfe546d3259/html5lib/sanitizer.py
acceptable_elements = [

View file

@ -5,7 +5,7 @@ from __future__ import unicode_literals, print_function
import os
def resize_images(path, maxdim=700):
import Image
from PIL import Image
size = (maxdim, maxdim)
for basepath, folders, files in os.walk(path):
for fname in files:
@ -17,3 +17,27 @@ def resize_images(path, maxdim=700):
im.save(os.path.join(basepath, fname))
print("resized {0}".format(os.path.join(basepath, fname)))
def strip_exif_data(content, content_type):
""" Strips EXIF from image files which support it.
Works by creating a new Image object which ignores exif by
default and then extracts the binary data back into content.
Returns:
Bytes: Stripped image content
"""
from PIL import Image
import io
original_image = Image.open(io.BytesIO(content))
output = io.BytesIO()
new_image = Image.new(original_image.mode, original_image.size)
new_image.putdata(list(original_image.getdata()))
new_image.save(output, format=content_type.split('/')[1])
content = output.getvalue()
return content

View file

@ -123,7 +123,7 @@ def make_logs(response = None):
def json_handler(obj):
"""serialize non-serializable data for json"""
# serialize date
import collections
import collections.abc
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)):
return text_type(obj)
@ -138,7 +138,7 @@ def json_handler(obj):
doc = obj.as_dict(no_nulls=True)
return doc
elif isinstance(obj, collections.Iterable):
elif isinstance(obj, collections.abc.Iterable):
return list(obj)
elif type(obj)==type or isinstance(obj, Exception):

View file

@ -13,7 +13,19 @@ from frappe.www.printview import get_visible_columns
import frappe.exceptions
import frappe.integrations.utils
class ServerScriptNotEnabled(frappe.PermissionError): pass
class ServerScriptNotEnabled(frappe.PermissionError):
pass
class NamespaceDict(frappe._dict):
"""Raise AttributeError if function not found in namespace"""
def __getattr__(self, key):
ret = self.get(key)
if (not ret and key.startswith("__")) or (key not in self):
def default_function(*args, **kwargs):
raise AttributeError(f"module has no attribute '{key}'")
return default_function
return ret
def safe_exec(script, _globals=None, _locals=None):
# script reports must be enabled via site_config.json
@ -46,13 +58,13 @@ def get_safe_globals():
user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest"
out = frappe._dict(
out = NamespaceDict(
# make available limited methods of frappe
json=json,
dict=dict,
log=frappe.log,
_dict=frappe._dict,
frappe=frappe._dict(
frappe=NamespaceDict(
flags=frappe._dict(),
format=frappe.format_value,
format_value=frappe.format_value,
@ -112,7 +124,7 @@ def get_safe_globals():
out.get_visible_columns = get_visible_columns
out.frappe.date_format = date_format
out.frappe.time_format = time_format
out.frappe.db = frappe._dict(
out.frappe.db = NamespaceDict(
get_list = frappe.get_list,
get_all = frappe.get_all,
get_value = frappe.db.get_value,

View file

@ -100,6 +100,7 @@ def get_rendered_template(doc, name=None, print_format=None, meta=None,
doc.print_section_headings = print_format.show_section_headings
doc.print_line_breaks = print_format.line_breaks
doc.align_labels_right = print_format.align_labels_right
doc.absolute_value = print_format.absolute_value
def get_template_from_string():
return jenv.from_string(get_print_format(doc.doctype,

View file

@ -32,7 +32,7 @@
"frappe-datatable": "^1.15.3",
"frappe-gantt": "^0.5.0",
"fuse.js": "^3.4.6",
"highlight.js": "^9.18.1",
"highlight.js": "^9.18.2",
"js-sha256": "^0.9.0",
"jsbarcode": "^3.9.0",
"moment": "^2.20.1",
@ -44,7 +44,7 @@
"qz-tray": "^2.0.8",
"redis": "^2.8.0",
"showdown": "^1.9.1",
"snyk": "^1.398.1",
"snyk": "^1.425.4",
"socket.io": "^2.3.0",
"superagent": "^3.8.2",
"touch": "^3.1.0",

View file

@ -15,6 +15,7 @@ Faker==2.0.4
future==0.18.2
gitdb2==2.0.6;python_version<'3.4'
GitPython==2.1.15
git-url-parse==1.2.2
google-api-python-client==1.9.3
google-auth-httplib2==0.0.3
google-auth-oauthlib==0.4.1
@ -34,7 +35,7 @@ oauthlib==3.1.0
openpyxl==2.6.4
passlib==1.7.3
pdfkit==0.6.1
Pillow==7.1.0
Pillow>=8.0.0
premailer==3.6.1
psycopg2-binary==2.8.4
pyasn1==0.4.8
@ -73,4 +74,4 @@ pycryptodome==3.9.8
paytmchecksum==1.7.0
wrapt==1.10.11
razorpay==1.2.0
rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability
rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability

363
yarn.lock
View file

@ -93,10 +93,21 @@
source-map-support "^0.5.19"
tslib "^1.13.0"
"@snyk/docker-registry-v2-client@^1.13.5":
version "1.13.5"
resolved "https://registry.yarnpkg.com/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.5.tgz#8d862f0c53d4a9a25db09cd48b4cd44aa8e385c9"
integrity sha512-lgJiC071abCpFVLp47OnykU8MMrhdQe386Wt6QaDmjI0s2DQn/S58NfdLrPU7s6l4zoGT7UwRW9+7paozRgFTA==
"@snyk/dep-graph@^1.19.5":
version "1.20.0"
resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.20.0.tgz#258ae85f8a066dc63af4444cfca8b8d092b94bc0"
integrity sha512-/TOzXGh+JFgAu8pWdo1oLFKDNfFk99TnSQG2lbEu+vKLI2ZrGAk9oGO0geNogAN7Ib4EDQOEhgb7YwqwL7aA7w==
dependencies:
graphlib "^2.1.8"
lodash.isequal "^4.5.0"
object-hash "^2.0.3"
semver "^6.0.0"
tslib "^1.13.0"
"@snyk/docker-registry-v2-client@1.13.9":
version "1.13.9"
resolved "https://registry.yarnpkg.com/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.9.tgz#54c2e3071de58fc6fc12c5fef5eaeae174ecda12"
integrity sha512-DIFLEhr8m1GrAwsLGInJmpcQMacjuhf3jcbpQTR+LeMvZA9IuKq+B7kqw2O2FzMiHMZmUb5z+tV+BR7+IUHkFQ==
dependencies:
needle "^2.5.0"
parse-link-header "^1.0.1"
@ -107,10 +118,10 @@
resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.2.0.tgz#919857944973cce74c650e5428aaf11bcd5c0457"
integrity sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA==
"@snyk/java-call-graph-builder@1.13.2":
version "1.13.2"
resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.13.2.tgz#6e4a9495d5c47bbab9bc69e066d4646473781b67"
integrity sha512-YN3a93ttscqFQRUeThrxa7i2SJkFPfYn0VpFqdPB6mIJz2fRVLxUkMtlCbG0aSEUvWiLnGVHN0IYxwWEzhq11w==
"@snyk/java-call-graph-builder@1.16.2":
version "1.16.2"
resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.16.2.tgz#a9f9a34107759cf2be847a114a759e347cef44e8"
integrity sha512-tJF+dY/wTfexwYuCgFB3RpWl4RGcf2H9RT9yurkTVi5wwKfvcNwZMUMwSlTDEFOqwmAsJ7e0uNVRlkPQHekCcQ==
dependencies:
ci-info "^2.0.0"
debug "^4.1.1"
@ -119,11 +130,29 @@
jszip "^3.2.2"
needle "^2.3.3"
progress "^2.0.3"
snyk-config "^3.0.0"
snyk-config "^4.0.0-rc.2"
source-map-support "^0.5.7"
temp-dir "^2.0.0"
tslib "^1.9.3"
"@snyk/java-call-graph-builder@1.16.5":
version "1.16.5"
resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.16.5.tgz#e57302cc6dc93f1adff7abe1e5eecff26d8a41f4"
integrity sha512-6H4hkq/qYljJoH1QnZsTRPMqp9Kt5AOEZYGJAeSHkhJdfUYSLtqwN4WsU6yVR3vWAaDQ8Lllp3m6EL7nstMPZA==
dependencies:
ci-info "^2.0.0"
debug "^4.1.1"
glob "^7.1.6"
graphlib "^2.1.8"
jszip "^3.2.2"
needle "^2.3.3"
progress "^2.0.3"
snyk-config "^4.0.0-rc.2"
source-map-support "^0.5.7"
temp-dir "^2.0.0"
tmp "^0.2.1"
tslib "^1.9.3"
"@snyk/rpm-parser@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@snyk/rpm-parser/-/rpm-parser-2.0.0.tgz#4ded7fa4b0a8efca7699359e4ca7a79bfbe38bc1"
@ -142,12 +171,12 @@
source-map-support "^0.5.7"
tslib "^2.0.0"
"@snyk/snyk-docker-pull@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.0.tgz#07c47b8be2d899d51d720099a73a0d89effe5d99"
integrity sha512-uWKtjh29I/d0mfmfBN7w6RwwNBQxQVKrauF5ND/gqb0PVsKV22GIpkI+viWjI7KNKso6/B0tMmsv7TX2tsNcLQ==
"@snyk/snyk-docker-pull@3.2.3":
version "3.2.3"
resolved "https://registry.yarnpkg.com/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.3.tgz#9743ea624098c7abd0f95c438c76067530494f4b"
integrity sha512-hiFiSmWGLc2tOI7FfgIhVdFzO2f69im8O6p3OV4xEZ/Ss1l58vwtqudItoswsk7wj/azRlgfBW8wGu2MjoudQg==
dependencies:
"@snyk/docker-registry-v2-client" "^1.13.5"
"@snyk/docker-registry-v2-client" "1.13.9"
child-process "^1.0.2"
tar-stream "^2.1.2"
tmp "^0.1.0"
@ -545,10 +574,10 @@ async-limiter@~1.0.0:
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
async@^1.4.0:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
async@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
asynckit@^0.4.0:
version "0.4.0"
@ -757,6 +786,13 @@ braces@^2.3.1:
split-string "^3.0.2"
to-regex "^3.0.1"
braces@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
browserify-zlib@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
@ -896,7 +932,7 @@ camelcase-keys@^2.0.0:
camelcase "^2.0.0"
map-obj "^1.0.0"
camelcase@^2.0.0, camelcase@^2.0.1:
camelcase@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
@ -1021,15 +1057,6 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
cliui@^3.0.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
dependencies:
string-width "^1.0.1"
strip-ansi "^3.0.1"
wrap-ansi "^2.0.0"
cliui@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
@ -1507,7 +1534,14 @@ debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
dependencies:
ms "^2.1.1"
decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
debug@^4.2.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"
decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@ -1751,6 +1785,13 @@ electron-to-chromium@^1.3.523:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.551.tgz#a94d243a4ca90705189bd4a5eca4e0f56b745a4f"
integrity sha512-11qcm2xvf2kqeFO5EIejaBx5cKXsW1quAyv3VctCMYwofnyVZLs97y6LCekss3/ghQpr7PYkSO3uId5FmxZsdw==
elfy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/elfy/-/elfy-1.0.0.tgz#7a1c86af7d41e0a568cbb4a3fa5b685648d9efcd"
integrity sha512-4Kp3AA94jC085IJox+qnvrZ3PudqTi4gQNvIoTZfJJ9IqkRuCoqP60vCVYlIg00c5aYusi5Wjh2bf0cHYt+6gQ==
dependencies:
endian-reader "^0.3.0"
email-validator@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed"
@ -1783,6 +1824,11 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
dependencies:
once "^1.4.0"
endian-reader@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/endian-reader/-/endian-reader-0.3.0.tgz#84eca436b80aed0d0639c47291338b932efe50a0"
integrity sha1-hOykNrgK7Q0GOcRykTOLky7+UKA=
engine.io-client@~3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.0.tgz#82a642b42862a9b3f7a188f41776b2deab643700"
@ -2177,6 +2223,13 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
finalhandler@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
@ -2247,9 +2300,9 @@ fragment-cache@^0.2.1:
map-cache "^0.2.2"
frappe-charts@^1.5.1:
version "1.5.3"
resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-1.5.3.tgz#0dcb86ea774fa7a3e1b79221e958d29701dfff04"
integrity sha512-VS5XVxek41ea8mVzetyFF3avNefiwGDcDSDJuHrZyJXgbqiTSXLoqlPFoMqTzuzRm1g+o6TXs+A7wLtVp3Vt0g==
version "1.5.4"
resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-1.5.4.tgz#5870f77ac6ffc8ea4dab32adda1d4e5e4fbda64b"
integrity sha512-hBr7cRLmsCC5VBj/HwKOCgdwyXnkeAO5CAvOd5H4IYFbk84VD9jOjx9fSaqAE0MygVVbY1nCN+5nb08WThW4Xw==
frappe-datatable@^1.15.3:
version "1.15.3"
@ -2649,10 +2702,10 @@ hex-color-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
highlight.js@^9.18.1:
version "9.18.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c"
integrity sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==
highlight.js@^9.18.2:
version "9.18.5"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825"
integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==
homedir-polyfill@^1.0.1:
version "1.0.3"
@ -2678,6 +2731,13 @@ hosted-git-info@^3.0.4:
dependencies:
lru-cache "^6.0.0"
hosted-git-info@^3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.7.tgz#a30727385ea85acfcee94e0aad9e368c792e036c"
integrity sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==
dependencies:
lru-cache "^6.0.0"
hsl-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e"
@ -2857,7 +2917,7 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^1.3.0, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
@ -2881,11 +2941,6 @@ inquirer@^7.3.3:
strip-ansi "^6.0.0"
through "^2.3.6"
invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
iota-array@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/iota-array/-/iota-array-1.0.0.tgz#81ef57fe5d05814cd58c2483632a99c30a0e8087"
@ -3122,6 +3177,11 @@ is-number@^3.0.0:
dependencies:
kind-of "^3.0.2"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
@ -3447,13 +3507,6 @@ latest-version@^5.0.0:
dependencies:
package-json "^6.3.0"
lcid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
dependencies:
invert-kv "^1.0.0"
less@^3.11.1:
version "3.11.1"
resolved "https://registry.yarnpkg.com/less/-/less-3.11.1.tgz#c6bf08e39e02404fe6b307a3dfffafdc55bd36e2"
@ -3750,6 +3803,14 @@ methods@^1.1.1, methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
micromatch@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
dependencies:
braces "^3.0.1"
picomatch "^2.0.5"
micromatch@^3.1.10:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@ -3896,7 +3957,7 @@ ms@2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
ms@^2.1.1:
ms@2.1.2, ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
@ -3928,16 +3989,6 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
nconf@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.10.0.tgz#da1285ee95d0a922ca6cee75adcf861f48205ad2"
integrity sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q==
dependencies:
async "^1.4.0"
ini "^1.3.0"
secure-keys "^1.0.0"
yargs "^3.19.0"
ndarray-linear-interpolate@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ndarray-linear-interpolate/-/ndarray-linear-interpolate-1.0.0.tgz#78bc92b85b9abc15b6e67ee65828f9e2137ae72b"
@ -4268,13 +4319,6 @@ os-homedir@^1.0.0, os-homedir@^1.0.1:
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
os-locale@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=
dependencies:
lcid "^1.0.0"
os-name@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801"
@ -4508,6 +4552,11 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picomatch@^2.0.5:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@ -5687,11 +5736,6 @@ scss-tokenizer@^0.2.3:
js-base64 "^2.1.8"
source-map "^0.4.2"
secure-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca"
integrity sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=
semver-diff@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
@ -5862,40 +5906,55 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^3.1.0"
snyk-config@3.1.1, snyk-config@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-3.1.1.tgz#a511ef8bf769545f0564e09d382b5ea3aacb9c6a"
integrity sha512-wwrMIEDozfLJ8LmakCsCC1FQ0siIX5icCQPCbUKKgRbeVsZ27NjPJs37BpTXX4rcHkaWpe8TbH3yOtp23qmszg==
snyk-config@4.0.0-rc.2:
version "4.0.0-rc.2"
resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-4.0.0-rc.2.tgz#c6c94afe733e9063df546cd71a7adf6957135594"
integrity sha512-HIXpMCRp5IdQDFH/CY6WqOUt5X5Ec55KC9dFVjlMLe/2zeqsImJn1vbjpE5uBoLYIdYi1SteTqtsJhyJZWRK8g==
dependencies:
async "^3.2.0"
debug "^4.1.1"
lodash.merge "^4.6.2"
nconf "^0.10.0"
minimist "^1.2.5"
snyk-cpp-plugin@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/snyk-cpp-plugin/-/snyk-cpp-plugin-1.5.0.tgz#2ec2068fdcf5e579eb7d9b9eed8bb984fd00a925"
integrity sha512-nBZ0cBmpT4RVJUFzYydQJOxwjcdXk7NtRJE1UIIOafQa2FcvIl3GBezfrCJ6pu61svOAf5r8Qi/likx6F15K1A==
snyk-config@^4.0.0-rc.2:
version "4.0.0"
resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-4.0.0.tgz#21d459f19087991246cc07a7ffb4501dce6f4159"
integrity sha512-E6jNe0oUjjzVASWBOAc/mA23DhbzABDF9MI6UZvl0gylh2NSXSXw2/LjlqMNOKL2c1qkbSkzLOdIX5XACoLCAQ==
dependencies:
async "^3.2.0"
debug "^4.1.1"
lodash.merge "^4.6.2"
minimist "^1.2.5"
snyk-cpp-plugin@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/snyk-cpp-plugin/-/snyk-cpp-plugin-2.2.1.tgz#55891511a43a6448e5a7c836a94f66f70fa705eb"
integrity sha512-NFwVLMCqKTocY66gcim0ukF6e31VRDJqDapg5sy3vCHqlD1OCNUXSK/aI4VQEEndDrsnFmQepsL5KpEU0dDRIQ==
dependencies:
"@snyk/dep-graph" "^1.19.3"
chalk "^4.1.0"
debug "^4.1.1"
hosted-git-info "^3.0.7"
tslib "^2.0.0"
snyk-docker-plugin@3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-3.21.0.tgz#a92074c0411578c1a7b86852a06f1421770e985d"
integrity sha512-A7oJS3QGR7bwm1qeeczCb8PDfi8go1KM6VWph/drJHBQ7JxVKKLb3j4AzrMmIM96mGZFbmyNOL4pznwumaOM8g==
snyk-docker-plugin@4.12.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-4.12.0.tgz#137a159baf627debef6178cfb8b40941a81a7168"
integrity sha512-iN5GUTpMR4dx/hmjxh1GnJ9vrMpbOUhD8gsdWgFPZ5Qg+ImPQ2WBJBal/hyfkauM0TaKQEAgIwT6xZ1ovaIvWQ==
dependencies:
"@snyk/dep-graph" "^1.19.4"
"@snyk/rpm-parser" "^2.0.0"
"@snyk/snyk-docker-pull" "^3.2.0"
"@snyk/snyk-docker-pull" "3.2.3"
chalk "^2.4.2"
debug "^4.1.1"
docker-modem "2.1.3"
dockerfile-ast "0.0.30"
elfy "^1.0.0"
event-loop-spinner "^2.0.0"
gunzip-maybe "^1.4.2"
mkdirp "^1.0.4"
semver "^6.1.0"
snyk-nodejs-lockfile-parser "1.28.1"
snyk-nodejs-lockfile-parser "1.30.1"
tar-stream "^2.1.0"
tmp "^0.2.1"
tslib "^1"
@ -5921,13 +5980,14 @@ snyk-go-plugin@1.16.2:
tmp "0.2.1"
tslib "^1.10.0"
snyk-gradle-plugin@3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.6.3.tgz#484059bcb98469b6a674bbcbdc995eafb5581041"
integrity sha512-j/eQSLSsK3DHmvVX2fNig4+ugYrKlCOV8Xvo6OYFkNzhMpdyNFiGWTS1uyP1HH75Gyc78MaLANMgjlSYePukzQ==
snyk-gradle-plugin@3.10.2:
version "3.10.2"
resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.10.2.tgz#f3e104d42989e49b5c05818f005cae8c544c9803"
integrity sha512-gTFKL0BLUN54asUQ4OIoa4lATGn27VZwWDJGQ0VuqSaaoy8I5W16Cbn/KN95oIKa7tgwrmasPLd5uviFWzo/Qw==
dependencies:
"@snyk/cli-interface" "2.9.1"
"@snyk/dep-graph" "^1.19.4"
"@snyk/java-call-graph-builder" "1.16.2"
"@types/debug" "^4.1.4"
chalk "^3.0.0"
debug "^4.1.1"
@ -5960,22 +6020,23 @@ snyk-module@^2.0.2:
debug "^3.1.0"
hosted-git-info "^2.7.1"
snyk-mvn-plugin@2.19.4:
version "2.19.4"
resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.19.4.tgz#4e29fa82b9ca409789d441939c766797d6a2360f"
integrity sha512-kYPUKOugnNd31PFqx1YHJTo90pospELYHME4AzBx8dkMDgs5ZPjAmQXSxegQ3AMUqfqcETMSTzlKHe6uHujI8A==
snyk-mvn-plugin@2.23.4:
version "2.23.4"
resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.23.4.tgz#3f43601058aa51e8a0f9e272a7c186cad4b26950"
integrity sha512-1dWqvFu6eo2KsXFDqRF28JFwrdzpc0k+GwpIqv7vF2kHarsMxnLnT/akhjbKzs+xlRTNFvqdKhEQxjdq2nSD1Q==
dependencies:
"@snyk/cli-interface" "2.9.1"
"@snyk/java-call-graph-builder" "1.13.2"
"@snyk/java-call-graph-builder" "1.16.5"
debug "^4.1.1"
glob "^7.1.6"
needle "^2.5.0"
tmp "^0.1.0"
tslib "1.11.1"
snyk-nodejs-lockfile-parser@1.28.1:
version "1.28.1"
resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.28.1.tgz#9eda1354bbca1fc881a4e63a1e1042f80c37bff2"
integrity sha512-0zbmtidYLI2ia/DQD4rZm2YKrhfHLvHlVBdF2cMAGPwhOoKW5ovG9eBO4wNQdvjxNi7b4VeUyAj8SfuhjDraDQ==
snyk-nodejs-lockfile-parser@1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.30.1.tgz#5d54180ae818ddbe8c2b55329528c4d68e390235"
integrity sha512-QyhE4pmy7GI7fQrVmZ+qrQB8GGSbxN7OoYueS4BEP9nDxIyH4dJAz8dME5zOUeUxh3frcgBWoWgZoSzE4VOYpg==
dependencies:
"@yarnpkg/lockfile" "^1.1.0"
event-loop-spinner "^2.0.0"
@ -5987,16 +6048,15 @@ snyk-nodejs-lockfile-parser@1.28.1:
lodash.set "^4.3.2"
lodash.topairs "^4.3.0"
p-map "2.1.0"
snyk-config "^3.0.0"
source-map-support "^0.5.7"
snyk-config "^4.0.0-rc.2"
tslib "^1.9.3"
uuid "^3.3.2"
uuid "^8.3.0"
yaml "^1.9.2"
snyk-nuget-plugin@1.19.3:
version "1.19.3"
resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.19.3.tgz#5b4d9a5a61a543810c98bd4e67b9f6b1d95e3c3a"
integrity sha512-KwKoMumwcXVz/DQH80ifXfX7CTnm29bmHJ2fczjCGohxLGb4EKBGQtA3t7K98O7lTISQGgXDxnWIaM9ZXkxPdw==
snyk-nuget-plugin@1.19.4:
version "1.19.4"
resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.19.4.tgz#cd1163a29f8002d54a965eab9e256345c97d4174"
integrity sha512-6BvLJc7gpNdfPJSnvpmTL4BrbaOVbXh/9q1FNMs5OVp8NbnZ3l97iM+bpQXWTJHOa3BJBZz7iEg+3suH4AWoWw==
dependencies:
debug "^4.1.1"
dotnet-deps-parser "5.0.0"
@ -6022,6 +6082,17 @@ snyk-php-plugin@1.9.2:
"@snyk/composer-lockfile-parser" "^1.4.1"
tslib "1.11.1"
snyk-poetry-lockfile-parser@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/snyk-poetry-lockfile-parser/-/snyk-poetry-lockfile-parser-1.1.1.tgz#3f062953802916f6ae1767ec13dd1892fff0541e"
integrity sha512-G3LX27V2KUsKObwVN4vDDjrYr5BERad9pXHAf+SST5+vZsdPUUZjd1ZUIrHgCv7IQhwq+7mZrtqedY5x7+LIGA==
dependencies:
"@snyk/cli-interface" "^2.9.2"
"@snyk/dep-graph" "^1.19.5"
debug "^4.2.0"
toml "^3.0.0"
tslib "^2.0.0"
snyk-policy@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.14.1.tgz#4e48ea993573aca18e8d883b8c62171b9d35a3e0"
@ -6037,12 +6108,13 @@ snyk-policy@1.14.1:
snyk-try-require "^1.3.1"
then-fs "^2.0.0"
snyk-python-plugin@1.17.1:
version "1.17.1"
resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.17.1.tgz#303ec2885ef748634d89f22f3099ef1febdc3325"
integrity sha512-KKklat9Hfbj4hw2y63LRhgmziYzmyRt+cSuzN5KDmBSAGYck0EAoPDtNpJXjrIs1kPNz28EXnE6NDnadXnOjiQ==
snyk-python-plugin@1.19.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.19.1.tgz#91febcd260094a9d900bc54bf200aa0c2632613a"
integrity sha512-JoOUHnA76L3pekCblSuE9jQ9CuA5jt+GqXpsLQbEIZ0FQQTBa+0F7vfolg3Q7+s1it4ZdtgSbSWrlxCngIJt8g==
dependencies:
"@snyk/cli-interface" "^2.0.3"
snyk-poetry-lockfile-parser "^1.1.1"
tmp "0.0.33"
snyk-resolve-deps@4.4.0:
@ -6104,10 +6176,10 @@ snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1:
lru-cache "^4.0.0"
then-fs "^2.0.0"
snyk@^1.398.1:
version "1.398.1"
resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.398.1.tgz#19aec8dfffa60e7412e6309117e96b2cfa960355"
integrity sha512-jH24ztdJY8DQlqkd1z8n/JutdOqHtTPccCynM2hfOedW20yAp9c108LFjXvqBEk/EH3YyNmWzyLkkHOySeDkwQ==
snyk@^1.425.4:
version "1.431.1"
resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.431.1.tgz#1e360dae1b63d83f74fe90979f7b9a0fb1607aa7"
integrity sha512-OW48lG89ffLsSZPHwsjfdqQcu3XG6aRQOkwASPCgTAGcVcnXzS9XHB89h0gLsDzk0fZRskEVgYpvXdh4RFjNqA==
dependencies:
"@snyk/cli-interface" "2.9.2"
"@snyk/dep-graph" "1.19.4"
@ -6120,28 +6192,28 @@ snyk@^1.398.1:
configstore "^5.0.1"
debug "^4.1.1"
diff "^4.0.1"
glob "^7.1.3"
graphlib "^2.1.8"
inquirer "^7.3.3"
lodash "^4.17.20"
micromatch "4.0.2"
needle "2.5.0"
open "^7.0.3"
os-name "^3.0.0"
proxy-agent "^3.1.1"
proxy-from-env "^1.0.0"
semver "^6.0.0"
snyk-config "3.1.1"
snyk-cpp-plugin "1.5.0"
snyk-docker-plugin "3.21.0"
snyk-config "4.0.0-rc.2"
snyk-cpp-plugin "2.2.1"
snyk-docker-plugin "4.12.0"
snyk-go-plugin "1.16.2"
snyk-gradle-plugin "3.6.3"
snyk-gradle-plugin "3.10.2"
snyk-module "3.1.0"
snyk-mvn-plugin "2.19.4"
snyk-nodejs-lockfile-parser "1.28.1"
snyk-nuget-plugin "1.19.3"
snyk-mvn-plugin "2.23.4"
snyk-nodejs-lockfile-parser "1.30.1"
snyk-nuget-plugin "1.19.4"
snyk-php-plugin "1.9.2"
snyk-policy "1.14.1"
snyk-python-plugin "1.17.1"
snyk-python-plugin "1.19.1"
snyk-resolve "1.0.1"
snyk-resolve-deps "4.4.0"
snyk-sbt-plugin "2.11.0"
@ -6760,6 +6832,13 @@ to-regex-range@^2.1.0:
is-number "^3.0.0"
repeat-string "^1.6.1"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
to-regex@^3.0.1, to-regex@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
@ -7023,6 +7102,11 @@ uuid@^8.2.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
uuid@^8.3.0:
version "8.3.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
@ -7147,11 +7231,6 @@ widest-line@^3.1.0:
dependencies:
string-width "^4.0.0"
window-size@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876"
integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=
windows-release@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f"
@ -7164,14 +7243,6 @@ word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
dependencies:
string-width "^1.0.1"
strip-ansi "^3.0.1"
wrap-ansi@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
@ -7241,11 +7312,6 @@ xtend@~4.0.1:
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^3.2.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
@ -7320,19 +7386,6 @@ yargs@^14.2:
y18n "^4.0.0"
yargs-parser "^15.0.0"
yargs@^3.19.0:
version "3.32.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=
dependencies:
camelcase "^2.0.1"
cliui "^3.0.3"
decamelize "^1.1.1"
os-locale "^1.4.0"
string-width "^1.0.1"
window-size "^0.1.4"
y18n "^3.2.0"
yeast@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"