Skip to content

Commit

Permalink
Merge 77e57da into 2e67159
Browse files Browse the repository at this point in the history
  • Loading branch information
claytonc committed Sep 28, 2017
2 parents 2e67159 + 77e57da commit 8b3cb10
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ There's a frood who really knows where his towel is.
2.12b2 (unreleased)
^^^^^^^^^^^^^^^^^^^

- Implement prefetch for Facebook.
[claytonc]

- Add Open Graph fallback image for content lacking lead image;
the image should be uploaded in the control panel configlet and must comply with best practices;
canonical domain field was also moved to the new Open Graph field set in the configlet (closes `#130 <https://github.com/collective/sc.social.like/issues/130>`_).
Expand Down
14 changes: 14 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@ For more information see:

You can disable content validation using an option in the control panel configlet.

Prefetching Facebook
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. note::
This feature is only available for Dexterity-based content types.

Prefetching allows Facebook to download mobile content before someone clicks a link.

Prefetching is especially beneficial for people using Facebook on slow or poor network connections.

According to Facebook's `documentation <https://www.facebook.com/business/help/1514372351922333>`_:

The default is disable, but you can enable prefetch using an option in the control panel configlet.

Canonical URL and migration to HTTPS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
14 changes: 14 additions & 0 deletions sc/social/like/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@
handler=".subscribers.check_sharing_best_practices"
/>

<subscriber
zcml:condition="installed plone.app.contenttypes"
for="plone.dexterity.interfaces.IDexterityContent
Products.DCWorkflow.interfaces.IAfterTransitionEvent"
handler=".subscribers.prefetch_facebook"
/>

<subscriber
zcml:condition="installed plone.app.contenttypes"
for="plone.dexterity.interfaces.IDexterityContent
plone.dexterity.interfaces.IEditFinishedEvent"
handler=".subscribers.prefetch_facebook"
/>

<include file="behaviors.zcml" />

</configure>
11 changes: 11 additions & 0 deletions sc/social/like/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ class ISocialLikeSettings(model.Schema):
'facebook_app_id',
'fbbuttons',
'fbshowlikes',
'facebook_prefetch_enabled'
],
)

Expand Down Expand Up @@ -223,6 +224,16 @@ class ISocialLikeSettings(model.Schema):
default=True,
)

facebook_prefetch_enabled = schema.Bool(
title=_(u'Enable Prefetching Facebook?'),
description=_(
u'help_facebook_prefetch_enabled',
default=u'If enabled, an event is triggered so that Facebook '
u'downloads mobile content before someone clicks on a link.'
),
default=False,
)

model.fieldset(
'twitter', label=u'Twitter', fields=['twitter_username'])

Expand Down
9 changes: 9 additions & 0 deletions sc/social/like/locales/pt_BR/LC_MESSAGES/sc.social.like.po
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,12 @@ msgstr "Cancelar"
#: sc/social/like/vocabularies.py:36
msgid "vertical"
msgstr "vertical"


#: ./sc/social/like/interfaces.py:220
msgid "Enable Prefetching Facebook?"
msgstr ""

#: ./sc/social/like/interfaces.py:222
msgid "help_facebook_prefetch_enabled"
msgstr "Se ativado, um evento é acionado para que o Facebook faça o download de conteúdo móvel antes de alguém clicar em um link."
8 changes: 8 additions & 0 deletions sc/social/like/locales/sc.social.like.pot
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,11 @@ msgstr ""
#: ./sc/social/like/vocabularies.py:36
msgid "vertical"
msgstr ""

#: ./sc/social/like/interfaces.py:220
msgid "Enable Prefetching Facebook?"
msgstr ""

#: ./sc/social/like/interfaces.py:222
msgid "help_facebook_prefetch_enabled"
msgstr ""
33 changes: 33 additions & 0 deletions sc/social/like/subscribers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from zope.component import getUtility
from zope.schema.interfaces import WrongType

import requests
import traceback


Expand Down Expand Up @@ -151,3 +152,35 @@ def check_sharing_best_practices(obj, event):
validate_og_lead_image(image)
except ValueError as e:
api.portal.show_message(message=e.message, request=request, type='warning')


def prefetch_facebook(obj, event):
"""Prefetching in object if enable."""
record = ISocialLikeSettings.__identifier__ + '.facebook_prefetch_enabled'
prefetch_enable = api.portal.get_registry_record(record, default=False)
if not prefetch_enable:
return

try:
review_state = api.content.get_state(obj)
except WorkflowException:
# images and files have no associated workflow by default
review_state = 'published'

if review_state not in ('published', ):
return # can't prefetch non-public content

url = obj.absolute_url()
endpoint = 'https://graph.facebook.com/?id=' + url + '&scrape=true'
try:
r = requests.post(endpoint, timeout=5)
except requests.exceptions.RequestException as e:
logger.warn('Prefetch failure: ' + str(e.message))
return

if r.status_code == '200':
logger.info('Prefetch successful: ' + url)
else:
logger.warn(
'Prefetch error {code} ({reason}): {debug}'.format(
code=r.status_code, reason=r.reason, debug=str(r.json())))
5 changes: 5 additions & 0 deletions sc/social/like/tests/test_controlpanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ def test_twitter_username_record_in_registry(self):
self.assertTrue(hasattr(self.settings, 'twitter_username'))
self.assertEqual(self.settings.twitter_username, '')

def test_facebook_prefetch_enabled_record_in_registry(self):
self.assertTrue(hasattr(self.settings, 'facebook_prefetch_enabled'))
self.assertEqual(self.settings.facebook_prefetch_enabled, False)

def test_records_removed_on_uninstall(self):
qi = self.portal['portal_quickinstaller']

Expand All @@ -131,6 +135,7 @@ def test_records_removed_on_uninstall(self):
ISocialLikeSettings.__identifier__ + '.fbbuttons',
ISocialLikeSettings.__identifier__ + '.fbshowlikes',
ISocialLikeSettings.__identifier__ + '.twitter_username',
ISocialLikeSettings.__identifier__ + '.facebook_prefetch_enabled',
]

for r in records:
Expand Down
70 changes: 70 additions & 0 deletions sc/social/like/tests/test_subscribers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@
from sc.social.like.utils import MSG_INVALID_OG_DESCRIPTION
from sc.social.like.utils import MSG_INVALID_OG_LEAD_IMAGE_DIMENSIONS
from sc.social.like.utils import MSG_INVALID_OG_TITLE
from testfixtures import log_capture
from zope import schema
from zope.component import getUtility
from zope.event import notify

import logging
import requests_mock
import unittest


Expand Down Expand Up @@ -221,13 +224,80 @@ def test_validate_with_package_uninstalled(self):
notify(EditFinishedEvent(self.news_item))


class PrefetchTestCase(unittest.TestCase):

layer = INTEGRATION_TESTING

def setUp(self):
self.portal = self.layer['portal']

record = ISocialLikeSettings.__identifier__ + '.facebook_prefetch_enabled'
api.portal.set_registry_record(record, True)

with api.env.adopt_roles(['Manager']):
self.news_item = api.content.create(
self.portal, 'News Item', title=u'Lorem ipsum')

url = self.news_item.absolute_url()
self.endpoint = 'https://graph.facebook.com/?id=' + url + '&scrape=true'

@requests_mock.mock()
@log_capture(level=logging.INFO)
def test_facebook_prefetch_non_public(self, m, l):
with api.env.adopt_roles(['Manager']):
api.content.transition(self.news_item, 'submit')

# check no log entries
l.check()

@requests_mock.mock()
@log_capture(level=logging.INFO)
def test_facebook_prefetch(self, m, l):
m.post(self.endpoint, status_code='200')

with api.env.adopt_roles(['Manager']):
api.content.transition(self.news_item, 'publish')

# check log entries
msg = 'Prefetch successful: http://nohost/plone/lorem-ipsum'
expected = ('sc.social.like', 'INFO', msg)
l.check(expected)

@requests_mock.mock()
@log_capture(level=logging.INFO)
def test_facebook_prefetch_error(self, m, l):
import json
text = '{"error":{"message":"foo","type":"bar"}}'
m.post(self.endpoint, text=text, status_code='403', reason='Forbidden')

with api.env.adopt_roles(['Manager']):
api.content.transition(self.news_item, 'publish')

# check log entries
msg = 'Prefetch error 403 (Forbidden): ' + str(json.loads(text))
expected = ('sc.social.like', 'WARNING', msg)
l.check(expected)

@requests_mock.mock()
def test_facebook_prefetch_failure(self, m):
import requests
m.post(self.endpoint, exc=requests.exceptions.ConnectTimeout)

with api.env.adopt_roles(['Manager']):
api.content.transition(self.news_item, 'publish')

# transition should not be aborted
self.assertEqual(api.content.get_state(self.news_item), 'published')


def load_tests(loader, tests, pattern):
from sc.social.like.testing import HAS_DEXTERITY

test_cases = [ControlPanelTestCase]
if HAS_DEXTERITY:
# load validation tests on Dexterity-based content types only
test_cases.append(ValidationTestCase)
test_cases.append(PrefetchTestCase)

suite = unittest.TestSuite()
for test_class in test_cases:
Expand Down
13 changes: 7 additions & 6 deletions sc/social/like/tests/test_upgrades.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from plone import api
from plone.registry.interfaces import IRegistry
from sc.social.like.config import IS_PLONE_5
from sc.social.like.interfaces import ISocialLikeSettings
from sc.social.like.testing import HAS_DEXTERITY
Expand Down Expand Up @@ -110,7 +111,6 @@ def test_migrate_settings_to_registry(self):
assert step is not None

# simulate state on previous version
from plone.registry.interfaces import IRegistry
from sc.social.like.config import PROJECTNAME

# restore old property sheet
Expand Down Expand Up @@ -230,7 +230,6 @@ def test_add_fbshowlikes_record(self):
assert step is not None

# simulate state on previous version
from plone.registry.interfaces import IRegistry
registry = getUtility(IRegistry)
record = ISocialLikeSettings.__identifier__ + '.fbshowlikes'
del registry.records[record]
Expand Down Expand Up @@ -342,7 +341,6 @@ def test_add_validation_enabled_record(self):
self.assertIsNotNone(step)

# simulate state on previous version
from plone.registry.interfaces import IRegistry
registry = getUtility(IRegistry)
record = ISocialLikeSettings.__identifier__ + '.validation_enabled'
del registry.records[record]
Expand Down Expand Up @@ -370,23 +368,26 @@ def test_registrations(self):
self.assertEqual(self.total_steps, 1)

def test_add_validation_enabled_record(self):
title = u'Add fallback image in configlet'
title = u'Add new fields to configlet'
step = self.get_upgrade_step(title)
self.assertIsNotNone(step)

# simulate state on previous version
from plone.registry.interfaces import IRegistry
registry = getUtility(IRegistry)
record = ISocialLikeSettings.__identifier__ + '.fallback_image'
del registry.records[record]
self.assertNotIn(record, registry)
record = ISocialLikeSettings.__identifier__ + '.facebook_prefetch_enabled'
del registry.records[record]
self.assertNotIn(record, registry)

with self.assertRaises(KeyError):
registry.forInterface(ISocialLikeSettings)

# run the upgrade step to validate the update
self.execute_upgrade_step(step)

# test the new field is in place
# test the new fields are in place
settings = registry.forInterface(ISocialLikeSettings)
self.assertIsNone(settings.fallback_image)
self.assertFalse(settings.facebook_prefetch_enabled)
4 changes: 2 additions & 2 deletions sc/social/like/upgrades/v3048/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
profile="sc.social.like:default">

<genericsetup:upgradeDepends
title="Add fallback image in configlet"
description="Adds new field to the control panel configlet."
title="Add new fields to configlet"
description="Adds fallback image and Facebook prefetch features."
import_steps="plone.app.registry"
/>

Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
'Products.CMFPlone >=4.3',
'Products.CMFQuickInstallerTool',
'Products.GenericSetup',
'requests',
'setuptools',
'zope.component',
'zope.i18nmessageid',
Expand All @@ -70,7 +71,9 @@
'plone.testing',
'Products.statusmessages',
'profilehooks',
'requests-mock',
'robotsuite',
'testfixtures',
'zope.event',
],
'develop': ['docutils'],
Expand Down

0 comments on commit 8b3cb10

Please sign in to comment.