Updated Opengraph metas adapters
authorThierry Florac <tflorac@ulthar.net>
Wed, 16 Jan 2019 18:56:36 +0100
changeset 379 240417d006df
parent 378 72293e510f03
child 380 f549a5cd4781
Updated Opengraph metas adapters
src/pyams_default_theme/root/__init__.py
src/pyams_default_theme/root/opengraph.py
src/pyams_default_theme/shared/common/opengraph.py
src/pyams_default_theme/shared/site/__init__.py
--- /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"""