--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/metas.py Thu Feb 13 11:43:31 2020 +0100
@@ -0,0 +1,201 @@
+#
+# Copyright (c) 2008-2015 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'
+
+from html import escape
+
+from pyramid.interfaces import IRequest
+from zope.interface import Interface, implementer
+
+from pyams_i18n.interfaces import II18n
+from pyams_skin.interfaces.configuration import IConfiguration
+from pyams_skin.interfaces.extension import IGoogleAnalyticsInfo
+from pyams_skin.interfaces.metas import IHTMLContentMetas, IMetaHeader
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
+from pyams_utils.interfaces.site import ISiteRoot
+from pyams_utils.interfaces.tales import ITALESExtension
+from pyams_utils.url import canonical_url
+
+
+#
+# 'metas' TALES extension
+#
+
+@adapter_config(name='metas', context=(Interface, IRequest, Interface), provides=ITALESExtension)
+class MetasTalesExtension(ContextRequestViewAdapter):
+ """extension:metas TALES extension"""
+
+ def render(self, context=None):
+ if context is None:
+ context = self.context
+ result = []
+ for name, adapter in sorted(self.request.registry.getAdapters((context, self.request, self.view),
+ IHTMLContentMetas),
+ key=lambda x: getattr(x[1], 'order', 9999)):
+ result.extend([meta.render() for meta in adapter.get_metas()])
+ return '\n\t'.join(result)
+
+
+#
+# Custom metas headers
+#
+
+def escape_value(value):
+ return escape(value) if isinstance(value, str) else value
+
+
+@implementer(IMetaHeader)
+class BaseMeta(object):
+ """Base HTML meta header"""
+
+ def __init__(self, tag='meta', value=None, **attrs):
+ self.tag = tag
+ self.value = value
+ self.attrs = attrs
+
+ def render(self):
+ attributes = ' '.join(('{}="{}"'.format(k, v) for k, v in self.attrs.items()))
+ if self.value:
+ return '<{0} {1}>{2}</{0}>'.format(self.tag, attributes, self.value)
+ else:
+ return '<{} {} />'.format(self.tag, attributes)
+
+
+class HTMLTagMeta(BaseMeta):
+ """HTML tag meta header"""
+
+ def __init__(self, tag, content, **attrs):
+ super(HTMLTagMeta, self).__init__(tag, content, **attrs)
+
+
+class HTTPEquivMeta(BaseMeta):
+ """HTTP-Equiv meta header"""
+
+ def __init__(self, http_equiv, value):
+ super(HTTPEquivMeta, self).__init__(**{'http-equiv': http_equiv, 'content': escape_value(value)})
+
+
+class ValueMeta(BaseMeta):
+ """Basic value meta header"""
+
+ def __init__(self, name, value):
+ super(ValueMeta, self).__init__(**{name: escape_value(value)})
+
+
+class ContentMeta(BaseMeta):
+ """Content meta header"""
+
+ def __init__(self, name, value):
+ super(ContentMeta, self).__init__(name=name, content=escape_value(value))
+
+
+class PropertyMeta(BaseMeta):
+ """Property meta header"""
+
+ def __init__(self, property, value):
+ super(PropertyMeta, self).__init__(property=property, content=escape_value(value))
+
+
+class SchemaMeta(BaseMeta):
+ """Schema.org property meta header"""
+
+ def __init__(self, name, value):
+ super(SchemaMeta, self).__init__(itemprop=name, content=escape_value(value))
+
+
+class LinkMeta(BaseMeta):
+ """Link meta header"""
+
+ def __init__(self, rel, type, href, **kwargs):
+ super(LinkMeta, self).__init__('link', rel=rel, type=type, href=escape_value(href), **kwargs)
+
+
+#
+# Default metas headers
+#
+
+@adapter_config(name='layout', context=(Interface, Interface, Interface), provides=IHTMLContentMetas)
+class LayoutMetasAdapter(ContextRequestViewAdapter):
+ """Basic layout metas adapter"""
+
+ order = -1
+
+ @staticmethod
+ def get_metas():
+ yield HTTPEquivMeta('X-UA-Compatible', 'IE=edge,chrome=1')
+ yield ContentMeta('HandheldFriendly', 'True')
+ yield ContentMeta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no')
+
+
+@adapter_config(name='title', context=(Interface, Interface, Interface), provides=IHTMLContentMetas)
+class TitleMetasAdapter(ContextRequestViewAdapter):
+ """Title metas adapter"""
+
+ order = 1
+
+ def get_metas(self):
+ title = II18n(self.context).query_attribute('title', request=self.request)
+ yield HTMLTagMeta('title', title or '----')
+
+
+@adapter_config(name='title', context=(ISiteRoot, Interface, Interface), provides=IHTMLContentMetas)
+class SiteRootTitleMetasAdapter(ContextRequestViewAdapter):
+ """Site root title metas adapter"""
+
+ order = 1
+
+ def get_metas(self):
+ config = IConfiguration(self.context)
+ yield HTMLTagMeta('title', config.title or '----')
+ yield ContentMeta('description', config.description)
+
+
+@adapter_config(name='canonical', context=(Interface, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
+class CanonicalURLMetasAdapter(ContextRequestViewAdapter):
+ """Canonical URL metas adapter"""
+
+ order = 2
+
+ def get_metas(self):
+ target_url = canonical_url(self.context, self.request).replace('+', '%2B')
+ yield BaseMeta(rel='canonical', href=target_url)
+
+
+@adapter_config(name='content-type', context=(Interface, Interface, Interface), provides=IHTMLContentMetas)
+class ContentTypeMetasAdapter(ContextRequestViewAdapter):
+ """Content-type metas adapter"""
+
+ order = 10
+
+ @staticmethod
+ def get_metas():
+ yield HTTPEquivMeta('Content-Type', 'text/html; charset=utf-8')
+ yield ValueMeta('charset', 'utf-8')
+
+
+@adapter_config(name='google-site-verification', context=(Interface, Interface, Interface), provides=IHTMLContentMetas)
+class VerificationCodeMetasAdapter(ContextRequestViewAdapter):
+ """Google verification code metas adapter"""
+
+ order = 11
+
+ def __new__(cls, context, request, view):
+ info = IGoogleAnalyticsInfo(request.root)
+ if not info.verification_code:
+ return None
+ return ContextRequestViewAdapter.__new__(cls)
+
+ def get_metas(self):
+ info = IGoogleAnalyticsInfo(self.request.root)
+ yield ContentMeta('google-site-verification', info.verification_code)