Commit edd3ccf4 authored by Gabriel Monnerat's avatar Gabriel Monnerat Committed by Georgios Dagkakis

WebSection: Fix RJS website rendering when trailing / in the URL is missing

renderJS gadget HTML uses relative URLs to link to there javascript/CSS files.
Absolute URL calculation is resolved dynamically by the browser when loading the gadget.

When accessing an ERP5 web section without a trailing / in the URL, the browser will calculate absolute URL from the parent document and not the web site itself.

Example:
In http://foo.com/web_site_module/bar , the relative URL couscous.js will be resolved http://foo.com/web_site_module/couscous.js
If couscous.js is a document from DMS (Web Page for example), such URL can not be resolved and leads to a 404 error.

One solution to solve this is to redirect (302) the browser when accessing a Web Section (Web Site is a web section) directly in ERP5.
Example:
http://foo.com/web_site_module/bar ->  http://foo.com/web_site_module/bar/
But  http://foo.com/web_site_module/bar/view should not redirect
parent 5e4668ea
......@@ -42,34 +42,34 @@ def getProtectedProperty(document, select):
url_template_dict = {
"form_action": "%(traversed_document_url)s/%(action_id)s",
"traverse_generator": "%(root_url)s/%(script_id)s?mode=traverse" + \
"traverse_generator": "%(root_url)s%(script_id)s?mode=traverse" + \
"&relative_url=%(relative_url)s&view=%(view)s",
"traverse_template": "%(root_url)s/%(script_id)s?mode=traverse" + \
"traverse_template": "%(root_url)s%(script_id)s?mode=traverse" + \
"{&relative_url,view}",
"search_template": "%(root_url)s/%(script_id)s?mode=search" + \
"search_template": "%(root_url)s%(script_id)s?mode=search" + \
"{&query,select_list*,limit*,sort_on*,local_roles*}",
"worklist_template": "%(root_url)s/%(script_id)s?mode=worklist",
"custom_search_template": "%(root_url)s/%(script_id)s?mode=search" + \
"worklist_template": "%(root_url)s%(script_id)s?mode=worklist",
"custom_search_template": "%(root_url)s%(script_id)s?mode=search" + \
"&relative_url=%(relative_url)s" \
"&form_relative_url=%(form_relative_url)s" \
"&list_method=%(list_method)s" \
"&default_param_json=%(default_param_json)s" \
"{&query,select_list*,limit*,sort_on*,local_roles*}",
"custom_search_template_no_editable": "%(root_url)s/%(script_id)s?mode=search" + \
"custom_search_template_no_editable": "%(root_url)s%(script_id)s?mode=search" + \
"&relative_url=%(relative_url)s" \
"&list_method=%(list_method)s" \
"&default_param_json=%(default_param_json)s" \
"{&query,select_list*,limit*,sort_on*,local_roles*}",
"new_content_action": "%(root_url)s/%(script_id)s?mode=newContent",
"bulk_action": "%(root_url)s/%(script_id)s?mode=bulk",
"new_content_action": "%(root_url)s%(script_id)s?mode=newContent",
"bulk_action": "%(root_url)s%(script_id)s?mode=bulk",
# XXX View is set by default to empty
"document_hal": "%(root_url)s/%(script_id)s?mode=traverse" + \
"document_hal": "%(root_url)s%(script_id)s?mode=traverse" + \
"&relative_url=%(relative_url)s",
"jio_get_template": "urn:jio:get:%(relative_url)s",
"jio_search_template": "urn:jio:allDocs?%(query)s",
# XXX Hardcoded sub websection
"login_template": "%(root_url)s/%(login)s",
"logout_template": "%(root_url)s/%(logout)s"
"login_template": "%(root_url)s%(login)s",
"logout_template": "%(root_url)s%(logout)s"
}
default_document_uri_template = url_template_dict["jio_get_template"]
......@@ -338,7 +338,7 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
if (editable_column_list):
list_method_custom = url_template_dict["custom_search_template"] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"script_id": script.id,
"relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"),
"form_relative_url": "%s/%s" % (getFormRelativeUrl(form), field.id),
......@@ -352,7 +352,7 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
list_method_query_dict["parent_uid"] = traversed_document.getUid()
else:
list_method_custom = url_template_dict["custom_search_template_no_editable"] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"script_id": script.id,
"relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"),
"list_method": list_method_name,
......@@ -367,7 +367,7 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
# document = row.getObject()
# line = {
# "url": url_template_dict["document_hal"] % {
# "root_url": site_root.absolute_url(),
# "root_url": site_root_absolute_url,
# "relative_url": document.getRelativeUrl(),
# "script_id": script.id
# }
......@@ -456,6 +456,7 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
action_to_call = "Base_callDialogMethod"
else:
action_to_call = form.action
if (action_to_call == 'Base_edit') and (not portal.portal_membership.checkPermission('Modify portal content', traversed_document)):
# prevent allowing editing if user doesn't have permission
include_action = False
......@@ -465,7 +466,7 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
response_dict['_actions'] = {
'put': {
"href": url_template_dict["form_action"] % {
"traversed_document_url": site_root.absolute_url() + "/" + traversed_document.getRelativeUrl(),
"traversed_document_url": site_root.absolute_url() + traversed_document.getRelativeUrl(),
"action_id": action_to_call
},
"action": form.action,
......@@ -475,7 +476,7 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
# Form traversed_document
response_dict['_links']['traversed_document'] = {
"href": default_document_uri_template % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"relative_url": traversed_document.getRelativeUrl(),
"script_id": script.id
},
......@@ -618,6 +619,10 @@ else:
site_root = portal
view_action_type = "object_view"
site_root_absolute_url = site_root.absolute_url()
if not site_root_absolute_url.endswith("/"):
site_root_absolute_url += "/"
context.Base_prepareCorsResponse(RESPONSE=response)
# Check if traversed_document is the site_root
......@@ -656,7 +661,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# Always inform about site root
"site_root": {
"href": default_document_uri_template % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"relative_url": site_root.getRelativeUrl(),
"script_id": script.id
},
......@@ -683,7 +688,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
response.setHeader(
'WWW-Authenticate',
'X-Delegate uri="%s"' % (url_template_dict["login_template"] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"login": login_relative_url
})
)
......@@ -711,7 +716,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
if not is_portal:
result_dict['_links']['type'] = {
"href": default_document_uri_template % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"relative_url": portal.portal_types[traversed_document.getPortalType()]\
.getRelativeUrl(),
"script_id": script.id
......@@ -726,7 +731,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# Jio does not support fetching the root document for now
result_dict['_links']['parent'] = {
"href": default_document_uri_template % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"relative_url": container.getRelativeUrl(),
"script_id": script.id
},
......@@ -755,7 +760,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
if erp5_action_key in (view_action_type, "view", "workflow", "object_new_content_action", "object_clone_action", "object_delete_action", "object_report_jio", "object_exchange_jio", "object_fast_input_jio", "object_search_jio", "object_action_jio"):
erp5_action_list[-1]['href'] = url_template_dict["traverse_generator"] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"script_id": script.id,
"relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"),
"view": erp5_action_list[-1]['name']
......@@ -884,7 +889,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# }
result_dict['_links']['raw_search'] = {
"href": url_template_dict["search_template"] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"script_id": script.id
},
'name': 'Raw Search',
......@@ -892,7 +897,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
}
result_dict['_links']['traverse'] = {
"href": url_template_dict["traverse_template"] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"script_id": script.id
},
'name': 'Traverse',
......@@ -900,7 +905,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
}
action_dict['add'] = {
"href": url_template_dict["new_content_action"] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"script_id": script.id
},
'method': 'POST',
......@@ -908,7 +913,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
}
action_dict['bulk'] = {
"href": url_template_dict["bulk_action"] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"script_id": script.id
},
'method': 'POST',
......@@ -920,7 +925,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
if person is not None and portal.portal_membership.checkPermission('View', person):
result_dict['_links']['me'] = {
"href": default_document_uri_template % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"relative_url": person.getRelativeUrl(),
"script_id": script.id
},
......@@ -938,7 +943,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
elif relative_url == 'portal_workflow':
result_dict['_links']['action_worklist'] = {
"href": url_template_dict['worklist_template'] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"script_id": script.id
}
}
......@@ -947,7 +952,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
if (logout_relative_url):
result_dict['_links']['logout'] = {
"href": url_template_dict['logout_template'] % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"logout": logout_relative_url,
"template": True
}
......@@ -1051,7 +1056,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
'_links': {
'self': {
"href": default_document_uri_template % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
# XXX ERP5 Site is not an ERP5 document
"relative_url": getRealRelativeUrl(document) or document.getId(),
"script_id": script.id
......@@ -1125,7 +1130,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
response.setStatus(201)
response.setHeader("X-Location",
default_document_uri_template % {
"root_url": site_root.absolute_url(),
"root_url": site_root_absolute_url,
"relative_url": document.getRelativeUrl(),
"script_id": script.id
})
......
......@@ -226,6 +226,14 @@ class WebSection(Domain, DocumentExtensibleTraversableMixin):
if self.REQUEST.get(self.web_section_key, MARKER) is MARKER:
self.REQUEST[self.web_section_key] = self.getPhysicalPath()
self.REQUEST.set('current_web_section', self)
actual_url = self.REQUEST.get("ACTUAL_URL", "").strip()
if actual_url and actual_url in actual_url and not actual_url.endswith("/"):
query_string = self.REQUEST.get("QUERY_STRING", "")
query_str = "?%s" % query_string if query_string else query_string
return self.REQUEST.RESPONSE.redirect(
"".join([actual_url, "/", query_str]))
if not self.REQUEST.get('editable_mode') and not self.REQUEST.get('ignore_layout'):
document = None
if self.isDefaultPageDisplayed():
......@@ -403,6 +411,23 @@ class WebSection(Domain, DocumentExtensibleTraversableMixin):
return result
def _add_trailing_slash(self, path):
path += "" if path.endswith("/") else "/"
return path
def absolute_url_path(self):
absolute_url_path = self.absolute_url(relative=1)
if not absolute_url_path.startswith("/"):
absolute_url_path = "/" + absolute_url_path
return absolute_url_path
def absolute_url(self, relative=0):
"""
Return absolute_url with / in the end to avoid redirections when
accessing Web sections. Please check the method WebSection.__call__
"""
return self._add_trailing_slash(Domain.absolute_url(self, relative=relative))
security.declareProtected(Permissions.View, 'getSiteMapTree')
def getSiteMapTree(self, **kw):
"""
......
......@@ -236,6 +236,60 @@ class TestERP5Web(ERP5TypeTestCase):
return webpage_list
def test_WebSection_add_trailing_slash_in_url(self):
"""
When accessing an ERP5 web section without a trailing / in the URL, the
browser will calculate absolute URL from the parent document and not the web
site itself.
Example:
In http://foo.com/web_site_module/bar , the relative URL couscous.js will
be resolved http://foo.com/web_site_module/couscous.js If couscous.js is a
document from DMS (Web Page for example), such URL can not be resolved and
leads to a 404 error.
One solution to solve this is to redirect (302) the browser when accessing a
Web Section (Web Site is a web section) directly in ERP5.
Example:
http://foo.com/web_site_module/bar -> http://foo.com/web_site_module/bar/
But http://foo.com/web_site_module/bar/view should not redirect
"""
# Web Site as context
website = self.setupWebSite()
response = self.publish(website.absolute_url_path()[:-1])
self.assertEqual(MOVED_TEMPORARILY, response.status)
response = self.publish(
"%s?ignore_layout:int=1" % website.absolute_url_path()[:-1])
self.assertEqual("%s?ignore_layout:int=1" % website.absolute_url(),
response.headers.get("location"))
self.assertEqual(MOVED_TEMPORARILY, response.status)
response = self.publish(
"%s/getTitle?ignore_layout:int=1" % website.absolute_url_path())
self.assertEqual(HTTP_OK, response.status)
self.assertEqual("test", response.body)
response = self.publish(
"%s/getTitle" % website.absolute_url_path())
self.assertEqual(HTTP_OK, response.status)
self.assertEqual("test", response.body)
response = self.publish(
"%s/a_non_existing_page" % website.absolute_url_path())
self.assertEqual(404, response.status)
# Web Section as context
websection = self.setupWebSection()
response = self.publish(
"%s?ignore_layout:int=1" % websection.absolute_url_path()[:-1])
self.assertEqual("%s?ignore_layout:int=1" % websection.absolute_url(),
response.headers.get("location"))
self.assertEqual(MOVED_TEMPORARILY, response.status)
response = self.publish(
"%s/getTitle?ignore_layout:int=1" % websection.absolute_url_path())
self.assertEqual(HTTP_OK, response.status)
self.assertEqual("1", response.body)
response = self.publish(
"%s/getTitle" % websection.absolute_url_path())
self.assertEqual(HTTP_OK, response.status)
self.assertEqual("1", response.body)
def test_01_WebSiteRecatalog(self):
"""
Test that a recataloging works for Web Site documents
......@@ -440,11 +494,12 @@ Hé Hé Hé!""", page.asText().strip())
self.assertEqual(web_page_en, websection.getDefaultDocumentValue())
# and make sure that the base meta tag which is generated
# uses the web section rather than the portal
self.REQUEST.set("ACTUAL_URL", websection.absolute_url())
html_page = websection()
from Products.ERP5.Document.Document import Document
base_list = re.findall(Document.base_parser, str(html_page))
base_url = base_list[0]
self.assertEqual(base_url, "%s/%s/" % (websection.absolute_url(),
self.assertEqual(base_url, "%s%s/" % (websection.absolute_url(),
web_page_en.getReference()))
def test_06b_DefaultDocumentForWebSite(self):
......@@ -477,11 +532,12 @@ Hé Hé Hé!""", page.asText().strip())
self.assertEqual(web_page_en, website.getDefaultDocumentValue())
# and make sure that the base meta tag which is generated
# uses the web site rather than the portal
self.REQUEST.set("ACTUAL_URL", website.absolute_url())
html_page = website()
from Products.ERP5.Document.Document import Document
base_list = re.findall(Document.base_parser, str(html_page))
base_url = base_list[0]
self.assertEqual(base_url, "%s/%s/" % (website.absolute_url(), web_page_en.getReference()))
self.assertEqual(base_url, "%s%s/" % (website.absolute_url(), web_page_en.getReference()))
def test_07_getDocumentValueList(self):
""" Check getting getDocumentValueList from Web Section.
......@@ -868,7 +924,7 @@ Hé Hé Hé!""", page.asText().strip())
website_relative_url = website.absolute_url(relative=1)
website_fr = self.portal.restrictedTraverse(
'web_site_module/%s/fr' % website_id)
website_relative_url_fr = '%s/fr' % website_relative_url
website_relative_url_fr = '%sfr/' % website_relative_url
websection_id = self.setupWebSection().getId()
websection = self.portal.restrictedTraverse(
......@@ -876,7 +932,7 @@ Hé Hé Hé!""", page.asText().strip())
websection_relative_url = websection.absolute_url(relative=1)
websection_fr = self.portal.restrictedTraverse(
'web_site_module/%s/fr/%s' % (website_id, websection_id))
websection_relative_url_fr = '%s/%s' % (website_relative_url_fr,
websection_relative_url_fr = '%s%s/' % (website_relative_url_fr,
websection.getId())
page_ref = 'foo'
......
......@@ -395,6 +395,7 @@ class TestERP5WebWithDms(ERP5TypeTestCase, ZopeTestCase.Functional):
# check Unauthorized exception is raised for anonymous when authorization_forced is set
self.logout()
self.REQUEST.set("ACTUAL_URL", websection.absolute_url())
self.assertEqual(None, websection.getDefaultDocumentValue())
self.assertRaises(Unauthorized, websection)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment