--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/redirect/__init__.py Thu Jul 19 16:15:30 2018 +0200
@@ -0,0 +1,88 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import re
+
+# import interfaces
+from pyams_content.features.redirect.interfaces import IRedirectionRule
+from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION
+from pyams_form.interfaces.form import IFormContextPermissionChecker
+
+# import packages
+from persistent import Persistent
+from pyams_sequence.reference import get_reference_target
+from pyams_utils.adapter import adapter_config, ContextAdapter
+from pyams_utils.url import canonical_url
+from pyams_utils.zodb import volatile_property
+from zope.container.contained import Contained
+from zope.interface import implementer
+from zope.schema.fieldproperty import FieldProperty
+
+
+@implementer(IRedirectionRule)
+class RedirectionRule(Persistent, Contained):
+ """Redirection rule persistent class"""
+
+ active = FieldProperty(IRedirectionRule['active'])
+ chained = FieldProperty(IRedirectionRule['chained'])
+ permanent = FieldProperty(IRedirectionRule['permanent'])
+ _url_pattern = FieldProperty(IRedirectionRule['url_pattern'])
+ reference = FieldProperty(IRedirectionRule['reference'])
+ target_url = FieldProperty(IRedirectionRule['target_url'])
+
+ @property
+ def url_pattern(self):
+ return self._url_pattern
+
+ @url_pattern.setter
+ def url_pattern(self, value):
+ if value != self._url_pattern:
+ self._url_pattern = value
+ del self.pattern
+
+ @volatile_property
+ def target(self):
+ return get_reference_target(self.reference)
+
+ def get_target(self, state=None):
+ if not state:
+ return self.target
+ else:
+ return get_reference_target(self.reference, state)
+
+ @volatile_property
+ def pattern(self):
+ return re.compile(self.url_pattern)
+
+ def match(self, source_url):
+ return self.pattern.match(source_url)
+
+ def rewrite(self, source_url, request):
+ target_url = None
+ if self.reference:
+ target = self.target
+ if target is not None:
+ target_url = canonical_url(target, request)
+ else:
+ target_url = self.pattern.sub(self.target_url, source_url)
+ return target_url
+
+
+@adapter_config(context=IRedirectionRule, provides=IFormContextPermissionChecker)
+class RedirectionRulePermissionChecker(ContextAdapter):
+ """Redirection rule permission checker"""
+
+ edit_permission = MANAGE_SITE_ROOT_PERMISSION
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/redirect/container.py Thu Jul 19 16:15:30 2018 +0200
@@ -0,0 +1,103 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.redirect.interfaces import IRedirectionManager, IRedirectionRule, IRedirectionManagerTarget, \
+ REDIRECT_MANAGER_KEY
+from zope.location.interfaces import ISublocations
+from zope.traversing.interfaces import ITraversable
+
+# import packages
+from pyams_catalog.utils import index_object
+from pyams_utils.adapter import adapter_config, get_annotation_adapter, ContextAdapter
+from pyramid.response import Response
+from zope.container.ordered import OrderedContainer
+from zope.interface import implementer
+from zope.location.location import locate
+
+from pyams_content import _
+
+
+@implementer(IRedirectionManager)
+class RedirectManager(OrderedContainer):
+ """Redirect manager"""
+
+ last_id = 1
+
+ def append(self, value, notify=True):
+ key = str(self.last_id)
+ if not notify:
+ # pre-locate item to avoid multiple notifications
+ locate(value, self, key)
+ self[key] = value
+ self.last_id += 1
+ if not notify:
+ # make sure that item is correctly indexed
+ index_object(value)
+
+ def get_active_items(self):
+ yield from filter(lambda x: IRedirectionRule(x).active, self.values())
+
+ def get_response(self, request):
+ target_url = request.path_qs
+ for rule in self.get_active_items():
+ match = rule.match(target_url)
+ if match:
+ target_url = rule.rewrite(target_url, request)
+ if not rule.chained:
+ response = Response()
+ response.status_code = 301 if rule.permanent else 302
+ response.location = target_url
+ return response
+
+ def test_rules(self, source_url, request, check_inactive_rules=False):
+ if check_inactive_rules:
+ rules = self.values()
+ else:
+ rules = self.get_active_items()
+ for rule in rules:
+ match = rule.match(source_url)
+ if match:
+ target_url = rule.rewrite(source_url, request)
+ yield rule, source_url, target_url
+ if not rule.chained:
+ raise StopIteration
+ source_url = target_url
+ else:
+ yield rule, source_url, request.localizer.translate(_("not matching"))
+
+
+@adapter_config(context=IRedirectionManagerTarget, provides=IRedirectionManager)
+def redirection_manager_factory(context):
+ """Redirection manager factory"""
+ return get_annotation_adapter(context, REDIRECT_MANAGER_KEY, RedirectManager, name='++redirect++')
+
+
+@adapter_config(name='redirect', context=IRedirectionManagerTarget, provides=ITraversable)
+class RedirectionManagerNamespace(ContextAdapter):
+ """Redirection manager ++redirect++ namespace"""
+
+ def traverse(self, name, furtherpath=None):
+ return IRedirectionManager(self.context)
+
+
+@adapter_config(name='redirect', context=IRedirectionManagerTarget, provides=ISublocations)
+class RedirectManagerSublocations(ContextAdapter):
+ """redirection manager sub-locations adapter"""
+
+ def sublocations(self):
+ return IRedirectionManager(self.context).values()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/redirect/interfaces/__init__.py Thu Jul 19 16:15:30 2018 +0200
@@ -0,0 +1,102 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.interfaces.container import IOrderedContainer
+from pyams_sequence.interfaces import IInternalReference
+
+# import packages
+from pyams_sequence.schema import InternalReferenceField
+from zope.container.constraints import contains, containers
+from zope.interface import Interface, Attribute, invariant, Invalid
+from zope.schema import Bool, TextLine, Choice
+
+from pyams_content import _
+
+
+REDIRECT_MANAGER_KEY = 'pyams_content.redirect'
+
+
+class IRedirectionRule(IInternalReference):
+ """Redirection rule interface"""
+
+ containers('.IRedirectManager')
+
+ active = Bool(title=_("Active rule?"),
+ description=_("If 'no', selected rule is inactive"),
+ required=True,
+ default=False)
+
+ chained = Bool(title=_("Chained rule?"),
+ description=_("If 'no', and if this rule is matching received request URL, the rule "
+ "returns a redirection response; otherwise, the rule just rewrites the "
+ "input URL which is forwarded to the next rule"),
+ required=True,
+ default=False)
+
+ permanent = Bool(title=_("Permanent redirect?"),
+ description=_("Define if this redirection should be permanent or temporary"),
+ required=True,
+ default=True)
+
+ url_pattern = TextLine(title=_("URL pattern"),
+ description=_("Regexp pattern of matching URLs for this redirection rule"),
+ required=True)
+
+ pattern = Attribute("Compiled URL pattern")
+
+ reference = InternalReferenceField(title=_("Internal redirection target"),
+ description=_("Internal redirection reference. You can search a reference using "
+ "'+' followed by internal number, of by entering text matching "
+ "content title."),
+ required=False)
+
+ target_url = TextLine(title=_("Target URL"),
+ description=_("URL to which source URL should be redirected"),
+ required=False)
+
+ @invariant
+ def check_reference_and_target(self):
+ if self.reference and self.target_url:
+ raise Invalid(_("You can only provide an internal reference OR a target URL"))
+ elif not (self.reference or self.target_url):
+ raise Invalid(_("You must provide an internal reference OR a target URL"))
+
+ def match(self, source_url):
+ """Return regexp URL match on given URL"""
+
+ def rewrite(self, source_url, request):
+ """Rewrite given source URL"""
+
+
+class IRedirectionManager(IOrderedContainer):
+ """Redirection manager"""
+
+ contains(IRedirectionRule)
+
+ def get_active_items(self):
+ """Get iterator over active items"""
+
+ def get_response(self, request):
+ """Get new response for given request"""
+
+ def test_rules(self, source_url, request, check_inactive_rules=False):
+ """Test rules against given URL"""
+
+
+class IRedirectionManagerTarget(Interface):
+ """Redirection manager target marker interface"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/redirect/tween.py Thu Jul 19 16:15:30 2018 +0200
@@ -0,0 +1,49 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.redirect.interfaces import IRedirectionManager
+
+# import packages
+from pyramid.exceptions import NotFound
+from pyramid.httpexceptions import HTTPNotFound
+
+
+def redirect_tween_factory(handler, registry):
+ """Redirect tween factory
+
+ This tween is used to handle NotFound errors: when a request which raises
+ a NotFound error is served, we look info redirects configuration to check if
+ given URL is matching any defined redirection, in which case another HTTPRedirect
+ response is returned with a new location; another content using a proxy request
+ can also be returned.
+ """
+
+ def redirect_tween(request):
+ try:
+ response = handler(request)
+ except (NotFound, HTTPNotFound):
+ manager = IRedirectionManager(request.root, None)
+ if manager is not None:
+ response = manager.get_response(request)
+ if response is not None:
+ return response
+ raise
+ else:
+ return response
+
+ return redirect_tween
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/redirect/zmi/__init__.py Thu Jul 19 16:15:30 2018 +0200
@@ -0,0 +1,126 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+from pyams_form.help import FormHelp
+from pyams_form.interfaces.form import IFormHelp
+from pyams_utils.adapter import adapter_config
+from pyams_zmi.layer import IAdminLayer
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.redirect.interfaces import IRedirectionRule, IRedirectionManagerTarget, IRedirectionManager
+from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION
+from pyams_skin.interfaces.viewlet import IWidgetTitleViewletManager
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.features.redirect import RedirectionRule
+from pyams_content.features.redirect.zmi.container import RedirectionsContainerView, RedirectionsContainerTable
+from pyams_form.form import ajax_config, AJAXAddForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.event import get_json_table_row_refresh_event
+from pyams_skin.viewlet.toolbar import ToolbarAction
+from pyams_utils.traversing import get_parent
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminDialogAddForm, AdminDialogEditForm
+from z3c.form import field
+
+from pyams_content import _
+
+
+@viewlet_config(name='add-rule.action', context=IRedirectionManagerTarget, layer=IPyAMSLayer,
+ view=RedirectionsContainerView, manager=IWidgetTitleViewletManager,
+ permission=MANAGE_SITE_ROOT_PERMISSION, weight=1)
+class RedirectionRuleAddAction(ToolbarAction):
+ """Redirection rule add action"""
+
+ label = _("Add rule")
+ label_css_class = 'fa fa-fw fa-plus'
+ url = 'add-rule.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-rule.html', context=IRedirectionManagerTarget, layer=IPyAMSLayer,
+ permission=MANAGE_SITE_ROOT_PERMISSION)
+@ajax_config(name='add-rule.json', context=IRedirectionManagerTarget, layer=IPyAMSLayer, base=AJAXAddForm)
+class RedirectionRuleAddForm(AdminDialogAddForm):
+ """Redirection rule add form"""
+
+ dialog_class = 'modal-large'
+ legend = _("Add new redirection rule")
+ icon_css_class = 'fa fa-fw fa-map-signs'
+
+ fields = field.Fields(IRedirectionRule).omit('__parent__', '__name__', 'active', 'chained')
+ edit_permission = MANAGE_SITE_ROOT_PERMISSION
+
+ def create(self, data):
+ return RedirectionRule()
+
+ def add(self, object):
+ IRedirectionManager(self.context).append(object)
+
+ def nextURL(self):
+ return absolute_url(self.context, self.request, 'redirections.html')
+
+
+@pagelet_config(name='properties.html', context=IRedirectionRule, layer=IPyAMSLayer,
+ permission=MANAGE_SITE_ROOT_PERMISSION)
+@ajax_config(name='properties.json', context=IRedirectionRule, layer=IPyAMSLayer)
+class RedirectionRulePropertiesEditForm(AdminDialogEditForm):
+ """Redirection rule properties edit form"""
+
+ dialog_class = 'modal-large'
+ prefix = 'rule_properties.'
+
+ legend = _("Edit redirection rule properties")
+ icon_css_class = 'fa fa-fw fa-map-signs'
+
+ fields = field.Fields(IRedirectionRule).omit('__parent__', '__name__', 'active', 'chained')
+ edit_permission = MANAGE_SITE_ROOT_PERMISSION
+
+ def get_ajax_output(self, changes):
+ output = super(self.__class__, self).get_ajax_output(changes)
+ updated = changes.get(IRedirectionRule, ())
+ if updated:
+ target = get_parent(self.context, IRedirectionManagerTarget)
+ output.setdefault('events', []).append(
+ get_json_table_row_refresh_event(target, self.request, RedirectionsContainerTable, self.context))
+ return output
+
+
+@adapter_config(context=(IRedirectionManagerTarget, IAdminLayer, RedirectionRuleAddForm), provides=IFormHelp)
+@adapter_config(context=(IRedirectionRule, IAdminLayer, RedirectionRulePropertiesEditForm), provides=IFormHelp)
+class RedirectionRuleFormHelp(FormHelp):
+ """Redirection rule form help"""
+
+ message = _("""URL pattern and target URL are defined by *regular expressions* (see |regexp|).
+
+In URL pattern, you can use any valid regular expression element, notably:
+
+- « .* » to match any list of characters
+
+- « ( ) » to "memorize" parts of the URL which can be replaced into target URL
+
+- special characters (like "+") must be escaped with an « \\\\ ».
+
+In target URL, memorized parts can be reused using « \\\\1 », « \\\\2 » and so on, where given number is
+the order of the matching pattern element.
+
+.. |regexp| raw:: html
+
+ <a href="https://docs.python.org/3/library/re.html" target="_blank">Python Regular Expressions</a>
+""")
+ message_format = 'rest'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/redirect/zmi/container.py Thu Jul 19 16:15:30 2018 +0200
@@ -0,0 +1,376 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+from pyams_form.form import ajax_config, AJAXAddForm
+from pyams_form.interfaces.form import IWidgetsSuffixViewletsManager
+from pyams_form.schema import CloseButton
+from pyams_skin.help import ContentHelp
+from pyams_skin.interfaces.viewlet import IToolbarViewletManager
+from pyams_skin.skin import apply_skin
+from pyams_skin.viewlet.toolbar import ToolbarAction
+from pyams_template.template import template_config
+from pyams_utils.request import copy_request
+from pyams_zmi.form import AdminDialogAddForm
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import json
+
+# import interfaces
+from pyams_content.features.redirect.interfaces import IRedirectionManagerTarget, IRedirectionManager
+from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION
+from pyams_i18n.interfaces import II18n
+from pyams_sequence.interfaces import ISequentialIdInfo
+from pyams_skin.interfaces import IPageHeader, IUserSkinnable, IContentHelp
+from pyams_skin.layer import IPyAMSLayer
+from pyams_zmi.interfaces.menu import ISiteManagementMenu
+from pyams_zmi.layer import IAdminLayer
+from z3c.table.interfaces import IValues, IColumn
+
+# import packages
+from pyams_content.skin import pyams_content
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.page import DefaultPageHeaderAdapter
+from pyams_skin.table import BaseTable, SorterColumn, VisibilitySwitcherColumn, TrashColumn, I18nColumn
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+from pyams_utils.fanstatic import get_resource_path
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config, Viewlet
+from pyams_zmi.view import ContainerAdminView
+from pyramid.decorator import reify
+from pyramid.exceptions import NotFound
+from pyramid.view import view_config
+from z3c.form import field, button
+from z3c.table.column import GetAttrColumn
+from zope.interface import Interface
+from zope.schema import TextLine, Bool
+
+from pyams_content import _
+
+
+@viewlet_config(name='redirections.menu', context=IRedirectionManagerTarget, layer=IPyAMSLayer,
+ manager=ISiteManagementMenu, permission=MANAGE_SITE_ROOT_PERMISSION, weight=35)
+class RedirectionMenu(MenuItem):
+ """Redirection manager menu"""
+
+ label = _("Redirections")
+ icon_class = 'fa-map-signs'
+ url = '#redirections.html'
+
+
+class RedirectionsContainerTable(BaseTable):
+ """Redirections container table"""
+
+ prefix = 'redirections'
+
+ hide_header = True
+ sortOn = None
+
+ cssClasses = {'table': 'table table-bordered table-striped table-hover table-tight table-dnd'}
+
+ @property
+ def data_attributes(self):
+ attributes = super(RedirectionsContainerTable, self).data_attributes
+ attributes.setdefault('table', {}).update({
+ 'data-ams-plugins': 'pyams_content',
+ 'data-ams-plugin-pyams_content-src': get_resource_path(pyams_content),
+ 'data-ams-location': absolute_url(IRedirectionManager(self.context), self.request),
+ 'data-ams-tablednd-drag-handle': 'td.sorter',
+ 'data-ams-tablednd-drop-target': 'set-rules-order.json',
+ 'data-ams-active-icon-on': 'fa fa-fw fa-check-square-o',
+ 'data-ams-active-icon-off': 'fa fa-fw fa-square-o txt-color-silver opacity-75',
+ 'data-ams-chained-icon-on': 'fa fa-fw fa-chain',
+ 'data-ams-chained-icon-off': 'fa fa-fw fa-chain txt-color-silver opacity-50'
+ })
+ attributes.setdefault('td', {}).update({
+ 'data-ams-attribute-switcher': self.get_switcher_target,
+ 'data-ams-switcher-attribute-name': self.get_switcher_attribute
+ })
+ return attributes
+
+ @staticmethod
+ def get_switcher_target(element, column):
+ if column.__name__ == 'enable-disable':
+ return 'switch-rule-activity.json'
+ elif column.__name__ == 'chain-unchain':
+ return 'switch-rule-chain.json'
+
+ @staticmethod
+ def get_switcher_attribute(element, column):
+ if column.__name__ == 'enable-disable':
+ return 'active'
+ elif column.__name__ == 'chain-unchain':
+ return 'chained'
+
+ @reify
+ def values(self):
+ return list(super(RedirectionsContainerTable, self).values)
+
+ def render(self):
+ if not self.values:
+ translate = self.request.localizer.translate
+ return translate(_("No currently defined redirection rule."))
+ return super(RedirectionsContainerTable, self).render()
+
+
+@adapter_config(context=(IRedirectionManagerTarget, IPyAMSLayer, RedirectionsContainerTable), provides=IValues)
+class RedirectionsContainerValues(ContextRequestViewAdapter):
+ """Redirections container values"""
+
+ @property
+ def values(self):
+ return IRedirectionManager(self.context).values()
+
+
+@adapter_config(name='sorter', context=(IRedirectionManagerTarget, IPyAMSLayer, RedirectionsContainerTable),
+ provides=IColumn)
+class RedirectionsContainerSorterColumn(SorterColumn):
+ """Redirections container sorter column"""
+
+
+@view_config(name='set-rules-order.json', context=IRedirectionManager, request_type=IPyAMSLayer,
+ permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
+def set_rules_order(request):
+ """Update redirection rules order"""
+ order = list(map(str, json.loads(request.params.get('names'))))
+ request.context.updateOrder(order)
+ return {'status': 'success'}
+
+
+@adapter_config(name='enable-disable', context=(IRedirectionManagerTarget, IPyAMSLayer, RedirectionsContainerTable),
+ provides=IColumn)
+class RedirectionsContainerShowHideColumn(VisibilitySwitcherColumn):
+ """Redirections container activity switcher column"""
+
+ switch_attribute = 'active'
+ visible_icon_class = 'fa fa-fw fa-check-square-o'
+ hidden_icon_class = 'fa fa-fw fa-square-o txt-color-silver opacity-75'
+
+ icon_hint = _("Enable/disable rule")
+
+ url = 'MyAMS.container.switchElementAttribute'
+ weight = 6
+
+
+@view_config(name='switch-rule-activity.json', context=IRedirectionManager, request_type=IPyAMSLayer,
+ permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
+def switch_rule_activity(request):
+ """Switch rule activity"""
+ container = IRedirectionManager(request.context)
+ rule = container.get(str(request.params.get('object_name')))
+ if rule is None:
+ raise NotFound()
+ rule.active = not rule.active
+ return {'on': rule.active}
+
+
+@adapter_config(name='chain-unchain', context=(IRedirectionManagerTarget, IPyAMSLayer, RedirectionsContainerTable),
+ provides=IColumn)
+class RedirectionsContainerChainedColumn(VisibilitySwitcherColumn):
+ """Redirections container chained switcher column"""
+
+ switch_attribute = 'chained'
+ visible_icon_class = 'fa fa-fw fa-chain'
+ hidden_icon_class = 'fa fa-fw fa-chain txt-color-silver opacity-50'
+
+ icon_hint = _("Chain/unchain rule")
+
+ url = 'MyAMS.container.switchElementAttribute'
+ weight = 7
+
+
+@view_config(name='switch-rule-chain.json', context=IRedirectionManager, request_type=IPyAMSLayer,
+ permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
+def switch_rule_chain(request):
+ """Switch rule chain"""
+ container = IRedirectionManager(request.context)
+ rule = container.get(str(request.params.get('object_name')))
+ if rule is None:
+ raise NotFound()
+ rule.chained = not rule.chained
+ return {'on': rule.chained}
+
+
+@adapter_config(name='name', context=(IRedirectionManagerTarget, IPyAMSLayer, RedirectionsContainerTable),
+ provides=IColumn)
+class RedirectionsContainerNameColumn(I18nColumn, GetAttrColumn):
+ """Redirections container name column"""
+
+ _header = _("URL pattern")
+ attrName = 'url_pattern'
+ weight = 10
+
+
+@adapter_config(name='target', context=(IRedirectionManagerTarget, IPyAMSLayer, RedirectionsContainerTable),
+ provides=IColumn)
+class RedirectionsContainerTargetColumn(I18nColumn, GetAttrColumn):
+ """Redirections container target column"""
+
+ _header = _("Target")
+ attrName = 'target_url'
+ weight = 20
+
+ def getValue(self, obj):
+ if obj.reference:
+ target = obj.target
+ return '{0} ({1})'.format(II18n(target).query_attribute('title', request=self.request),
+ ISequentialIdInfo(target).get_short_oid())
+ else:
+ return super(RedirectionsContainerTargetColumn, self).getValue(obj)
+
+
+@adapter_config(name='trash', context=(IRedirectionManagerTarget, IPyAMSLayer, RedirectionsContainerTable),
+ provides=IColumn)
+class RedirectionsContainerTrashColumn(TrashColumn):
+ """Redirections container trash column"""
+
+ permission = MANAGE_SITE_ROOT_PERMISSION
+
+
+@pagelet_config(name='redirections.html', context=IRedirectionManagerTarget, layer=IPyAMSLayer,
+ permission=MANAGE_SITE_ROOT_PERMISSION)
+class RedirectionsContainerView(ContainerAdminView):
+ """Redirections container view"""
+
+ title = _("Redirections list")
+ table_class = RedirectionsContainerTable
+
+
+@adapter_config(context=(IRedirectionManagerTarget, IAdminLayer, RedirectionsContainerView), provides=IPageHeader)
+class RedirectionsContainerViewHeaderAdapter(DefaultPageHeaderAdapter):
+ """Redirections container view header adapter"""
+
+ icon_class = 'fa fa-fw fa-map-signs'
+
+
+@adapter_config(context=(IRedirectionManagerTarget, IAdminLayer, RedirectionsContainerView), provides=IContentHelp)
+class RedirectionsContainerHelpAdapter(ContentHelp):
+ """Redirections container help adapter"""
+
+ header = _("Redirection rules")
+ message = _("""Redirection rules are use to handle redirections responses when a request generates
+a famous « 404 NotFound » error.
+
+Redirections are particularly useful when you are migrating from a previous site and don't want to lose
+your SEO.
+
+You can define a set of rules which will be applied to every \"NotFound\" request; rules are based on
+regular expressions which are applied to input URL: if the rule is \"matching\", the target URL is rewritten
+and a \"Redirect\" response is send.
+
+You can chain rules together: when a rule is chained, it's rewritten URL is passed as input URL to the
+next rule, until a matching rule is found.
+""")
+ message_format = 'rest'
+
+
+#
+# Redirections container test form
+#
+
+@viewlet_config(name='test.action', context=IRedirectionManagerTarget, layer=IAdminLayer,
+ view=RedirectionsContainerView, manager=IToolbarViewletManager,
+ permission=MANAGE_SITE_ROOT_PERMISSION, weight=75)
+class RedirectionsContainerTestAction(ToolbarAction):
+ """redirections container test action"""
+
+ label = _("Test")
+
+ group_css_class = 'btn-group margin-left-5'
+ label_css_class = 'fa fa-fw fa-magic'
+ css_class = 'btn btn-xs btn-default'
+
+ url = 'test-redirection-rules.html'
+ modal_target = True
+
+
+class IRedirectionsContainerTestFields(Interface):
+ """Redirections container test fields"""
+
+ source_url = TextLine(title=_("Test URL"),
+ required=True)
+
+ check_inactive_rules = Bool(title=_("Check inactive rules?"),
+ description=_("If 'yes', inactive rules will also be tested"),
+ required=True,
+ default=False)
+
+
+class IRedirectionsContainerTestButtons(Interface):
+ """Redirections container test form buttons"""
+
+ close = CloseButton(name='close', title=_("Close"))
+ test = button.Button(name='test', title=_("Test rules"))
+
+
+@pagelet_config(name='test-redirection-rules.html', context=IRedirectionManagerTarget, layer=IPyAMSLayer,
+ permission=MANAGE_SITE_ROOT_PERMISSION)
+class RedirectionsContainerTestForm(AdminDialogAddForm):
+ """Redirections container test form"""
+
+ dialog_class = 'modal-max'
+ legend = _("Test redirection rules")
+ icon_css_class = 'fa fa-fw fa-magic'
+
+ prefix = 'rules_test_form.'
+ fields = field.Fields(IRedirectionsContainerTestFields)
+ buttons = button.Buttons(IRedirectionsContainerTestButtons)
+ ajax_handler = 'test-redirection-rules.json'
+ edit_permission = MANAGE_SITE_ROOT_PERMISSION
+
+ @property
+ def form_target(self):
+ return '#{0}_test_result'.format(self.id)
+
+ def updateActions(self):
+ super(RedirectionsContainerTestForm, self).updateActions()
+ if 'test' in self.actions:
+ self.actions['test'].addClass('btn-primary')
+
+ def createAndAdd(self, data):
+ data = data.get(self, data)
+ request = copy_request(self.request)
+ apply_skin(request, IUserSkinnable(self.context).get_skin())
+ return IRedirectionManager(self.context).test_rules(data['source_url'], request, data['check_inactive_rules'])
+
+
+@viewlet_config(name='test-indexer-process.suffix', layer=IAdminLayer, manager=IWidgetsSuffixViewletsManager,
+ view=RedirectionsContainerTestForm, weight=50)
+@template_config(template='templates/manager-test.pt')
+class RedirectionsContainerTestSuffix(Viewlet):
+ """Redirections container test form suffix"""
+
+
+@view_config(name='test-redirection-rules.json', context=IRedirectionManagerTarget, request_type=IPyAMSLayer,
+ permission=MANAGE_SITE_ROOT_PERMISSION, renderer='json', xhr=True)
+class RedirectionsContainerAJAXTestForm(AJAXAddForm, RedirectionsContainerTestForm):
+ """Redirections container test form, JSON renderer"""
+
+ def get_ajax_output(self, changes):
+ message = []
+ translate = self.request.localizer.translate
+ for rule, source_url, target_url in changes:
+ if not message:
+ message.append('{:<40} {:<40} => {:<40}'.format(translate(_("URL pattern")),
+ translate(_("Input URL")),
+ translate(_("Output URL"))))
+ message.append('{:<40} {:<40} => {:<40}'.format('-' * 40, '-' * 40, '-' * 40))
+ message.append('{:<40} {:<40} => {:<40}'.format(rule.url_pattern, source_url, target_url))
+ if not message:
+ message.append(translate(_("No matching rule!")))
+ return {
+ 'status': 'success',
+ 'content': {'html': '\n'.join(message)},
+ 'close_form': False
+ }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/features/redirect/zmi/templates/manager-test.pt Thu Jul 19 16:15:30 2018 +0200
@@ -0,0 +1,4 @@
+<div class="no-widget-toolbar">
+ <pre class="height-min-200"
+ tal:attributes="id string:${view.__parent__.id}_test_result"></pre>
+</div>
--- a/src/pyams_content/include.py Thu Jul 19 10:38:08 2018 +0200
+++ b/src/pyams_content/include.py Thu Jul 19 16:15:30 2018 +0200
@@ -18,6 +18,7 @@
# import interfaces
# import packages
+from pyramid.tweens import MAIN
def include_package(config):
@@ -26,6 +27,9 @@
# add translations
config.add_translation_dirs('pyams_content:locales')
+ # add custom twwen
+ config.add_tween('pyams_content.features.redirect.tween.redirect_tween_factory', over=MAIN)
+
# add custom routes
config.add_route('oid_access', '/+/{oid}*view')
Binary file src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.mo has changed
--- a/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po Thu Jul 19 10:38:08 2018 +0200
+++ b/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po Thu Jul 19 16:15:30 2018 +0200
@@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-07-18 14:46+0200\n"
+"POT-Creation-Date: 2018-07-19 15:57+0200\n"
"PO-Revision-Date: 2015-09-10 10:42+0200\n"
"Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
"Language-Team: French\n"
@@ -1389,27 +1389,27 @@
msgid "Content collections"
msgstr "Collections associées au contenu"
-#: src/pyams_content/component/theme/zmi/manager.py:51
+#: src/pyams_content/component/theme/zmi/manager.py:58
msgid "Tags settings..."
msgstr "Paramétrage des tags"
-#: src/pyams_content/component/theme/zmi/manager.py:65
+#: src/pyams_content/component/theme/zmi/manager.py:72
msgid "Selected tags"
msgstr "Tags sélectionnés"
-#: src/pyams_content/component/theme/zmi/manager.py:101
+#: src/pyams_content/component/theme/zmi/manager.py:108
msgid "Themes settings..."
msgstr "Paramétrage des thèmes"
-#: src/pyams_content/component/theme/zmi/manager.py:115
+#: src/pyams_content/component/theme/zmi/manager.py:122
msgid "Selected themes"
msgstr "Thèmes sélectionnés"
-#: src/pyams_content/component/theme/zmi/manager.py:151
+#: src/pyams_content/component/theme/zmi/manager.py:158
msgid "Collections settings..."
msgstr "Paramétrage des collections"
-#: src/pyams_content/component/theme/zmi/manager.py:165
+#: src/pyams_content/component/theme/zmi/manager.py:172
msgid "Selected collections"
msgstr "Collections sélectionnées"
@@ -1556,6 +1556,7 @@
#: src/pyams_content/component/links/interfaces/__init__.py:61
#: src/pyams_content/shared/logo/interfaces/__init__.py:56
+#: src/pyams_content/features/redirect/interfaces/__init__.py:68
msgid "Target URL"
msgstr "URL cible"
@@ -4411,7 +4412,7 @@
msgid "Default length used for inner tables and dashboards"
msgstr "Longueur par défaut des tableaux internes et des tableaux de bord"
-#: src/pyams_content/root/__init__.py:68
+#: src/pyams_content/root/__init__.py:70
msgid "Site root"
msgstr "Racine du site"
@@ -5079,6 +5080,247 @@
msgid "No currently defined alert."
msgstr "Aucune alerte n'est définie actuellement."
+#: src/pyams_content/features/redirect/container.py:81
+msgid "not matching"
+msgstr "pas de correspondance"
+
+#: src/pyams_content/features/redirect/zmi/__init__.py:50
+msgid "Add rule"
+msgstr "Ajouter une règle"
+
+#: src/pyams_content/features/redirect/zmi/__init__.py:63
+msgid "Add new redirection rule"
+msgstr "Ajout d'une règle de redirection"
+
+#: src/pyams_content/features/redirect/zmi/__init__.py:88
+msgid "Edit redirection rule properties"
+msgstr "Propriétés de la règle de redirection"
+
+#: src/pyams_content/features/redirect/zmi/__init__.py:109
+msgid ""
+"URL pattern and target URL are defined by *regular expressions* (see |"
+"regexp|).\n"
+" \n"
+"In URL pattern, you can use any valid regular expression element, notably:\n"
+"\n"
+"- « .* » to match any list of characters \n"
+"\n"
+"- « ( ) » to \"memorize\" parts of the URL which can be replaced into target "
+"URL\n"
+"\n"
+"- special characters (like \"+\") must be escaped with an « \\\\ ».\n"
+"\n"
+"In target URL, memorized parts can be reused using « \\\\1 », « \\\\2 » and "
+"so on, where given number is\n"
+"the order of the matching pattern element.\n"
+"\n"
+".. |regexp| raw:: html\n"
+"\n"
+" <a href=\"https://docs.python.org/3/library/re.html\" target=\"_blank"
+"\">Python Regular Expressions</a>\n"
+msgstr ""
+"Le schéma d'URL et l'URL cible sont définis en tant que « expressions "
+"rationelles » (voir |regexp|).\n"
+"\n"
+"Dans le schéma d'URL utilisé pour identifier les requêtes en entrée, vous "
+"pouvez utiliser tout élément d'une expression rationnelle valide, "
+"notamment :\n"
+"\n"
+"- « .* » pour rechercher n'importe quelle suite de caractères\n"
+"\n"
+"- « ^ » et « $ » pour identifier le début ou la fin de l'URL\n"
+"\n"
+"- « ( ) » pour \"mémoriser\" certains éléments de l'URL qui pourront être "
+"repris dans l'URL cible\n"
+"\n"
+"- les caractères spéciaux (comme les \"+\") doivent être protégés par un "
+"caractère « \\\\ ».\n"
+"\n"
+"For exemple : dans le schéma « ^/.*?oid=([a-z0-9]+)$ », toute URL contenant "
+"un paramètre \"oid\" composé de minuscules et/ou de chiffres sera mémorisé "
+"pour pouvoir être réutilisé dans l'URL cible.\n"
+"\n"
+"Dans l'URL cible, les éléments mémorisés peuvent être réutilisés en "
+"utilisant une expression comme « \\\\1 », « \\\\2 » (et ainsi de suite), le "
+"chiffre indiquant la position de l'élément dans la liste des éléments "
+"mémorisés.\n"
+"\n"
+".. |regexp| raw:: html\n"
+"\n"
+" <a href=\"https://docs.python.org/fr/3/library/re.html\" target=\"_blank"
+"\">Expressions rationnelles en Python</a>\n"
+
+#: src/pyams_content/features/redirect/zmi/container.py:67
+msgid "Redirections"
+msgstr "Redirections"
+
+#: src/pyams_content/features/redirect/zmi/container.py:160
+msgid "Enable/disable rule"
+msgstr "Activer/désactiver la règle"
+
+#: src/pyams_content/features/redirect/zmi/container.py:187
+msgid "Chain/unchain rule"
+msgstr "Enchaîner la règle avec la suivante"
+
+#: src/pyams_content/features/redirect/zmi/container.py:210
+#: src/pyams_content/features/redirect/zmi/container.py:365
+#: src/pyams_content/features/redirect/interfaces/__init__.py:56
+msgid "URL pattern"
+msgstr "Schéma d'URL"
+
+#: src/pyams_content/features/redirect/zmi/container.py:220
+msgid "Target"
+msgstr "Cible"
+
+#: src/pyams_content/features/redirect/zmi/container.py:246
+msgid "Redirections list"
+msgstr "Liste des règles de redirection"
+
+#: src/pyams_content/features/redirect/zmi/container.py:261
+msgid "Redirection rules"
+msgstr "Règles de redirection"
+
+#: src/pyams_content/features/redirect/zmi/container.py:262
+msgid ""
+"Redirection rules are use to handle redirections responses when a request "
+"generates \n"
+"a famous « 404 NotFound » error.\n"
+"\n"
+"Redirections are particularly useful when you are migrating from a previous "
+"site and don't want to lose \n"
+"your SEO.\n"
+"\n"
+"You can define a set of rules which will be applied to every \"NotFound\" "
+"request; rules are based on \n"
+"regular expressions which are applied to input URL: if the rule is \"matching"
+"\", the target URL is rewritten\n"
+"and a \"Redirect\" response is send.\n"
+"\n"
+"You can chain rules together: when a rule is chained, it's rewritten URL is "
+"passed as input URL to the \n"
+"next rule, until a matching rule is found.\n"
+msgstr ""
+"Les règles de redirection sont utilisées pour transmettre des réponses de redirection "
+"au lieu de la fameuse erreur « 404 - Page non trouvée ».\n"
+"\n"
+"La gestion des redirections est particulièrement importante en phase de migration d'un site web, "
+"pour éviter les liens cassés, ne pas perdre votre référencement et faciliter la mise à jour "
+"des moteurs de recherche.\n"
+"\n"
+"Vous pouvez définir un ensemble de règles qui seront appliquées dès lors qu'une requête "
+"adressée au serveur génère une erreur de page non trouvée ; les règles sont basées sur des "
+"expressions rationnelles que l'on applique à l'URL de la requête reçue : si la règle correspond, "
+"l'URL est réécrite et une réponse de redirection vers cette nouvelle URL est renvoyée.\n"
+"\n"
+"Vous pouvez également enchaîner les règles : lorsqu'une règle est \"chaînée\", la nouvelle URL "
+"qu'elle génère est passée aux règles suivantes, jusqu'à ce qu'une règle s'applique à cette "
+"nouvelle URL.\n"
+
+#: src/pyams_content/features/redirect/zmi/container.py:288
+msgid "Test"
+msgstr "Tester !"
+
+#: src/pyams_content/features/redirect/zmi/container.py:323
+msgid "Test redirection rules"
+msgstr "Test des règles de redirection"
+
+#: src/pyams_content/features/redirect/zmi/container.py:301
+msgid "Test URL"
+msgstr "URL à tester"
+
+#: src/pyams_content/features/redirect/zmi/container.py:304
+msgid "Check inactive rules?"
+msgstr "Tester les règles inactive ?"
+
+#: src/pyams_content/features/redirect/zmi/container.py:305
+msgid "If 'yes', inactive rules will also be tested"
+msgstr "Si 'oui', les règles inactives seront également testées"
+
+#: src/pyams_content/features/redirect/zmi/container.py:313
+msgid "Close"
+msgstr "Fermer"
+
+#: src/pyams_content/features/redirect/zmi/container.py:314
+msgid "Test rules"
+msgstr "Tester cette URL"
+
+#: src/pyams_content/features/redirect/zmi/container.py:123
+msgid "No currently defined redirection rule."
+msgstr "Aucune règle de redirection n'est définie actuellement."
+
+#: src/pyams_content/features/redirect/zmi/container.py:371
+msgid "No matching rule!"
+msgstr "Aucune règle ne correspond !"
+
+#: src/pyams_content/features/redirect/zmi/container.py:366
+msgid "Input URL"
+msgstr "URL en entrée"
+
+#: src/pyams_content/features/redirect/zmi/container.py:367
+msgid "Output URL"
+msgstr "URL générée"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:39
+msgid "Active rule?"
+msgstr "Règle active ?"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:40
+msgid "If 'no', selected rule is inactive"
+msgstr "Si 'non', la règle est inactive"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:44
+msgid "Chained rule?"
+msgstr "Règle chaînée ?"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:45
+msgid ""
+"If 'no', and if this rule is matching received request URL, the rule returns "
+"a redirection response; otherwise, the rule just rewrites the input URL "
+"which is forwarded to the next rule"
+msgstr ""
+"Si 'non', et si cette règle correspond à l'URL reçue en entrée, une réponde "
+"de redirection est renvoyée directement ; dans le cas contraire, l'URL "
+"générée par cette règle est passée en entrée de la règle suivante"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:51
+msgid "Permanent redirect?"
+msgstr "Redirection permanente ?"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:52
+msgid "Define if this redirection should be permanent or temporary"
+msgstr ""
+"Indique si cette redirection doit être considérée comme permanente ou "
+"temporaire"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:57
+msgid "Regexp pattern of matching URLs for this redirection rule"
+msgstr "Modèle de l'URL d'origine de cette règle de redirection"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:62
+msgid "Internal redirection target"
+msgstr "Redirection interne"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:63
+msgid ""
+"Internal redirection reference. You can search a reference using '+' "
+"followed by internal number, of by entering text matching content title."
+msgstr ""
+"Référence interne vers une cible de redirection. Vous pouvez la rechercher "
+"par des mots de son titre, ou par son numéro interne (précédé d'un '+')"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:69
+msgid "URL to which source URL should be redirected"
+msgstr "URL vers laquelle l'URL d'origine doit être redirigée"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:75
+msgid "You can only provide an internal reference OR a target URL"
+msgstr ""
+"Vous ne pouvez fournir qu'une référence interne OU une URL de redirection !"
+
+#: src/pyams_content/features/redirect/interfaces/__init__.py:77
+msgid "You must provide an internal reference OR a target URL"
+msgstr "Vous devez fournir une référence interne OU une URL de redirection !"
+
#: src/pyams_content/features/menu/zmi/__init__.py:81
msgid "Add menu..."
msgstr "Ajouter un menu"
@@ -5405,6 +5647,21 @@
msgid "Hidden header"
msgstr "Ne pas afficher d'en-tête de pages"
+#~ msgid "Rewrite to another internal reference or URL"
+#~ msgstr "Rediriger vers une autre référence ou URL interne"
+
+#~ msgid "Redirect to another external URL"
+#~ msgstr "Rediriger vers une URL externe"
+
+#~ msgid "Return content in proxy mode without redirection"
+#~ msgstr "Charger le contenu ciblé sans redirection"
+
+#~ msgid "Redirect mode"
+#~ msgstr "Mode de redirection"
+
+#~ msgid "Mode of redirection for this URL pattern"
+#~ msgstr "Mode de redirection utilisé par cette règle"
+
#~ msgid "Subtitle"
#~ msgstr "Sous-titre"
--- a/src/pyams_content/locales/pyams_content.pot Thu Jul 19 10:38:08 2018 +0200
+++ b/src/pyams_content/locales/pyams_content.pot Thu Jul 19 16:15:30 2018 +0200
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-07-18 14:46+0200\n"
+"POT-Creation-Date: 2018-07-19 15:57+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -1337,27 +1337,27 @@
msgid "Content collections"
msgstr ""
-#: ./src/pyams_content/component/theme/zmi/manager.py:51
+#: ./src/pyams_content/component/theme/zmi/manager.py:58
msgid "Tags settings..."
msgstr ""
-#: ./src/pyams_content/component/theme/zmi/manager.py:65
+#: ./src/pyams_content/component/theme/zmi/manager.py:72
msgid "Selected tags"
msgstr ""
-#: ./src/pyams_content/component/theme/zmi/manager.py:101
+#: ./src/pyams_content/component/theme/zmi/manager.py:108
msgid "Themes settings..."
msgstr ""
-#: ./src/pyams_content/component/theme/zmi/manager.py:115
+#: ./src/pyams_content/component/theme/zmi/manager.py:122
msgid "Selected themes"
msgstr ""
-#: ./src/pyams_content/component/theme/zmi/manager.py:151
+#: ./src/pyams_content/component/theme/zmi/manager.py:158
msgid "Collections settings..."
msgstr ""
-#: ./src/pyams_content/component/theme/zmi/manager.py:165
+#: ./src/pyams_content/component/theme/zmi/manager.py:172
msgid "Selected collections"
msgstr ""
@@ -1500,6 +1500,7 @@
#: ./src/pyams_content/component/links/interfaces/__init__.py:61
#: ./src/pyams_content/shared/logo/interfaces/__init__.py:56
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:68
msgid "Target URL"
msgstr ""
@@ -4131,7 +4132,7 @@
msgid "Default length used for inner tables and dashboards"
msgstr ""
-#: ./src/pyams_content/root/__init__.py:68
+#: ./src/pyams_content/root/__init__.py:70
msgid "Site root"
msgstr ""
@@ -4781,6 +4782,185 @@
msgid "No currently defined alert."
msgstr ""
+#: ./src/pyams_content/features/redirect/container.py:81
+msgid "not matching"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/__init__.py:50
+msgid "Add rule"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/__init__.py:63
+msgid "Add new redirection rule"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/__init__.py:88
+msgid "Edit redirection rule properties"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/__init__.py:109
+msgid ""
+"URL pattern and target URL are defined by *regular expressions* (see |regexp|).\n"
+" \n"
+"In URL pattern, you can use any valid regular expression element, notably:\n"
+"\n"
+"- « .* » to match any list of characters \n"
+"\n"
+"- « ( ) » to \"memorize\" parts of the URL which can be replaced into target URL\n"
+"\n"
+"- special characters (like \"+\") must be escaped with an « \\\\ ».\n"
+"\n"
+"In target URL, memorized parts can be reused using « \\\\1 », « \\\\2 » and so on, where given number is\n"
+"the order of the matching pattern element.\n"
+"\n"
+".. |regexp| raw:: html\n"
+"\n"
+" <a href=\"https://docs.python.org/3/library/re.html\" target=\"_blank\">Python Regular Expressions</a>\n"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:67
+msgid "Redirections"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:160
+msgid "Enable/disable rule"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:187
+msgid "Chain/unchain rule"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:210
+#: ./src/pyams_content/features/redirect/zmi/container.py:365
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:56
+msgid "URL pattern"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:220
+msgid "Target"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:246
+msgid "Redirections list"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:261
+msgid "Redirection rules"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:262
+msgid ""
+"Redirection rules are use to handle redirections responses when a request generates \n"
+"a famous « 404 NotFound » error.\n"
+"\n"
+"Redirections are particularly useful when you are migrating from a previous site and don't want to lose \n"
+"your SEO.\n"
+"\n"
+"You can define a set of rules which will be applied to every \"NotFound\" request; rules are based on \n"
+"regular expressions which are applied to input URL: if the rule is \"matching\", the target URL is rewritten\n"
+"and a \"Redirect\" response is send.\n"
+"\n"
+"You can chain rules together: when a rule is chained, it's rewritten URL is passed as input URL to the \n"
+"next rule, until a matching rule is found.\n"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:288
+msgid "Test"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:323
+msgid "Test redirection rules"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:301
+msgid "Test URL"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:304
+msgid "Check inactive rules?"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:305
+msgid "If 'yes', inactive rules will also be tested"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:313
+msgid "Close"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:314
+msgid "Test rules"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:123
+msgid "No currently defined redirection rule."
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:371
+msgid "No matching rule!"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:366
+msgid "Input URL"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/zmi/container.py:367
+msgid "Output URL"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:39
+msgid "Active rule?"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:40
+msgid "If 'no', selected rule is inactive"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:44
+msgid "Chained rule?"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:45
+msgid ""
+"If 'no', and if this rule is matching received request URL, the rule returns "
+"a redirection response; otherwise, the rule just rewrites the input URL which"
+" is forwarded to the next rule"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:51
+msgid "Permanent redirect?"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:52
+msgid "Define if this redirection should be permanent or temporary"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:57
+msgid "Regexp pattern of matching URLs for this redirection rule"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:62
+msgid "Internal redirection target"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:63
+msgid ""
+"Internal redirection reference. You can search a reference using '+' followed"
+" by internal number, of by entering text matching content title."
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:69
+msgid "URL to which source URL should be redirected"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:75
+msgid "You can only provide an internal reference OR a target URL"
+msgstr ""
+
+#: ./src/pyams_content/features/redirect/interfaces/__init__.py:77
+msgid "You must provide an internal reference OR a target URL"
+msgstr ""
+
#: ./src/pyams_content/features/menu/zmi/__init__.py:81
msgid "Add menu..."
msgstr ""
--- a/src/pyams_content/root/__init__.py Thu Jul 19 10:38:08 2018 +0200
+++ b/src/pyams_content/root/__init__.py Thu Jul 19 16:15:30 2018 +0200
@@ -24,6 +24,7 @@
from pyams_content.features.footer.interfaces import IFooterTarget
from pyams_content.features.header.interfaces import IHeaderTarget
from pyams_content.features.preview.interfaces import IPreviewTarget
+from pyams_content.features.redirect.interfaces import IRedirectionManagerTarget
from pyams_content.interfaces import WEBMASTER_ROLE, OPERATOR_ROLE
from pyams_content.root.interfaces import ISiteRootRoles, ISiteRootConfiguration, ISiteRoot, \
ISiteRootToolsConfiguration, ISiteRootBackOfficeConfiguration
@@ -52,7 +53,8 @@
@implementer(IDefaultProtectionPolicy, ISiteRoot, ISiteRootRoles, IPortalContext, ITagsManagerTarget,
- IIllustrationTarget, IHeaderTarget, IFooterTarget, IAlertTarget, IPreviewTarget)
+ IIllustrationTarget, IHeaderTarget, IFooterTarget, IAlertTarget, IRedirectionManagerTarget,
+ IPreviewTarget)
class SiteRoot(ProtectedObject, BaseSiteRoot, UserSkinnableContent):
"""Main site root"""