Merge branch 'develop'

This commit is contained in:
Nabin Hait 2017-04-10 16:08:19 +05:30
commit f911dbf02f
60 changed files with 2665 additions and 1677 deletions

View file

@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template
__version__ = '8.0.7'
__version__ = '8.0.8'
__title__ = "Frappe Framework"
local = Local()
@ -1321,3 +1321,14 @@ def get_desk_link(doctype, name):
def bold(text):
return '<b>{0}</b>'.format(text)
def safe_eval(code, eval_globals=None, eval_locals=None):
'''A safer `eval`'''
if '__' in code:
throw('Illegal rule {0}. Cannot use "__"'.format(bold(code)))
if not eval_globals:
eval_globals = {}
eval_globals['__builtins__'] = {}
return eval(code, eval_globals, eval_locals)

View file

@ -134,6 +134,12 @@ def handle_exception(e):
# code 409 represents conflict
http_status_code = 508
if http_status_code==401:
frappe.respond_as_web_page(_("Session Expired"),
_("Your session has expired, please login again to continue."),
http_status_code=http_status_code, indicator_color='red')
return_as_message = True
if http_status_code==403:
frappe.respond_as_web_page(_("Not Permitted"),
_("You do not have enough permissions to complete the action"),

File diff suppressed because it is too large Load diff

View file

@ -12,29 +12,41 @@ from frappe.utils.jinja import validate_template
class FeedbackTrigger(Document):
def validate(self):
frappe.cache().delete_value('feedback_triggers')
validate_template(self.subject)
validate_template(self.message)
self.validate_condition()
def on_trash(self):
frappe.cache().delete_value('feedback_triggers')
def validate_condition(self):
temp_doc = frappe.new_doc(self.document_type)
if self.condition:
try:
eval(self.condition, get_context(temp_doc))
frappe.safe_eval(self.condition, None, get_context(temp_doc))
except:
frappe.throw(_("The condition '{0}' is invalid").format(self.condition))
def trigger_feedback_request(doc, method):
""" trigger the feedback alert"""
"""Trigger the feedback alert, or delete feedback requests on delete"""
if doc.flags.in_delete:
frappe.enqueue('frappe.core.doctype.feedback_trigger.feedback_trigger.delete_feedback_request_and_feedback',
reference_doctype=doc.doctype, reference_name=doc.name, now=frappe.flags.in_test)
else:
feedback_trigger = frappe.db.get_value("Feedback Trigger", { "enabled": 1, "document_type": doc.doctype })
if feedback_trigger:
def _get():
triggers = {}
for d in frappe.get_all('Feedback Trigger', dict(enabled=1), ['name', 'document_type']):
triggers[d.document_type] = d.name
return triggers
feedback_triggers = frappe.cache().get_value('feedback_triggers', _get)
if doc.doctype in feedback_triggers:
if doc.flags.in_delete:
frappe.enqueue('frappe.core.doctype.feedback_trigger.feedback_trigger.delete_feedback_request_and_feedback',
reference_doctype=doc.doctype, reference_name=doc.name, now=frappe.flags.in_test)
else:
frappe.enqueue('frappe.core.doctype.feedback_trigger.feedback_trigger.send_feedback_request',
trigger=feedback_trigger, reference_doctype=doc.doctype, reference_name=doc.name, now=frappe.flags.in_test)
trigger=feedback_triggers[doc.doctype], reference_doctype=doc.doctype,
reference_name=doc.name, now=frappe.flags.in_test)
@frappe.whitelist()
def send_feedback_request(reference_doctype, reference_name, trigger="Manual", details=None, is_manual=False):
@ -68,8 +80,6 @@ def send_feedback_request(reference_doctype, reference_name, trigger="Manual", d
@frappe.whitelist()
def get_feedback_request_details(reference_doctype, reference_name, trigger="Manual", request=None):
feedback_url = ""
if not frappe.db.get_value(reference_doctype, reference_name):
# reference document is either deleted or renamed
return
@ -101,7 +111,7 @@ def get_feedback_request_details(reference_doctype, reference_name, trigger="Man
frappe.msgprint(_("At least one reply is mandatory before requesting feedback"))
return None
if recipients and eval(feedback_trigger.condition, context):
if recipients and frappe.safe_eval(feedback_trigger.condition, None, context):
subject = feedback_trigger.subject
context.update({ "feedback_trigger": feedback_trigger })
@ -132,7 +142,7 @@ def get_feedback_request_url(reference_doctype, reference_name, recipients, trig
"reference_doctype": reference_doctype,
}).insert(ignore_permissions=True)
feedback_url = "{base_url}/feedback?reference_doctype={doctype}&reference_name={docname}&email={email_id}&key={nonce}".format(
feedback_url = "{base_url}/feedback?reference_doctype={doctype}&reference_name={docname}&email={email_id}&key={nonce}".format(
base_url=get_url(),
doctype=reference_doctype,
docname=reference_name,
@ -143,7 +153,7 @@ def get_feedback_request_url(reference_doctype, reference_name, recipients, trig
return [ feedback_request.name, feedback_url ]
def is_feedback_request_already_sent(reference_doctype, reference_name, is_manual=False):
"""
"""
check if feedback request mail is already sent but feedback is not submitted
to avoid sending multiple feedback request mail
"""

View file

@ -119,6 +119,7 @@ frappe.DataImportTool = Class.extend({
args: {
method: 'frappe.core.page.data_import_tool.importer.upload',
},
allow_multiple: 0,
onerror: function(r) {
me.onerror(r);
},

View file

@ -204,7 +204,7 @@
"no_copy": 0,
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"options": "Attach\nAttach Image\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,

View file

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "DL.####",
@ -22,6 +23,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Enter Form Type",
@ -51,6 +53,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
@ -78,6 +81,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Change Label (via Custom Translation)",
@ -106,6 +110,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Default Print Format",
@ -135,6 +140,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Max Attachments",
@ -162,6 +168,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Hide Copy",
@ -189,6 +196,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Table",
@ -218,6 +226,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Editable Grid",
@ -247,6 +256,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Quick Entry",
@ -264,6 +274,35 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "track_changes",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Track Changes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -276,6 +315,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image View",
@ -304,6 +344,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -332,6 +373,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Title Field",
@ -361,6 +403,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image Field",
@ -390,6 +433,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Search Fields",
@ -418,6 +462,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -445,6 +490,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Sort Field",
@ -472,6 +518,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -499,6 +546,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Sort Order",
@ -529,6 +577,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Fields",
@ -556,6 +605,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Fields",
@ -574,18 +624,18 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 1,
"icon": "fa fa-glass",
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2016-12-29 14:40:32.113132",
"modified": "2017-04-10 12:17:23.627634",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form",
@ -601,7 +651,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -617,6 +666,7 @@
"read_only": 0,
"read_only_onload": 0,
"search_fields": "doc_type",
"show_name_in_global_search": 0,
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0

View file

@ -27,7 +27,8 @@ doctype_properties = {
'quick_entry': 'Check',
'editable_grid': 'Check',
'max_attachments': 'Int',
'image_view': 'Check'
'image_view': 'Check',
'track_changes': 'Check',
}
docfield_properties = {
@ -63,7 +64,7 @@ docfield_properties = {
}
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
('Text', 'Data'), ('Text', 'Text Editor', 'Code'), ('Data', 'Select'),
('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature'), ('Data', 'Select'),
('Text', 'Small Text'))
class CustomizeForm(Document):
@ -312,6 +313,7 @@ class CustomizeForm(Document):
for allowed_changes in allowed_fieldtype_change:
if (old_value in allowed_changes and new_value in allowed_changes):
allowed = True
break
if not allowed:
frappe.throw(_("Fieldtype cannot be changed from {0} to {1} in row {2}").format(old_value, new_value, df.idx))

View file

@ -46,7 +46,7 @@ class TestCustomizeForm(unittest.TestCase):
d = self.get_customize_form("Event")
self.assertEquals(d.doc_type, "Event")
self.assertEquals(len(d.get("fields")), 27)
self.assertEquals(len(d.get("fields")), 29)
d = self.get_customize_form("User")
self.assertEquals(d.doc_type, "User")

View file

@ -90,7 +90,7 @@
"no_copy": 0,
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"options": "Attach\nAttach Image\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nText\nText Editor\nTime",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@ -1133,7 +1133,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-03-27 21:43:19.748233",
"modified": "2017-03-27 21:43:19.748243",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form Field",

View file

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "EV.#####",
@ -129,6 +130,34 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "repeat_this_event",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Repeat this Event",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -272,8 +301,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "repeat_this_event",
"fieldtype": "Check",
"default": "blue",
"fieldname": "color",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Color",
"length": 0,
"no_copy": 0,
"options": "red\ngreen\nblue\nyellow\nskyblue\norange",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_11",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -281,10 +341,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Repeat this Event",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@ -301,7 +361,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "repeat_this_event",
"fieldname": "section_break_11",
"fieldname": "section_break_13",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@ -388,7 +448,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_11",
"fieldname": "column_break_16",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
@ -795,19 +855,19 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-calendar",
"idx": 1,
"image_view": 0,
"in_create": 1,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-02-22 16:25:55.420314",
"modified_by": "Administrator",
"modified": "2017-04-05 11:16:04.281342",
"modified_by": "faris@erpnext.com",
"module": "Desk",
"name": "Event",
"owner": "Administrator",

View file

@ -65,7 +65,7 @@ def get_events(start, end, user=None, for_reminder=False):
if not user:
user = frappe.session.user
roles = frappe.get_roles(user)
events = frappe.db.sql("""select name, subject, description,
events = frappe.db.sql("""select name, subject, description, color,
starts_on, ends_on, owner, all_day, event_type, repeat_this_event, repeat_on,repeat_till,
monday, tuesday, wednesday, thursday, friday, saturday, sunday
from tabEvent where ((

View file

@ -6,6 +6,7 @@ frappe.views.calendar["Event"] = {
"allDay": "all_day",
"title": "subject",
"status": "event_type",
"color": "color"
},
style_map: {
"Public": "success",

View file

@ -55,7 +55,7 @@ def get_context(context):
temp_doc = frappe.new_doc(self.document_type)
if self.condition:
try:
eval(self.condition, get_context(temp_doc))
frappe.safe_eval(self.condition, None, get_context(temp_doc))
except:
frappe.throw(_("The Condition '{0}' is invalid").format(self.condition))
@ -81,7 +81,7 @@ def get_context(context):
doc = frappe.get_doc(self.document_type, name)
if self.condition and not eval(self.condition, get_context(doc)):
if self.condition and not frappe.safe_eval(self.condition, None, get_context(doc)):
continue
docs.append(doc)
@ -95,7 +95,7 @@ def get_context(context):
recipients = []
for recipient in self.recipients:
if recipient.condition:
if not eval(recipient.condition, context):
if not frappe.safe_eval(recipient.condition, None, context):
continue
if recipient.email_by_document_field:
if validate_email_add(doc.get(recipient.email_by_document_field)):
@ -189,16 +189,16 @@ def evaluate_alert(doc, alert, event):
context = get_context(doc)
if alert.condition:
if not eval(alert.condition, context):
if not frappe.safe_eval(alert.condition, None, context):
return
if event=="Value Change" and not doc.is_new():
db_value = frappe.db.get_value(doc.doctype, doc.name, alert.value_changed)
# cast to string if not already for comparing to doc.get's value
if not isinstance(db_value, basestring):
db_value = str(frappe.db.get_value(doc.doctype, doc.name, alert.value_changed))
if doc.get(alert.value_changed) == db_value:
return # value not changed

View file

@ -14,6 +14,9 @@ class ValidationError(Exception):
class AuthenticationError(Exception):
http_status_code = 401
class SessionExpired(Exception):
http_status_code = 401
class PermissionError(Exception):
http_status_code = 403

View file

@ -847,6 +847,7 @@
"currency_fraction_units": 100,
"currency_symbol": "\u20ac",
"number_format": "# ###,##",
"date_format": "dd/mm/yyyy",
"timezones": [
"Europe/Paris"
]
@ -1196,7 +1197,8 @@
"currency_fraction": "Cent",
"currency_fraction_units": 100,
"currency_symbol": "\u20ac",
"number_format": "#,###.##",
"number_format": "#.###,##",
"date_format": "dd/mm/yyyy",
"timezones": [
"Europe/Rome"
]

View file

@ -1,290 +1,294 @@
[
{
"code": "am",
"code": "am",
"name": "\u12a0\u121b\u122d\u129b"
},
},
{
"code": "ar",
"code": "ar",
"name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629"
},
},
{
"code": "bg",
"code": "bg",
"name": "b\u01celgarski"
},
},
{
"code": "bn",
"code": "bn",
"name": "\u09ac\u09be\u0999\u09be\u09b2\u09bf"
},
},
{
"code": "bo",
"code": "bo",
"name": "\u0f63\u0fb7\u0f0b\u0f66\u0f60\u0f72\u0f0b\u0f66\u0f90\u0f51\u0f0b"
},
},
{
"code": "bs",
"code": "bs",
"name": "bosanski"
},
},
{
"code": "ca",
"code": "ca",
"name": "catal\u00e0"
},
},
{
"code": "cs",
"code": "cs",
"name": "\u010desky"
},
},
{
"code": "da",
"code": "da",
"name": "dansk"
},
},
{
"code": "da-DK",
"code": "da-DK",
"name": "Dansk (Danmark)"
},
},
{
"code": "de",
"code": "de",
"name": "deutsch"
},
},
{
"code": "el",
"code": "el",
"name": "\u03b5\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac"
},
},
{
"code": "en",
"name": "english"
},
"code": "en",
"name": "English"
},
{
"code": "en-US",
"code": "en-GB",
"name": "English (United Kingdom)"
},
{
"code": "en-US",
"name": "English (United States)"
},
},
{
"code": "es",
"code": "es",
"name": "espa\u00f1ol"
},
},
{
"code": "es-AR",
"code": "es-AR",
"name": "Espa\u00f1ol (Argentina)"
},
},
{
"code": "es-CL",
"code": "es-CL",
"name": "Espa\u00f1ol (Chile)"
},
},
{
"code": "es-GT",
"code": "es-GT",
"name": "Espa\u00f1ol (Guatemala)"
},
},
{
"code": "es-MX",
"code": "es-MX",
"name": "Espa\u00f1ol (M\u00e9xico)"
},
},
{
"code": "es-NI",
"code": "es-NI",
"name": "Espa\u00f1ol (Nicaragua)"
},
},
{
"code": "es-PE",
"code": "es-PE",
"name": "Espa\u00f1ol (Per\u00fa)"
},
},
{
"code": "et",
"code": "et",
"name": "eesti"
},
},
{
"code": "fa",
"code": "fa",
"name": "\u067e\u0627\u0631\u0633\u06cc"
},
},
{
"code": "fi",
"code": "fi",
"name": "suomalainen"
},
},
{
"code": "fr",
"code": "fr",
"name": "fran\u00e7ais"
},
},
{
"code": "fr-CA",
"code": "fr-CA",
"name": "fran\u00e7ais canadien"
},
},
{
"code": "gu",
"code": "gu",
"name": "\u0a97\u0ac1\u0a9c\u0ab0\u0abe\u0aa4\u0ac0"
},
},
{
"code": "he",
"code": "he",
"name": "\u05e2\u05d1\u05e8\u05d9\u05ea"
},
},
{
"code": "hi",
"code": "hi",
"name": "\u0939\u093f\u0902\u0926\u0940"
},
},
{
"code": "hr",
"code": "hr",
"name": "hrvatski"
},
},
{
"code": "hu",
"code": "hu",
"name": "magyar"
},
},
{
"code": "id",
"code": "id",
"name": "Indonesia"
},
},
{
"code": "is",
"code": "is",
"name": "\u00edslenska"
},
},
{
"code": "it",
"code": "it",
"name": "italiano"
},
},
{
"code": "ja",
"code": "ja",
"name": "\u65e5\u672c\u8a9e"
},
},
{
"code": "km",
"code": "km",
"name": "\u1797\u17b6\u179f\u17b6\u1781\u17d2\u1798\u17c2\u179a"
},
},
{
"code": "kn",
"code": "kn",
"name": "\u0c95\u0ca8\u0ccd\u0ca8\u0ca1"
},
},
{
"code": "ko",
"code": "ko",
"name": "\ud55c\uad6d\uc758"
},
},
{
"code": "ku",
"code": "ku",
"name": "\u06a9\u0648\u0631\u062f\u06cc"
},
},
{
"code": "lo",
"code": "lo",
"name": "\u0ea5\u0eb2\u0ea7"
},
},
{
"code": "lt",
"code": "lt",
"name": "lietuvi\u0173 kalba"
},
},
{
"code": "lv",
"code": "lv",
"name": "latvie\u0161u valoda"
},
},
{
"code": "mk",
"code": "mk",
"name": "\u043c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438"
},
},
{
"code": "ml",
"code": "ml",
"name": "\u0d2e\u0d32\u0d2f\u0d3e\u0d33\u0d02"
},
},
{
"code": "mr",
"code": "mr",
"name": "\u092e\u0930\u093e\u0920\u0940"
},
},
{
"code": "ms",
"code": "ms",
"name": "Melayu"
},
},
{
"code": "my",
"code": "my",
"name": "\u1019\u103c\u1014\u103a\u1019\u102c"
},
},
{
"code": "nl",
"code": "nl",
"name": "nederlands"
},
},
{
"code": "no",
"code": "no",
"name": "norsk"
},
},
{
"code": "pl",
"code": "pl",
"name": "polski"
},
},
{
"code": "ps",
"code": "ps",
"name": "\u067e\u069a\u062a\u0648"
},
},
{
"code": "pt",
"code": "pt",
"name": "portugu\u00eas"
},
},
{
"code": "pt-BR",
"code": "pt-BR",
"name": "portugu\u00eas brasileiro"
},
},
{
"code": "ro",
"code": "ro",
"name": "rom\u00e2n"
},
},
{
"code": "ru",
"code": "ru",
"name": "\u0440\u0443\u0441\u0441\u043a\u0438\u0439"
},
},
{
"code": "rw",
"code": "rw",
"name": "Kinyarwanda"
},
},
{
"code": "si",
"code": "si",
"name": "\u0dc3\u0dd2\u0d82\u0dc4\u0dbd"
},
},
{
"code": "sk",
"code": "sk",
"name": "sloven\u010dina (Slovak)"
},
},
{
"code": "sl",
"code": "sl",
"name": "sloven\u0161\u010dina (Slovene)"
},
},
{
"code": "sq",
"code": "sq",
"name": "shqiptar"
},
},
{
"code": "sr",
"code": "sr",
"name": "\u0441\u0440\u043f\u0441\u043a\u0438"
},
},
{
"code": "sr-SP",
"code": "sr-SP",
"name": "srpski"
},
},
{
"code": "sv",
"code": "sv",
"name": "svenska"
},
},
{
"code": "ta",
"code": "ta",
"name": "\u0ba4\u0bae\u0bbf\u0bb4\u0bcd"
},
},
{
"code": "te",
"code": "te",
"name": "\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41"
},
},
{
"code": "th",
"code": "th",
"name": "\u0e44\u0e17\u0e22"
},
},
{
"code": "tr",
"code": "tr",
"name": "T\u00fcrk"
},
},
{
"code": "uk",
"code": "uk",
"name": "\u0443\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430"
},
},
{
"code": "ur",
"code": "ur",
"name": "\u0627\u0631\u062f\u0648"
},
},
{
"code": "vi",
"code": "vi",
"name": "vi\u1ec7t"
},
},
{
"code": "zh",
"code": "zh",
"name": "\u7b80\u4f53\u4e2d\u6587"
},
},
{
"code": "zh-TW",
"code": "zh-TW",
"name": "\u7e41\u9ad4\u4e2d\u6587"
}
]
]

View file

@ -275,8 +275,8 @@ def update_site_config(key, value, validate=True, site_config_path=None):
value = int(value)
# boolean
if value in ("false", "true"):
value = eval(value.title())
if value == 'false': value = False
if value == 'true': value = True
# remove key if value is None
if value == "None":

View file

@ -0,0 +1,8 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Stripe Settings', {
refresh: function(frm) {
}
});

View file

@ -0,0 +1,120 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-03-09 17:18:29.458397",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "publishable_key",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Publishable Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "secret_key",
"fieldtype": "Password",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Secret Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-03-09 17:19:25.087475",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Stripe Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}

View file

@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
import urllib
from frappe.utils import get_url, call_hook_method, cint
from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway
class StripeSettings(Document):
supported_currencies = [
"AED", "ALL", "ANG", "ARS", "AUD", "AWG", "BBD", "BDT", "BIF", "BMD", "BND",
"BOB", "BRL", "BSD", "BWP", "BZD", "CAD", "CHF", "CLP", "CNY", "COP", "CRC", "CVE", "CZK", "DJF",
"DKK", "DOP", "DZD", "EGP", "ETB", "EUR", "FJD", "FKP", "GBP", "GIP", "GMD", "GNF", "GTQ", "GYD",
"HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "ISK", "JMD", "JPY", "KES", "KHR", "KMF",
"KRW", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "MAD", "MDL", "MNT", "MOP", "MRO", "MUR", "MVR",
"MWK", "MXN", "MYR", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "PAB", "PEN", "PGK", "PHP", "PKR",
"PLN", "PYG", "QAR", "RUB", "SAR", "SBD", "SCR", "SEK", "SGD", "SHP", "SLL", "SOS", "STD", "SVC",
"SZL", "THB", "TOP", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VND", "VUV", "WST",
"XAF", "XOF", "XPF", "YER", "ZAR"
]
def validate(self):
create_payment_gateway('Stripe')
call_hook_method('payment_gateway_enabled', gateway='Stripe')
if not self.flags.ignore_mandatory:
self.validate_stripe_credentails()
def validate_stripe_credentails(self):
if self.publishable_key and self.secret_key:
header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))}
try:
make_get_request(url="https://api.stripe.com/v1/charges", headers=header)
except Exception:
frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!"))
def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies:
frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency))
def get_payment_url(self, **kwargs):
return get_url("./integrations/stripe_checkout?{0}".format(urllib.urlencode(kwargs)))
def create_request(self, data):
self.data = frappe._dict(data)
try:
self.integration_request = create_request_log(self.data, "Host", "Stripe")
return self.create_charge_on_stripe()
except Exception:
frappe.log_error(frappe.get_traceback())
return{
"redirect_to": frappe.redirect_to_message(_('Server Error'), _("Seems issue with server's razorpay config. Don't worry, in case of failure amount will get refunded to your account.")),
"status": 401
}
def create_charge_on_stripe(self):
headers = {"Authorization":
"Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))}
data = {
"amount": cint(self.data.amount)*100,
"currency": self.data.currency,
"source": self.data.stripe_token_id,
"description": self.data.description
}
redirect_to = self.data.get('redirect_to') or None
redirect_message = self.data.get('redirect_message') or None
try:
resp = make_post_request(url="https://api.stripe.com/v1/charges", headers=headers, data=data)
if resp.get("captured") == True:
self.integration_request.db_set('status', 'Completed', update_modified=False)
self.flags.status_changed_to = "Completed"
else:
frappe.log_error(str(resp), 'Stripe Payment not completed')
except:
frappe.log_error(frappe.get_traceback())
# failed
pass
status = frappe.flags.integration_request.status_code
if self.flags.status_changed_to == "Completed":
if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None
try:
custom_redirect_to = frappe.get_doc(self.data.reference_doctype,
self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to)
except Exception:
frappe.log_error(frappe.get_traceback())
if custom_redirect_to:
redirect_to = custom_redirect_to
redirect_url = 'payment-success'
else:
redirect_url = 'payment-failed'
if redirect_to:
redirect_url += '?' + urllib.urlencode({'redirect_to': redirect_to})
if redirect_message:
redirect_url += '&' + urllib.urlencode({'redirect_message': redirect_message})
return {
"redirect_to": redirect_url,
"status": status
}

View file

@ -8,15 +8,17 @@ import json, urlparse
from frappe.utils import get_request_session
from frappe import _
def make_get_request(url, auth=None, data=None):
def make_get_request(url, auth=None, headers=None, data=None):
if not auth:
auth = ''
if not data:
data = {}
if not headers:
headers = {}
try:
s = get_request_session()
frappe.flags.integration_request = s.get(url, data={}, auth=auth)
frappe.flags.integration_request = s.get(url, data={}, auth=auth, headers=headers)
frappe.flags.integration_request.raise_for_status()
return frappe.flags.integration_request.json()
@ -24,20 +26,23 @@ def make_get_request(url, auth=None, data=None):
frappe.log_error(frappe.get_traceback())
raise exc
def make_post_request(url, auth=None, data=None):
def make_post_request(url, auth=None, headers=None, data=None):
if not auth:
auth = ''
if not data:
data = {}
if not headers:
headers = {}
try:
s = get_request_session()
res = s.post(url, data=data, auth=auth)
res.raise_for_status()
frappe.flags.integration_request = s.post(url, data=data, auth=auth, headers=headers)
frappe.flags.integration_request.raise_for_status()
if res.headers.get("content-type") == "text/plain; charset=utf-8":
return urlparse.parse_qs(res.text)
if frappe.flags.integration_request.headers.get("content-type") == "text/plain; charset=utf-8":
return urlparse.parse_qs(frappe.flags.integration_request.text)
return res.json()
return frappe.flags.integration_request.json()
except Exception, exc:
frappe.log_error()
raise exc

View file

@ -42,6 +42,7 @@ type_map = {
,'Read Only': ('varchar', varchar_len)
,'Attach': ('text', '')
,'Attach Image':('text', '')
,'Signature': ('longtext', '')
}
default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner',

View file

@ -55,7 +55,8 @@
],
"css/frappe-rtl.css": [
"public/css/bootstrap-rtl.css",
"public/css/desk-rtl.css"
"public/css/desk-rtl.css",
"public/css/report-rtl.css"
],
"js/libs.min.js": [
"public/js/lib/awesomplete/awesomplete.min.js",
@ -68,6 +69,7 @@
"public/js/lib/moment/moment-timezone-with-data.min.js",
"public/js/lib/socket.io.min.js",
"public/js/lib/markdown.js",
"public/js/lib/jSignature.min.js",
"public/js/frappe/translate.js",
"public/js/lib/datepicker/datepicker.min.js",
"public/js/lib/datepicker/locale-all.js"

View file

@ -569,8 +569,19 @@ fieldset[disabled] .form-control {
display: inline-block;
vertical-align: middle;
}
.file-upload .input-upload {
vertical-align: top;
}
.file-upload .uploaded-filename {
border: 1px solid #d1d8dd;
border-radius: 3px;
}
.file-upload .uploaded-filename .btn-group {
margin-right: 5px;
margin-bottom: 5px;
}
.file-upload .uploaded-filename-display {
max-width: 194px;
max-width: 150px;
}
.frappe-rtl input,
.frappe-rtl textarea {
@ -668,6 +679,9 @@ fieldset[disabled] .form-control {
max-height: 300px;
overflow: auto;
}
.note-editor .note-image-input {
height: auto;
}
.modal .note-editor .note-btn-italic,
.modal .note-editor .note-btn-underline,
.modal .note-editor [data-original-title="Font Size"],

View file

@ -415,6 +415,36 @@ h6.uppercase,
border-radius: 0px;
}
}
.signature-field {
min-height: 300px;
background: #fff;
border: 1px solid #d1d8dd;
border-radius: 3px;
position: relative;
margin-top: -10px;
}
.signature-display {
margin: 7px 0px;
background: #fff;
}
.signature-btn-row {
position: absolute;
bottom: 12px;
right: 12px;
}
.signature-reset {
height: 30px;
width: 30px;
padding: 4px 0px;
}
.signature-img {
border: 1px solid #d1d8dd;
background: #fff;
border-radius: 3px;
margin-top: 5px;
width: 100%;
max-height: 300px;
}
.timeline-new-email {
margin: 30px 0px;
padding-left: 70px;
@ -474,15 +504,6 @@ h6.uppercase,
.shared-user {
margin-bottom: 10px;
}
.linked-with-dialog {
width: 75%;
}
.linked-with-dialog .panel-body {
padding: 0px;
}
.linked-with-dialog .form-section {
padding-top: 15px;
}
.attach-missing-image,
.attach-image-display {
cursor: pointer;

View file

@ -393,6 +393,10 @@
.list-item-container:last-child {
border-bottom: none;
}
.list-item-table {
border: 1px solid #d1d8dd;
border-radius: 3px;
}
.list-item {
display: flex;
align-items: center;

View file

@ -182,9 +182,6 @@ body {
body[data-sidebar="0"] .navbar-home {
margin-left: 15px !important;
}
.linked-with-dialog {
width: 100% !important;
}
}
@media (max-width: 991px) and (max-width: 480px) {
#navbar-breadcrumbs li a {

View file

@ -0,0 +1,3 @@
.grid-report {
direction: ltr;
}

View file

@ -311,13 +311,61 @@ frappe.Application = Class.extend({
method:'logout',
callback: function(r) {
if(r.exc) {
console.log(r.exc);
return;
}
me.redirect_to_login();
}
})
},
handle_session_expired: function() {
if(!frappe.app.session_expired_dialog) {
var dialog = new frappe.ui.Dialog({
title: __('Session Expired'),
fields: [
{ fieldtype:'Password', fieldname:'password',
label: __('Please Enter Your Password to Continue') },
],
onhide: () => {
if (!dialog.logged_in) {
frappe.app.redirect_to_login();
}
}
});
dialog.set_primary_action(__('Login'), () => {
frappe.call({
method: 'login',
args: {
usr: frappe.session.user,
pwd: dialog.get_values().password
},
callback: (r) => {
if (r.message==='Logged In') {
dialog.logged_in = true;
// revert backdrop
$('.modal-backdrop').css({
'opacity': '',
'background-color': '#334143'
});
}
dialog.hide();
},
statusCode: () => {
dialog.hide();
}
});
});
frappe.app.session_expired_dialog = dialog;
}
if(!frappe.app.session_expired_dialog.display) {
frappe.app.session_expired_dialog.show();
// add backdrop
$('.modal-backdrop').css({
'opacity': 1,
'background-color': '#EBEFF2'
});
}
},
redirect_to_login: function() {
window.location.href = '/';
},

114
frappe/public/js/frappe/form/control.js Normal file → Executable file
View file

@ -1000,6 +1000,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
this.upload_options = {
parent: this.dialog.get_field("upload_area").$wrapper,
args: {},
allow_multiple: 0,
max_width: this.df.max_width,
max_height: this.df.max_height,
options: this.df.options,
@ -1792,7 +1793,118 @@ frappe.ui.form.ControlTable = frappe.ui.form.Control.extend({
return this.grid.get_data();
}
}
})
});
frappe.ui.form.ControlSignature = frappe.ui.form.ControlData.extend({
saving: false,
loading: false,
make: function() {
var me = this;
this._super();
// make jSignature field
this.$pad = $('<div class="signature-field"></div>')
.appendTo(me.wrapper)
.jSignature({height:300, width: "100%", "lineWidth": 0.8})
.on('change', this.on_save_sign.bind(this));
this.img_wrapper = $(`<div class="signature-display">
<div class="missing-image attach-missing-image">
<i class="octicon octicon-circle-slash"></i>
</div></div>`)
.appendTo(this.wrapper);
this.img = $("<img class='img-responsive attach-image-display'>")
.appendTo(this.img_wrapper).toggle(false);
this.$btnWrapper = $(`<div class="signature-btn-row">
<a href="#" type="button" class="signature-reset btn btn-default">
<i class="glyphicon glyphicon-repeat"></i></a>`)
.appendTo(this.$pad)
.on("click", '.signature-reset', function() {
me.on_reset_sign();
return false;
});
// handle refresh by reloading the pad
this.$wrapper.on("refresh", this.on_refresh.bind(this));
},
on_refresh: function(e) {
// prevent to load the second time
this.$wrapper.find(".control-input").toggle(false);
this.set_editable(this.get_status()=="Write");
this.load_pad();
if(this.get_status()=="Read") {
$(this.disp_area).toggle(false);
}
},
set_image: function(value) {
if(value) {
$(this.img_wrapper).find(".missing-image").toggle(false);
this.img.attr("src", value).toggle(true);
} else {
$(this.img_wrapper).find(".missing-image").toggle(true);
this.img.toggle(false);
}
},
load_pad: function() {
// make sure not triggered during saving
if (this.saving) return;
// get value
var value = this.get_value();
// import data for pad
if (this.$pad) {
this.loading = true;
// reset in all cases
this.$pad.jSignature('reset');
if (value) {
// load the image to find out the size, because scaling will affect
// stroke width
try {
this.$pad.jSignature('setData', value);
this.set_image(value);
}
catch (e){
console.log("Cannot set data for signature", value, e);
}
}
this.loading = false;
}
},
set_editable: function(editable) {
this.$pad.toggle(editable);
this.img_wrapper.toggle(!editable);
this.$btnWrapper.toggle(editable);
if (editable) {
this.$btnWrapper.addClass('editing');
}
else {
this.$btnWrapper.removeClass('editing');
}
},
set_my_value: function(value) {
if (this.saving || this.loading) return;
this.saving = true;
this.set_value(value);
this.value = value;
this.saving = false;
},
get_value: function() {
return this.value? this.value: this.get_model_value();
},
// reset signature canvas
on_reset_sign: function() {
this.$pad.jSignature("reset");
this.set_my_value("");
},
// save signature value to model and display
on_save_sign: function() {
if (this.saving || this.loading) return;
var base64_img = this.$pad.jSignature("getData");
this.set_my_value(base64_img);
this.set_image(this.get_value());
}
});
frappe.ui.form.fieldtype_icons = {
"Date": "fa fa-calendar",

View file

@ -234,6 +234,7 @@ frappe.ui.get_upload_dialog = function(opts){
}
},
callback: function(r){
if(!r.message) return;
dialog.$wrapper.find('[name="file_url"]').val(r.message.file_url);
dialog.$wrapper.find('.private-file input').prop('checked', r.message.is_private);
opts.args.filename = r.message.file_name

View file

@ -3,156 +3,162 @@
frappe.provide("frappe.ui.form");
frappe.ui.form.LinkedWith = Class.extend({
init: function(opts) {
var me = this;
frappe.ui.form.LinkedWith = class LinkedWith {
constructor(opts) {
$.extend(this, opts);
},
show: function() {
}
show() {
if(!this.dialog)
this.make_dialog();
this.dialog.fields_dict.list.$wrapper.html('<div class="text-muted text-center" style="padding: 30px 0px">'
+ __("Loading") + '...</div>');
$(this.dialog.body).html(
`<div class="text-muted text-center" style="padding: 30px 0px">
${__("Loading")}...
</div>`);
this.dialog.show();
},
make_dialog: function() {
}
make_dialog() {
var me = this;
this.dialog = new frappe.ui.Dialog({
hide_on_page_refresh: true,
title: __("Linked With"),
fields: [
{ fieldtype: "HTML", label: "list" }
]
title: __("Linked With")
});
this.dialog.$wrapper.find(".modal-dialog").addClass("linked-with-dialog");
this.dialog.on_page_show = function() {
this.dialog.on_page_show = () => {
// execute ajax calls sequentially
// 1. get linked doctypes
// 2. load all doctypes
// 3. load linked docs
$.when(me.get_linked_doctypes())
.then(function() { return me.load_doctypes() })
.then(function() {
if (me.links_not_permitted_or_missing()) {
return;
}
return me.get_linked_docs();
});
this.get_linked_doctypes()
.then(() => this.load_doctypes())
.then(() => this.links_not_permitted_or_missing())
.then(() => this.get_linked_docs())
.then(() => this.make_html())
}
}
},
make_html() {
const linked_docs = this.frm.__linked_docs;
load_doctypes: function() {
var me = this;
var already_loaded = Object.keys(locals.DocType);
var doctypes_to_load = [];
if (me.frm.__linked_doctypes) {
$.each(Object.keys(me.frm.__linked_doctypes), function(i, v) {
if (already_loaded.indexOf(v)===-1) {
doctypes_to_load.push(v);
}
let html;
if(Object.keys(linked_docs).length === 0) {
html = __("Not Linked to any record");
} else {
html = Object.keys(linked_docs).map(dt => {
return `<div class="list-item-table" style="margin-bottom: 15px">
${this.make_doc_head(dt)}
${linked_docs[dt]
.map(doc => this.make_doc_row(doc, dt))
.join("")}
</div>`;
});
}
// load all doctypes sequentially using with_doctype
return $.when.apply($, $.map(doctypes_to_load, function(dt) {
return frappe.model.with_doctype(dt, function() {
if (frappe.listview_settings[dt]) {
// add additional fields to __linked_doctypes
me.frm.__linked_doctypes[dt].add_fields = frappe.listview_settings[dt].add_fields;
}
}, /*async*/ false);
}));
},
links_not_permitted_or_missing: function() {
var me = this;
var links = [];
$.each(me.frm.__linked_doctypes, function(doctype, tmp) {
if(frappe.model.can_get_report(doctype)) {
links.push({label: __(doctype), value: doctype});
}
});
$(this.dialog.body).html(html);
}
links = frappe.utils.sort(links, "label");
load_doctypes() {
const already_loaded = Object.keys(locals.DocType);
let doctypes_to_load = [];
if(!links) {
me.dialog.fields_dict.list.$wrapper.html("<div class='alert alert-warning'>"
+ me.frm.doctype + ": "
+ (me.frm.__linked_doctypes ? __("Not Linked to any record.") : __("Not enough permission to see links."))
+ "</div>")
return true;
}
return false;
},
get_linked_doctypes: function() {
var me = this;
if (this.frm.__linked_doctypes) {
return;
doctypes_to_load =
Object.keys(this.frm.__linked_doctypes)
.filter(doctype => !already_loaded.includes(doctype));
}
return frappe.call({
method: "frappe.desk.form.linked_with.get_linked_doctypes",
args: {
doctype: this.frm.doctype
},
callback: function(r) {
me.frm.__linked_doctypes = r.message;
}
});
},
get_linked_docs: function() {
var me = this;
return frappe.call({
method:"frappe.desk.form.linked_with.get_linked_docs",
args: {
doctype: me.frm.doctype,
name: me.frm.docname,
linkinfo: me.frm.__linked_doctypes,
for_doctype: me.for_doctype
},
callback: function(r) {
var parent = me.dialog.fields_dict.list.$wrapper.empty();
if(keys(r.message || {}).length) {
$.each(keys(r.message).sort(), function(i, doctype) {
if (Object.keys(locals.DocType).indexOf(doctype)=== -1) {
frappe.model.with_doctype(doctype, function() {
if (frappe.listview_settings[doctype]) {
// add additional fields to __linked_doctypes
me.frm.__linked_doctypes[doctype] = {}
me.frm.__linked_doctypes[doctype].add_fields = frappe.listview_settings[doctype].add_fields;
}
}, /*async*/ false);
}
var listview = frappe.views.get_listview(doctype, me);
listview.no_delete = true;
me.current_view = "List"
var wrapper = $('<div class="panel panel-default"><div>').appendTo(parent);
$('<div class="panel-heading">').html(__(doctype).bold()).appendTo(wrapper);
var body = $('<div class="panel-body">').appendTo(wrapper);
$.each(r.message[doctype], function(i, d) {
d.doctype = doctype;
listview.render($('<div class="list-row"></div>')
.appendTo(body), d, me);
})
})
} else {
parent.html(__("Not Linked to any record."));
// load all doctypes asynchronously using with_doctype
const promises = doctypes_to_load.map(dt => {
return frappe.model.with_doctype(dt, () => {
if(frappe.listview_settings[dt]) {
// add additional fields to __linked_doctypes
this.frm.__linked_doctypes[dt].add_fields =
frappe.listview_settings[dt].add_fields;
}
});
});
return Promise.all(promises);
}
links_not_permitted_or_missing() {
var me = this;
let links = null;
links =
Object.keys(this.frm.__linked_doctypes)
.filter(frappe.model.can_get_report);
let flag;
if(!links) {
$(this.dialog.body).html(
`${this.frm.__linked_doctypes
? __("Not enough permission to see links")
: __("Not Linked to any record")}`);
flag = true;
}
flag = false;
// reject Promise if not_permitted or missing
return new Promise(
(resolve, reject) => flag ? reject() : resolve()
);
}
get_linked_doctypes() {
return new Promise((resolve, reject) => {
if (this.frm.__linked_doctypes) {
resolve();
}
frappe.call({
method: "frappe.desk.form.linked_with.get_linked_doctypes",
args: {
doctype: this.frm.doctype
},
callback: (r) => {
this.frm.__linked_doctypes = r.message;
resolve();
}
});
});
}
get_linked_docs() {
return frappe.call({
method: "frappe.desk.form.linked_with.get_linked_docs",
args: {
doctype: this.frm.doctype,
name: this.frm.docname,
linkinfo: this.frm.__linked_doctypes,
for_doctype: this.for_doctype
},
callback: (r) => {
this.frm.__linked_docs = r.message || {};
}
});
}
});
make_doc_head(heading) {
return `<div class="list-item list-item--head">
<div class="list-item__content">
${heading}
</div></div>`;
}
make_doc_row(doc, doctype) {
return `<div class="list-item-container">
<div class="list-item">
<div class="list-item__content bold">
<a href="#Form/${doctype}/${doc.name}">${doc.name}</a>
</div>
</div>
</div>`;
}
}

View file

@ -503,6 +503,8 @@ frappe.views.ListRenderer = Class.extend({
this.render_view = function (values) {
// prepare data before rendering view
values = values.map(me.prepare_data.bind(this));
// remove duplicates
values = values.uniqBy(value => value.name);
if (lib_exists) {
me.load_lib(function () {

View file

@ -77,6 +77,14 @@ frappe.slickgrid_tools = {
get_filtered_items: function(dataView) {
var data = [];
for (var i=0, len=dataView.getLength(); i<len; i++) {
// remove single quotes at start and end of total labels when print/pdf
var obj = dataView.getItem(i);
for (var item in obj) {
if(obj.hasOwnProperty(item) && typeof(obj[item]) == "string"
&& obj[item].charAt(0) == "'" && obj[item].charAt(obj[item].length -1) == "'") {
dataView.getItem(i)[item] = obj[item].substr(1, obj[item].length-2);
}
}
data.push(dataView.getItem(i));
}
return data;
@ -94,6 +102,16 @@ frappe.slickgrid_tools = {
if(val===null || val===undefined) {
val = "";
}
if(typeof(val) == "string") {
// export to csv and get first or second column of the grid indented if it is. e.g: account_name
if((i<3) && d['indent'] > 0 && (isNaN((new Date(val)).valueOf()))) {
val = " ".repeat(d['indent'] * 8) + val;
}
// remove single quotes at start and end of total labels when export to csv
if(val.charAt(0) == "'" && val.charAt(val.length -1) == "'") {
val = val.substr(1, val.length-2);
}
}
row.push(val);
});

View file

@ -67,15 +67,22 @@ frappe.request.call = function(opts) {
opts.success_callback && opts.success_callback(data, xhr.responseText);
},
401: function(xhr) {
msgprint({message:__("You have been logged out"), indicator: 'red'});
frappe.app.logout();
if(frappe.app.session_expired_dialog && frappe.app.session_expired_dialog.display) {
frappe.app.redirect_to_login();
} else {
frappe.app.handle_session_expired();
};
},
404: function(xhr) {
msgprint({title:__("Not found"), indicator:'red',
message: __('The resource you are looking for is not available')});
},
403: function(xhr) {
if (xhr.responseJSON && xhr.responseJSON._server_messages) {
if (frappe.get_cookie('sid')==='Guest') {
// session expired
frappe.app.handle_session_expired();
}
else if (xhr.responseJSON && xhr.responseJSON._server_messages) {
var _server_messages = JSON.parse(xhr.responseJSON._server_messages);
// avoid double messages
@ -83,10 +90,13 @@ frappe.request.call = function(opts) {
return;
}
}
else {
frappe.msgprint({
title:__("Not permitted"), indicator:'red',
message: __('You do not have enough permissions to access this resource. Please contact your manager to get access.')});
}
frappe.utils.play_sound("error");
msgprint({title:__("Not permitted"), indicator:'red',
message: __('You do not have enough permissions to access this resource. Please contact your manager to get access.')});
},
508: function(xhr) {
frappe.utils.play_sound("error");
@ -233,11 +243,7 @@ frappe.request.cleanup = function(opts, r) {
// session expired? - Guest has no business here!
if(r.session_expired || frappe.get_cookie("sid")==="Guest") {
if(!frappe.app.logged_out) {
localStorage.setItem("session_last_route", location.hash);
msgprint(__('Session Expired. Logging you out'));
frappe.app.logout();
}
frappe.app.handle_session_expired();
return;
}

View file

@ -47,7 +47,8 @@ frappe.ui.Dialog = frappe.ui.FieldGroup.extend({
cur_dialog = null;
}
}
me.onhide && me.onhide.apply(me);
me.onhide && me.onhide();
me.on_hide && me.on_hide();
})
.on("shown.bs.modal", function() {
// focus on first input

View file

@ -1,9 +1,9 @@
<div class="file-upload">
<div class="input-upload">
<input class="input-upload-file hidden" type="file" name="filedata" />
<input class="input-upload-file hidden" type="file" {{ opts.allow_multiple ? "multiple" : "" }} name="filedata" />
<button class="btn btn-primary btn-sm btn-browse">{%= __("Browse") %}</button>
</div>
<div class="uploaded-filename hidden" style="width: calc(100% - 80px);"></div>
<div class="uploaded-filename hidden" style="width: 100%; margin-top: 12px;"></div>
<div class="web-link-wrapper" style="width: calc(100% - 80px);">
<span class="text-muted file-upload-or">{%= __("or") %}</span>
<div class="input-link" style="width: calc(100% - 30px);">

View file

@ -5,45 +5,72 @@
frappe.upload = {
make: function(opts) {
if(!opts.args) opts.args = {};
if(opts.allow_multiple === undefined) {
opts.allow_multiple = 1
}
var d = null;
// create new dialog if no parent given
if(!opts.parent) {
d = new frappe.ui.Dialog({
title: __('Upload Attachment'),
primary_action_label: __('Attach'),
primary_action: function() {}
});
opts.parent = d.body;
opts.btn = d.get_primary_btn();
d.show();
}
var $upload = $(frappe.render_template("upload", {opts:opts})).appendTo(opts.parent);
var $file_input = $upload.find(".input-upload-file");
var $uploaded_files_wrapper = $upload.find('.uploaded-filename');
// bind pseudo browse button
$upload.find(".btn-browse").on("click",
function() { $file_input.click(); });
$file_input.on("change", function() {
if (this.files.length > 0) {
if (this.files.length > 0 || opts.files) {
var fileobjs = null;
if (opts.files) {
// files added programmatically
fileobjs = opts.files;
delete opts.files;
} else {
// files from input type file
fileobjs = $upload.find(":file").get(0).files;
}
var file_array = $.makeArray(fileobjs);
$upload.find(".web-link-wrapper").addClass("hidden");
$upload.find(".btn-browse").removeClass("btn-primary").addClass("btn-default");
$uploaded_files_wrapper.removeClass('hidden').empty();
var $uploaded_file_display = $(repl('<div class="btn-group" role="group">\
<button type="button" class="btn btn-default btn-sm \
ellipsis uploaded-filename-display">%(filename)s\
</button>\
<button type="button" class="btn btn-default btn-sm uploaded-file-remove">\
&times;</button>\
</div>', {filename: this.files[0].name}))
.appendTo($upload.find(".uploaded-filename").removeClass("hidden").empty());
$uploaded_file_display.find(".uploaded-filename-display").on("click", function() {
$file_input.click();
});
$uploaded_file_display.find(".uploaded-file-remove").on("click", function() {
$file_input.val("");
$file_input.trigger("change");
});
if(opts.on_select) {
opts.on_select();
}
if ( !("is_private" in opts) ) {
// show Private checkbox
$upload.find(".private-file").removeClass("hidden");
}
file_array = file_array.map(
file => Object.assign(file, {is_private: 1})
)
$upload.data('attached_files', file_array);
// List of files in a grid
$uploaded_files_wrapper.append(`
<div class="list-item list-item--head">
<div class="list-item__content list-item__content--flex-2">
${__('Filename')}
</div>
<div class="list-item__content" style="flex: 0 0 64px">
${__('Is Private')}
</div>
<div class="list-item__content list-item__content--activity" style="flex: 0 0 32px">
</div>
</div>
`);
var file_pills = file_array.map(
file => frappe.upload.make_file_row(file.name, !("is_private" in opts))
);
$uploaded_files_wrapper.append(file_pills);
} else {
$upload.find(".uploaded-filename").addClass("hidden")
$upload.find(".web-link-wrapper").removeClass("hidden");
@ -52,6 +79,40 @@ frappe.upload = {
}
});
if(opts.files && opts.files.length > 0) {
$file_input.trigger('change');
}
// events
$uploaded_files_wrapper.on('click', '.list-item-container', function (e) {
var $item = $(this);
var filename = $item.attr('data-filename');
var attached_files = $upload.data('attached_files');
var $target = $(e.target);
if ($target.is(':checkbox')) {
var is_private = $target.is(':checked');
attached_files = attached_files.map(file => {
if (file.name === filename) {
file.is_private = is_private ? 1 : 0;
}
return file;
});
$upload.data('attached_files', attached_files);
}
else if ($target.is('.uploaded-file-remove, .fa-remove')) {
// remove file from attached_files object
attached_files = attached_files.filter(file => file.name !== filename);
$upload.data('attached_files', attached_files);
// remove row from dom
$uploaded_files_wrapper
.find(`.list-item-container[data-filename="${filename}"]`)
.remove();
}
});
if(!opts.btn) {
opts.btn = $('<button class="btn btn-default btn-sm">' + __("Attach")
@ -60,21 +121,77 @@ frappe.upload = {
$(opts.btn).unbind("click");
}
// get the first file
// Primary button handler
opts.btn.click(function() {
// convert functions to values
// close created dialog
d && d.hide();
// convert functions to values
if(opts.get_params) {
opts.args.params = opts.get_params();
}
opts.args.file_url = $upload.find('[name="file_url"]').val();
opts.args.is_private = $upload.find('.private-file input').prop('checked') ? 1 : 0;
// Get file url if input is visible
var file_url = $upload.find('[name="file_url"]:visible');
file_url = file_url.length && file_url.get(0).value;
var fileobj = $upload.find(":file").get(0).files[0];
frappe.upload.upload_file(fileobj, opts.args, opts);
if(file_url) {
opts.args.file_url = file_url;
frappe.upload.upload_file(null, opts.args, opts);
} else {
var files = $upload.data('attached_files');
frappe.upload.upload_multiple_files(files, opts.args, opts);
}
});
},
make_file_row: function(filename, show_private) {
var template = `
<div class="list-item-container" data-filename="${filename}">
<div class="list-item">
<div class="list-item__content list-item__content--flex-2 ellipsis">
${filename}
</div>
${show_private
? `<div class="list-item__content ellipsis" style="flex: 0 0 64px;">
<input type="checkbox" checked/>
</div>`
: ''}
<div class="list-item__content list-item__content--activity ellipsis" style="flex: 0 0 32px;">
<button class="btn btn-default btn-xs text-muted uploaded-file-remove">
<span class="fa fa-remove"></span>
</button>
</div>
</div>
</div>`;
return $(template);
},
upload_multiple_files: function(files /*FileData array*/, args, opts) {
var i = -1;
// upload the first file
upload_next();
// subsequent files will be uploaded after
// upload_complete event is fired for the previous file
$(document).on('upload_complete', on_upload);
function upload_next() {
i += 1;
var file = files[i];
args.is_private = file.is_private;
frappe.upload.upload_file(file, args, opts);
frappe.show_progress(__('Uploading'), i+1, files.length);
}
function on_upload(e, attachment) {
if (i === files.length - 1) {
$(document).off('upload_complete', on_upload);
frappe.hide_progress();
return;
}
upload_next();
}
},
upload_file: function(fileobj, args, opts) {
if(!fileobj && !args.file_url) {
if(opts.on_no_attach) {
@ -153,7 +270,7 @@ frappe.upload = {
},
upload_to_server: function(fileobj, args, opts, dataurl) {
var msgbox = msgprint(__("Uploading..."));
// var msgbox = msgprint(__("Uploading..."));
if(opts.start) {
opts.start();
}
@ -162,11 +279,12 @@ frappe.upload = {
args: args,
callback: function(r) {
if(!r._server_messages) {
msgbox.hide();
// msgbox.hide();
}
if(r.exc) {
// if no onerror, assume callback will handle errors
opts.onerror ? opts.onerror(r) : opts.callback(null, r);
frappe.hide_progress();
return;
}
var attachment = r.message;
@ -177,6 +295,7 @@ frappe.upload = {
error: function(r) {
// if no onerror, assume callback will handle errors
opts.onerror ? opts.onerror(r) : opts.callback(null, null, r);
frappe.hide_progress();
return;
}
}
@ -216,9 +335,9 @@ frappe.upload = {
for (var i =0,j = fileobjs.length;i<j;i++) {
var filename = fileobjs[i].name;
fields.push({'fieldname': 'label1', 'fieldtype': 'Heading', 'label': filename});
fields.push({'fieldname': 'is_private', 'fieldtype': 'Check', 'label': 'Private', 'default': 1});
fields.push({'fieldname': filename+'_is_private', 'fieldtype': 'Check', 'label': 'Private', 'default': 1});
}
var d = new frappe.ui.Dialog({
'title': __('Make file(s) private or public?'),
'fields': fields,
@ -227,11 +346,12 @@ frappe.upload = {
d.hide();
opts.loopcallback = function (){
if (i < j) {
args.is_private = d.fields_dict[fileobjs[i].name + "_is_private"].get_value()
frappe.upload.upload_file(fileobjs[i], args, opts);
i++;
}
}
}
opts.loopcallback();
}

View file

@ -60,7 +60,7 @@ frappe.breadcrumbs = {
label = module_info ? module_info.label : breadcrumbs.module;
if(module_info && !module_info.blocked && !module_info.hidden) {
if(module_info && !module_info.blocked) {
$(repl('<li><a href="#modules/%(module)s">%(label)s</a></li>',
{ module: breadcrumbs.module, label: __(label) }))
.appendTo($breadcrumbs);

View file

@ -96,31 +96,10 @@ frappe.views.Calendar = Class.extend({
"end": "end",
"allDay": "all_day",
},
styles: {
"standard": {
"color": "#F0F4F7"
},
"important": {
"color": "#FFDCDC"
},
"danger": {
"color": "#FFDCDC"
},
"warning": {
"color": "#FFE6BF",
},
"success": {
"color": "#E4FFC1",
},
"info": {
"color": "#E8DDFF"
},
"inverse": {
"color": "#D9F6FF"
},
"": {
"color": "#F0F4F7"
}
color_map: {
"danger": "red",
"success": "green",
"warning": "orange"
},
get_system_datetime: function(date) {
date._offset = moment.user_utc_offset;
@ -146,7 +125,7 @@ frappe.views.Calendar = Class.extend({
args: me.get_args(start, end),
callback: function(r) {
var events = r.message;
me.prepare_events(events);
events = me.prepare_events(events);
callback(events);
}
})
@ -168,7 +147,6 @@ frappe.views.Calendar = Class.extend({
if (view.name==="month" && (endDate - startDate)===86400000) {
// detect single day click in month view
return;
}
var event = frappe.model.get_new_doc(me.doctype);
@ -221,7 +199,8 @@ frappe.views.Calendar = Class.extend({
},
prepare_events: function(events) {
var me = this;
$.each(events || [], function(i, d) {
return (events || []).map(d => {
d.id = d.name;
d.editable = frappe.model.can_write(d.doctype || me.doctype);
@ -243,25 +222,21 @@ frappe.views.Calendar = Class.extend({
me.fix_end_date_for_event_render(d);
let color;
if(me.get_css_class) {
$.extend(d, me.styles[me.get_css_class(d)] || {});
} else if(me.style_map) {
$.extend(d, me.styles[me.style_map[d.status]] || {});
color = me.color_map[me.get_css_class(d)];
} else {
$.extend(d, me.styles[frappe.utils.guess_style(d.status, "standard")]);
// color field can be set in {doctype}_calendar.js
// see event_calendar.js
color = d.color;
}
d["textColor"] = "#36414C";
})
var palette_colors = ['red', 'green', 'blue', 'yellow', 'skyblue', 'orange'];
var index = 0;
if(events) {
events = events.map(function(event) {
event.className = "fc-bg-" + palette_colors[index];
index = (index + 1) % palette_colors.length;
})
}
// if invalid, fallback to blue color
if(!Object.values(me.color_map).includes(color)) {
color = "blue";
}
d.className = "fc-bg-" + color;
return d;
});
},
update_event: function(event, revertFunc) {
var me = this;

View file

@ -145,12 +145,12 @@ _f.Frm.prototype.setup_drag_drop = function() {
throw "attach error";
}
frappe.upload.multifile_upload(dataTransfer.files, me.attachments.get_args(), {
frappe.upload.make({
args: me.attachments.get_args(),
files: dataTransfer.files,
callback: function(attachment, r) {
me.attachments.attachment_uploaded(attachment, r);
},
confirm_is_private: true
}
});
});
}

View file

@ -38,6 +38,19 @@
}; })(jQuery);
;(function ($) { $.fn.datepicker.language['en'] = {
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
months: ['January','February','March','April','May','June', 'July','August','September','October','November','December'],
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
today: 'Today',
clear: 'Clear',
dateFormat: 'dd/mm/yyyy',
timeFormat: 'hh:ii aa',
firstDay: 0
}; })(jQuery);
;(function ($) { $.fn.datepicker.language['en-GB'] = {
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
@ -234,4 +247,4 @@
dateFormat: 'yyyy-mm-dd',
timeFormat: 'hh:ii',
firstDay: 1
}; })(jQuery);
}; })(jQuery);

79
frappe/public/js/lib/jSignature.min.js vendored Executable file
View file

@ -0,0 +1,79 @@
/*
jSignature v2 "2013-12-09T05:51" "commit ID ebe94c351d7267e21b4fc741c79a8191391cb579"
Copyright (c) 2012 Willow Systems Corp http://willow-systems.com
Copyright (c) 2010 Brinley Ang http://www.unbolt.net
MIT License <http://www.opensource.org/licenses/mit-license.php>
base64 encoder
MIT, GPL
http://phpjs.org/functions/base64_encode
+ original by: Tyler Akins (http://rumkin.com)
+ improved by: Bayron Guevara
+ improved by: Thunder.m
+ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ bugfixed by: Pellentesque Malesuada
+ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ improved by: Rafal Kukawski (http://kukawski.pl)
jSignature v2 jSignature's Undo Button and undo functionality plugin
jSignature v2 jSignature's custom "base30" format export and import plugins.
jSignature v2 SVG export plugin.
Simplify.js BSD
(c) 2012, Vladimir Agafonkin
mourner.github.com/simplify-js
*/
(function(){function r(c){var a,b=c.css("color"),d;c=c[0];for(var l=!1;c&&!d&&!l;){try{a=$(c).css("background-color")}catch(E){a="transparent"}"transparent"!==a&&"rgba(0, 0, 0, 0)"!==a&&(d=a);l=c.body;c=c.parentNode}c=/rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/;var l=/#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/,h;a=void 0;(a=b.match(c))?h={r:parseInt(a[1],10),g:parseInt(a[2],10),b:parseInt(a[3],10)}:(a=b.match(l))&&(h={r:parseInt(a[1],16),g:parseInt(a[2],16),b:parseInt(a[3],16)});var e;
d?(a=void 0,(a=d.match(c))?e={r:parseInt(a[1],10),g:parseInt(a[2],10),b:parseInt(a[3],10)}:(a=d.match(l))&&(e={r:parseInt(a[1],16),g:parseInt(a[2],16),b:parseInt(a[3],16)})):e=h?127<Math.max.apply(null,[h.r,h.g,h.b])?{r:0,g:0,b:0}:{r:255,g:255,b:255}:{r:255,g:255,b:255};a=function(a){return"rgb("+[a.r,a.g,a.b].join(", ")+")"};h&&e?(c=Math.max.apply(null,[h.r,h.g,h.b]),h=Math.max.apply(null,[e.r,e.g,e.b]),h=Math.round(h+-0.75*(h-c)),h={r:h,g:h,b:h}):h?(h=Math.max.apply(null,[h.r,h.g,h.b]),c=1,127<
h&&(c=-1),h=Math.round(h+96*c),h={r:h,g:h,b:h}):h={r:191,g:191,b:191};return{color:b,"background-color":e?a(e):d,"decor-color":a(h)}}function k(c,a){this.x=c;this.y=a;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var b=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(a){if(0===this.x&&0===this.y)this._length=
0;else if(0===this.x)this._length=a,this.y=a*b(this.y);else if(0===this.y)this._length=a,this.x=a*b(this.x);else{var c=Math.abs(this.y/this.x),e=Math.sqrt(Math.pow(a,2)/(1+Math.pow(c,2))),c=c*e;this._length=a;this.x=e*b(this.x);this.y=c*b(this.y)}return this};this.angleTo=function(a){var b=this.getLength()*a.getLength();return 0===b?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/b,-1),1))/Math.PI}}function g(c,a){this.x=c;this.y=a;this.getVectorToCoordinates=function(a,c){return new k(a-this.x,
c-this.y)};this.getVectorFromCoordinates=function(a,c){return this.getVectorToCoordinates(a,c).reverse()};this.getVectorToPoint=function(a){return new k(a.x-this.x,a.y-this.y)};this.getVectorFromPoint=function(a){return this.getVectorToPoint(a).reverse()}}function n(c,a,b,d,l){this.data=c;this.context=a;if(c.length)for(var e=c.length,h,m,A=0;A<e;A++){h=c[A];m=h.x.length;b.call(a,h);for(var f=1;f<m;f++)d.call(a,h,f);l.call(a,h)}this.changed=function(){};this.startStrokeFn=b;this.addToStrokeFn=d;this.endStrokeFn=
l;this.inStroke=!1;this._stroke=this._lastPoint=null;this.startStroke=function(a){if(a&&"number"==typeof a.x&&"number"==typeof a.y){this._stroke={x:[a.x],y:[a.y]};this.data.push(this._stroke);this._lastPoint=a;this.inStroke=!0;var b=this._stroke,c=this.startStrokeFn,d=this.context;setTimeout(function(){c.call(d,b)},3);return a}return null};this.addToStroke=function(a){if(this.inStroke&&"number"===typeof a.x&&"number"===typeof a.y&&4<Math.abs(a.x-this._lastPoint.x)+Math.abs(a.y-this._lastPoint.y)){var b=
this._stroke.x.length;this._stroke.x.push(a.x);this._stroke.y.push(a.y);this._lastPoint=a;var c=this._stroke,d=this.addToStrokeFn,l=this.context;setTimeout(function(){d.call(l,c,b)},3);return a}return null};this.endStroke=function(){var a=this.inStroke;this.inStroke=!1;this._lastPoint=null;if(a){var b=this._stroke,c=this.endStrokeFn,d=this.context,l=this.changed;setTimeout(function(){c.call(d,b);l.call(d)},3);return!0}return null}}function s(c,a,b,d){if("ratio"===a||"%"===a.split("")[a.length-1])this.eventTokens[b+
".parentresized"]=d.subscribe(b+".parentresized",function(a,e,h,m){return function(){var m=e.width();if(m!==h){for(var f in a)a.hasOwnProperty(f)&&(d.unsubscribe(a[f]),delete a[f]);var p=c.settings;c.$parent.children().remove();for(f in c)c.hasOwnProperty(f)&&delete c[f];f=p.data;var m=1*m/h,x=[],D,q,g,k,s,n;q=0;for(g=f.length;q<g;q++){n=f[q];D={x:[],y:[]};k=0;for(s=n.x.length;k<s;k++)D.x.push(n.x[k]*m),D.y.push(n.y[k]*m);x.push(D)}p.data=x;e[b](p)}}}(this.eventTokens,this.$parent,this.$parent.width(),
1*this.canvas.width/this.canvas.height))}function v(c,a,b){var d=this.$parent=$(c);c=this.eventTokens={};this.events=new t(this);var l=$.fn[f]("globalEvents"),e={width:"ratio",height:"ratio",sizeRatio:4,color:"#000","background-color":"#fff","decor-color":"#eee",lineWidth:0,minFatFingerCompensation:-10,showUndoButton:!1,readOnly:!1,data:[]};$.extend(e,r(d));a&&$.extend(e,a);this.settings=e;for(var h in b)b.hasOwnProperty(h)&&b[h].call(this,h);this.events.publish(f+".initializing");this.$controlbarUpper=
$('<div style="padding:0 !important; margin:0 !important;width: 100% !important; height: 0 !important; -ms-touch-action: none;margin-top:-1em !important; margin-bottom:1em !important;"></div>').appendTo(d);this.isCanvasEmulator=!1;a=this.canvas=this.initializeCanvas(e);b=$(a);this.$controlbarLower=$('<div style="padding:0 !important; margin:0 !important;width: 100% !important; height: 0 !important; -ms-touch-action: none;margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;"></div>').appendTo(d);
this.canvasContext=a.getContext("2d");b.data(f+".this",this);e.lineWidth=function(a,b){return a?a:Math.max(Math.round(b/400),2)}(e.lineWidth,a.width);this.lineCurveThreshold=3*e.lineWidth;e.cssclass&&""!=$.trim(e.cssclass)&&b.addClass(e.cssclass);this.fatFingerCompensation=0;d=function(a){var b,c,d=function(d){d=d.changedTouches&&0<d.changedTouches.length?d.changedTouches[0]:d;return new g(Math.round(d.pageX+b),Math.round(d.pageY+c)+a.fatFingerCompensation)},e=new u(750,function(){a.dataEngine.endStroke()});
this.drawEndHandler=function(b){if(!a.settings.readOnly){try{b.preventDefault()}catch(c){}e.clear();a.dataEngine.endStroke()}};this.drawStartHandler=function(l){if(!a.settings.readOnly){l.preventDefault();var h=$(a.canvas).offset();b=-1*h.left;c=-1*h.top;a.dataEngine.startStroke(d(l));e.kick()}};this.drawMoveHandler=function(b){a.settings.readOnly||(b.preventDefault(),a.dataEngine.inStroke&&(a.dataEngine.addToStroke(d(b)),e.kick()))};return this}.call({},this);(function(a,b,c){var d=this.canvas,l=
$(d);this.isCanvasEmulator?(l.bind("mousemove."+f,c),l.bind("mouseup."+f,a),l.bind("mousedown."+f,b)):(d.ontouchstart=function(l){d.onmousedown=d.onmouseup=d.onmousemove=void 0;this.fatFingerCompensation=e.minFatFingerCompensation&&-3*e.lineWidth>e.minFatFingerCompensation?-3*e.lineWidth:e.minFatFingerCompensation;b(l);d.ontouchend=a;d.ontouchstart=b;d.ontouchmove=c},d.onmousedown=function(e){d.ontouchstart=d.ontouchend=d.ontouchmove=void 0;b(e);d.onmousedown=b;d.onmouseup=a;d.onmousemove=c},window.navigator.msPointerEnabled&&
(d.onmspointerdown=b,d.onmspointerup=a,d.onmspointermove=c))}).call(this,d.drawEndHandler,d.drawStartHandler,d.drawMoveHandler);c[f+".windowmouseup"]=l.subscribe(f+".windowmouseup",d.drawEndHandler);this.events.publish(f+".attachingEventHandlers");s.call(this,this,e.width.toString(10),f,l);this.resetCanvas(e.data);this.events.publish(f+".initialized");return this}function w(c){if(c.getContext)return!1;var a=c.ownerDocument.parentWindow,b=a.FlashCanvas?c.ownerDocument.parentWindow.FlashCanvas:"undefined"===
typeof FlashCanvas?void 0:FlashCanvas;if(b){c=b.initElement(c);b=1;a&&a.screen&&a.screen.deviceXDPI&&a.screen.logicalXDPI&&(b=1*a.screen.deviceXDPI/a.screen.logicalXDPI);if(1!==b)try{$(c).children("object").get(0).resize(Math.ceil(c.width*b),Math.ceil(c.height*b)),c.getContext("2d").scale(b,b)}catch(d){}return!0}throw Error("Canvas element does not support 2d context. jSignature cannot proceed.");}var f="jSignature",u=function(c,a){var b;this.kick=function(){clearTimeout(b);b=setTimeout(a,c)};this.clear=
function(){clearTimeout(b)};return this},t=function(c){this.topics={};this.context=c?c:this;this.publish=function(a,b,c,e){if(this.topics[a]){var f=this.topics[a],h=Array.prototype.slice.call(arguments,1),m=[],q,g,p,x;g=0;for(p=f.length;g<p;g++)x=f[g],q=x[0],x[1]&&(x[0]=function(){},m.push(g)),q.apply(this.context,h);g=0;for(p=m.length;g<p;g++)f.splice(m[g],1)}};this.subscribe=function(a,b,c){this.topics[a]?this.topics[a].push([b,c]):this.topics[a]=[[b,c]];return{topic:a,callback:b}};this.unsubscribe=
function(a){if(this.topics[a.topic])for(var b=this.topics[a.topic],c=0,e=b.length;c<e;c++)b[c]&&b[c][0]===a.callback&&b.splice(c,1)}},y=function(c,a,b,d,e){c.beginPath();c.moveTo(a,b);c.lineTo(d,e);c.closePath();c.stroke()},C=function(c){var a=this.canvasContext,b=c.x[0];c=c.y[0];var d=this.settings.lineWidth,e=a.fillStyle;a.fillStyle=a.strokeStyle;a.fillRect(b+d/-2,c+d/-2,d,d);a.fillStyle=e},q=function(c,a){var b=new g(c.x[a-1],c.y[a-1]),d=new g(c.x[a],c.y[a]),e=b.getVectorToPoint(d);if(1<a){var f=
new g(c.x[a-2],c.y[a-2]),h=f.getVectorToPoint(b),m;if(h.getLength()>this.lineCurveThreshold){m=2<a?(new g(c.x[a-3],c.y[a-3])).getVectorToPoint(f):new k(0,0);var q=0.35*h.getLength(),n=h.angleTo(m.reverse()),p=e.angleTo(h.reverse());m=(new k(m.x+h.x,m.y+h.y)).resizeTo(Math.max(0.05,n)*q);var x=(new k(h.x+e.x,h.y+e.y)).reverse().resizeTo(Math.max(0.05,p)*q),h=this.canvasContext,q=f.x,p=f.y,n=b.x,D=b.y,s=f.x+m.x,f=f.y+m.y;m=b.x+x.x;x=b.y+x.y;h.beginPath();h.moveTo(q,p);h.bezierCurveTo(s,f,m,x,n,D);h.closePath();
h.stroke()}}e.getLength()<=this.lineCurveThreshold&&y(this.canvasContext,b.x,b.y,d.x,d.y)},e=function(c){var a=c.x.length-1;if(0<a){var b=new g(c.x[a],c.y[a]),d=new g(c.x[a-1],c.y[a-1]),e=d.getVectorToPoint(b);if(e.getLength()>this.lineCurveThreshold)if(1<a){c=(new g(c.x[a-2],c.y[a-2])).getVectorToPoint(d);var f=(new k(c.x+e.x,c.y+e.y)).resizeTo(e.getLength()/2),e=this.canvasContext;c=d.x;var a=d.y,h=b.x,m=b.y,q=d.x+f.x,d=d.y+f.y,f=b.x,b=b.y;e.beginPath();e.moveTo(c,a);e.bezierCurveTo(q,d,f,b,h,m);
e.closePath();e.stroke()}else y(this.canvasContext,d.x,d.y,b.x,b.y)}};v.prototype.resetCanvas=function(c,a){var b=this.canvas,d=this.settings,l=this.canvasContext,g=this.isCanvasEmulator,h=b.width,m=b.height;a||l.clearRect(0,0,h+30,m+30);l.shadowColor=l.fillStyle=d["background-color"];g&&l.fillRect(0,0,h+30,m+30);l.lineWidth=Math.ceil(parseInt(d.lineWidth,10));l.lineCap=l.lineJoin="round";if(null!=d["decor-color"]){l.strokeStyle=d["decor-color"];l.shadowOffsetX=0;l.shadowOffsetY=0;var A=Math.round(m/
5);y(l,1.5*A,m-A,h-1.5*A,m-A)}l.strokeStyle=d.color;g||(l.shadowColor=l.strokeStyle,l.shadowOffsetX=0.5*l.lineWidth,l.shadowOffsetY=-0.6*l.lineWidth,l.shadowBlur=0);c||(c=[]);l=this.dataEngine=new n(c,this,C,q,e);d.data=c;$(b).data(f+".data",c).data(f+".settings",d);l.changed=function(a,b,c){return function(){b.publish(c+".change");a.trigger("change")}}(this.$parent,this.events,f);l.changed();return!0};v.prototype.initializeCanvas=function(c){var a=document.createElement("canvas"),b=$(a);c.width===
c.height&&"ratio"===c.height&&(c.width="100%");b.css("margin",0).css("padding",0).css("border","none").css("height","ratio"!==c.height&&c.height?c.height.toString(10):1).css("width","ratio"!==c.width&&c.width?c.width.toString(10):1).css("-ms-touch-action","none");b.appendTo(this.$parent);"ratio"===c.height?b.css("height",Math.round(b.width()/c.sizeRatio)):"ratio"===c.width&&b.css("width",Math.round(b.height()*c.sizeRatio));b.addClass(f);a.width=b.width();a.height=b.height();this.isCanvasEmulator=
w(a);a.onselectstart=function(a){a&&a.preventDefault&&a.preventDefault();a&&a.stopPropagation&&a.stopPropagation();return!1};return a};(function(c){function a(a,b,c){var d=new Image,e=this;d.onload=function(){e.getContext("2d").drawImage(d,0,0,d.width<e.width?d.width:e.width,d.height<e.height?d.height:e.height)};d.src="data:"+b+","+a}function b(a,b){this.find("canvas."+f).add(this.filter("canvas."+f)).data(f+".this").resetCanvas(a,b);return this}function d(a,b){if(void 0===b&&"string"===typeof a&&
"data:"===a.substr(0,5)&&(b=a.slice(5).split(",")[0],a=a.slice(6+b.length),b===a))return;var c=this.find("canvas."+f).add(this.filter("canvas."+f));if(m.hasOwnProperty(b))0!==c.length&&m[b].call(c[0],a,b,function(a){return function(){return a.resetCanvas.apply(a,arguments)}}(c.data(f+".this")));else throw Error(f+" is unable to find import plugin with for format '"+String(b)+"'");return this}var e=new t;(function(a,b,c,d){var e,h=function(){a.publish(b+".parentresized")};c(d).bind("resize."+b,function(){e&&
clearTimeout(e);e=setTimeout(h,500)}).bind("mouseup."+b,function(c){a.publish(b+".windowmouseup")})})(e,f,$,c);var q={},h={"default":function(a){return this.toDataURL()},"native":function(a){return a},image:function(a){a=this.toDataURL();if("string"===typeof a&&4<a.length&&"data:"===a.slice(0,5)&&-1!==a.indexOf(",")){var b=a.indexOf(",");return[a.slice(5,b),a.substr(b+1)]}return[]}},m={"native":function(a,b,c){c(a)},image:a,"image/png;base64":a,"image/jpeg;base64":a,"image/jpg;base64":a},g={"export":h,
"import":m,instance:q},k={init:function(a){return this.each(function(){var b,c=!1;for(b=this.parentNode;b&&!c;)c=b.body,b=b.parentNode;c&&new v(this,a,q)})},getSettings:function(){return this.find("canvas."+f).add(this.filter("canvas."+f)).data(f+".this").settings},isModified:function(){return null!==this.find("canvas."+f).add(this.filter("canvas."+f)).data(f+".this").dataEngine._stroke},updateSetting:function(a,b,c){var d=this.find("canvas."+f).add(this.filter("canvas."+f)).data(f+".this");d.settings[a]=
b;d.resetCanvas(c?null:d.settings.data,!0);return d.settings[a]},clear:b,reset:b,addPlugin:function(a,b,c){g.hasOwnProperty(a)&&(g[a][b]=c);return this},listPlugins:function(a){var b=[];if(g.hasOwnProperty(a)){a=g[a];for(var c in a)a.hasOwnProperty(c)&&b.push(c)}return b},getData:function(a){var b=this.find("canvas."+f).add(this.filter("canvas."+f));void 0===a&&(a="default");if(0!==b.length&&h.hasOwnProperty(a))return h[a].call(b.get(0),b.data(f+".data"),b.data(f+".settings"))},importData:d,setData:d,
globalEvents:function(){return e},disable:function(){this.find("input").attr("disabled",1);this.find("canvas."+f).addClass("disabled").data(f+".this").settings.readOnly=!0},enable:function(){this.find("input").removeAttr("disabled");this.find("canvas."+f).removeClass("disabled").data(f+".this").settings.readOnly=!1},events:function(){return this.find("canvas."+f).add(this.filter("canvas."+f)).data(f+".this").events}};$.fn[f]=function(a){if(a&&"object"!==typeof a){if("string"===typeof a&&k[a])return k[a].apply(this,
Array.prototype.slice.call(arguments,1));$.error("Method "+String(a)+" does not exist on jQuery."+f)}else return k.init.apply(this,arguments)}})(window)})();
(function(){function r(k,g,n){k=k.call(this);(function(g,k,n){g.events.subscribe(n+".change",function(){g.dataEngine.data.length?k.show():k.hide()})})(this,k,g);(function(g,k,n){var f=n+".undo";k.bind("click",function(){g.events.publish(f)});g.events.subscribe(f,function(){var f=g.dataEngine.data;f.length&&(f.pop(),g.resetCanvas(f))})})(this,k,this.events.topics.hasOwnProperty(g+".undo")?n:g)}$.fn.jSignature("addPlugin","instance","UndoButton",function(k){this.events.subscribe("jSignature.attachingEventHandlers",
function(){if(this.settings[k]){var g=this.settings[k];"function"!==typeof g&&(g=function(){var g=$('<input type="button" value="Undo last stroke" style="position:absolute;display:none;margin:0 !important;top:auto" />').appendTo(this.$controlbarLower),k=g.width();g.css("left",Math.round((this.canvas.width-k)/2));k!==g.width()&&g.width(k);return g});r.call(this,g,"jSignature",k)}})})})();
(function(){for(var r={},k={},g="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX".split(""),n=g.length/2,s=n-1;-1<s;s--)r[g[s]]=g[s+n],k[g[s+n]]=g[s];var v=function(f){f=f.split("");for(var e=f.length,c=1;c<e;c++)f[c]=r[f[c]];return f.join("")},w=function(f){for(var e=[],c=0,a=1,b=f.length,d,l,g=0;g<b;g++)d=Math.round(f[g]),l=d-c,c=d,0>l&&0<a?(a=-1,e.push("Z")):0<l&&0>a&&(a=1,e.push("Y")),d=Math.abs(l),d>=n?e.push(v(d.toString(n))):e.push(d.toString(n));return e.join("")},f=function(f){var e=
[];f=f.split("");for(var c=f.length,a,b=1,d=[],l=0,g=0;g<c;g++)a=f[g],a in r||"Z"===a||"Y"===a?(0!==d.length&&(d=parseInt(d.join(""),n)*b+l,e.push(d),l=d),"Z"===a?(b=-1,d=[]):"Y"===a?(b=1,d=[]):d=[a]):d.push(k[a]);e.push(parseInt(d.join(""),n)*b+l);return e},u=function(f){for(var e=[],c=f.length,a,b=0;b<c;b++)a=f[b],e.push(w(a.x)),e.push(w(a.y));return e.join("_")},t=function(g){var e=[];g=g.split("_");for(var c=g.length/2,a=0;a<c;a++)e.push({x:f(g[2*a]),y:f(g[2*a+1])});return e},y=function(f){return["image/jsignature;base30",
u(f)]},C=function(f,e,c){"string"===typeof f&&("image/jsignature;base30"===f.substring(0,23).toLowerCase()&&(f=f.substring(24)),c(t(f)))};if(null==this.jQuery)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");(function(f){f=f.fn.jSignature;f("addPlugin","export","base30",y);f("addPlugin","export","image/jsignature;base30",y);f("addPlugin","import","base30",C);f("addPlugin","import","image/jsignature;base30",C)})(this.jQuery);this.jSignatureDebug&&
(this.jSignatureDebug.base30={remapTailChars:v,compressstrokeleg:w,uncompressstrokeleg:f,compressstrokes:u,uncompressstrokes:t,charmap:r})}).call("undefined"!==typeof window?window:this);
(function(){function r(e,c){this.x=e;this.y=c;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var a=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(b){if(0===this.x&&0===this.y)this._length=0;else if(0===this.x)this._length=b,this.y=b*a(this.y);else if(0===this.y)this._length=b,this.x=b*a(this.x);else{var c=Math.abs(this.y/
this.x),e=Math.sqrt(Math.pow(b,2)/(1+Math.pow(c,2))),c=c*e;this._length=b;this.x=e*a(this.x);this.y=c*a(this.y)}return this};this.angleTo=function(a){var c=this.getLength()*a.getLength();return 0===c?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/c,-1),1))/Math.PI}}function k(e,c){this.x=e;this.y=c;this.getVectorToCoordinates=function(a,b){return new r(a-this.x,b-this.y)};this.getVectorFromCoordinates=function(a,b){return this.getVectorToCoordinates(a,b).reverse()};this.getVectorToPoint=function(a){return new r(a.x-
this.x,a.y-this.y)};this.getVectorFromPoint=function(a){return this.getVectorToPoint(a).reverse()}}function g(e,c){var a=Math.pow(10,c);return Math.round(e*a)/a}function n(e,c,a){c+=1;var b=new k(e.x[c-1],e.y[c-1]),d=new k(e.x[c],e.y[c]),d=b.getVectorToPoint(d),f=new k(e.x[c-2],e.y[c-2]),b=f.getVectorToPoint(b);return b.getLength()>a?(a=2<c?(new k(e.x[c-3],e.y[c-3])).getVectorToPoint(f):new r(0,0),e=0.35*b.getLength(),f=b.angleTo(a.reverse()),c=d.angleTo(b.reverse()),a=(new r(a.x+b.x,a.y+b.y)).resizeTo(Math.max(0.05,
f)*e),d=(new r(b.x+d.x,b.y+d.y)).reverse().resizeTo(Math.max(0.05,c)*e),d=new r(b.x+d.x,b.y+d.y),["c",g(a.x,2),g(a.y,2),g(d.x,2),g(d.y,2),g(b.x,2),g(b.y,2)]):["l",g(b.x,2),g(b.y,2)]}function s(e,c){var a=e.x.length-1,b=new k(e.x[a],e.y[a]),d=new k(e.x[a-1],e.y[a-1]),b=d.getVectorToPoint(b);if(1<a&&b.getLength()>c){var a=(new k(e.x[a-2],e.y[a-2])).getVectorToPoint(d),d=b.angleTo(a.reverse()),f=0.35*b.getLength(),a=(new r(a.x+b.x,a.y+b.y)).resizeTo(Math.max(0.05,d)*f);return["c",g(a.x,2),g(a.y,2),g(b.x,
2),g(b.y,2),g(b.x,2),g(b.y,2)]}return["l",g(b.x,2),g(b.y,2)]}function v(e,c,a){c=["M",g(e.x[0]-c,2),g(e.y[0]-a,2)];a=1;for(var b=e.x.length-1;a<b;a++)c.push.apply(c,n(e,a,1));0<b?c.push.apply(c,s(e,a,1)):0===b&&c.push.apply(c,["l",1,1]);return c.join(" ")}function w(e){for(var c=[],a=[["fill",void 0,"none"],["stroke","color","#000000"],["stroke-width","lineWidth",2],["stroke-linecap",void 0,"round"],["stroke-linejoin",void 0,"round"]],b=a.length-1;0<=b;b--){var d=a[b][1],f=a[b][2];c.push(a[b][0]+
'="'+(d in e&&e[d]?e[d]:f)+'"')}return c.join(" ")}function f(e,c){var a=['<?xml version="1.0" encoding="UTF-8" standalone="no"?>','<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'],b,d=e.length,f,g=[],h=[],m=f=b=0,k=0,n=[];if(0!==d){for(b=0;b<d;b++){m=e[b];k=[];f={x:[],y:[]};for(var p=void 0,q=void 0,p=0,q=m.x.length;p<q;p++)k.push({x:m.x[p],y:m.y[p]});k=simplify(k,0.7,!0);p=0;for(q=k.length;p<q;p++)f.x.push(k[p].x),f.y.push(k[p].y);n.push(f);g=
g.concat(f.x);h=h.concat(f.y)}d=Math.min.apply(null,g)-1;b=Math.max.apply(null,g)+1;g=Math.min.apply(null,h)-1;h=Math.max.apply(null,h)+1;m=0>d?0:d;k=0>g?0:g;b-=d;f=h-g}a.push('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'+b.toString()+'" height="'+f.toString()+'">');b=0;for(d=n.length;b<d;b++)f=n[b],a.push("<path "+w(c)+' d="'+v(f,m,k)+'"/>');a.push("</svg>");return a.join("")}function u(e,c){return[C,f(e,c)]}function t(e,c){return[q,y(f(e,c))]}(function(e,c){"use strict";(typeof exports!=
c+""?exports:e).simplify=function(a,b,d){b=b!==c?b*b:1;if(!d){var e=a.length,f,g=a[0],m=[g];for(d=1;d<e;d++){f=a[d];var k=f.x-g.x,n=f.y-g.y;k*k+n*n>b&&(m.push(f),g=f)}a=(g!==f&&m.push(f),m)}f=a;d=f.length;var e=new (typeof Uint8Array!=c+""?Uint8Array:Array)(d),g=0,m=d-1,p,q,r=[],s=[],y=[];for(e[g]=e[m]=1;m;){n=0;for(k=g+1;k<m;k++){p=f[k];var z=f[g],v=f[m],t=z.x,u=z.y,z=v.x-t,B=v.y-u,w=void 0;if(0!==z||0!==B)w=((p.x-t)*z+(p.y-u)*B)/(z*z+B*B),1<w?(t=v.x,u=v.y):0<w&&(t+=z*w,u+=B*w);p=(z=p.x-t,B=p.y-
u,z*z+B*B);p>n&&(q=k,n=p)}n>b&&(e[q]=1,r.push(g),s.push(q),r.push(q),s.push(m));g=r.pop();m=s.pop()}for(k=0;k<d;k++)e[k]&&y.push(f[k]);return a=y,a}})(window);if("function"!==typeof y)var y=function(e){var c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(""),a,b,d,f,g=0,h=0,k="",k=[];do a=e.charCodeAt(g++),b=e.charCodeAt(g++),d=e.charCodeAt(g++),f=a<<16|b<<8|d,a=f>>18&63,b=f>>12&63,d=f>>6&63,f&=63,k[h++]=c[a]+c[b]+c[d]+c[f];while(g<e.length);k=k.join("");e=e.length%3;return(e?
k.slice(0,e-3):k)+"===".slice(e||3)};var C="image/svg+xml",q="image/svg+xml;base64";if("undefined"===typeof $)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");(function(e){e=e.fn.jSignature;e("addPlugin","export","svg",u);e("addPlugin","export",C,u);e("addPlugin","export","svgbase64",t);e("addPlugin","export",q,t)})($)})();

View file

@ -409,8 +409,22 @@ textarea.form-control {
vertical-align: middle;
}
.input-upload {
vertical-align: top;
}
.uploaded-filename {
border: 1px solid @border-color;
border-radius: 3px;
}
.uploaded-filename .btn-group {
margin-right: 5px;
margin-bottom: 5px;
}
.uploaded-filename-display {
max-width: 194px;
max-width: 150px;
}
}
@ -534,6 +548,9 @@ textarea.form-control {
max-height: 300px;
overflow: auto;
}
.note-image-input {
height: auto;
}
}
// hide some buttons in modal

View file

@ -538,6 +538,39 @@ h6.uppercase, .h6.uppercase {
}
}
.signature-field {
min-height: 300px;
background: #fff;
border: 1px solid @border-color;
border-radius: 3px;
position: relative;
margin-top: -10px;
}
.signature-display {
margin: 7px 0px;
background: #fff;
}
.signature-btn-row {
position: absolute;
bottom: 12px;
right: 12px;
}
.signature-reset {
height: 30px;
width: 30px;
padding: 4px 0px;
}
.signature-img {
border: 1px solid @border-color;
background: #fff;
border-radius: 3px;
margin-top: 5px;
width: 100%;
max-height: 300px;
}
.timeline-new-email {
margin: 30px 0px;
padding-left: 70px;
@ -603,18 +636,6 @@ h6.uppercase, .h6.uppercase {
margin-bottom: 10px;
}
.linked-with-dialog {
width: 75%;
.panel-body {
padding: 0px;
}
.form-section {
padding-top: 15px;
}
}
.attach-missing-image,
.attach-image-display {
cursor: pointer;

View file

@ -483,6 +483,11 @@
}
}
.list-item-table {
border: 1px solid @border-color;
border-radius: 3px;
}
.list-item {
display: flex;
align-items: center;

View file

@ -220,10 +220,6 @@ body {
margin-left: 15px !important;
}
}
.linked-with-dialog {
width: 100% !important;
}
}
@media(max-width: 767px) {

View file

@ -286,6 +286,7 @@ class Session:
"""get session record, or return the standard Guest Record"""
from frappe.auth import clear_cookies
r = self.get_session_data()
if not r:
frappe.response["session_expired"] = 1
clear_cookies()

View file

@ -0,0 +1,47 @@
$(document).ready(function(){
(function(e){
var handler = StripeCheckout.configure({
key: "{{ publishable_key }}",
token: function(token) {
// You can access the token ID with `token.id`.
// Get the token ID to your server-side code for use.
stripe.make_payment_log(token, {{ frappe.form_dict|json }}, "{{ reference_doctype }}", "{{ reference_docname }}");
}
});
handler.open({
name: "{{payer_name}}",
description: "{{description}}",
amount: cint("{{ amount }}" * 100), // 2000 paise = INR 20
email: "{{payer_email}}",
currency: "{{currency}}"
});
})();
})
frappe.provide('stripe');
stripe.make_payment_log = function(token, data, doctype, docname){
$('.stripe-loading').addClass('hidden');
$('.stripe-confirming').removeClass('hidden');
frappe.call({
method:"frappe.templates.pages.integrations.stripe_checkout.make_payment",
freeze:true,
headers: {"X-Requested-With": "XMLHttpRequest"},
args: {
"stripe_token_id": token.id,
"data": JSON.stringify(data),
"reference_doctype": doctype,
"reference_docname": docname
},
callback: function(r){
if (r.message && r.message.status == 200) {
window.location.href = r.message.redirect_to
}
else if (r.message && ([401,400,500].indexOf(r.message.status) > -1)) {
window.location.href = r.message.redirect_to
}
}
})
}

View file

@ -170,11 +170,12 @@ login.login_handlers = (function() {
window.location.href = data.home_page;
}
} else if(window.location.hash === '#forgot') {
console.log(data.message);
if(data.message==='not found') {
login.set_indicator(__("Not a valid user"), 'red');
} else if (data.message=='not allowed') {
login.set_indicator(__("Not Allowed"), 'red');
} else {
login.set_indicator(__("Instructions Emailed"), 'green');
}

View file

@ -0,0 +1,28 @@
{% extends "templates/web.html" %}
{% block title %} Payment {% endblock %}
{%- block header -%}{% endblock %}
{% block script %}
<script src="https://checkout.stripe.com/checkout.js"></script>
<script>{% include "templates/includes/integrations/stripe_checkout.js" %}</script>
{% endblock %}
{%- block page_content -%}
<p class='lead text-center centered'>
<span class='stripe-loading'>Loading Payment System</span>
<span class='stripe-confirming hidden'>Confirming Payment</span>
</p>
{% endblock %}
{% block style %}
<style>
header, footer {
display: none;
}
</style>
{% endblock %}

View file

@ -0,0 +1,49 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt, cint
import json
no_cache = 1
no_sitemap = 1
expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname',
'payer_name', 'payer_email', 'order_id', 'currency')
def get_context(context):
context.no_cache = 1
context.publishable_key = get_api_key()
# all these keys exist in form_dict
if not (set(expected_keys) - set(frappe.form_dict.keys())):
for key in expected_keys:
context[key] = frappe.form_dict[key]
context['amount'] = flt(context['amount'])
else:
frappe.redirect_to_message(_('Some information is missing'),
_('Looks like someone sent you to an incomplete URL. Please ask them to look into it.'))
frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect
def get_api_key():
publishable_key = frappe.db.get_value("Stripe Settings", None, "publishable_key")
if cint(frappe.form_dict.get("use_sandbox")):
publishable_key = frappe.conf.sandbox_publishable_key
return publishable_key
@frappe.whitelist(allow_guest=True)
def make_payment(stripe_token_id, data, reference_doctype=None, reference_docname=None):
data = json.loads(data)
data.update({
"stripe_token_id": stripe_token_id
})
data = frappe.get_doc("Stripe Settings").create_request(data)
frappe.db.commit()
return data

View file

@ -5,7 +5,7 @@
<div>{{ frappe.render_template(df.options, {"doc": doc}) or "" }}</div>
{%- elif df.fieldtype in ("Text", "Text Editor", "Code") -%}
{{ render_text_field(df, doc) }}
{%- elif df.fieldtype in ("Image", "Attach Image", "Attach")
{%- elif df.fieldtype in ("Image", "Attach Image", "Attach", "Signature")
and (guess_mimetype(doc[df.fieldname])[0] or "").startswith("image/") -%}
{{ render_image(df, doc) }}
{%- else -%}
@ -94,7 +94,6 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
{%- endmacro -%}
{%- macro render_image(df, doc) -%}
{%- if df.label %}<label>{{ _(df.label) }}</label>{%- endif %}
{{ print_value(df, doc) }}
{% endmacro %}
@ -108,6 +107,8 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
<img src="{{ doc[doc.meta.get_field(df.fieldname).options] }}"
class="img-responsive"
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
{% elif df.fieldtype=="Signature" %}
<img src="{{ doc[df.fieldname] }}" class="signature-img img-responsive">
{% elif df.fieldtype in ("Attach", "Attach Image") and doc[df.fieldname]
and (guess_mimetype(doc[df.fieldname])[0] or "").startswith("image/") %}
<img src="{{ doc[df.fieldname] }}" class="img-responsive"

View file

View file

@ -28,7 +28,7 @@ def enqueue(method, queue='default', timeout=300, event=None,
:param now: if now=True, the method is executed via frappe.call
:param kwargs: keyword arguments to be passed to the method
'''
if now:
if now or frappe.flags.in_migrate:
return frappe.call(method, **kwargs)
q = get_queue(queue, async=async)

View file

@ -50,6 +50,7 @@ def upload():
"name": filedata.name,
"file_name": filedata.file_name,
"file_url": filedata.file_url,
"is_private": filedata.is_private,
"comment": comment.as_dict() if comment else {}
}

View file

@ -156,7 +156,7 @@ def get_context(context):
context.parents = self.get_parents(context)
if self.breadcrumbs:
context.parents = eval(self.breadcrumbs)
context.parents = frappe.safe_eval(self.breadcrumbs)
context.has_header = ((frappe.form_dict.name or frappe.form_dict.new)
and (frappe.session.user!="Guest" or not self.login_required))

View file

@ -148,9 +148,9 @@ def get_html(doc, name=None, print_format=None, meta=None,
"footer": letter_head.footer,
"print_settings": frappe.get_doc("Print Settings")
}
html = template.render(args, filters={"len": len})
if cint(trigger_print):
html += trigger_print_script