diff --git a/webnotes/auth.py b/webnotes/auth.py
index e0bc737c6c..fba197667b 100644
--- a/webnotes/auth.py
+++ b/webnotes/auth.py
@@ -107,7 +107,7 @@ class LoginManager:
def set_user_info(self):
info = webnotes.conn.get_value("Profile", self.user,
- ["user_type", "first_name", "last_name"], as_dict=1)
+ ["user_type", "first_name", "last_name", "user_image"], as_dict=1)
if info.user_type=="Website User":
webnotes._response.set_cookie("system_user", "no")
webnotes.response["message"] = "No App"
@@ -119,6 +119,7 @@ class LoginManager:
webnotes.response["full_name"] = full_name
webnotes._response.set_cookie("full_name", full_name)
webnotes._response.set_cookie("user_id", self.user)
+ webnotes._response.set_cookie("user_image", info.user_image or "")
def make_session(self, resume=False):
# start session
diff --git a/webnotes/core/doctype/profile/profile.py b/webnotes/core/doctype/profile/profile.py
index 66fb92ebe8..e39cf97830 100644
--- a/webnotes/core/doctype/profile/profile.py
+++ b/webnotes/core/doctype/profile/profile.py
@@ -347,6 +347,57 @@ def reset_password(user):
else:
return "No such user (%s)" % user
+@webnotes.whitelist(allow_guest=True)
+def facebook_login(data):
+ data = json.loads(data)
+
+ if not (data.get("id") and data.get("fb_access_token")):
+ raise webnotes.ValidationError
+
+ user = data["email"]
+
+ if not get_fb_userid(data.get("fb_access_token")):
+ # garbage
+ raise webnotes.ValidationError
+
+ if not webnotes.conn.exists("Profile", user):
+ if data.get("birthday"):
+ b = data.get("birthday").split("/")
+ data["birthday"] = b[2] + "-" + b[0] + "-" + b[1]
+
+ profile = webnotes.bean({
+ "doctype":"Profile",
+ "first_name": data["first_name"],
+ "last_name": data["last_name"],
+ "email": data["email"],
+ "enabled": 1,
+ "new_password": webnotes.generate_hash(data["email"]),
+ "fb_username": data["username"],
+ "fb_userid": data["id"],
+ "fb_location": data.get("location", {}).get("name"),
+ "fb_hometown": data.get("hometown", {}).get("name"),
+ "fb_age_range": data.get("age_range") and "{min}-{max}".format(**data.get("age_range")),
+ "birth_date": data.get("birthday"),
+ "fb_bio": data.get("bio"),
+ "fb_education": data.get("education") and data.get("education")[-1].get("type"),
+ "user_type": "Website User"
+ })
+ profile.ignore_permissions = True
+ profile.get_controller().no_welcome_mail = True
+ profile.insert()
+
+ webnotes.local.login_manager.user = user
+ webnotes.local.login_manager.post_login()
+
+def get_fb_userid(fb_access_token):
+ import requests
+ response = requests.get("https://graph.facebook.com/me?access_token=" + fb_access_token)
+ if response.status_code==200:
+ print response.json()
+ return response.json().get("id")
+ else:
+ return webnotes.AuthenticationError
+
def profile_query(doctype, txt, searchfield, start, page_len, filters):
from webnotes.widgets.reportview import get_match_cond
return webnotes.conn.sql("""select name, concat_ws(' ', first_name, middle_name, last_name)
diff --git a/webnotes/public/build.json b/webnotes/public/build.json
index ce627b9139..68a807e494 100644
--- a/webnotes/public/build.json
+++ b/webnotes/public/build.json
@@ -7,10 +7,17 @@
],
"js/webnotes-web.min.js": [
"public/js/lib/bootstrap.min.js",
+ "public/js/wn/provide.js",
"public/js/wn/misc/number_format.js",
"public/js/lib/nprogress.js",
"public/js/wn/translate.js",
- "website/js/website.js"
+ "public/js/wn/misc/pretty_date.js",
+ "website/js/website.js",
+ "website/js/website_group.js"
+ ],
+ "js/canvasResize.min.js": [
+ "website/js/jquery.exif.js",
+ "website/js/jquery.canvasResize.js"
],
"js/editor.min.js": [
"public/js/lib/jquery/jquery.hotkeys.js",
diff --git a/webnotes/public/css/common.css b/webnotes/public/css/common.css
index 422dd78245..d0bc1c77d3 100644
--- a/webnotes/public/css/common.css
+++ b/webnotes/public/css/common.css
@@ -316,6 +316,11 @@ div#freeze {
max-height: 15px;
}
+.navbar-brand {
+ min-height: 20px;
+ height: auto;
+}
+
.navbar #spinner {
display: block;
float: right;
diff --git a/webnotes/templates/generators/website_group.html b/webnotes/templates/generators/website_group.html
index ee5a840c07..d5213f4881 100644
--- a/webnotes/templates/generators/website_group.html
+++ b/webnotes/templates/generators/website_group.html
@@ -11,25 +11,23 @@
{{ post_list_html }}
@@ -6,17 +5,24 @@
More
-{%- endblock -%}
-{%- block group_script -%}
-{%- endblock -%}
\ No newline at end of file
diff --git a/webnotes/templates/website_group/forum.py b/webnotes/templates/website_group/forum.py
index df485a42fc..f711ab7f11 100644
--- a/webnotes/templates/website_group/forum.py
+++ b/webnotes/templates/website_group/forum.py
@@ -5,22 +5,36 @@ from __future__ import unicode_literals
import webnotes
from webnotes.utils import now_datetime, get_datetime_str
from webnotes.webutils import get_access
-from webnotes.templates.generators.website_group import get_views
+from webnotes.templates.website_group.settings import get_settings_context
+from webnotes.templates.website_group.post import get_post_context
def get_views():
return views
def get_context(group_context):
- return {
- "post_list_html": get_post_list_html(group_context["group"]["name"], group_context["view"])
- }
+ forum_context = {}
+
+ if group_context.view.name in ("popular", "feed"):
+ forum_context["post_list_html"] = get_post_list_html(group_context["group"]["name"], group_context["view"])
+
+ elif group_context.view.name == "edit":
+ forum_context["session_user"] = webnotes.session.user
+ forum_context["post"] = webnotes.doc("Post", webnotes.form_dict.name).fields
+
+ elif group_context.view.name == "settings":
+ forum_context.update(get_settings_context(group_context))
+
+ elif group_context.view.name == "post":
+ forum_context.update(get_post_context(group_context))
+
+ return forum_context
@webnotes.whitelist(allow_guest=True)
def get_post_list_html(group, view, limit_start=0, limit_length=20):
access = get_access(group)
if isinstance(view, basestring):
- view = get_views(group)["view"]
+ view = get_views()[view]
view = webnotes._dict(view)
@@ -50,7 +64,7 @@ def get_post_list_html(group, view, limit_start=0, limit_length=20):
views = {
"popular": {
"name": "popular",
- "template_path": "templates/unit_templates/forum_list.html",
+ "template_path": "templates/website_group/forum.html",
"url": "/{group}",
"label": "Popular",
"icon": "icon-heart",
@@ -60,7 +74,7 @@ views = {
},
"feed": {
"name": "feed",
- "template_path": "templates/unit_templates/forum_list.html",
+ "template_path": "templates/website_group/forum.html",
"url": "/{group}?view=feed",
"label": "Feed",
"icon": "icon-rss",
@@ -69,7 +83,7 @@ views = {
},
"post": {
"name": "post",
- "template_path": "templates/unit_templates/base_post.html",
+ "template_path": "templates/website_group/post.html",
"url": "/{group}?view=post&name={post}",
"label": "Post",
"icon": "icon-comments",
@@ -80,7 +94,7 @@ views = {
},
"edit": {
"name": "edit",
- "template_path": "templates/unit_templates/base_edit.html",
+ "template_path": "templates/website_group/edit_post.html",
"url": "/{group}?view=edit&name={post}",
"label": "Edit Post",
"icon": "icon-pencil",
@@ -90,7 +104,7 @@ views = {
},
"add": {
"name": "add",
- "template_path": "templates/unit_templates/base_edit.html",
+ "template_path": "templates/website_group/edit_post.html",
"url": "/{group}?view=add",
"label": "Add Post",
"icon": "icon-plus",
@@ -99,8 +113,8 @@ views = {
},
"settings": {
"name": "settings",
- "template_path": "templates/unit_templates/base_settings.html",
- "url": "/{group}&view=settings",
+ "template_path": "templates/website_group/settings.html",
+ "url": "/{group}?view=settings",
"label": "Settings",
"icon": "icon-cog",
"hidden": True,
diff --git a/webnotes/templates/website_group/post.html b/webnotes/templates/website_group/post.html
index e69de29bb2..1290a659d3 100644
--- a/webnotes/templates/website_group/post.html
+++ b/webnotes/templates/website_group/post.html
@@ -0,0 +1,34 @@
+
+
{{ parent_post_html }}
+
+
+ Show Earlier Replies
+
+
+{{ post_list_html }}
+
+
+{% include "templates/includes/post_editor.html" %}
+
+
+
+
diff --git a/webnotes/templates/website_group/post.py b/webnotes/templates/website_group/post.py
new file mode 100644
index 0000000000..32a42796fa
--- /dev/null
+++ b/webnotes/templates/website_group/post.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import webnotes
+from webnotes.utils import get_fullname
+
+def get_post_context(group_context):
+ post = webnotes.doc("Post", webnotes.form_dict.name)
+ if post.parent_post:
+ raise webnotes.PermissionError
+
+ fullname = get_fullname(post.owner)
+ return {
+ "title": "{} by {}".format(post.title, fullname),
+ # "group_title": group_context.get("unit_title") + " by {}".format(fullname),
+ "parent_post_html": get_parent_post_html(post, group_context.get("view")),
+ "post_list_html": get_child_posts_html(post, group_context.get("view")),
+ "parent_post": post.name
+ }
+
+def get_parent_post_html(post, view):
+ profile = webnotes.bean("Profile", post.owner).doc
+ for fieldname in ("first_name", "last_name", "user_image", "fb_hometown", "fb_location"):
+ post.fields[fieldname] = profile.fields[fieldname]
+
+ return webnotes.get_template("templates/includes/inline_post.html")\
+ .render({"post": post.fields, "view": view})
+
+def get_child_posts_html(post, view):
+ posts = webnotes.conn.sql("""select p.*, pr.user_image, pr.first_name, pr.last_name
+ from tabPost p, tabProfile pr
+ where p.parent_post=%s and pr.name = p.owner
+ order by p.creation asc""", (post.name,), as_dict=True)
+
+ return webnotes.get_template("templates/includes/post_list.html")\
+ .render({
+ "posts": posts,
+ "parent_post": post.name,
+ "view": view
+ })
\ No newline at end of file
diff --git a/webnotes/templates/website_group/settings.html b/webnotes/templates/website_group/settings.html
index e69de29bb2..32d230875b 100644
--- a/webnotes/templates/website_group/settings.html
+++ b/webnotes/templates/website_group/settings.html
@@ -0,0 +1,77 @@
+
+
+
1. Edit Description
+
+
+
2. Add Sub Groups
+
+
+
+
+ Group Type
+
+ {%- for group_type in ("Forum", "Tasks", "Events") -%}
+ {{ group_type }}
+ {%- endfor -%}
+
+
+
+
+ Allow all users to read
+
+
Private if unchecked, only users with explicit read access will be allowed to read
+
+
+
+ Allow all users to write
+ Public Forum
+
+
+
+ Add
+
+
+
+
+
3. Manage Users
+
+
+
+
+
+ User
+ Read
+ Write
+ Admin
+
+
+
+ {% for profile in profiles %}
+ {% include "templates/includes/sitemap_permission.html" %}
+ {% endfor %}
+
+
+
+
+
+
diff --git a/webnotes/templates/website_group/settings.py b/webnotes/templates/website_group/settings.py
new file mode 100644
index 0000000000..56624070c1
--- /dev/null
+++ b/webnotes/templates/website_group/settings.py
@@ -0,0 +1,105 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import webnotes
+from webnotes.webutils import get_access
+from webnotes.website.doctype.website_sitemap_permission.website_sitemap_permission import clear_permissions
+from webnotes.utils.email_lib.bulk import send
+
+def get_settings_context(group_context):
+ if not get_access(group_context.group.name).get("admin"):
+ raise webnotes.PermissionError
+
+ return {
+ "profiles": webnotes.conn.sql("""select p.*, wsp.`read`, wsp.`write`, wsp.`admin`
+ from `tabProfile` p, `tabWebsite Sitemap Permission` wsp
+ where wsp.website_sitemap=%s and wsp.profile=p.name""", (group_context.group.name,), as_dict=True)
+ }
+
+@webnotes.whitelist()
+def suggest_user(term, group):
+ profiles = webnotes.conn.sql("""select pr.name, pr.first_name, pr.last_name,
+ pr.user_image, pr.fb_location, pr.fb_hometown
+ from `tabProfile` pr
+ where (pr.first_name like %(term)s or pr.last_name like %(term)s)
+ and pr.user_image is not null and pr.enabled=1
+ and not exists(select wsp.name from `tabWebsite Sitemap Permission` wsp
+ where wsp.website_sitemap=%(group)s and wsp.profile=pr.name)""",
+ {"term": "%{}%".format(term), "group": group}, as_dict=True)
+
+ template = webnotes.get_template("templates/includes/profile_display.html")
+ return [{
+ "value": "{} {}".format(pr.first_name, pr.last_name),
+ "profile_html": template.render({"profile": pr}),
+ "profile": pr.name
+ } for pr in profiles]
+
+@webnotes.whitelist()
+def add_sitemap_permission(sitemap_page, profile):
+ if not get_access(sitemap_page).get("admin"):
+ raise webnotes.PermissionError
+
+ permission = webnotes.bean({
+ "doctype": "Website Sitemap Permission",
+ "website_sitemap": sitemap_page,
+ "profile": profile,
+ "read": 1
+ })
+ permission.insert(ignore_permissions=True)
+
+ profile = permission.doc.fields
+ profile.update(webnotes.conn.get_value("Profile", profile.profile,
+ ["name", "first_name", "last_name", "user_image", "fb_location", "fb_hometown"], as_dict=True))
+
+ return webnotes.get_template("templates/includes/sitemap_permission.html").render({
+ "profile": profile
+ })
+
+@webnotes.whitelist()
+def update_permission(sitemap_page, profile, perm, value):
+ if not get_access(sitemap_page).get("admin"):
+ raise webnotes.PermissionError
+
+ permission = webnotes.bean("Website Sitemap Permission", {"website_sitemap": sitemap_page, "profile": profile})
+ permission.doc.fields[perm] = int(value)
+ permission.save(ignore_permissions=True)
+
+ # send email
+ if perm=="admin" and int(value):
+ group_title = webnotes.conn.get_value("Website Sitemap", sitemap_page, "page_title")
+
+ subject = "You have been made Administrator of Group " + group_title
+
+ send(recipients=[profile],
+ subject= subject, add_unsubscribe_link=False,
+ message="""
Group Notification\
+ %s
\
+
This is just for your information.
""" % subject)
+
+@webnotes.whitelist()
+def update_description(group, description):
+ if not get_access(group).get("admin"):
+ raise webnotes.PermissionError
+
+ group = webnotes.bean("Website Group", group)
+ group.doc.group_description = description
+ group.save(ignore_permissions=True)
+
+@webnotes.whitelist()
+def add_website_group(group, new_group, public_read, public_write, group_type="Forum"):
+ if not get_access(group).get("admin"):
+ raise webnotes.PermissionError
+
+ parent_website_sitemap = webnotes.conn.get_value("Website Sitemap",
+ {"ref_doctype": "Website Group", "docname": group})
+
+ webnotes.bean({
+ "doctype": "Website Group",
+ "group_name": group + "-" + new_group,
+ "group_title": new_group,
+ "parent_website_sitemap": parent_website_sitemap,
+ "group_type": group_type,
+ "public_read": int(public_read),
+ "public_write": int(public_write)
+ }).insert(ignore_permissions=True)
\ No newline at end of file
diff --git a/webnotes/templates/website_group/tasks.html b/webnotes/templates/website_group/tasks.html
index e69de29bb2..7ac084dd5c 100644
--- a/webnotes/templates/website_group/tasks.html
+++ b/webnotes/templates/website_group/tasks.html
@@ -0,0 +1,32 @@
+
+
+{{ post_list_html }}
+
+
+ More
+
+
+
diff --git a/webnotes/templates/website_group/tasks.py b/webnotes/templates/website_group/tasks.py
new file mode 100644
index 0000000000..36c96dceef
--- /dev/null
+++ b/webnotes/templates/website_group/tasks.py
@@ -0,0 +1,126 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import webnotes
+from webnotes.utils import now_datetime, get_datetime_str
+from webnotes.webutils import get_access
+from webnotes.templates.website_group.settings import get_settings_context
+from webnotes.templates.website_group.post import get_post_context
+
+def get_views():
+ return views
+
+def get_context(group_context):
+ tasks_context = {}
+
+ if group_context.view.name in ("open", "closed"):
+ tasks_context["post_list_html"] = get_post_list_html(group_context["group"]["name"], group_context["view"])
+
+ elif group_context.view.name == "edit":
+ post = webnotes.doc("Post", webnotes.form_dict.name).fields
+ tasks_context["session_user"] = webnotes.session.user
+ tasks_context["post"] = post
+ if post.assigned_to:
+ tasks_context["profile"] = webnotes.doc("Profile", post.assigned_to)
+
+ elif group_context.view.name == "settings":
+ tasks_context.update(get_settings_context(group_context))
+
+ elif group_context.view.name == "post":
+ tasks_context.update(get_post_context(group_context))
+
+ return tasks_context
+
+@webnotes.whitelist(allow_guest=True)
+def get_post_list_html(group, view, limit_start=0, limit_length=20, status="Open"):
+ access = get_access(group)
+
+ if isinstance(view, basestring):
+ view = get_views()[view]
+
+ view = webnotes._dict(view)
+
+ # verify permission for paging
+ if webnotes.local.form_dict.cmd == "get_post_list_html":
+ if not access.get("read"):
+ return webnotes.PermissionError
+
+ if view.name=="open":
+ now = get_datetime_str(now_datetime())
+ order_by = "(p.upvotes + post_reply_count - (timestampdiff(hour, p.creation, \"{}\") / 2)) desc, p.creation desc".format(now)
+ else:
+ status = "Closed"
+ order_by = "p.creation desc"
+
+ posts = webnotes.conn.sql("""select p.*, pr.user_image, pr.first_name, pr.last_name,
+ (select count(pc.name) from `tabPost` pc where pc.parent_post=p.name) as post_reply_count
+ from `tabPost` p, `tabProfile` pr
+ where p.website_group = %s and pr.name = p.owner and ifnull(p.parent_post, '')=''
+ and p.is_task=1 and p.status=%s
+ order by {order_by} limit %s, %s""".format(order_by=order_by),
+ (group, status, int(limit_start), int(limit_length)), as_dict=True)
+
+ context = {"posts": posts, "limit_start": limit_start, "view": view}
+
+ return webnotes.get_template("templates/includes/post_list.html").render(context)
+
+views = {
+ "open": {
+ "name": "open",
+ "template_path": "templates/website_group/tasks.html",
+ "url": "/{group}",
+ "label": "Open",
+ "icon": "icon-inbox",
+ "default": True,
+ "upvote": True,
+ "idx": 1
+ },
+ "closed": {
+ "name": "closed",
+ "template_path": "templates/website_group/tasks.html",
+ "url": "/{group}?view=closed",
+ "label": "Closed",
+ "icon": "icon-smile",
+ "idx": 2
+ },
+ "post": {
+ "name": "post",
+ "template_path": "templates/website_group/post.html",
+ "url": "/{group}?view=post&name={post}",
+ "label": "Post",
+ "icon": "icon-comments",
+ "hidden": True,
+ "no_cache": True,
+ "upvote": True,
+ "idx": 3
+ },
+ "edit": {
+ "name": "edit",
+ "template_path": "templates/website_group/edit_post.html",
+ "url": "/{group}?view=edit&name={post}",
+ "label": "Edit Post",
+ "icon": "icon-pencil",
+ "hidden": True,
+ "no_cache": True,
+ "idx": 4
+ },
+ "add": {
+ "name": "add",
+ "template_path": "templates/website_group/edit_post.html",
+ "url": "/{group}?view=add",
+ "label": "Add Post",
+ "icon": "icon-plus",
+ "hidden": True,
+ "idx": 5
+ },
+ "settings": {
+ "name": "settings",
+ "template_path": "templates/website_group/settings.html",
+ "url": "/{group}?view=settings",
+ "label": "Settings",
+ "icon": "icon-cog",
+ "hidden": True,
+ "idx": 6
+ }
+}
\ No newline at end of file
diff --git a/webnotes/website/css/website.css b/webnotes/website/css/website.css
index bb51a28bc8..d9ba145655 100644
--- a/webnotes/website/css/website.css
+++ b/webnotes/website/css/website.css
@@ -1,4 +1,4 @@
-@media (min-width: 992px) {
+@media (min-width: 768px) {
.login-wrapper {
border-right: 1px solid #f2f2f2;
}
@@ -163,6 +163,8 @@ img {
.navbar-brand {
padding-right: 30px;
max-width: 80%;
+ min-height: 20px;
+ height: auto;
}
@media (min-width: 768px) {
@@ -283,6 +285,7 @@ body {
.post .media-object {
border-radius: 4px;
+ max-width: 50px;
}
.post .media-heading {
@@ -298,4 +301,12 @@ body {
padding-left: 15px;
background-color: #f8f8f8;
margin-top: 0px;
+}
+
+textarea {
+ resize: vertical;
+}
+
+.post-add-textarea {
+ height: 200px !important;
}
\ No newline at end of file
diff --git a/webnotes/website/doctype/post/post.py b/webnotes/website/doctype/post/post.py
index 7db277da8a..86863330e3 100644
--- a/webnotes/website/doctype/post/post.py
+++ b/webnotes/website/doctype/post/post.py
@@ -9,11 +9,13 @@ import webnotes
from webnotes.utils import get_fullname
from webnotes.utils.email_lib.bulk import send
from webnotes.utils.email_lib import sendmail
+from webnotes.utils.file_manager import save_file
+
+from webnotes.webutils import get_access
# TODO move these functions to framework
-from aapkamanch.helpers import get_access
-from aapkamanch.post import clear_post_cache
-from aapkamanch.unit import clear_unit_views
+# from aapkamanch.post import clear_post_cache
+# from aapkamanch.unit import clear_unit_views
class DocType:
def __init__(self, d, dl):
@@ -42,8 +44,8 @@ class DocType:
self.doc.event_datetime = None
def on_update(self):
- clear_unit_views(self.doc.unit)
- clear_post_cache(self.doc.parent_post or self.doc.name)
+ # clear_unit_views(self.doc.website_group)
+ # clear_post_cache(self.doc.parent_post or self.doc.name)
if self.doc.assigned_to and self.doc.assigned_to != self.assigned_to \
and webnotes.session.user != self.doc.assigned_to:
@@ -99,4 +101,102 @@ class DocType:
message += "
Click here to view the post
".format(fullname=owner_fullname,
post_name=post_name)
return message
-
\ No newline at end of file
+
+@webnotes.whitelist(allow_guest=True)
+def add_post(group, content, picture, picture_name, title=None, parent_post=None,
+ assigned_to=None, status=None, event_datetime=None):
+
+ access = get_access(group)
+ if not access.get("write"):
+ raise webnotes.PermissionError
+
+ if parent_post:
+ if webnotes.conn.get_value("Post", parent_post, "parent_post"):
+ webnotes.throw("Cannot reply to a reply")
+
+ group = webnotes.doc("Website Group", group)
+ post = webnotes.bean({
+ "doctype":"Post",
+ "title": (title or "").title(),
+ "content": content,
+ "website_group": group.name,
+ "parent_post": parent_post or None
+ })
+
+ if not parent_post:
+ if group.group_type == "Tasks":
+ post.doc.is_task = 1
+ post.doc.assigned_to = assigned_to
+ elif group.group_type == "Events":
+ post.doc.is_event = 1
+ post.doc.event_datetime = event_datetime
+
+ post.ignore_permissions = True
+ post.insert()
+
+ if picture_name and picture:
+ process_picture(post, picture_name, picture)
+
+ # send email
+ if parent_post:
+ post.run_method("send_email_on_reply")
+
+ return post.doc.parent_post or post.doc.name
+
+@webnotes.whitelist(allow_guest=True)
+def save_post(post, content, picture=None, picture_name=None, title=None,
+ assigned_to=None, status=None, event_datetime=None):
+
+ post = webnotes.bean("Post", post)
+
+ access = get_access(post.doc.website_group)
+ if not access.get("write"):
+ raise webnotes.PermissionError
+
+ # TODO improve error message
+ if webnotes.session.user != post.doc.owner:
+ for fieldname in ("title", "content"):
+ if post.doc.fields.get(fieldname) != locals().get(fieldname):
+ webnotes.throw("You cannot change: {}".format(fieldname.title()))
+
+ if picture and picture_name:
+ webnotes.throw("You cannot change: Picture")
+
+ post.doc.fields.update({
+ "title": (title or "").title(),
+ "content": content,
+ "assigned_to": assigned_to,
+ "status": status,
+ "event_datetime": event_datetime
+ })
+ post.ignore_permissions = True
+ post.save()
+
+ if picture_name and picture:
+ process_picture(post, picture_name, picture)
+
+ return post.doc.parent_post or post.doc.name
+
+def process_picture(post, picture_name, picture):
+ file_data = save_file(picture_name, picture, "Post", post.doc.name, decode=True)
+ post.doc.picture_url = file_data.file_name or file_data.file_url
+ webnotes.conn.set_value("Post", post.doc.name, "picture_url", post.doc.picture_url)
+ # clear_unit_views(post.doc.website_group)
+
+@webnotes.whitelist()
+def suggest_user(group, term):
+ """suggest a user that has read permission in this group tree"""
+ profiles = webnotes.conn.sql("""select
+ pr.name, pr.first_name, pr.last_name,
+ pr.user_image, pr.fb_location, pr.fb_hometown
+ from `tabProfile` pr
+ where (pr.first_name like %(term)s or pr.last_name like %(term)s)
+ and pr.name not in ("Guest", "Administrator") is not null and pr.enabled=1""",
+ {"term": "%{}%".format(term), "group": group}, as_dict=True)
+
+ template = webnotes.get_template("templates/includes/profile_display.html")
+ return [{
+ "value": "{} {}".format(pr.first_name or "", pr.last_name or "").strip(),
+ "profile_html": template.render({"profile": pr}),
+ "profile": pr.name
+ } for pr in profiles]
diff --git a/webnotes/website/doctype/user_vote/user_vote.py b/webnotes/website/doctype/user_vote/user_vote.py
index 9e84e41e86..fb11cea746 100644
--- a/webnotes/website/doctype/user_vote/user_vote.py
+++ b/webnotes/website/doctype/user_vote/user_vote.py
@@ -5,6 +5,7 @@
from __future__ import unicode_literals
import webnotes
+from webnotes.webutils import get_access
class DocType:
def __init__(self, d, dl):
@@ -31,3 +32,22 @@ class DocType:
def on_doctype_update():
webnotes.conn.add_index("User Vote", ["ref_doctype", "ref_name"])
+
+# don't allow guest to give vote
+@webnotes.whitelist()
+def set_vote(ref_doctype, ref_name):
+ website_group = webnotes.conn.get_value(ref_doctype, ref_name, "website_group")
+ if not get_access(website_group).get("read"):
+ raise webnotes.PermissionError
+
+ try:
+ user_vote = webnotes.bean({
+ "doctype": "User Vote",
+ "ref_doctype": ref_doctype,
+ "ref_name": ref_name
+ })
+ user_vote.ignore_permissions = True
+ user_vote.insert()
+ return "ok"
+ except webnotes.DuplicateEntryError:
+ return "duplicate"
diff --git a/webnotes/website/doctype/website_group/website_group.py b/webnotes/website/doctype/website_group/website_group.py
index 01ee4e4cd3..779f499913 100644
--- a/webnotes/website/doctype/website_group/website_group.py
+++ b/webnotes/website/doctype/website_group/website_group.py
@@ -27,3 +27,6 @@ class DocType(WebsiteGenerator):
if not self.doc.page_name:
webnotes.throw(_("Page Name is mandatory"), raise_exception=webnotes.MandatoryError)
+
+ def get_page_title(self):
+ return self.doc.group_title
diff --git a/webnotes/website/doctype/website_settings/website_settings.txt b/webnotes/website/doctype/website_settings/website_settings.txt
index 9741d17ee3..68c00e7fab 100644
--- a/webnotes/website/doctype/website_settings/website_settings.txt
+++ b/webnotes/website/doctype/website_settings/website_settings.txt
@@ -2,7 +2,7 @@
{
"creation": "2013-04-30 12:58:46",
"docstatus": 0,
- "modified": "2013-12-27 16:37:52",
+ "modified": "2014-02-03 15:25:54",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -25,6 +25,8 @@
"permlevel": 0
},
{
+ "cancel": 0,
+ "delete": 0,
"doctype": "DocPerm",
"name": "__common__",
"parent": "Website Settings",
@@ -48,8 +50,9 @@
"description": "Link that is the website home page. Standard Links (index, login, products, blog, about, contact)",
"doctype": "DocField",
"fieldname": "home_page",
- "fieldtype": "Data",
+ "fieldtype": "Link",
"label": "Home Page",
+ "options": "Website Sitemap",
"reqd": 0
},
{
@@ -241,7 +244,6 @@
},
{
"amend": 0,
- "cancel": 0,
"create": 0,
"doctype": "DocPerm",
"permlevel": 1,
diff --git a/webnotes/website/doctype/website_sitemap/website_sitemap.py b/webnotes/website/doctype/website_sitemap/website_sitemap.py
index ff171a1f88..d55272833f 100644
--- a/webnotes/website/doctype/website_sitemap/website_sitemap.py
+++ b/webnotes/website/doctype/website_sitemap/website_sitemap.py
@@ -8,7 +8,7 @@ import webnotes
from webnotes.utils.nestedset import DocTypeNestedSet
sitemap_fields = ("page_name", "ref_doctype", "docname", "page_or_generator",
- "lastmod", "parent_website_sitemap", "public_read", "public_write")
+ "lastmod", "parent_website_sitemap", "public_read", "public_write", "page_title")
class DocType(DocTypeNestedSet):
def __init__(self, d, dl):
diff --git a/webnotes/website/doctype/website_sitemap_permission/website_sitemap_permission.py b/webnotes/website/doctype/website_sitemap_permission/website_sitemap_permission.py
index 6982d9478e..5de997cfcb 100644
--- a/webnotes/website/doctype/website_sitemap_permission/website_sitemap_permission.py
+++ b/webnotes/website/doctype/website_sitemap_permission/website_sitemap_permission.py
@@ -10,6 +10,7 @@ class DocType:
def on_update(self):
remove_empty_permissions()
+ clear_permissions(self.doc.profile)
def remove_empty_permissions():
permissions_cache_to_be_cleared = webnotes.conn.sql_list("""select distinct profile
@@ -21,20 +22,20 @@ def remove_empty_permissions():
clear_permissions(permissions_cache_to_be_cleared)
-def get_access(website_node, profile=None):
+def get_access(sitemap_page, profile=None):
profile = profile or webnotes.session.user
key = "website_sitemap_permissions:{}".format(profile)
cache = webnotes.cache()
permissions = cache.get_value(key) or {}
- if not permissions.get(website_node):
- permissions[website_node] = _get_access(website_node, profile)
+ if not permissions.get(sitemap_page):
+ permissions[sitemap_page] = _get_access(sitemap_page, profile)
cache.set_value(key, permissions)
- return permissions.get(website_node)
+ return permissions.get(sitemap_page)
-def _get_access(website_node, profile):
- lft, rgt, public_read, public_write = webnotes.conn.get_value("Website Sitemap", website_node,
+def _get_access(sitemap_page, profile):
+ lft, rgt, public_read, public_write = webnotes.conn.get_value("Website Sitemap", sitemap_page,
["lft", "rgt", "public_read", "public_write"])
if not (lft and rgt):
@@ -52,7 +53,7 @@ def _get_access(website_node, profile):
for perm in webnotes.conn.sql("""select wsp.`read`, wsp.`write`, wsp.`admin`,
ws.lft, ws.rgt, ws.name
- from `tabWebsite Sitemap Permission` up, `tabWebsite Sitemap` ws
+ from `tabWebsite Sitemap Permission` wsp, `tabWebsite Sitemap` ws
where wsp.profile = %s and wsp.website_sitemap = ws.name
order by lft asc""", (profile,), as_dict=True):
if perm.lft <= lft and perm.rgt >= rgt:
diff --git a/webnotes/website/js/jquery.canvasResize.js b/webnotes/website/js/jquery.canvasResize.js
new file mode 100644
index 0000000000..0f8a352b7b
--- /dev/null
+++ b/webnotes/website/js/jquery.canvasResize.js
@@ -0,0 +1,313 @@
+/*
+ * jQuery canvasResize plugin
+ *
+ * Version: 1.2.0
+ * Date (d/m/y): 02/10/12
+ * Update (d/m/y): 14/05/13
+ * Original author: @gokercebeci
+ * Licensed under the MIT license
+ * - This plugin working with jquery.exif.js
+ * (It's under the MPL License http://www.nihilogic.dk/licenses/mpl-license.txt)
+ * Demo: http://ios6-image-resize.gokercebeci.com/
+ *
+ * - I fixed iOS6 Safari's image file rendering issue for large size image (over mega-pixel)
+ * using few functions from https://github.com/stomita/ios-imagefile-megapixel
+ * (detectSubsampling, )
+ * And fixed orientation issue by edited http://blog.nihilogic.dk/2008/05/jquery-exif-data-plugin.html
+ * Thanks, Shinichi Tomita and Jacob Seidelin
+ */
+
+(function($) {
+ var pluginName = 'canvasResize',
+ methods = {
+ newsize: function(w, h, W, H, C) {
+ var c = C ? 'h' : '';
+ if ((W && w > W) || (H && h > H)) {
+ var r = w / h;
+ if ((r >= 1 || H === 0) && W && !C) {
+ w = W;
+ h = (W / r) >> 0;
+ } else if (C && r <= (W / H)) {
+ w = W;
+ h = (W / r) >> 0;
+ c = 'w';
+ } else {
+ w = (H * r) >> 0;
+ h = H;
+ }
+ }
+ return {
+ 'width': w,
+ 'height': h,
+ 'cropped': c
+ };
+ },
+ dataURLtoBlob: function(data) {
+ var mimeString = data.split(',')[0].split(':')[1].split(';')[0];
+ var byteString = atob(data.split(',')[1]);
+ var ab = new ArrayBuffer(byteString.length);
+ var ia = new Uint8Array(ab);
+ for (var i = 0; i < byteString.length; i++) {
+ ia[i] = byteString.charCodeAt(i);
+ }
+ var bb = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder);
+ if (bb) {
+ // console.log('BlobBuilder');
+ bb = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder)();
+ bb.append(ab);
+ return bb.getBlob(mimeString);
+ } else {
+ // console.log('Blob');
+ bb = new Blob([ab], {
+ 'type': (mimeString)
+ });
+ return bb;
+ }
+ },
+ /**
+ * Detect subsampling in loaded image.
+ * In iOS, larger images than 2M pixels may be subsampled in rendering.
+ */
+ detectSubsampling: function(img) {
+ var iw = img.width, ih = img.height;
+ if (iw * ih > 1048576) { // subsampling may happen over megapixel image
+ var canvas = document.createElement('canvas');
+ canvas.width = canvas.height = 1;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(img, -iw + 1, 0);
+ // subsampled image becomes half smaller in rendering size.
+ // check alpha channel value to confirm image is covering edge pixel or not.
+ // if alpha value is 0 image is not covering, hence subsampled.
+ return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
+ } else {
+ return false;
+ }
+ },
+ /**
+ * Update the orientation according to the specified rotation angle
+ */
+ rotate: function(orientation, angle) {
+ var o = {
+ // nothing
+ 1: {90: 6, 180: 3, 270: 8},
+ // horizontal flip
+ 2: {90: 7, 180: 4, 270: 5},
+ // 180 rotate left
+ 3: {90: 8, 180: 1, 270: 6},
+ // vertical flip
+ 4: {90: 5, 180: 2, 270: 7},
+ // vertical flip + 90 rotate right
+ 5: {90: 2, 180: 7, 270: 4},
+ // 90 rotate right
+ 6: {90: 3, 180: 8, 270: 1},
+ // horizontal flip + 90 rotate right
+ 7: {90: 4, 180: 5, 270: 2},
+ // 90 rotate left
+ 8: {90: 1, 180: 6, 270: 3}
+ };
+ return o[orientation][angle] ? o[orientation][angle] : orientation;
+ },
+ /**
+ * Transform canvas coordination according to specified frame size and orientation
+ * Orientation value is from EXIF tag
+ */
+ transformCoordinate: function(canvas, width, height, orientation) {
+ //console.log(width, height);
+ switch (orientation) {
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ canvas.width = height;
+ canvas.height = width;
+ break;
+ default:
+ canvas.width = width;
+ canvas.height = height;
+ }
+ var ctx = canvas.getContext('2d');
+ switch (orientation) {
+ case 1:
+ // nothing
+ break;
+ case 2:
+ // horizontal flip
+ ctx.translate(width, 0);
+ ctx.scale(-1, 1);
+ break;
+ case 3:
+ // 180 rotate left
+ ctx.translate(width, height);
+ ctx.rotate(Math.PI);
+ break;
+ case 4:
+ // vertical flip
+ ctx.translate(0, height);
+ ctx.scale(1, -1);
+ break;
+ case 5:
+ // vertical flip + 90 rotate right
+ ctx.rotate(0.5 * Math.PI);
+ ctx.scale(1, -1);
+ break;
+ case 6:
+ // 90 rotate right
+ ctx.rotate(0.5 * Math.PI);
+ ctx.translate(0, -height);
+ break;
+ case 7:
+ // horizontal flip + 90 rotate right
+ ctx.rotate(0.5 * Math.PI);
+ ctx.translate(width, -height);
+ ctx.scale(-1, 1);
+ break;
+ case 8:
+ // 90 rotate left
+ ctx.rotate(-0.5 * Math.PI);
+ ctx.translate(-width, 0);
+ break;
+ default:
+ break;
+ }
+ },
+ /**
+ * Detecting vertical squash in loaded image.
+ * Fixes a bug which squash image vertically while drawing into canvas for some images.
+ */
+ detectVerticalSquash: function(img, iw, ih) {
+ var canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = ih;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(img, 0, 0);
+ var data = ctx.getImageData(0, 0, 1, ih).data;
+ // search image edge pixel position in case it is squashed vertically.
+ var sy = 0;
+ var ey = ih;
+ var py = ih;
+ while (py > sy) {
+ var alpha = data[(py - 1) * 4 + 3];
+ if (alpha === 0) {
+ ey = py;
+ } else {
+ sy = py;
+ }
+ py = (ey + sy) >> 1;
+ }
+ var ratio = py / ih;
+ return ratio === 0 ? 1 : ratio;
+ },
+ callback: function(d) {
+ return d;
+ }
+ },
+ defaults = {
+ width: 300,
+ height: 0,
+ crop: false,
+ quality: 80,
+ 'callback': methods.callback
+ };
+ function Plugin(file, options) {
+ this.file = file;
+ this.options = $.extend({}, defaults, options);
+ this._defaults = defaults;
+ this._name = pluginName;
+ this.init();
+ }
+ Plugin.prototype = {
+ init: function() {
+ //this.options.init(this);
+ var $this = this;
+ var file = this.file;
+
+ var reader = new FileReader();
+ reader.onloadend = function(e) {
+ var dataURL = e.target.result;
+ var img = new Image();
+ img.onload = function(e) {
+ // Read Orientation Data in EXIF
+ $(img).exifLoadFromDataURL(function() {
+ var orientation = $(img).exif('Orientation')[0] || 1;
+ orientation = methods.rotate(orientation, $this.options.rotate);
+
+ // CW or CCW ? replace width and height
+ var size = (orientation >= 5 && orientation <= 8)
+ ? methods.newsize(img.height, img.width, $this.options.width, $this.options.height, $this.options.crop)
+ : methods.newsize(img.width, img.height, $this.options.width, $this.options.height, $this.options.crop);
+
+ var iw = img.width, ih = img.height;
+ var width = size.width, height = size.height;
+
+ //console.log(iw, ih, size.width, size.height, orientation);
+
+ var canvas = document.createElement("canvas");
+ var ctx = canvas.getContext("2d");
+ ctx.save();
+ methods.transformCoordinate(canvas, width, height, orientation);
+
+ // over image size
+ if (methods.detectSubsampling(img)) {
+ iw /= 2;
+ ih /= 2;
+ }
+ var d = 1024; // size of tiling canvas
+ var tmpCanvas = document.createElement('canvas');
+ tmpCanvas.width = tmpCanvas.height = d;
+ var tmpCtx = tmpCanvas.getContext('2d');
+ var vertSquashRatio = methods.detectVerticalSquash(img, iw, ih);
+ var sy = 0;
+ while (sy < ih) {
+ var sh = sy + d > ih ? ih - sy : d;
+ var sx = 0;
+ while (sx < iw) {
+ var sw = sx + d > iw ? iw - sx : d;
+ tmpCtx.clearRect(0, 0, d, d);
+ tmpCtx.drawImage(img, -sx, -sy);
+ var dx = Math.floor(sx * width / iw);
+ var dw = Math.ceil(sw * width / iw);
+ var dy = Math.floor(sy * height / ih / vertSquashRatio);
+ var dh = Math.ceil(sh * height / ih / vertSquashRatio);
+ ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh);
+ sx += d;
+ }
+ sy += d;
+ }
+ ctx.restore();
+ tmpCanvas = tmpCtx = null;
+
+ // if cropped or rotated width and height data replacing issue
+ var newcanvas = document.createElement('canvas');
+ newcanvas.width = size.cropped === 'h' ? height : width;
+ newcanvas.height = size.cropped === 'w' ? width : height;
+ var x = size.cropped === 'h' ? (height - width) * .5 : 0;
+ var y = size.cropped === 'w' ? (width - height) * .5 : 0;
+ newctx = newcanvas.getContext('2d');
+ newctx.drawImage(canvas, x, y, width, height);
+
+ if (file.type === "image/png") {
+ var data = newcanvas.toDataURL(file.type);
+ } else {
+ var data = newcanvas.toDataURL("image/jpeg", ($this.options.quality * .01));
+ }
+
+ // CALLBACK
+ $this.options.callback(data, width, height);
+
+ });
+ };
+ img.src = dataURL;
+ // =====================================================
+ };
+ reader.readAsDataURL(file);
+
+ }
+ };
+ $[pluginName] = function(file, options) {
+ if (typeof file === 'string')
+ return methods[file](options);
+ else
+ new Plugin(file, options);
+ };
+
+})(jQuery);
\ No newline at end of file
diff --git a/webnotes/website/js/jquery.exif.js b/webnotes/website/js/jquery.exif.js
new file mode 100644
index 0000000000..15b891bf3c
--- /dev/null
+++ b/webnotes/website/js/jquery.exif.js
@@ -0,0 +1,957 @@
+
+/*
+ * Javascript EXIF Reader - jQuery plugin 0.1.3
+ * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/
+ * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt]
+ */
+
+/*
+ * I added three functions for read EXIF from dataURL
+ * - getImageDataFromDataURL
+ * - getDataFromDataURL
+ * - jQuery.fn.exifLoadFromDataURL
+ *
+ * http://orientation.gokercebeci.com
+ * @gokercebeci
+ */
+
+(function() {
+
+
+ var BinaryFile = function(strData, iDataOffset, iDataLength) {
+ var data = strData;
+ var dataOffset = iDataOffset || 0;
+ var dataLength = 0;
+
+ this.getRawData = function() {
+ return data;
+ }
+
+ if (typeof strData == "string") {
+ dataLength = iDataLength || data.length;
+
+ this.getByteAt = function(iOffset) {
+ return data.charCodeAt(iOffset + dataOffset) & 0xFF;
+ }
+ } else if (typeof strData == "unknown") {
+ dataLength = iDataLength || IEBinary_getLength(data);
+
+ this.getByteAt = function(iOffset) {
+ return IEBinary_getByteAt(data, iOffset + dataOffset);
+ }
+ }
+
+ this.getLength = function() {
+ return dataLength;
+ }
+
+ this.getSByteAt = function(iOffset) {
+ var iByte = this.getByteAt(iOffset);
+ if (iByte > 127)
+ return iByte - 256;
+ else
+ return iByte;
+ }
+
+ this.getShortAt = function(iOffset, bBigEndian) {
+ var iShort = bBigEndian ?
+ (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1)
+ : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset)
+ if (iShort < 0)
+ iShort += 65536;
+ return iShort;
+ }
+ this.getSShortAt = function(iOffset, bBigEndian) {
+ var iUShort = this.getShortAt(iOffset, bBigEndian);
+ if (iUShort > 32767)
+ return iUShort - 65536;
+ else
+ return iUShort;
+ }
+ this.getLongAt = function(iOffset, bBigEndian) {
+ var iByte1 = this.getByteAt(iOffset),
+ iByte2 = this.getByteAt(iOffset + 1),
+ iByte3 = this.getByteAt(iOffset + 2),
+ iByte4 = this.getByteAt(iOffset + 3);
+
+ var iLong = bBigEndian ?
+ (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4
+ : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
+ if (iLong < 0)
+ iLong += 4294967296;
+ return iLong;
+ }
+ this.getSLongAt = function(iOffset, bBigEndian) {
+ var iULong = this.getLongAt(iOffset, bBigEndian);
+ if (iULong > 2147483647)
+ return iULong - 4294967296;
+ else
+ return iULong;
+ }
+ this.getStringAt = function(iOffset, iLength) {
+ var aStr = [];
+ for (var i = iOffset, j = 0; i < iOffset + iLength; i++, j++) {
+ aStr[j] = String.fromCharCode(this.getByteAt(i));
+ }
+ return aStr.join("");
+ }
+
+ this.getCharAt = function(iOffset) {
+ return String.fromCharCode(this.getByteAt(iOffset));
+ }
+ this.toBase64 = function() {
+ return window.btoa(data);
+ }
+ this.fromBase64 = function(strBase64) {
+ data = window.atob(strBase64);
+ }
+ }
+
+
+ var BinaryAjax = (function() {
+
+ function createRequest() {
+ var oHTTP = null;
+ if (window.XMLHttpRequest) {
+ oHTTP = new XMLHttpRequest();
+ } else if (window.ActiveXObject) {
+ oHTTP = new ActiveXObject("Microsoft.XMLHTTP");
+ }
+ return oHTTP;
+ }
+
+ function getHead(strURL, fncCallback, fncError) {
+ var oHTTP = createRequest();
+ if (oHTTP) {
+ if (fncCallback) {
+ if (typeof(oHTTP.onload) != "undefined") {
+ oHTTP.onload = function() {
+ if (oHTTP.status == "200") {
+ fncCallback(this);
+ } else {
+ if (fncError)
+ fncError();
+ }
+ oHTTP = null;
+ };
+ } else {
+ oHTTP.onreadystatechange = function() {
+ if (oHTTP.readyState == 4) {
+ if (oHTTP.status == "200") {
+ fncCallback(this);
+ } else {
+ if (fncError)
+ fncError();
+ }
+ oHTTP = null;
+ }
+ };
+ }
+ }
+ oHTTP.open("HEAD", strURL, true);
+ oHTTP.send(null);
+ } else {
+ if (fncError)
+ fncError();
+ }
+ }
+
+ function sendRequest(strURL, fncCallback, fncError, aRange, bAcceptRanges, iFileSize) {
+ var oHTTP = createRequest();
+ if (oHTTP) {
+
+ var iDataOffset = 0;
+ if (aRange && !bAcceptRanges) {
+ iDataOffset = aRange[0];
+ }
+ var iDataLen = 0;
+ if (aRange) {
+ iDataLen = aRange[1] - aRange[0] + 1;
+ }
+
+ if (fncCallback) {
+ if (typeof(oHTTP.onload) != "undefined") {
+ oHTTP.onload = function() {
+
+ if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") {
+ this.binaryResponse = new BinaryFile(this.responseText, iDataOffset, iDataLen);
+ this.fileSize = iFileSize || this.getResponseHeader("Content-Length");
+ fncCallback(this);
+ } else {
+ if (fncError)
+ fncError();
+ }
+ oHTTP = null;
+ };
+ } else {
+ oHTTP.onreadystatechange = function() {
+ if (oHTTP.readyState == 4) {
+ if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") {
+ this.binaryResponse = new BinaryFile(oHTTP.responseBody, iDataOffset, iDataLen);
+ this.fileSize = iFileSize || this.getResponseHeader("Content-Length");
+ fncCallback(this);
+ } else {
+ if (fncError)
+ fncError();
+ }
+ oHTTP = null;
+ }
+ };
+ }
+ }
+ oHTTP.open("GET", strURL, true);
+
+ if (oHTTP.overrideMimeType)
+ oHTTP.overrideMimeType('text/plain; charset=x-user-defined');
+
+ if (aRange && bAcceptRanges) {
+ oHTTP.setRequestHeader("Range", "bytes=" + aRange[0] + "-" + aRange[1]);
+ }
+
+ oHTTP.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 1970 00:00:00 GMT");
+
+ oHTTP.send(null);
+ } else {
+ if (fncError)
+ fncError();
+ }
+ }
+
+ return function(strURL, fncCallback, fncError, aRange) {
+
+ if (aRange) {
+ getHead(
+ strURL,
+ function(oHTTP) {
+ var iLength = parseInt(oHTTP.getResponseHeader("Content-Length"), 10);
+ var strAcceptRanges = oHTTP.getResponseHeader("Accept-Ranges");
+
+ var iStart, iEnd;
+ iStart = aRange[0];
+ if (aRange[0] < 0)
+ iStart += iLength;
+ iEnd = iStart + aRange[1] - 1;
+
+ sendRequest(strURL, fncCallback, fncError, [iStart, iEnd], (strAcceptRanges == "bytes"), iLength);
+ }
+ );
+
+ } else {
+ sendRequest(strURL, fncCallback, fncError);
+ }
+ }
+
+ }());
+
+ var script = document.createElement("script");
+ script.type = 'text/vbscript';
+ script.innerHTML =
+ "Function IEBinary_getByteAt(strBinary, iOffset)\r\n"
+ + " IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n"
+ + "End Function\r\n"
+ + "Function IEBinary_getLength(strBinary)\r\n"
+ + " IEBinary_getLength = LenB(strBinary)\r\n"
+ + "End Function";
+ document.head.appendChild(script);
+
+ var EXIF = {};
+
+ (function() {
+
+ var bDebug = false;
+
+ EXIF.Tags = {
+
+ // version tags
+ 0x9000: "ExifVersion", // EXIF version
+ 0xA000: "FlashpixVersion", // Flashpix format version
+
+ // colorspace tags
+ 0xA001: "ColorSpace", // Color space information tag
+
+ // image configuration
+ 0xA002: "PixelXDimension", // Valid width of meaningful image
+ 0xA003: "PixelYDimension", // Valid height of meaningful image
+ 0x9101: "ComponentsConfiguration", // Information about channels
+ 0x9102: "CompressedBitsPerPixel", // Compressed bits per pixel
+
+ // user information
+ 0x927C: "MakerNote", // Any desired information written by the manufacturer
+ 0x9286: "UserComment", // Comments by user
+
+ // related file
+ 0xA004: "RelatedSoundFile", // Name of related sound file
+
+ // date and time
+ 0x9003: "DateTimeOriginal", // Date and time when the original image was generated
+ 0x9004: "DateTimeDigitized", // Date and time when the image was stored digitally
+ 0x9290: "SubsecTime", // Fractions of seconds for DateTime
+ 0x9291: "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
+ 0x9292: "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
+
+ // picture-taking conditions
+ 0x829A: "ExposureTime", // Exposure time (in seconds)
+ 0x829D: "FNumber", // F number
+ 0x8822: "ExposureProgram", // Exposure program
+ 0x8824: "SpectralSensitivity", // Spectral sensitivity
+ 0x8827: "ISOSpeedRatings", // ISO speed rating
+ 0x8828: "OECF", // Optoelectric conversion factor
+ 0x9201: "ShutterSpeedValue", // Shutter speed
+ 0x9202: "ApertureValue", // Lens aperture
+ 0x9203: "BrightnessValue", // Value of brightness
+ 0x9204: "ExposureBias", // Exposure bias
+ 0x9205: "MaxApertureValue", // Smallest F number of lens
+ 0x9206: "SubjectDistance", // Distance to subject in meters
+ 0x9207: "MeteringMode", // Metering mode
+ 0x9208: "LightSource", // Kind of light source
+ 0x9209: "Flash", // Flash status
+ 0x9214: "SubjectArea", // Location and area of main subject
+ 0x920A: "FocalLength", // Focal length of the lens in mm
+ 0xA20B: "FlashEnergy", // Strobe energy in BCPS
+ 0xA20C: "SpatialFrequencyResponse", //
+ 0xA20E: "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
+ 0xA20F: "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
+ 0xA210: "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
+ 0xA214: "SubjectLocation", // Location of subject in image
+ 0xA215: "ExposureIndex", // Exposure index selected on camera
+ 0xA217: "SensingMethod", // Image sensor type
+ 0xA300: "FileSource", // Image source (3 == DSC)
+ 0xA301: "SceneType", // Scene type (1 == directly photographed)
+ 0xA302: "CFAPattern", // Color filter array geometric pattern
+ 0xA401: "CustomRendered", // Special processing
+ 0xA402: "ExposureMode", // Exposure mode
+ 0xA403: "WhiteBalance", // 1 = auto white balance, 2 = manual
+ 0xA404: "DigitalZoomRation", // Digital zoom ratio
+ 0xA405: "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
+ 0xA406: "SceneCaptureType", // Type of scene
+ 0xA407: "GainControl", // Degree of overall image gain adjustment
+ 0xA408: "Contrast", // Direction of contrast processing applied by camera
+ 0xA409: "Saturation", // Direction of saturation processing applied by camera
+ 0xA40A: "Sharpness", // Direction of sharpness processing applied by camera
+ 0xA40B: "DeviceSettingDescription", //
+ 0xA40C: "SubjectDistanceRange", // Distance to subject
+
+ // other tags
+ 0xA005: "InteroperabilityIFDPointer",
+ 0xA420: "ImageUniqueID" // Identifier assigned uniquely to each image
+ };
+
+ EXIF.TiffTags = {
+ 0x0100: "ImageWidth",
+ 0x0101: "ImageHeight",
+ 0x8769: "ExifIFDPointer",
+ 0x8825: "GPSInfoIFDPointer",
+ 0xA005: "InteroperabilityIFDPointer",
+ 0x0102: "BitsPerSample",
+ 0x0103: "Compression",
+ 0x0106: "PhotometricInterpretation",
+ 0x0112: "Orientation",
+ 0x0115: "SamplesPerPixel",
+ 0x011C: "PlanarConfiguration",
+ 0x0212: "YCbCrSubSampling",
+ 0x0213: "YCbCrPositioning",
+ 0x011A: "XResolution",
+ 0x011B: "YResolution",
+ 0x0128: "ResolutionUnit",
+ 0x0111: "StripOffsets",
+ 0x0116: "RowsPerStrip",
+ 0x0117: "StripByteCounts",
+ 0x0201: "JPEGInterchangeFormat",
+ 0x0202: "JPEGInterchangeFormatLength",
+ 0x012D: "TransferFunction",
+ 0x013E: "WhitePoint",
+ 0x013F: "PrimaryChromaticities",
+ 0x0211: "YCbCrCoefficients",
+ 0x0214: "ReferenceBlackWhite",
+ 0x0132: "DateTime",
+ 0x010E: "ImageDescription",
+ 0x010F: "Make",
+ 0x0110: "Model",
+ 0x0131: "Software",
+ 0x013B: "Artist",
+ 0x8298: "Copyright"
+ }
+
+ EXIF.GPSTags = {
+ 0x0000: "GPSVersionID",
+ 0x0001: "GPSLatitudeRef",
+ 0x0002: "GPSLatitude",
+ 0x0003: "GPSLongitudeRef",
+ 0x0004: "GPSLongitude",
+ 0x0005: "GPSAltitudeRef",
+ 0x0006: "GPSAltitude",
+ 0x0007: "GPSTimeStamp",
+ 0x0008: "GPSSatellites",
+ 0x0009: "GPSStatus",
+ 0x000A: "GPSMeasureMode",
+ 0x000B: "GPSDOP",
+ 0x000C: "GPSSpeedRef",
+ 0x000D: "GPSSpeed",
+ 0x000E: "GPSTrackRef",
+ 0x000F: "GPSTrack",
+ 0x0010: "GPSImgDirectionRef",
+ 0x0011: "GPSImgDirection",
+ 0x0012: "GPSMapDatum",
+ 0x0013: "GPSDestLatitudeRef",
+ 0x0014: "GPSDestLatitude",
+ 0x0015: "GPSDestLongitudeRef",
+ 0x0016: "GPSDestLongitude",
+ 0x0017: "GPSDestBearingRef",
+ 0x0018: "GPSDestBearing",
+ 0x0019: "GPSDestDistanceRef",
+ 0x001A: "GPSDestDistance",
+ 0x001B: "GPSProcessingMethod",
+ 0x001C: "GPSAreaInformation",
+ 0x001D: "GPSDateStamp",
+ 0x001E: "GPSDifferential"
+ }
+
+ EXIF.StringValues = {
+ ExposureProgram: {
+ 0: "Not defined",
+ 1: "Manual",
+ 2: "Normal program",
+ 3: "Aperture priority",
+ 4: "Shutter priority",
+ 5: "Creative program",
+ 6: "Action program",
+ 7: "Portrait mode",
+ 8: "Landscape mode"
+ },
+ MeteringMode: {
+ 0: "Unknown",
+ 1: "Average",
+ 2: "CenterWeightedAverage",
+ 3: "Spot",
+ 4: "MultiSpot",
+ 5: "Pattern",
+ 6: "Partial",
+ 255: "Other"
+ },
+ LightSource: {
+ 0: "Unknown",
+ 1: "Daylight",
+ 2: "Fluorescent",
+ 3: "Tungsten (incandescent light)",
+ 4: "Flash",
+ 9: "Fine weather",
+ 10: "Cloudy weather",
+ 11: "Shade",
+ 12: "Daylight fluorescent (D 5700 - 7100K)",
+ 13: "Day white fluorescent (N 4600 - 5400K)",
+ 14: "Cool white fluorescent (W 3900 - 4500K)",
+ 15: "White fluorescent (WW 3200 - 3700K)",
+ 17: "Standard light A",
+ 18: "Standard light B",
+ 19: "Standard light C",
+ 20: "D55",
+ 21: "D65",
+ 22: "D75",
+ 23: "D50",
+ 24: "ISO studio tungsten",
+ 255: "Other"
+ },
+ Flash: {
+ 0x0000: "Flash did not fire",
+ 0x0001: "Flash fired",
+ 0x0005: "Strobe return light not detected",
+ 0x0007: "Strobe return light detected",
+ 0x0009: "Flash fired, compulsory flash mode",
+ 0x000D: "Flash fired, compulsory flash mode, return light not detected",
+ 0x000F: "Flash fired, compulsory flash mode, return light detected",
+ 0x0010: "Flash did not fire, compulsory flash mode",
+ 0x0018: "Flash did not fire, auto mode",
+ 0x0019: "Flash fired, auto mode",
+ 0x001D: "Flash fired, auto mode, return light not detected",
+ 0x001F: "Flash fired, auto mode, return light detected",
+ 0x0020: "No flash function",
+ 0x0041: "Flash fired, red-eye reduction mode",
+ 0x0045: "Flash fired, red-eye reduction mode, return light not detected",
+ 0x0047: "Flash fired, red-eye reduction mode, return light detected",
+ 0x0049: "Flash fired, compulsory flash mode, red-eye reduction mode",
+ 0x004D: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
+ 0x004F: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
+ 0x0059: "Flash fired, auto mode, red-eye reduction mode",
+ 0x005D: "Flash fired, auto mode, return light not detected, red-eye reduction mode",
+ 0x005F: "Flash fired, auto mode, return light detected, red-eye reduction mode"
+ },
+ SensingMethod: {
+ 1: "Not defined",
+ 2: "One-chip color area sensor",
+ 3: "Two-chip color area sensor",
+ 4: "Three-chip color area sensor",
+ 5: "Color sequential area sensor",
+ 7: "Trilinear sensor",
+ 8: "Color sequential linear sensor"
+ },
+ SceneCaptureType: {
+ 0: "Standard",
+ 1: "Landscape",
+ 2: "Portrait",
+ 3: "Night scene"
+ },
+ SceneType: {
+ 1: "Directly photographed"
+ },
+ CustomRendered: {
+ 0: "Normal process",
+ 1: "Custom process"
+ },
+ WhiteBalance: {
+ 0: "Auto white balance",
+ 1: "Manual white balance"
+ },
+ GainControl: {
+ 0: "None",
+ 1: "Low gain up",
+ 2: "High gain up",
+ 3: "Low gain down",
+ 4: "High gain down"
+ },
+ Contrast: {
+ 0: "Normal",
+ 1: "Soft",
+ 2: "Hard"
+ },
+ Saturation: {
+ 0: "Normal",
+ 1: "Low saturation",
+ 2: "High saturation"
+ },
+ Sharpness: {
+ 0: "Normal",
+ 1: "Soft",
+ 2: "Hard"
+ },
+ SubjectDistanceRange: {
+ 0: "Unknown",
+ 1: "Macro",
+ 2: "Close view",
+ 3: "Distant view"
+ },
+ FileSource: {
+ 3: "DSC"
+ },
+ Components: {
+ 0: "",
+ 1: "Y",
+ 2: "Cb",
+ 3: "Cr",
+ 4: "R",
+ 5: "G",
+ 6: "B"
+ }
+ }
+
+ function addEvent(oElement, strEvent, fncHandler)
+ {
+ if (oElement.addEventListener) {
+ oElement.addEventListener(strEvent, fncHandler, false);
+ } else if (oElement.attachEvent) {
+ oElement.attachEvent("on" + strEvent, fncHandler);
+ }
+ }
+
+
+ function imageHasData(oImg)
+ {
+ return !!(oImg.exifdata);
+ }
+
+ function getImageData(oImg, fncCallback)
+ {
+ BinaryAjax(
+ oImg.src,
+ function(oHTTP) {
+ console.log('BINARY', oHTTP.binaryResponse);
+ var oEXIF = findEXIFinJPEG(oHTTP.binaryResponse);
+ oImg.exifdata = oEXIF || {};
+ if (fncCallback)
+ fncCallback();
+ }
+ )
+ }
+
+ function getImageDataFromDataURL(oImg, fncCallback)
+ {
+ var byteString = atob(oImg.src.split(',')[1]);
+ var f = new BinaryFile(byteString, 0, byteString.length)
+ var oEXIF = findEXIFinJPEG(f);
+ oImg.exifdata = oEXIF || {};
+ if (fncCallback)
+ fncCallback();
+ }
+
+ function findEXIFinJPEG(oFile) {
+ var aMarkers = [];
+
+ if (oFile.getByteAt(0) != 0xFF || oFile.getByteAt(1) != 0xD8) {
+ return false; // not a valid jpeg
+ }
+
+ var iOffset = 2;
+ var iLength = oFile.getLength();
+ while (iOffset < iLength) {
+ if (oFile.getByteAt(iOffset) != 0xFF) {
+ if (bDebug)
+ console.log("Not a valid marker at offset " + iOffset + ", found: " + oFile.getByteAt(iOffset));
+ return false; // not a valid marker, something is wrong
+ }
+
+ var iMarker = oFile.getByteAt(iOffset + 1);
+
+ // we could implement handling for other markers here,
+ // but we're only looking for 0xFFE1 for EXIF data
+
+ if (iMarker == 22400) {
+ if (bDebug)
+ console.log("Found 0xFFE1 marker");
+ return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset + 2, true) - 2);
+ iOffset += 2 + oFile.getShortAt(iOffset + 2, true);
+
+ } else if (iMarker == 225) {
+ // 0xE1 = Application-specific 1 (for EXIF)
+ if (bDebug)
+ console.log("Found 0xFFE1 marker");
+ return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset + 2, true) - 2);
+
+ } else {
+ iOffset += 2 + oFile.getShortAt(iOffset + 2, true);
+ }
+
+ }
+
+ }
+
+
+ function readTags(oFile, iTIFFStart, iDirStart, oStrings, bBigEnd)
+ {
+ var iEntries = oFile.getShortAt(iDirStart, bBigEnd);
+ var oTags = {};
+ for (var i = 0; i < iEntries; i++) {
+ var iEntryOffset = iDirStart + i * 12 + 2;
+ var strTag = oStrings[oFile.getShortAt(iEntryOffset, bBigEnd)];
+ if (!strTag && bDebug)
+ console.log("Unknown tag: " + oFile.getShortAt(iEntryOffset, bBigEnd));
+ oTags[strTag] = readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd);
+ }
+ return oTags;
+ }
+
+
+ function readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd)
+ {
+ var iType = oFile.getShortAt(iEntryOffset + 2, bBigEnd);
+ var iNumValues = oFile.getLongAt(iEntryOffset + 4, bBigEnd);
+ var iValueOffset = oFile.getLongAt(iEntryOffset + 8, bBigEnd) + iTIFFStart;
+
+ switch (iType) {
+ case 1: // byte, 8-bit unsigned int
+ case 7: // undefined, 8-bit byte, value depending on field
+ if (iNumValues == 1) {
+ return oFile.getByteAt(iEntryOffset + 8, bBigEnd);
+ } else {
+ var iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
+ var aVals = [];
+ for (var n = 0; n < iNumValues; n++) {
+ aVals[n] = oFile.getByteAt(iValOffset + n);
+ }
+ return aVals;
+ }
+ break;
+
+ case 2: // ascii, 8-bit byte
+ var iStringOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
+ return oFile.getStringAt(iStringOffset, iNumValues - 1);
+ break;
+
+ case 3: // short, 16 bit int
+ if (iNumValues == 1) {
+ return oFile.getShortAt(iEntryOffset + 8, bBigEnd);
+ } else {
+ var iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8);
+ var aVals = [];
+ for (var n = 0; n < iNumValues; n++) {
+ aVals[n] = oFile.getShortAt(iValOffset + 2 * n, bBigEnd);
+ }
+ return aVals;
+ }
+ break;
+
+ case 4: // long, 32 bit int
+ if (iNumValues == 1) {
+ return oFile.getLongAt(iEntryOffset + 8, bBigEnd);
+ } else {
+ var aVals = [];
+ for (var n = 0; n < iNumValues; n++) {
+ aVals[n] = oFile.getLongAt(iValueOffset + 4 * n, bBigEnd);
+ }
+ return aVals;
+ }
+ break;
+ case 5: // rational = two long values, first is numerator, second is denominator
+ if (iNumValues == 1) {
+ return oFile.getLongAt(iValueOffset, bBigEnd) / oFile.getLongAt(iValueOffset + 4, bBigEnd);
+ } else {
+ var aVals = [];
+ for (var n = 0; n < iNumValues; n++) {
+ aVals[n] = oFile.getLongAt(iValueOffset + 8 * n, bBigEnd) / oFile.getLongAt(iValueOffset + 4 + 8 * n, bBigEnd);
+ }
+ return aVals;
+ }
+ break;
+ case 9: // slong, 32 bit signed int
+ if (iNumValues == 1) {
+ return oFile.getSLongAt(iEntryOffset + 8, bBigEnd);
+ } else {
+ var aVals = [];
+ for (var n = 0; n < iNumValues; n++) {
+ aVals[n] = oFile.getSLongAt(iValueOffset + 4 * n, bBigEnd);
+ }
+ return aVals;
+ }
+ break;
+ case 10: // signed rational, two slongs, first is numerator, second is denominator
+ if (iNumValues == 1) {
+ return oFile.getSLongAt(iValueOffset, bBigEnd) / oFile.getSLongAt(iValueOffset + 4, bBigEnd);
+ } else {
+ var aVals = [];
+ for (var n = 0; n < iNumValues; n++) {
+ aVals[n] = oFile.getSLongAt(iValueOffset + 8 * n, bBigEnd) / oFile.getSLongAt(iValueOffset + 4 + 8 * n, bBigEnd);
+ }
+ return aVals;
+ }
+ break;
+ }
+ }
+
+
+ function readEXIFData(oFile, iStart, iLength)
+ {
+ if (oFile.getStringAt(iStart, 4) != "Exif") {
+ if (bDebug)
+ console.log("Not valid EXIF data! " + oFile.getStringAt(iStart, 4));
+ return false;
+ }
+
+ var bBigEnd;
+
+ var iTIFFOffset = iStart + 6;
+
+ // test for TIFF validity and endianness
+ if (oFile.getShortAt(iTIFFOffset) == 0x4949) {
+ bBigEnd = false;
+ } else if (oFile.getShortAt(iTIFFOffset) == 0x4D4D) {
+ bBigEnd = true;
+ } else {
+ if (bDebug)
+ console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
+ return false;
+ }
+
+ if (oFile.getShortAt(iTIFFOffset + 2, bBigEnd) != 0x002A) {
+ if (bDebug)
+ console.log("Not valid TIFF data! (no 0x002A)");
+ return false;
+ }
+
+ if (oFile.getLongAt(iTIFFOffset + 4, bBigEnd) != 0x00000008) {
+ if (bDebug)
+ console.log("Not valid TIFF data! (First offset not 8)", oFile.getShortAt(iTIFFOffset + 4, bBigEnd));
+ return false;
+ }
+
+ var oTags = readTags(oFile, iTIFFOffset, iTIFFOffset + 8, EXIF.TiffTags, bBigEnd);
+
+ if (oTags.ExifIFDPointer) {
+ var oEXIFTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.ExifIFDPointer, EXIF.Tags, bBigEnd);
+ for (var strTag in oEXIFTags) {
+ switch (strTag) {
+ case "LightSource" :
+ case "Flash" :
+ case "MeteringMode" :
+ case "ExposureProgram" :
+ case "SensingMethod" :
+ case "SceneCaptureType" :
+ case "SceneType" :
+ case "CustomRendered" :
+ case "WhiteBalance" :
+ case "GainControl" :
+ case "Contrast" :
+ case "Saturation" :
+ case "Sharpness" :
+ case "SubjectDistanceRange" :
+ case "FileSource" :
+ oEXIFTags[strTag] = EXIF.StringValues[strTag][oEXIFTags[strTag]];
+ break;
+
+ case "ExifVersion" :
+ case "FlashpixVersion" :
+ oEXIFTags[strTag] = String.fromCharCode(oEXIFTags[strTag][0], oEXIFTags[strTag][1], oEXIFTags[strTag][2], oEXIFTags[strTag][3]);
+ break;
+
+ case "ComponentsConfiguration" :
+ oEXIFTags[strTag] =
+ EXIF.StringValues.Components[oEXIFTags[strTag][0]]
+ + EXIF.StringValues.Components[oEXIFTags[strTag][1]]
+ + EXIF.StringValues.Components[oEXIFTags[strTag][2]]
+ + EXIF.StringValues.Components[oEXIFTags[strTag][3]];
+ break;
+ }
+ oTags[strTag] = oEXIFTags[strTag];
+ }
+ }
+
+ if (oTags.GPSInfoIFDPointer) {
+ var oGPSTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.GPSInfoIFDPointer, EXIF.GPSTags, bBigEnd);
+ for (var strTag in oGPSTags) {
+ switch (strTag) {
+ case "GPSVersionID" :
+ oGPSTags[strTag] = oGPSTags[strTag][0]
+ + "." + oGPSTags[strTag][1]
+ + "." + oGPSTags[strTag][2]
+ + "." + oGPSTags[strTag][3];
+ break;
+ }
+ oTags[strTag] = oGPSTags[strTag];
+ }
+ }
+
+ return oTags;
+ }
+
+
+ EXIF.getData = function(oImg, fncCallback)
+ {
+ if (!oImg.complete)
+ return false;
+ if (!imageHasData(oImg)) {
+ getImageData(oImg, fncCallback);
+ } else {
+ if (fncCallback)
+ fncCallback();
+ }
+ return true;
+ }
+
+ EXIF.getDataFromDataURL = function(oImg, fncCallback)
+ {
+ if (!oImg.complete)
+ return false;
+ if (!imageHasData(oImg)) {
+ getImageDataFromDataURL(oImg, fncCallback);
+ } else {
+ if (fncCallback)
+ fncCallback();
+ }
+ return true;
+ }
+
+ EXIF.getTag = function(oImg, strTag)
+ {
+ if (!imageHasData(oImg))
+ return;
+ return oImg.exifdata[strTag];
+ }
+
+ EXIF.getAllTags = function(oImg)
+ {
+ if (!imageHasData(oImg))
+ return {};
+ var oData = oImg.exifdata;
+ var oAllTags = {};
+ for (var a in oData) {
+ if (oData.hasOwnProperty(a)) {
+ oAllTags[a] = oData[a];
+ }
+ }
+ return oAllTags;
+ }
+
+ EXIF.pretty = function(oImg)
+ {
+ if (!imageHasData(oImg))
+ return "";
+ var oData = oImg.exifdata;
+ var strPretty = "";
+ for (var a in oData) {
+ if (oData.hasOwnProperty(a)) {
+ if (typeof oData[a] == "object") {
+ strPretty += a + " : [" + oData[a].length + " values]\r\n";
+ } else {
+ strPretty += a + " : " + oData[a] + "\r\n";
+ }
+ }
+ }
+ return strPretty;
+ }
+
+ EXIF.readFromBinaryFile = function(oFile) {
+ return findEXIFinJPEG(oFile);
+ }
+
+ function loadAllImages()
+ {
+ var aImages = document.getElementsByTagName("img");
+ for (var i = 0; i < aImages.length; i++) {
+ if (aImages[i].getAttribute("exif") == "true") {
+ if (!aImages[i].complete) {
+ addEvent(aImages[i], "load",
+ function() {
+ EXIF.getData(this);
+ }
+ );
+ } else {
+ EXIF.getData(aImages[i]);
+ }
+ }
+ }
+ }
+
+ // automatically load exif data for all images with exif=true when doc is ready
+ jQuery(document).ready(loadAllImages);
+
+ // load data for images manually
+ jQuery.fn.exifLoad = function(fncCallback) {
+ return this.each(function() {
+ EXIF.getData(this, fncCallback)
+ });
+ }
+
+ // load data for images manually
+ jQuery.fn.exifLoadFromDataURL = function(fncCallback) {
+ return this.each(function() {
+ EXIF.getDataFromDataURL(this, fncCallback)
+ return true;
+ });
+ }
+
+ jQuery.fn.exif = function(strTag) {
+ var aStrings = [];
+ this.each(function() {
+ aStrings.push(EXIF.getTag(this, strTag));
+ });
+ return aStrings;
+ }
+
+ jQuery.fn.exifAll = function() {
+ var aStrings = [];
+ this.each(function() {
+ aStrings.push(EXIF.getAllTags(this));
+ });
+ return aStrings;
+ }
+
+ jQuery.fn.exifPretty = function() {
+ var aStrings = [];
+ this.each(function() {
+ aStrings.push(EXIF.pretty(this));
+ });
+ return aStrings;
+ }
+
+
+ })();
+
+
+})();
diff --git a/webnotes/website/js/website.js b/webnotes/website/js/website.js
index 7e098a6dbd..66f2207981 100644
--- a/webnotes/website/js/website.js
+++ b/webnotes/website/js/website.js
@@ -1,20 +1,9 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
-// MIT License. See license.txt
-if(!window.wn) wn = {};
+// MIT License. See license.txt
+
+wn.provide("website");
$.extend(wn, {
- provide: function(namespace) {
- var nsl = namespace.split('.');
- var parent = window;
- for(var i=0; i
Switch To App ');
}
+ wn.render_user();
+
$(document).trigger("page_change");
});
$(document).on("page_change", function() {
$(".page-header").toggleClass("hidden", !!!$(".page-header").text().trim());
$(".page-footer").toggleClass("hidden", !!!$(".page-footer").text().trim());
-});
+ // add prive pages to sidebar
+ if(website.private_pages && $(".page-sidebar").length) {
+ $(data.private_pages).prependTo(".page-sidebar");
+ }
+
+ $(document).trigger("apply_permissions");
+ wn.datetime.refresh_when();
+});
diff --git a/webnotes/website/js/website_group.js b/webnotes/website/js/website_group.js
new file mode 100644
index 0000000000..b14d82df1a
--- /dev/null
+++ b/webnotes/website/js/website_group.js
@@ -0,0 +1,529 @@
+// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+// MIT License. See license.txt
+
+wn.provide("website");
+$.extend(website, {
+ toggle_permitted: function() {
+ if(website.access) {
+ // hide certain views
+ $('li[data-view="add"]').toggleClass("hide", !website.access.write);
+ $('li[data-view="settings"]').toggleClass("hide", !website.access.admin);
+ $('li[data-view="edit"]').toggleClass("hide", website.view!=="edit");
+ // $('li[data-view="settings"]').toggleClass("hide", !website.access.admin);
+
+ // show message
+ $(".post-list-help").html(!website.access.write ? "You do not have permission to post" : "");
+ }
+ },
+ setup_pagination: function($btn, opts) {
+ $btn.removeClass("hide");
+
+ $btn.on("click", function() {
+ wn.call($.extend({
+ btn: $btn,
+ type: "GET",
+ callback: function(data) {
+ if(opts.prepend) {
+ opts.$wrapper.prepend(data.message);
+ } else {
+ opts.$wrapper.append(data.message);
+ }
+
+ $btn.toggleClass("hide", !(data.message && data.message.length===opts.args.limit_length));
+ }
+ }, opts))
+ });
+ },
+ bind_add_post: function() {
+ $(".btn-post-add").on("click", website.add_post);
+
+ $pic_input = $(".control-post-add-picture").on("change", website.add_picture);
+ $(".btn-post-add-picture").on("click", function() {
+ $pic_input.click();
+ });
+ },
+ add_post: function() {
+ if(website.post) {
+ wn.msgprint("Post already exists. Cannot add again!");
+ return;
+ }
+
+ website._update_post(this, "webnotes.website.doctype.post.post.add_post");
+ },
+ bind_save_post: function() {
+ $(".btn-post-add").addClass("hide");
+ $(".btn-post-save").removeClass("hide").on("click", website.save_post);
+ $(".post-picture").toggleClass("hide", !$(".post-picture").attr("src"));
+ },
+ save_post: function() {
+ if(!website.post) {
+ wn.msgprint("Post does not exist. Please add post!");
+ return;
+ }
+
+ website._update_post(this, "webnotes.website.doctype.post.post.save_post");
+ },
+ _update_post: function(btn, cmd) {
+ var values = website.get_editor_values();
+ if(!values) {
+ return;
+ }
+
+ wn.call({
+ btn: btn,
+ type: "POST",
+ args: $.extend({
+ cmd: cmd,
+ group: website.group,
+ post: website.post || undefined
+ }, values),
+ callback: function(data) {
+ var url = "/" + website.group + "?view=post&name=" + data.message;
+ window.location.href = url;
+
+ // if(history.pushState) {
+ // app.get_content(url);
+ // } else {
+ // window.location.href = url;
+ // }
+ }
+ });
+ },
+ get_editor_values: function() {
+ var values = {};
+ $.each($('.post-editor [data-fieldname]'), function(i, ele) {
+ var $ele = $(ele);
+ values[$ele.attr("data-fieldname")] = $ele.val();
+ });
+
+ values.parent_post = $(".post-editor").attr("data-parent-post");
+ values.picture_name = $(".control-post-add-picture").val() || null;
+
+ var dataurl = $(".post-picture img").attr("src");
+ values.picture = dataurl ? dataurl.split(",")[1] : ""
+
+ // validations
+ if(!values.parent_post && !values.title) {
+ wn.msgprint("Please enter title!");
+ return;
+ } else if(!values.content) {
+ wn.msgprint("Please enter some text!");
+ return;
+ } else if($('.post-editor [data-fieldname="event_datetime"]').length && !values.event_datetime) {
+ wn.msgprint("Please enter Event's Date and Time!");
+ return;
+ }
+
+ // post process
+ // convert links in content
+ values.content = website.process_external_links(values.content);
+
+ return values;
+ },
+ process_external_links: function(content) {
+ return content.replace(/([^\s]*)(http|https|ftp):\/\/[^\s\[\]\(\)]+/g, function(match, p1) {
+ // mimicing look behind! should not have anything in p1
+ // replace(/match/g)
+ // replace(/(p1)(p2)/g)
+ // so, when there is a character before http://, it shouldn't be replaced!
+ if(p1) return match;
+
+ return "["+match+"]("+match+")";
+ });
+ },
+ add_picture: function() {
+ if (this.type === 'file' && this.files && this.files.length > 0) {
+ $.each(this.files, function (idx, fileobj) {
+ if (/^image\//.test(fileobj.type)) {
+ $.canvasResize(fileobj, {
+ width: 500,
+ height: 0,
+ crop: false,
+ quality: 80,
+ callback: function(data, width, height) {
+ $(".post-picture").removeClass("hide").find("img").attr("src", data);
+ }
+ });
+ }
+ });
+ }
+ return false;
+ },
+ setup_tasks_editor: function() {
+ // assign events
+ var $post_editor = $(".post-editor");
+ var $control_assign = $post_editor.find('.control-assign');
+
+ var bind_close = function() {
+ var close = $post_editor.find("a.close")
+ if(close.length) {
+ close.on("click", function() {
+ // clear assignment
+ $post_editor.find(".assigned-to").addClass("hide");
+ $post_editor.find(".assigned-profile").html("");
+ $post_editor.find('[data-fieldname="assigned_to"]').val(null);
+ $control_assign.val(null);
+ });
+ }
+ }
+
+ if($control_assign.length) {
+ website.setup_autosuggest({
+ $control: $control_assign,
+ select: function(value, item) {
+ var $assigned_to = $post_editor.find(".assigned-to").removeClass("hide");
+ $assigned_to.find(".assigned-profile").html(item.profile_html);
+ $post_editor.find('[data-fieldname="assigned_to"]').val(value);
+ bind_close();
+ },
+ method: "webnotes.website.doctype.post.post.suggest_user"
+ });
+ bind_close();
+ }
+ },
+ setup_event_editor: function() {
+ var $post_editor = $(".post-editor");
+ var $control_event = $post_editor.find('.control-event').empty();
+ var $event_field = $post_editor.find('[data-fieldname="event_datetime"]');
+
+ var set_event = function($control) {
+ var datetime = website.datetimepicker.obj_to_str($control_event.datepicker("getDate"));
+ if($event_field.val() !== datetime) {
+ $event_field.val(datetime);
+ }
+ };
+
+ website.setup_datepicker({
+ $control: $control_event,
+ onClose: function() { set_event($control_event) }
+ });
+
+ if($event_field.val()) {
+ $control_event.val(website.datetimepicker.format_datetime($event_field.val()));
+ }
+ },
+ format_event_timestamps: function() {
+ var format = function(datetime) {
+ if(!datetime) return "";
+ var date = datetime.split(" ")[0].split("-");
+ var time = datetime.split(" ")[1].split(":");
+ var tt = "am";
+ if(time[0] >= 12) {
+ time[0] = parseInt(time[0]) - 12;
+ tt = "pm";
+ }
+ if(!parseInt(time[0])) {
+ time[0] = 12;
+ }
+
+ var hhmm = [time[0], time[1]].join(":")
+
+ return [date[2], date[1], date[0]].join("-") + " " + hhmm + " " + tt;
+ }
+ $(".event-timestamp").each(function() {
+ $(this).html(format($(this).attr("data-timestamp")));
+ })
+ },
+ toggle_earlier_replies: function() {
+ var $earlier_replies = $(".child-post").slice(0, $(".child-post").length - 2);
+ var $btn = $(".btn-earlier-replies").on("click", function() {
+ if($earlier_replies.hasClass("hide")) {
+ $earlier_replies.removeClass("hide");
+ $(".btn-earlier-label").html("Hide");
+ } else {
+ $earlier_replies.addClass("hide");
+ $(".btn-earlier-label").html("Show");
+ }
+ });
+
+ if($earlier_replies.length) {
+ $btn.toggleClass("hide", false).click();
+ }
+ },
+ toggle_edit: function(only_owner) {
+ if(only_owner) {
+ var user = wn.get_cookie("user_id");
+ $(".edit-post").each(function() {
+ $(this).toggleClass("hide", !(website.access.write && $(this).attr("data-owner")===user));
+ });
+ } else {
+ $(".edit-post").toggleClass("hide", !website.access.write);
+ }
+ },
+ toggle_upvote: function() {
+ if(!website.access.read) {
+ $(".upvote").remove();
+ }
+ },
+ toggle_post_editor: function() {
+ $(".post-editor").toggleClass("hide", !website.access.write);
+ },
+ setup_upvote: function() {
+ $(".post-list, .parent-post").on("click", ".upvote a", function() {
+ var sid = wn.get_cookie("sid");
+ if(!sid || sid==="Guest") {
+ wn.msgprint("Please login to Upvote!");
+ return;
+ }
+ var $post = $(this).parents(".post");
+ var post = $post.attr("data-name");
+ var $btn = $(this).prop("disabled", true);
+
+ $.ajax({
+ url: "/",
+ type: "POST",
+ data: {
+ cmd: "webnotes.website.doctype.user_vote.user_vote.set_vote",
+ ref_doctype: "Post",
+ ref_name: post
+ },
+ statusCode: {
+ 200: function(data) {
+ if(data.exc) {
+ console.log(data.exc);
+ } else {
+ var text_class = data.message === "ok" ? "text-success" : "text-danger";
+ if(data.message==="ok") {
+ var count = parseInt($post.find(".upvote-count").text());
+ $post.find(".upvote-count").text(count + 1).removeClass("hide");
+ }
+ $btn.addClass(text_class);
+ setTimeout(function() { $btn.removeClass(text_class); }, 2000);
+ }
+ }
+ }
+ }).always(function() {
+ $btn.prop("disabled", false);
+ });
+ });
+ },
+ setup_autosuggest: function(opts) {
+ if(opts.$control.hasClass("ui-autocomplete-input")) return;
+
+ wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.min.js");
+ wn.require("/assets/webnotes/js/lib/jquery/bootstrap_theme/jquery-ui.selected.css");
+
+ var $user_suggest = opts.$control.autocomplete({
+ source: function(request, response) {
+ $.ajax({
+ url: "/",
+ data: {
+ cmd: opts.method,
+ term: request.term,
+ group: website.group
+ },
+ success: function(data) {
+ if(data.exc) {
+ console.log(data.exc);
+ } else {
+ response(data.message);
+ }
+ }
+ });
+ },
+ select: function(event, ui) {
+ opts.$control.val("");
+ opts.select(ui.item.profile, ui.item);
+ }
+ });
+
+ $user_suggest.data( "ui-autocomplete" )._renderItem = function(ul, item) {
+ return $("").html("" + item.profile_html + " ")
+ .css("padding", "5px")
+ .appendTo(ul);
+ };
+
+ return opts.$control
+ },
+ setup_datepicker: function(opts) {
+ if(opts.$control.hasClass("hasDatetimepicker")) return;
+
+ // libs required for datetime picker
+ wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.min.js");
+ wn.require("/assets/webnotes/js/lib/jquery/bootstrap_theme/jquery-ui.selected.css");
+ wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.slider.min.js");
+ wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.sliderAccess.js");
+ wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.timepicker-addon.css");
+ wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.timepicker-addon.js");
+
+ opts.$control.datetimepicker({
+ timeFormat: "hh:mm tt",
+ dateFormat: 'dd-mm-yy',
+ changeYear: true,
+ yearRange: "-70Y:+10Y",
+ stepMinute: 5,
+ hour: 10,
+ onClose: opts.onClose
+ });
+
+ website.setup_datetime_functions();
+
+ return opts.$control;
+ },
+ setup_datetime_functions: function() {
+ // requires datetime picker
+ wn.provide("website.datetimepicker");
+ website.datetimepicker.str_to_obj = function(datetime_str) {
+ return $.datepicker.parseDateTime("yy-mm-dd", "HH:mm:ss", datetime_str);
+ };
+
+ website.datetimepicker.obj_to_str = function(datetime) {
+ if(!datetime) {
+ return "";
+ }
+ // requires datepicker
+ var date_str = $.datepicker.formatDate("yy-mm-dd", datetime)
+ var time_str = $.datepicker.formatTime("HH:mm:ss", {
+ hour: datetime.getHours(),
+ minute: datetime.getMinutes(),
+ second: datetime.getSeconds()
+ })
+ return date_str + " " + time_str;
+ };
+
+ website.datetimepicker.format_datetime = function(datetime) {
+ if (typeof(datetime)==="string") {
+ datetime = website.datetimepicker.str_to_obj(datetime);
+ }
+ var date_str = $.datepicker.formatDate("dd-mm-yy", datetime)
+ var time_str = $.datepicker.formatTime("hh:mm tt", {
+ hour: datetime.getHours(),
+ minute: datetime.getMinutes(),
+ second: datetime.getSeconds()
+ })
+ return date_str + " " + time_str;
+ }
+ },
+ setup_settings: function() {
+ // autosuggest
+ website.setup_autosuggest({
+ $control: $(".add-user-control"),
+ select: function(value) {
+ website.add_sitemap_permission(value);
+ },
+ method: "webnotes.templates.website_group.settings.suggest_user"
+ });
+
+
+ // trigger for change permission
+ $(".permission-editor-area").on("click", ".sitemap-permission [type='checkbox']",
+ website.update_permission);
+ $(".permission-editor-area").find(".btn-add-group").on("click", website.add_group);
+ $(".btn-settings").parent().addClass("active");
+
+ // disabled public_write if not public_read
+ var control_public_read = $(".control-add-group-public_read").click(function() {
+ if(!$(this).prop("checked")) {
+ $(".control-add-group-public_write").prop("checked", false).prop("disabled", true);
+ } else {
+ $(".control-add-group-public_write").prop("disabled", false);
+ }
+ }).trigger("click").trigger("click"); // hack
+ },
+ add_group: function() {
+ var $control = $(".control-add-group"),
+ $btn = $(".btn-add-group");
+
+ if($control.val()) {
+ $btn.prop("disabled", true);
+ $.ajax({
+ url:"/",
+ type:"POST",
+ data: {
+ cmd:"webnotes.templates.website_group.settings.add_website_group",
+ group: website.group,
+ new_group: $control.val(),
+ group_type: $(".control-add-group-type").val(),
+ public_read: $(".control-add-group-public_read").is(":checked") ? 1 : 0,
+ public_write: $(".control-add-group-public_write").is(":checked") ? 1 : 0
+ },
+ statusCode: {
+ 403: function() {
+ wn.msgprint("Name Not Permitted");
+ },
+ 200: function(data) {
+ if(data.exc) {
+ console.log(data.exc);
+ if(data._server_messages) wn.msgprint(data._server_messages);
+ } else {
+ wn.msgprint("Group Added, refreshing...");
+ setTimeout(function() { window.location.reload(); }, 1000)
+ }
+ }
+ }
+ }).always(function() {
+ $btn.prop("disabled",false);
+ $control.val("");
+ })
+ }
+ },
+ update_permission: function() {
+ var $chk = $(this);
+ var $tr = $chk.parents("tr:first");
+ $chk.prop("disabled", true);
+
+ $.ajax({
+ url: "/",
+ type: "POST",
+ data: {
+ cmd: "webnotes.templates.website_group.settings.update_permission",
+ profile: $tr.attr("data-profile"),
+ perm: $chk.attr("data-perm"),
+ value: $chk.prop("checked") ? "1" : "0",
+ sitemap_page: website.group
+ },
+ statusCode: {
+ 403: function() {
+ wn.msgprint("Not Allowed");
+ },
+ 200: function(data) {
+ $chk.prop("disabled", false);
+ if(data.exc) {
+ $chk.prop("checked", !$chk.prop("checked"));
+ console.log(data.exc);
+ } else {
+ if(!$tr.find(":checked").length) $tr.remove();
+ }
+ }
+ },
+ });
+ },
+ add_sitemap_permission: function(profile) {
+ $.ajax({
+ url: "/",
+ type: "POST",
+ data: {
+ cmd: "webnotes.templates.website_group.settings.add_sitemap_permission",
+ profile: profile,
+ sitemap_page: website.group
+ },
+ success: function(data) {
+ $(".add-user-control").val("");
+ if(data.exc) {
+ console.log(data.exc);
+ } else {
+ $(data.message).prependTo($(".permission-editor tbody"));
+ }
+ }
+ });
+ },
+ update_group_description: function() {
+ $(".btn-update-description").prop("disabled", true);
+ $.ajax({
+ url: "/",
+ type: "POST",
+ data: {
+ cmd: "webnotes.templates.website_group.settings.update_description",
+ description: $(".control-description").val() || "",
+ group: website.group
+ },
+ success: function(data) {
+ window.location.reload();
+ }
+ }).always(function() { $(".btn-update-description").prop("disabled", false); });
+ }
+});
+
+$(document).on("apply_permissions", function() {
+ website.toggle_permitted();
+});
diff --git a/webnotes/webutils.py b/webnotes/webutils.py
index 4c8bd2270c..7a2d40741d 100644
--- a/webnotes/webutils.py
+++ b/webnotes/webutils.py
@@ -13,8 +13,6 @@ from urllib import quote
import mimetypes
from webnotes.website.doctype.website_sitemap.website_sitemap import add_to_sitemap, update_sitemap, remove_sitemap
-
-# for access as webnotes.webutils.fn
from webnotes.website.doctype.website_sitemap_permission.website_sitemap_permission \
import get_access
@@ -73,7 +71,6 @@ def build_json(page_name):
def build_page(page_name):
context = get_context(page_name)
- context.update(get_website_settings())
html = webnotes.get_template(context.base_template_path).render(context)
@@ -149,6 +146,7 @@ def build_context(sitemap_options):
"""get_context method of bean or module is supposed to render content templates and push it into context"""
context = webnotes._dict({ "_": webnotes._ })
context.update(sitemap_options)
+ context.update(get_website_settings())
if sitemap_options.get("controller"):
module = webnotes.get_module(sitemap_options.get("controller"))
@@ -195,7 +193,7 @@ def get_website_settings():
"utils": webnotes.utils,
"post_login": [
{"label": "Reset Password", "url": "update-password", "icon": "icon-key"},
- {"label": "Logout", "url": "/?cmd=web_logout", "icon": "icon-signout"}
+ {"label": "Logout", "url": "?cmd=web_logout", "icon": "icon-signout"}
]
})
@@ -222,6 +220,10 @@ def get_website_settings():
context.web_include_js = hooks.web_include_js or []
context.web_include_css = hooks.web_include_css or []
+ # get settings from site config
+ if webnotes.conf.get("fb_app_id"):
+ context.fb_app_id = webnotes.conf.fb_app_id
+
return context
def is_ajax():
@@ -342,7 +344,9 @@ class WebsiteGenerator(DocListController):
"page_name": page_name,
"link_name": self._website_config.name,
"lastmod": webnotes.utils.get_datetime(self.doc.modified).strftime("%Y-%m-%d"),
- "parent_website_sitemap": self.doc.parent_website_sitemap
+ "parent_website_sitemap": self.doc.parent_website_sitemap,
+ "page_title": self.get_page_title() \
+ if hasattr(self, "get_page_title") else (self.doc.title or self.doc.name)
})
if self.meta.get_field("public_read"):