Commit 258e3cd5 authored by Vladimir Popescu's avatar Vladimir Popescu
Browse files

[web/views] extract cube embed (closes #1916015)

parent d92919c995cc
......@@ -7,6 +7,9 @@ API changes
* The SIOC views and adapters have been removed from CubicWeb and moved to the
`sioc` cube.
* The web page embedding views and adapters have been removed from CubicWeb and
moved to the `embed` cube.
Deprecated Code Drops
......@@ -41,6 +41,12 @@ if applcubicwebversion < (3, 17, 0) and cubicwebversion >= (3, 17, 0):
if not confirm('In cubicweb 3.17 sioc views have been moved to the sioc '
'cube, which is not installed. Continue anyway?'):
add_cube('embed', update_database=False)
except ImportError:
if not confirm('In cubicweb 3.17 embedding views have been moved to the embed '
'cube, which is not installed. Continue anyway?'):
if applcubicwebversion <= (3, 13, 0) and cubicwebversion >= (3, 13, 1):
sql('ALTER TABLE entities ADD asource VARCHAR(64)')
......@@ -533,18 +533,6 @@ class EditControllerTC(CubicWebTC):
p.__class__.skip_copy_for = old_skips
class EmbedControllerTC(CubicWebTC):
def test_nonregr_embed_publish(self):
# This test looks a bit stupid but at least it will probably
# fail if the controller API changes and if EmbedController is not
# updated (which is what happened before this test)
req = self.request()
req.form['url'] = ''
controller = self.vreg['controllers'].select('embed', req)
result = controller.publish(rset=None)
class ReportBugControllerTC(CubicWebTC):
def test_usable_by_guest(self):
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact --
# This file is part of CubicWeb.
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <>.
from logilab.common.testlib import TestCase, unittest_main
from cubicweb.web.views.embedding import prefix_links
class UILIBTC(TestCase):
def test_prefix_links(self):
"""suppose we are embedding"""
orig = ['<a href="">perdu ?</a>',
'<a href="">perdu ?</a>',
'<a href="/page2.html">perdu ?</a>',
'<a href="page3.html">perdu ?</a>',
'<img src=""/>',
'<img src="/img.png"/>',
'<img src="img.png"/>',
expected = ['<a href="">perdu ?</a>',
'<a href="">perdu ?</a>',
'<a href="">perdu ?</a>',
'<a href="">perdu ?</a>',
'<img src=""/>',
'<img src=""/>',
'<img src=""/>',
for orig_a, expected_a in zip(orig, expected):
got = prefix_links(orig_a, 'PREFIX', '')
self.assertEqual(got, expected_a)
if __name__ == '__main__':
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact --
# This file is part of CubicWeb.
......@@ -19,172 +19,20 @@
__docformat__ = "restructuredtext en"
_ = unicode
import re
from urlparse import urljoin
from urllib2 import urlopen, Request, HTTPError
from urllib import quote as urlquote # XXX should use view.url_quote method
from logilab.mtconverter import guess_encoding
from cubicweb.predicates import (one_line_rset, score_entity, implements,
adaptable, match_search_state)
from cubicweb.interfaces import IEmbedable
from cubicweb.view import NOINDEX, NOFOLLOW, EntityAdapter, implements_adapter_compat
from cubicweb.uilib import soup2xhtml
from cubicweb.web.controller import Controller
from cubicweb.web.action import Action
from cubicweb.web.views import basetemplates
class IEmbedableAdapter(EntityAdapter):
"""interface for embedable entities"""
__needs_bw_compat__ = True
__regid__ = 'IEmbedable'
__select__ = implements(IEmbedable, warn=False) # XXX for bw compat, should be abstract
def embeded_url(self):
"""embed action interface"""
raise NotImplementedError
class ExternalTemplate(basetemplates.TheMainTemplate):
"""template embeding an external web pages into CubicWeb web interface
__regid__ = 'external'
def call(self, body):
# XXX fallback to HTML 4 mode when embeding ?
self._cw.search_state = ('normal',)
self.template_header(self.content_type, None, self._cw._('external page'),
class EmbedController(Controller):
__regid__ = 'embed'
template = 'external'
def publish(self, rset=None):
req = self._cw
if 'custom_css' in req.form:
embedded_url = req.form['url']
allowed = self._cw.vreg.config['embed-allowed']
_ = req._
if allowed is None or not allowed.match(embedded_url):
body = '<h2>%s</h2><h3>%s</h3>' % (
_('error while embedding page'),
_('embedding this url is forbidden'))
prefix = req.build_url(self.__regid__, url='')
authorization = req.get_header('Authorization')
if authorization:
headers = {'Authorization' : authorization}
headers = {}
body = embed_external_page(embedded_url, prefix,
headers, req.form.get('custom_css'))
body = soup2xhtml(body, self._cw.encoding)
except HTTPError as err:
body = '<h2>%s</h2><h3>%s</h3>' % (
_('error while embedding page'), err)
rset = self.process_rql()
return self._cw.vreg['views'].main_template(req, self.template,
rset=rset, body=body)
def entity_has_embedable_url(entity):
"""return 1 if the entity provides an allowed embedable url"""
url = entity.cw_adapt_to('IEmbedable').embeded_url()
if not url or not url.strip():
return 0
allowed = entity._cw.vreg.config['embed-allowed']
if allowed is None or not allowed.match(url):
return 0
return 1
class EmbedAction(Action):
"""display an 'embed' link on entity implementing `embeded_url` method
if the returned url match embeding configuration
__regid__ = 'embed'
__select__ = (one_line_rset() & match_search_state('normal')
& adaptable('IEmbedable')
& score_entity(entity_has_embedable_url))
title = _('embed')
def url(self, row=0):
entity = self.cw_rset.get_entity(row, 0)
url = urljoin(self._cw.base_url(), entity.cw_adapt_to('IEmbedable').embeded_url())
if 'rql' in self._cw.form:
return self._cw.build_url('embed', url=url, rql=self._cw.form['rql'])
return self._cw.build_url('embed', url=url)
# functions doing necessary substitutions to embed an external html page ######
BODY_RGX = re.compile('<body.*?>(.*?)</body>', re.I | re.S | re.U)
HREF_RGX = re.compile('<a\s+href="([^"]*)"', re.I | re.S | re.U)
SRC_RGX = re.compile('<img\s+src="([^"]*)"', re.I | re.S | re.U)
class replace_href:
def __init__(self, prefix, custom_css=None):
self.prefix = prefix
self.custom_css = custom_css
def __call__(self, match):
original_url =
url = self.prefix + urlquote(original_url, safe='')
if self.custom_css is not None:
if '?' in url:
url = '%s&amp;custom_css=%s' % (url, self.custom_css)
url = '%s?custom_css=%s' % (url, self.custom_css)
return '<a href="%s"' % url
class absolutize_links:
def __init__(self, embedded_url, tag, custom_css=None):
self.embedded_url = embedded_url
self.tag = tag
self.custom_css = custom_css
def __call__(self, match):
original_url =
if '://' in original_url:
return # leave it unchanged
return '%s="%s"' % (self.tag, urljoin(self.embedded_url, original_url))
def prefix_links(body, prefix, embedded_url, custom_css=None):
filters = ((HREF_RGX, absolutize_links(embedded_url, '<a href', custom_css)),
(SRC_RGX, absolutize_links(embedded_url, '<img src')),
(HREF_RGX, replace_href(prefix, custom_css)))
for rgx, repl in filters:
body = rgx.sub(repl, body)
return body
def embed_external_page(url, prefix, headers=None, custom_css=None):
req = Request(url, headers=(headers or {}))
content = urlopen(req).read()
page_source = unicode(content, guess_encoding(content), 'replace')
page_source = page_source
match =
if match is None:
return page_source
return prefix_links(, prefix, url, custom_css)
from logilab.common.deprecation import class_moved, moved
from cubes.embed.views import *
IEmbedableAdapter = class_moved(IEmbedableAdapter, message='[3.17] IEmbedableAdapter moved to cubes.embed.views')
ExternalTemplate = class_moved(ExternalTemplate, message='[3.17] IEmbedableAdapter moved to cubes.embed.views')
EmbedController = class_moved(EmbedController, message='[3.17] IEmbedableAdapter moved to cubes.embed.views')
entity_has_embedable_url = moved('cubes.embed.views', 'entity_has_embedable_url')
EmbedAction = class_moved(EmbedAction, message='[3.17] EmbedAction moved to cubes.embed.views')
replace_href = class_moved(replace_href, message='[3.17] replace_href moved to cubes.embed.views')
embed_external_page = moved('cubes.embed.views', 'embed_external_page')
absolutize_links = class_moved(absolutize_links, message='[3.17] absolutize_links moved to cubes.embed.views')
prefix_links = moved('cubes.embed.views', 'prefix_links')
except ImportError:
from cubicweb.web import LOGGER
LOGGER.warning('[3.17] embedding extracted to cube embed that was not found. try installing it.')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment