--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/root/__init__.py Wed Jan 16 18:56:36 2019 +0100
@@ -0,0 +1,15 @@
+#
+# Copyright (c) 2008-2019 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'
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/root/opengraph.py Wed Jan 16 18:56:36 2019 +0100
@@ -0,0 +1,107 @@
+#
+# Copyright (c) 2008-2019 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 zope.interface import Interface
+
+from pyams_content.component.illustration import IIllustration
+from pyams_content.features.share.interfaces import ISocialShareInfo
+from pyams_content.root import ISiteRoot
+from pyams_file.interfaces import IThumbnails
+from pyams_i18n.interfaces import II18n, INegotiator
+from pyams_skin.interfaces.configuration import IConfiguration
+from pyams_skin.interfaces.metas import IHTMLContentMetas
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_skin.metas import ContentMeta, PropertyMeta, SchemaMeta
+from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
+from pyams_utils.registry import get_utility
+from pyams_utils.url import absolute_url, canonical_url
+
+
+@adapter_config(name='opengraph', context=(ISiteRoot, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
+class SiteRootOpengraphMetasAdapter(ContextRequestViewAdapter):
+ """Opengraph site root metas adapter"""
+
+ order = 15
+
+ def get_metas(self):
+ context = self.context
+ request = self.request
+ i18n = II18n(context)
+ negotiator = get_utility(INegotiator)
+ lang = negotiator.server_language
+
+ configuration = IConfiguration(context)
+ title = configuration.title
+ description = configuration.description
+
+ # main properties
+ yield PropertyMeta('og:type', 'website')
+ if title:
+ yield PropertyMeta('og:title', title)
+ if description:
+ yield PropertyMeta('og:description', description)
+
+ # URL and site name
+ yield PropertyMeta('og:url', canonical_url(context, request))
+ yield PropertyMeta('og:site_name', title)
+
+ # illustration properties
+ registry = request.registry
+ thumbnail = None
+ illustration = registry.queryAdapter(context, IIllustration, name='link')
+ if (illustration is None) or (not illustration.has_data()):
+ illustration = registry.queryAdapter(context, IIllustration)
+ if (illustration is not None) and illustration.has_data():
+ data = II18n(illustration).query_attribute('data', lang=lang, request=request)
+ thumbnail = IThumbnails(data).get_thumbnail('800x600')
+ yield PropertyMeta('og:image', absolute_url(thumbnail, request))
+ if request.scheme == 'https':
+ yield PropertyMeta('og:image:secure_url', absolute_url(thumbnail, request))
+ else:
+ yield PropertyMeta('og:image:url', absolute_url(thumbnail, request))
+ yield PropertyMeta('og:image:type', thumbnail.content_type)
+ image_size = thumbnail.image_size
+ yield PropertyMeta('og:image:width', image_size[0])
+ yield PropertyMeta('og:image:height', image_size[1])
+ alt = II18n(illustration).query_attribute('alt_title', lang=lang, request=request)
+ if alt:
+ yield PropertyMeta('og:image:alt', alt)
+
+ # locales properties
+ yield PropertyMeta('og:locale', lang)
+ for other_lang in negotiator.offered_languages or ():
+ if other_lang != lang:
+ yield PropertyMeta('og:locale:alternate', other_lang)
+
+ # twitter properties
+ share_info = ISocialShareInfo(request.root, None)
+ if (share_info is not None) and share_info.twitter_account:
+ yield ContentMeta('twitter:site', share_info.twitter_account)
+ yield ContentMeta('twitter:creator', share_info.twitter_creator_account or share_info.twitter_account)
+ if illustration is not None:
+ yield ContentMeta('twitter:card', 'summary_large_image')
+ else:
+ yield ContentMeta('twitter:card', 'summary')
+ if title:
+ yield ContentMeta('twitter:title', title)
+ if description:
+ yield ContentMeta('twitter:description', description)
+
+ # Schema.org properties
+ if title:
+ yield SchemaMeta('name', title)
+ if description:
+ yield SchemaMeta('description', description)
+ if thumbnail is not None:
+ yield SchemaMeta('image', absolute_url(thumbnail, request))
--- a/src/pyams_default_theme/shared/common/opengraph.py Wed Jan 16 18:55:19 2019 +0100
+++ b/src/pyams_default_theme/shared/common/opengraph.py Wed Jan 16 18:56:36 2019 +0100
@@ -17,13 +17,14 @@
from pyams_content.component.illustration import IIllustration, IIllustrationTarget
from pyams_content.component.theme import ITagsInfo
+from pyams_content.features.share.interfaces import ISocialShareInfo
from pyams_content.shared.common import IWfSharedContent
from pyams_file.interfaces import IThumbnails
from pyams_i18n.interfaces import II18n, II18nManager, INegotiator
from pyams_skin.interfaces.configuration import IConfiguration
from pyams_skin.interfaces.metas import IHTMLContentMetas
from pyams_skin.layer import IPyAMSUserLayer
-from pyams_skin.metas import PropertyMeta
+from pyams_skin.metas import ContentMeta, PropertyMeta, SchemaMeta
from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
from pyams_utils.registry import get_utility
from pyams_utils.traversing import get_parent
@@ -32,21 +33,27 @@
@adapter_config(name='opengraph', context=(IWfSharedContent, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
-class OpengraphSharedContentMetasAdapter(ContextRequestViewAdapter):
- """Opengraph shared content metas adapter"""
+class SharedContentOpengraphMetasAdapter(ContextRequestViewAdapter):
+ """Shared content opengraph metas adapter"""
order = 15
def get_metas(self):
context = self.context
+ request = self.request
i18n = II18n(context)
- request = self.request
negotiator = get_utility(INegotiator)
lang = negotiator.server_language
+
+ description = i18n.query_attribute('description', lang=lang, request=request) or \
+ i18n.query_attribute('header', lang=lang, request=request)
+
# main properties
+ yield PropertyMeta('og:type', 'article')
yield PropertyMeta('og:title', i18n.query_attribute('title', lang=lang, request=request))
- yield PropertyMeta('og:description', i18n.query_attribute('description', lang=lang, request=request) or '')
- yield PropertyMeta('og:type', 'article')
+ if description:
+ yield PropertyMeta('og:description', description)
+
# workflow informations
dc = IZopeDublinCore(context, None)
if (dc is not None) and dc.modified:
@@ -57,18 +64,22 @@
yield PropertyMeta('article:published_time', pub_info.first_publication_date.isoformat())
if pub_info.publication_expiration_date:
yield PropertyMeta('article:expiration_time', pub_info.publication_expiration_date.isoformat())
+
# tags
tags = ITagsInfo(context, None)
if tags is not None:
for tag in tags.tags or ():
yield PropertyMeta('article:tag', tag.label)
+
# URL and site name
yield PropertyMeta('og:url', canonical_url(context, request))
configuration = IConfiguration(request.root)
yield PropertyMeta('og:site_name', configuration.title)
+
# illustration properties
registry = request.registry
illustration = None
+ thumbnail = None
target = context
while target is not None:
illustration = registry.queryAdapter(target, IIllustration, name='link')
@@ -80,14 +91,41 @@
if (target is not None) and (illustration is not None):
data = II18n(illustration).query_attribute('data', lang=lang, request=request)
thumbnail = IThumbnails(data).get_thumbnail('800x600')
- yield PropertyMeta('og:image:url', absolute_url(thumbnail, self.request))
+ yield PropertyMeta('og:image', absolute_url(thumbnail, request))
+ if request.scheme == 'https':
+ yield PropertyMeta('og:image:secure_url', absolute_url(thumbnail, request))
+ else:
+ yield PropertyMeta('og:image:url', absolute_url(thumbnail, request))
yield PropertyMeta('og:image:type', thumbnail.content_type)
image_size = thumbnail.image_size
yield PropertyMeta('og:image:width', image_size[0])
yield PropertyMeta('og:image:height', image_size[1])
- yield PropertyMeta('og:image:alt', II18n(illustration).query_attribute('alt_title', lang=lang,
- request=request))
+ alt = II18n(illustration).query_attribute('alt_title', lang=lang, request=request)
+ if alt:
+ yield PropertyMeta('og:image:alt', alt)
+
# locales properties
yield PropertyMeta('og:locale', lang)
- for lang in II18nManager(context).languages or ():
- yield PropertyMeta('og:locale:alternate', lang)
+ for other_lang in II18nManager(context).languages or ():
+ if other_lang != lang:
+ yield PropertyMeta('og:locale:alternate', other_lang)
+
+ # twitter properties
+ share_info = ISocialShareInfo(request.root, None)
+ if (share_info is not None) and share_info.twitter_account:
+ yield ContentMeta('twitter:site', share_info.twitter_account)
+ yield ContentMeta('twitter:creator', share_info.twitter_creator_account or share_info.twitter_account)
+ if illustration is not None:
+ yield ContentMeta('twitter:card', 'summary_large_image')
+ else:
+ yield ContentMeta('twitter:card', 'summary')
+ yield ContentMeta('twitter:title', i18n.query_attribute('title', lang=lang, request=request))
+ if description:
+ yield ContentMeta('twitter:description', description)
+
+ # Schema.org properties
+ yield SchemaMeta('name', i18n.query_attribute('title', lang=lang, request=request))
+ if description:
+ yield SchemaMeta('description', description)
+ if thumbnail is not None:
+ yield SchemaMeta('image', absolute_url(thumbnail, request))
--- a/src/pyams_default_theme/shared/site/__init__.py Wed Jan 16 18:55:19 2019 +0100
+++ b/src/pyams_default_theme/shared/site/__init__.py Wed Jan 16 18:56:36 2019 +0100
@@ -10,6 +10,15 @@
# FOR A PARTICULAR PURPOSE.
#
+from pyams_content.component.illustration import IIllustration
+from pyams_content.component.theme import ITagsInfo
+from pyams_content.features.share.interfaces import ISocialShareInfo
+from pyams_file.interfaces import IThumbnails
+from pyams_skin.interfaces.configuration import IConfiguration
+from pyams_utils.registry import get_utility
+from pyams_workflow.interfaces import IWorkflowPublicationInfo
+
+
__docformat__ = 'restructuredtext'
from zope.interface import Interface
@@ -17,16 +26,16 @@
from pyams_content.shared.site import IWfSiteTopic
from pyams_content.shared.site.interfaces import ISiteContainer, ISiteFolder, ISiteManager
from pyams_default_theme.viewlet.breadcrumb import BreadcrumbsAdapter
-from pyams_i18n.interfaces import II18n
+from pyams_i18n.interfaces import II18n, INegotiator, II18nManager
from pyams_sequence.interfaces import ISequentialIdInfo
from pyams_skin.interfaces.metas import IHTMLContentMetas
from pyams_skin.interfaces.viewlet import IBreadcrumbs
from pyams_skin.layer import IPyAMSUserLayer
-from pyams_skin.metas import ContentMeta, HTMLTagMeta
+from pyams_skin.metas import ContentMeta, HTMLTagMeta, PropertyMeta, SchemaMeta
from pyams_utils.adapter import ContextRequestAdapter, ContextRequestViewAdapter, adapter_config
from pyams_utils.interfaces.url import ICanonicalURL
from pyams_utils.traversing import get_parent
-from pyams_utils.url import absolute_url
+from pyams_utils.url import absolute_url, canonical_url
@adapter_config(name='title', context=(ISiteManager, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
@@ -37,16 +46,116 @@
order = 1
def get_metas(self):
- i18n = II18n(self.context)
- title = i18n.query_attribute('title', request=self.request)
+ context = self.context
+ request = self.request
+ i18n = II18n(context)
+ negotiator = get_utility(INegotiator)
+ lang = negotiator.server_language
+ title = i18n.query_attribute('title', lang=lang, request=request)
yield HTMLTagMeta('title', title)
- description = i18n.query_attribute('description', request=self.request)
- if (not description) and hasattr(self.context, 'header'):
- description = i18n.query_attribute('header', request=self.request)
+ description = i18n.query_attribute('description', lang=lang, request=request)
+ if (not description) and hasattr(context, 'header'):
+ description = i18n.query_attribute('header', lang=lang, request=request)
if description:
yield ContentMeta('description', description)
+#
+# Site container Opengraph metas
+#
+
+@adapter_config(name='opengraph', context=(ISiteManager, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
+@adapter_config(name='opengraph', context=(ISiteFolder, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
+class SiteManagerOpengraphMetasAdapter(ContextRequestViewAdapter):
+ """Site manager Opengraph metas adapter"""
+
+ order = 15
+
+ def get_metas(self):
+ context = self.context
+ request = self.request
+ i18n = II18n(context)
+ negotiator = get_utility(INegotiator)
+ lang = negotiator.server_language
+
+ description = i18n.query_attribute('description', lang=lang, request=request)
+ if (not description) and hasattr(context, 'header'):
+ description = i18n.query_attribute('header', request=request)
+
+ # main properties
+ yield PropertyMeta('og:type', 'article')
+ yield PropertyMeta('og:title', i18n.query_attribute('title', lang=lang, request=request))
+ if description:
+ yield PropertyMeta('og:description', description)
+
+ # workflow informations
+ pub_info = IWorkflowPublicationInfo(context, None)
+ if pub_info is not None:
+ if pub_info.first_publication_date:
+ yield PropertyMeta('article:published_time', pub_info.first_publication_date.isoformat())
+ if pub_info.publication_expiration_date:
+ yield PropertyMeta('article:expiration_time', pub_info.publication_expiration_date.isoformat())
+
+ # tags
+ tags = ITagsInfo(context, None)
+ if tags is not None:
+ for tag in tags.tags or ():
+ yield PropertyMeta('article:tag', tag.label)
+
+ # URL and site name
+ yield PropertyMeta('og:url', canonical_url(context, request))
+ configuration = IConfiguration(request.root)
+ yield PropertyMeta('og:site_name', configuration.title)
+
+ # illustration properties
+ registry = request.registry
+ thumbnail = None
+ illustration = registry.queryAdapter(context, IIllustration, name='link')
+ if (illustration is None) or (not illustration.has_data()):
+ illustration = registry.queryAdapter(context, IIllustration)
+ if (illustration is not None) and illustration.has_data():
+ data = II18n(illustration).query_attribute('data', lang=lang, request=request)
+ thumbnail = IThumbnails(data).get_thumbnail('800x600')
+ yield PropertyMeta('og:image', absolute_url(thumbnail, request))
+ if request.scheme == 'https':
+ yield PropertyMeta('og:image:secure_url', absolute_url(thumbnail, request))
+ else:
+ yield PropertyMeta('og:image:url', absolute_url(thumbnail, request))
+ yield PropertyMeta('og:image:type', thumbnail.content_type)
+ image_size = thumbnail.image_size
+ yield PropertyMeta('og:image:width', image_size[0])
+ yield PropertyMeta('og:image:height', image_size[1])
+ alt = II18n(illustration).query_attribute('alt_title', lang=lang, request=request)
+ if alt:
+ yield PropertyMeta('og:image:alt', alt)
+
+ # locales properties
+ yield PropertyMeta('og:locale', lang)
+ for other_lang in II18nManager(context).languages or ():
+ if other_lang != lang:
+ yield PropertyMeta('og:locale:alternate', other_lang)
+
+ # twitter properties
+ share_info = ISocialShareInfo(request.root, None)
+ if (share_info is not None) and share_info.twitter_account:
+ yield ContentMeta('twitter:site', share_info.twitter_account)
+ yield ContentMeta('twitter:creator', share_info.twitter_creator_account or share_info.twitter_account)
+ if illustration is not None:
+ yield ContentMeta('twitter:card', 'summary_large_image')
+ else:
+ yield ContentMeta('twitter:card', 'summary')
+ yield ContentMeta('twitter:title', i18n.query_attribute('title', lang=lang, request=request))
+ if description:
+ yield ContentMeta('twitter:description', description)
+
+ # Schema.org properties
+ yield SchemaMeta('name', i18n.query_attribute('title', lang=lang, request=request))
+ if description:
+ yield SchemaMeta('description', description)
+ if thumbnail is not None:
+ yield SchemaMeta('image', absolute_url(thumbnail, request))
+
+
@adapter_config(context=(IWfSiteTopic, IPyAMSUserLayer), provides=ICanonicalURL)
class SiteTopicCanonicalUrlAdapter(ContextRequestAdapter):
"""Site topic canonical URL adapter"""