# HG changeset patch # User Thierry Florac # Date 1547661396 -3600 # Node ID 240417d006df5130b6cc263ebdf92e7ec6361328 # Parent 72293e510f032436befefe9ed8bc15492e7847c3 Updated Opengraph metas adapters diff -r 72293e510f03 -r 240417d006df src/pyams_default_theme/root/__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 +# 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' + + diff -r 72293e510f03 -r 240417d006df src/pyams_default_theme/root/opengraph.py --- /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 +# 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)) diff -r 72293e510f03 -r 240417d006df src/pyams_default_theme/shared/common/opengraph.py --- 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)) diff -r 72293e510f03 -r 240417d006df src/pyams_default_theme/shared/site/__init__.py --- 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"""