@@ -490,10 +490,11 @@ def setup_module_map(): | |||||
_cache.set_value("module_app", local.module_app) | _cache.set_value("module_app", local.module_app) | ||||
def get_file_items(path, raise_not_found=False, ignore_empty_lines=True): | def get_file_items(path, raise_not_found=False, ignore_empty_lines=True): | ||||
import frappe.utils | |||||
content = read_file(path, raise_not_found=raise_not_found) | content = read_file(path, raise_not_found=raise_not_found) | ||||
if content: | if content: | ||||
# \ufeff is no-width-break, \u200b is no-width-space | |||||
content = content.replace("\ufeff", "").replace("\u200b", "").strip() | |||||
content = frappe.utils.strip(content) | |||||
return [p.strip() for p in content.splitlines() if (not ignore_empty_lines) or (p.strip() and not p.startswith("#"))] | return [p.strip() for p in content.splitlines() if (not ignore_empty_lines) or (p.strip() and not p.startswith("#"))] | ||||
else: | else: | ||||
@@ -1 +1 @@ | |||||
__version__ = "4.10.2" | |||||
__version__ = "4.11.0" |
@@ -3,7 +3,7 @@ app_title = "Frappe Framework" | |||||
app_publisher = "Web Notes Technologies Pvt. Ltd." | app_publisher = "Web Notes Technologies Pvt. Ltd." | ||||
app_description = "Full Stack Web Application Framework in Python" | app_description = "Full Stack Web Application Framework in Python" | ||||
app_icon = "assets/frappe/images/frappe.svg" | app_icon = "assets/frappe/images/frappe.svg" | ||||
app_version = "4.10.2" | |||||
app_version = "4.11.0" | |||||
app_color = "#3498db" | app_color = "#3498db" | ||||
app_email = "support@frappe.io" | app_email = "support@frappe.io" | ||||
@@ -64,7 +64,11 @@ def extract_email_id(email): | |||||
def validate_email_add(email_str): | def validate_email_add(email_str): | ||||
"""Validates the email string""" | """Validates the email string""" | ||||
email = extract_email_id(email_str) | email = extract_email_id(email_str) | ||||
return re.match("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", email.lower()) | |||||
match = re.match("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", email.lower()) | |||||
if not match: | |||||
return False | |||||
return match.group(0)==email.lower() | |||||
def random_string(length): | def random_string(length): | ||||
"""generate a random string""" | """generate a random string""" | ||||
@@ -0,0 +1,40 @@ | |||||
import click | |||||
import frappe | |||||
import importlib | |||||
def main(): | |||||
click.Group(commands=get_app_groups())() | |||||
def get_cli_options(): | |||||
pass | |||||
def get_app_groups(): | |||||
ret = {} | |||||
for app in get_apps(): | |||||
app_group = get_app_group(app) | |||||
if app_group: | |||||
ret[app] = app_group | |||||
return ret | |||||
def get_app_group(app): | |||||
app_commands = get_app_commands(app) | |||||
if app_commands: | |||||
return click.Group(name=app, commands=app_commands) | |||||
def get_app_commands(app): | |||||
try: | |||||
app_command_module = importlib.import_module(app + '.commands') | |||||
except ImportError: | |||||
return [] | |||||
ret = {} | |||||
for command in getattr(app_command_module, 'commands', []): | |||||
ret[command.name] = command | |||||
return ret | |||||
def get_apps(): | |||||
return frappe.get_all_apps(with_internal_apps=False, sites_path='.') | |||||
if __name__ == "__main__": | |||||
main() | |||||
@@ -598,3 +598,7 @@ def unique(seq): | |||||
seen = set() | seen = set() | ||||
seen_add = seen.add | seen_add = seen.add | ||||
return [ x for x in seq if not (x in seen or seen_add(x)) ] | return [ x for x in seq if not (x in seen or seen_add(x)) ] | ||||
def strip(val, chars=None): | |||||
# \ufeff is no-width-break, \u200b is no-width-space | |||||
return (val or "").replace("\ufeff", "").replace("\u200b", "").strip(chars) |
@@ -4,7 +4,7 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
from frappe import msgprint, throw, _ | from frappe import msgprint, throw, _ | ||||
from frappe.utils import scrub_urls, get_url | |||||
from frappe.utils import scrub_urls, get_url, strip | |||||
from frappe.utils.pdf import get_pdf | from frappe.utils.pdf import get_pdf | ||||
import email.utils | import email.utils | ||||
from markdown2 import markdown | from markdown2 import markdown | ||||
@@ -41,7 +41,7 @@ class EMail: | |||||
recipients = recipients.split(',') | recipients = recipients.split(',') | ||||
# remove null | # remove null | ||||
recipients = filter(None, (r.strip() for r in recipients)) | |||||
recipients = filter(None, (strip(r) for r in recipients)) | |||||
self.sender = sender | self.sender = sender | ||||
self.reply_to = reply_to or sender | self.reply_to = reply_to or sender | ||||
@@ -175,23 +175,24 @@ class EMail: | |||||
msgprint(_("Alternatively, you can also specify 'auto_email_id' in site_config.json")) | msgprint(_("Alternatively, you can also specify 'auto_email_id' in site_config.json")) | ||||
raise frappe.ValidationError, msg | raise frappe.ValidationError, msg | ||||
self.sender = _validate(self.sender) | |||||
self.reply_to = _validate(self.reply_to) | |||||
self.sender = _validate(strip(self.sender)) | |||||
self.reply_to = _validate(strip(self.reply_to) or self.sender) | |||||
self.recipients = [strip(r) for r in self.recipients] | |||||
self.cc = [strip(r) for r in self.cc] | |||||
for e in self.recipients + (self.cc or []): | for e in self.recipients + (self.cc or []): | ||||
_validate(e.strip()) | |||||
_validate(e) | |||||
def make(self): | def make(self): | ||||
"""build into msg_root""" | """build into msg_root""" | ||||
self.msg_root['Subject'] = self.subject.encode("utf-8") | |||||
self.msg_root['Subject'] = strip(self.subject).encode("utf-8") | |||||
self.msg_root['From'] = self.sender.encode("utf-8") | self.msg_root['From'] = self.sender.encode("utf-8") | ||||
self.msg_root['To'] = ', '.join([r.strip() for r in self.recipients]).encode("utf-8") | |||||
self.msg_root['To'] = ', '.join(self.recipients).encode("utf-8") | |||||
self.msg_root['Date'] = email.utils.formatdate() | self.msg_root['Date'] = email.utils.formatdate() | ||||
if not self.reply_to: | |||||
self.reply_to = self.sender | |||||
self.msg_root['Reply-To'] = self.reply_to.encode("utf-8") | self.msg_root['Reply-To'] = self.reply_to.encode("utf-8") | ||||
if self.cc: | if self.cc: | ||||
self.msg_root['CC'] = ', '.join([r.strip() for r in self.cc]).encode("utf-8") | |||||
self.msg_root['CC'] = ', '.join(self.cc).encode("utf-8") | |||||
# add frappe site header | # add frappe site header | ||||
self.msg_root.add_header(b'X-Frappe-Site', get_url().encode('utf-8')) | self.msg_root.add_header(b'X-Frappe-Site', get_url().encode('utf-8')) | ||||
@@ -23,7 +23,8 @@ def send(email, as_bulk=False): | |||||
email.reply_to = email.sender | email.reply_to = email.sender | ||||
email.sender = smtpserver.login | email.sender = smtpserver.login | ||||
smtpserver.sess.sendmail(email.sender, email.recipients + (email.cc or []), | |||||
smtpserver.sess.sendmail(email.sender.encode("utf-8"), | |||||
[e.encode("utf-8") for e in (email.recipients + (email.cc or []))], | |||||
email.as_string()) | email.as_string()) | ||||
except smtplib.SMTPSenderRefused: | except smtplib.SMTPSenderRefused: | ||||
@@ -6,7 +6,7 @@ import frappe | |||||
import os, base64, re | import os, base64, re | ||||
import hashlib | import hashlib | ||||
import mimetypes | import mimetypes | ||||
from frappe.utils import get_site_path, get_hook_method, get_files_path | |||||
from frappe.utils import get_site_path, get_hook_method, get_files_path, random_string | |||||
from frappe import _ | from frappe import _ | ||||
from frappe import conf | from frappe import conf | ||||
from copy import copy | from copy import copy | ||||
@@ -89,8 +89,16 @@ def extract_images_from_html(doc, fieldname): | |||||
def _save_file(match): | def _save_file(match): | ||||
data = match.group(1) | data = match.group(1) | ||||
data = data.split("data:")[1] | |||||
headers, content = data.split(",") | headers, content = data.split(",") | ||||
filename = headers.split("filename=")[-1] | |||||
if "filename=" in headers: | |||||
filename = headers.split("filename=")[-1] | |||||
else: | |||||
mtype = headers.split(";")[0] | |||||
extn = mimetypes.guess_extension(mtype) | |||||
filename = random_string(7) + extn | |||||
# TODO fix this | # TODO fix this | ||||
file_url = save_file(filename, content, doc.doctype, doc.name, decode=True).get("file_url") | file_url = save_file(filename, content, doc.doctype, doc.name, decode=True).get("file_url") | ||||
if not frappe.flags.has_dataurl: | if not frappe.flags.has_dataurl: | ||||
@@ -99,7 +107,7 @@ def extract_images_from_html(doc, fieldname): | |||||
return '<img src="{file_url}"'.format(file_url=file_url) | return '<img src="{file_url}"'.format(file_url=file_url) | ||||
if content: | if content: | ||||
content = re.sub('<img\s*src=\s*["\'](data:[^"\']*)["\']', _save_file, content) | |||||
content = re.sub('<img\s*src=\s*["\'](.*?)["\']', _save_file, content) | |||||
if frappe.flags.has_dataurl: | if frappe.flags.has_dataurl: | ||||
doc.set(fieldname, content) | doc.set(fieldname, content) | ||||
@@ -1,7 +1,7 @@ | |||||
from setuptools import setup, find_packages | from setuptools import setup, find_packages | ||||
import os | import os | ||||
version = "4.10.2" | |||||
version = "4.11.0" | |||||
with open("requirements.txt", "r") as f: | with open("requirements.txt", "r") as f: | ||||
install_requires = f.readlines() | install_requires = f.readlines() | ||||