diff --git a/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSection_getPermanentURL.py b/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSection_getPermanentURL.py index a495b1122eae6dfde2d4b1ff31ee0ae244c6346c..8f7c0f52a16b522948db1cd3341d8df35eb4b2a9 100644 --- a/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSection_getPermanentURL.py +++ b/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSection_getPermanentURL.py @@ -60,4 +60,6 @@ if document.getValidationState() not in validation_state: web_section = context.getWebSectionValue() if web_section is None: web_section = context -return "%s/%s" % (web_section.absolute_url(), reference) +return web_section.constructUrlFor( + document_reference=reference +) diff --git a/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSite_login.zpt b/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSite_login.zpt index bc95dceda2146f859cb5fac3757fae6ad7190822..502e54ef1a7da4cde08ca321349c023419475496 100644 --- a/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSite_login.zpt +++ b/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSite_login.zpt @@ -10,8 +10,9 @@ came_from_valid python: not came_from or url_topmost_document.isURLAncestorOf(came_from);"> <tal:block tal:condition="isAnon"> <tal:block tal:define="dummy python: response.expireCookie('__ac', path='/'); - url python: '%s/login_form?portal_status_message=%s' % (here.absolute_url(), here.Base_translateString('Login and/or password is incorrect.')); - url python: came_from and '%s&came_from=%s' % (url, came_from) or url; + parameter_dict python: {'portal_status_message': here.Base_translateString('Login and/or password is incorrect.')}; + parameter_dict python: parameter_dict.update({'came_from': came_from}) if came_from else parameter_dict; + url python: here.constructUrlFor(form_id='login_form', parameter_dict=parameter_dict); dummy python: response.redirect(url);" /> </tal:block> <tal:block tal:condition="not: isAnon"> @@ -19,8 +20,8 @@ <tal:block tal:define="dummy python: response.redirect(came_from or here.getPermanentURL(here));" /> </tal:block> <tal:block tal:condition="not: came_from_valid"> - <tal:block tal:define="dummy python: response.redirect('%s?portal_status_message=%s' % (url_topmost_document.absolute_url(), here.Base_translateString('Redirection to an external site prevented.')));" /> + <tal:block tal:define="dummy python: response.redirect(url_topmost_document.constructUrlFor(parameter_dict={'portal_status_message': here.Base_translateString('Redirection to an external site prevented.')}))" /> </tal:block> </tal:block> </tal:block> -</tal:block> \ No newline at end of file +</tal:block> diff --git a/bt5/erp5_web_download_theme/SkinTemplateItem/portal_skins/erp5_web_download_theme/WebSection_getPermanentURL.py b/bt5/erp5_web_download_theme/SkinTemplateItem/portal_skins/erp5_web_download_theme/WebSection_getPermanentURL.py index ac2302c716b95941414eae4a75ce73c51fdd8150..aeb2a2cfdde24b04569910c1faba81de6d4580b2 100644 --- a/bt5/erp5_web_download_theme/SkinTemplateItem/portal_skins/erp5_web_download_theme/WebSection_getPermanentURL.py +++ b/bt5/erp5_web_download_theme/SkinTemplateItem/portal_skins/erp5_web_download_theme/WebSection_getPermanentURL.py @@ -4,6 +4,6 @@ """ if document.hasReference(): file_name = document.Document_getStandardFileName() - return "%s/%s" % (context.absolute_url(), file_name) + return context.constructUrlFor(document_reference=file_name) else: - return "%s%s" % (document.getAbsoluteUrl(),view and '/view' or '') + return context.constructUrlFor(form_id='view' if view else None) diff --git a/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/WebSection_renderDefaultPageAsGadget.py b/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/WebSection_renderDefaultPageAsGadget.py index 73564d89c2c084a553c9d199d596fee53caaa975..14cecdf05a9069a5674f6426524db03396edf4f5 100644 --- a/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/WebSection_renderDefaultPageAsGadget.py +++ b/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/WebSection_renderDefaultPageAsGadget.py @@ -15,16 +15,17 @@ default_language = web_section.getLayoutProperty("default_available_language", d website_url_set = {} #simplify code of Base_doLanguage, can't ues Base_doLanguage directly -root_website_url = web_section.getOriginalDocument().absolute_url() +root_website_url = web_section.getOriginalDocument().constructUrlFor() website_url_pattern = r'^%s(?:%s)*(/|$)' % ( re.escape(root_website_url), '|'.join('/' + re.escape(x) for x in available_language_set)) for language in available_language_set: + web_section_url = web_section.constructUrlFor() if language == default_language: - website_url_set[language] = re.sub(website_url_pattern, r'%s/\1' % root_website_url, web_section.absolute_url()) + website_url_set[language] = re.sub(website_url_pattern, r'%s/\1' % root_website_url, web_section_url) else: - website_url_set[language]= re.sub(website_url_pattern, r'%s/%s/\1' % (root_website_url, language), web_section.absolute_url()) + website_url_set[language]= re.sub(website_url_pattern, r'%s/%s/\1' % (root_website_url, language), web_section_url) view_as_web_method = default_web_page.getTypeBasedMethod( "viewAsWeb", diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_callDialogMethod.py b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_callDialogMethod.py index d2e6fceceac04d20820666e7ee462ab82e8148ba..4ccc79183cce2a4f5e03bab87303596679a5d81b 100644 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_callDialogMethod.py +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_callDialogMethod.py @@ -192,13 +192,11 @@ if dialog_method != update_method and clean_kw.get('deferred_style', 0): request.set('deferred_style_dialog_method', dialog_method) dialog_method = 'Base_activateSimpleView' -url_params_string = make_query(clean_kw) - # XXX: We always redirect in report mode to make sure portal_skin # parameter is taken into account by SkinTool. # If url is too long, we do not redirect to avoid crash. # XXX: 2000 is an arbitrary value resulted from trial and error. -if (not(can_redirect) or len(url_params_string) > 2000): +if (not(can_redirect) or len(make_query(clean_kw)) > 2000): if dialog_method != update_method: # When we are not executing the update action, we have to change the skin # manually, @@ -226,22 +224,11 @@ if (not(can_redirect) or len(url_params_string) > 2000): request.set('URL', '%s/%s' % (context.absolute_url(), dialog_method)) return dialog_form(**kw) -if error_message != '': - redirect_url = '%s/%s?%s' % ( context.absolute_url() - , dialog_method - , 'portal_status_message=%s' % error_message - ) -elif url_params_string != '': - redirect_url = '%s/%s?%s' % ( context.absolute_url() - , dialog_method - , url_params_string - ) - -else: - redirect_url = '%s/%s' % ( context.absolute_url() - , dialog_method - ) - +redirect_url = context.constructUrlFor( + form_id=dialog_method, + parameter_dict={'portal_status_message': error_message} if error_message != '' \ + else clean_kw + ) return request.RESPONSE.redirect(redirect_url) # vim: syntax=python diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.py b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.py index 31e780a8b8b7873ac00b83efb6c998cfae6cc47c..fe58272974fd8f03e561486b0771bb3be0a3df0e 100644 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.py +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.py @@ -25,9 +25,16 @@ if dialog_id not in ('', None): # Prevent users who don't have rights to edit the object from # editing it by calling the Base_edit script with correct # parameters directly. + if not silent_mode and not request.AUTHENTICATED_USER.has_permission('Modify portal content', context) : - msg = Base_translateString("You do not have the permissions to edit the object.") - redirect_url = '%s/%s?selection_index=%s&selection_name=%s&%s' % (context.absolute_url(), form_id, selection_index, selection_name, 'portal_status_message=%s' % msg) + redirect_url = context.constructUrlFor( + form_id=form_id, + parameter_dict={ + 'selection_index': selection_index, + 'selection_name': selection_name, + 'portal_status_message': Base_translateString("You do not have the permissions to edit the object.") + } + ) return request['RESPONSE'].redirect(redirect_url) # Get the form @@ -69,7 +76,7 @@ def editListBox(listbox_field, listbox): if hasattr(value, 'edit'): encapsulated_editor_list.append(value) else: - if value == '': + if value == '': value = None cleaned_v[key] = value @@ -232,7 +239,7 @@ try: elif(field_meta_type == 'MatrixBox'): editMatrixBox(field, request.get(field.id)) - # Return parsed values + # Return parsed values if silent_mode: return (kw, encapsulated_editor_list), 'edit' # Maybe we should build a list of objects we need @@ -248,12 +255,6 @@ if message_only: ignore_layout = int(ignore_layout) editable_mode = int(editable_mode) -spp = context.getPhysicalPath() -spp =list(spp) -s_url = request["SERVER_URL"] -spp.insert(0,s_url) -#calculate direct the url instead of using absolute_url -new_url = '/'.join(spp) # for web mode, we should use 'view' instead of passed form_id # after 'Save & View'. @@ -262,26 +263,26 @@ if context.REQUEST.get('is_web_mode', False) and \ form_id = 'view' if not selection_index: - redirect_url = '%s/%s?ignore_layout:int=%s&editable_mode:int=%s&portal_status_message=%s' % ( - context.absolute_url(), - form_id, - ignore_layout, - editable_mode, - message) - - + redirect_url = context.constructUrlFor( + form_id=form_id, + parameter_dict={ + 'ignore_layout': ignore_layout, + 'editable_mode': editable_mode, + 'portal_status_message': message, + } + ) else: - redirect_url = '%s/%s?selection_index=%s&selection_name=%s&ignore_layout:int=%s&editable_mode=%s&portal_status_message=%s' % ( - context.absolute_url(), - form_id, - selection_index, - selection_name, - ignore_layout, - editable_mode, - message) - - -result = request['RESPONSE'].redirect(redirect_url) + redirect_url = context.constructUrlFor( + form_id=form_id, + parameter_dict={ + 'selection_index': selection_index, + 'selection_name': selection_name, + 'ignore_layout': ignore_layout, + 'editable_mode': editable_mode, + 'portal_status_message': message, + } + ) +result = request['RESPONSE'].redirect(redirect_url) if silent_mode: return result, 'redirect' return result diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_doLanguage.py b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_doLanguage.py index d32ea23eebe465c5d489d98a7fa09e0aa138c6d4..cec867fc9154027397be319a39f3e42f1121a097 100644 --- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_doLanguage.py +++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_doLanguage.py @@ -9,7 +9,7 @@ if website is not None and website.isStaticLanguageSelection(): # Web Mode root_website = website.getOriginalDocument() default_language = root_website.getDefaultAvailableLanguage() - root_website_url = root_website.absolute_url() + root_website_url = root_website.constructUrlFor() website_url_pattern = r'^%s(?:%s)*(/|$)' % ( re.escape(root_website_url), '|'.join('/' + re.escape(x) for x in root_website.getAvailableLanguageList())) @@ -24,7 +24,7 @@ if website is not None and website.isStaticLanguageSelection(): if select_language == default_language: redirect_url = root_website_url else: - redirect_url = root_website_url + '/' + select_language + redirect_url = root_website.constructUrlFor(document_reference=select_language) return context.REQUEST.RESPONSE.redirect(redirect_url) else: # ERP5 Mode diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_redirect.py b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_redirect.py index a8d084ffc52144768532dfe2d48c3a69ed98feaa..2878093b694033a92dfb86dceab154efef51a29f 100644 --- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_redirect.py +++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_redirect.py @@ -3,30 +3,37 @@ page from a script. It should probably be extended, reviewed and documented so that less code is copied and pasted in dialog scripts. - TODO: improve API and extensively document. ERP5Site_redirect may + TODO: improve API and extensively document. ERP5Site_redirect may be redundant. """ -# BBB: originally, form_id was the first positional argument -if not redirect_url or '/' not in redirect_url: - form_id = redirect_url or kw.pop('form_id', None) - redirect_url = context.absolute_url() - if form_id: - redirect_url += '/' + form_id - from ZTUtils import make_query + request = context.getPortalObject().REQUEST request_form = request.form request_form.update(kw) request_form = context.ERP5Site_filterParameterList(request_form) request_form.update(keep_items) +parameter_dict = dict([(k, v) for k, v in request_form.items() if k and v is not None]) -parameters = make_query(dict([(k, v) for k, v in request_form.items() if k and v is not None])) -if len(parameters): - if '?' in redirect_url: - separator = '&' - else: - separator = '?' - redirect_url = '%s%s%s' % (redirect_url, separator, parameters) +if not redirect_url or '/' not in redirect_url: + # Used when redirect_url is not given explicitely and we redirect to an ERP5 document + # BBB: originally, form_id was the first positional argument + form_id = redirect_url or kw.pop('form_id', None) + redirect_url = context.constructUrlFor( + form_id=form_id, + parameter_dict=parameter_dict, + ) +else: + # Used if redirect_url is given explicitely (e.g. if we redirect to external web page) + # so constructUrlFor is not usable, + # since we have no document to redirect using is as context. + parameters = make_query(parameter_dict) + if len(parameters): + if '?' in redirect_url: + separator = '&' + else: + separator = '?' + redirect_url = '%s%s%s' % (redirect_url, separator, parameters) if abort_transaction: from zExceptions import Redirect diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/global_definitions.zpt b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/global_definitions.zpt index 4a14ba9ef826ede2de4a2847bb7733320ca9f973..a4c9e8efbbc119b89adf8971c580f3dee179b328 100644 --- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/global_definitions.zpt +++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/global_definitions.zpt @@ -9,7 +9,7 @@ local_parameter_list local_parameter_list | python: {}; action_context python: portal.restrictedTraverse(request.get('object_path', '?'), here); global actions python: here.Base_filterDuplicateActions(portal.portal_actions.listFilteredActionsFor(action_context)); - global url here/absolute_url; + global url python: here.constructUrlFor(); global current_form_id python: local_parameter_list.get('dialog_id', local_parameter_list.get('form_id', 'view')); dummy python: request.set('current_form_id', current_form_id); global current_url python: '%s/%s' % (url, current_form_id); diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/logged_in.py b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/logged_in.py index c5ed70e2b238253de48dfa4cf46e3811169786bc..8dbc9410f0a11b46a26f76676226fdfd382f26e6 100644 --- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/logged_in.py +++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/logged_in.py @@ -1,17 +1,22 @@ portal = context.getPortalObject() if portal.portal_skins.updateSkinCookie(): portal.setupCurrentSkin() -url = REQUEST.get("came_from") +came_from = REQUEST.get("came_from") if portal.portal_membership.isAnonymousUser(): RESPONSE.expireCookie("__ac", path="/") - url = "%s/login_form?portal_status_message=%s" % ( - context.absolute_url(), - context.Base_translateString("Login and/or password is incorrect.") - + ("&came_from=" + url if url else "")) -elif not url: - url = context.absolute_url() + parameter_dict = {'portal_status_message': context.Base_translateString("Login and/or password is incorrect.")} + if came_from: + parameter_dict['came_from'] = came_from + url = context.constructUrlFor( + form_id='login_form', + parameter_dict=parameter_dict + ) +elif came_from: + url = came_from +else: + url = context.constructUrlFor() topmost_url_document = context.Base_getURLTopmostDocumentValue() if not topmost_url_document.isURLAncestorOf(url): - return context.ERP5Site_redirect(topmost_url_document.absolute_url(), + return context.ERP5Site_redirect(topmost_url_document.constructUrlFor(), keep_items={'portal_status_message': 'Redirection to an external site prevented.'}) return RESPONSE.redirect(url) diff --git a/product/ERP5Type/Core/Folder.py b/product/ERP5Type/Core/Folder.py index f8b064c5a5c6b8a40d1c55e359d9b93a0958c8e0..933b3ef6411ce76be01f8d7556e9ac74d3b3a3a0 100644 --- a/product/ERP5Type/Core/Folder.py +++ b/product/ERP5Type/Core/Folder.py @@ -74,6 +74,7 @@ import warnings from urlparse import urlparse REINDEX_SPLIT_COUNT = 100 # if folder containes more than this, reindexing should be splitted. from Products.ERP5Type.Message import translateString +from ZTUtils import make_query # Dummy Functions for update / upgrade def dummyFilter(object,REQUEST=None): @@ -496,6 +497,48 @@ class FolderMixIn(ExtensionClass.Base): raise AccessControl_Unauthorized(method_id) return self._recurseCallMethod(method_id, restricted=True, *args, **kw) + security.declarePublic('constructUrlFor') + def constructUrlFor( + self, + form_id=None, + document_reference=None, + parameter_dict=None, + **kw + ): + ''' + Utility method to help in the construction of urls. + It should be run in the context of a document that we want as base for the url. + Arguments: + - form_id: the form that is invoked + - document_reference: the reference of a document that is rendered, + - parameter_dict: dictionary containing all get parameters + Other parameters can be used for type based methods. + ''' + # Try to get type based method, but not for ERP5 Site, since + # _getTypeBasedMethod is defined in Base so not available for ERP5 Site + if self.getPortalType() != 'ERP5 Site': + method = self._getTypeBasedMethod('constructUrlFor') + if method is not None: + return method( + form_id=form_id, + document_reference=document_reference, + parameter_dict=parameter_dict, + **kw + ) + url = self.absolute_url() + # form_id and document_reference are mutually exclusive, + # we should not expect both in a url + assert not (form_id and document_reference), 'Not allowed to have both form and document in the same url' + # Note that form_id and document_reference are handled the same way, + # it is different variable for semantic reasons + if form_id: + url = '%s/%s' % (url, form_id) + elif document_reference: + url = '%s/%s' % (url, document_reference) + if parameter_dict: + url = '%s?%s' % (url, make_query(parameter_dict)) + return url + security.declarePublic('isURLAncestorOf') def isURLAncestorOf(self, given_url): """ @@ -506,7 +549,7 @@ class FolderMixIn(ExtensionClass.Base): not access any document in given path, hence it does not compute the inner acquisition path. """ - document_url = self.absolute_url() + document_url = self.constructUrlFor() parsed_given_url = urlparse(given_url) parsed_document_url = urlparse(document_url) # XXX note that the following check: diff --git a/product/ERP5Type/tests/testERP5Type.py b/product/ERP5Type/tests/testERP5Type.py index c3eeaf7048c1cdbecd751ce382039dbe04680f46..6da23c5f45ffd57dcfe5bb7eeeffe0cfb001873b 100644 --- a/product/ERP5Type/tests/testERP5Type.py +++ b/product/ERP5Type/tests/testERP5Type.py @@ -3244,6 +3244,54 @@ return [ # but this did not affect the other role self.assertTrue(hasRole(role2)) + def test_constructUrlFor(self): + portal_url = self.portal.absolute_url() + # check url built for portal + portal_url = self.portal.absolute_url() + self.assertEqual(self.portal.constructUrlFor(), '%s' % portal_url) + # check urls built for a module + module_id = 'test_module' + module = self.portal.newContent(portal_type='Folder', id=module_id) + module_url = module.absolute_url() + self.assertEqual( + module.constructUrlFor(), + '%s/%s' % (portal_url, module_id) + ) + # check url built for a form + form_id = 'view' + self.assertEqual( + module.constructUrlFor(form_id=form_id), + '%s/%s' % (module_url, form_id) + ) + # check url built for a document reference + file_reference = 'test_file_reference' + self.portal.test_module.newContent( + portal_type='File', + reference=file_reference + ) + self.assertEqual( + module.constructUrlFor(document_reference=file_reference), + '%s/%s' % (module_url, file_reference) + ) + # check that form_id and document_reference are exclusive + # check url built with GET parameters + self.assertRaises( + AssertionError, + module.constructUrlFor, + form_id=form_id, + document_reference=file_reference + ) + parameter_dict = {'ignore_layout': 1, 'came_from': 'foo/bar'} + self.assertIn( + module.constructUrlFor( + parameter_dict=parameter_dict, + ), + ( + '%s/%s?came_from=foo/bar&ignore_layout:int=1' % (portal_url, module_id), + '%s/%s?ignore_layout:int=1&came_from=foo/bar' % (portal_url, module_id), + ) + ) + class TestAccessControl(ERP5TypeTestCase): # Isolate test in a dedicaced class in order not to break other tests # when this one fails.