Plone 5 Upgrade Without UUID Links
If you're upgrading a Plone site to 5 and the site was not use UUID links, you'll notice a lot of your links are probably broken. The reason this happens is because Plone 5 no longer provides a <base> tag in the HTML output which non-uuid linked sites required to work properly.
To fix, create a python file named migrate-to-uuid.py with the following contents:
from AccessControl.SecurityManagement import newSecurityManager
from Acquisition import aq_parent
from Products.Archetypes.Field import TextField
from Products.CMFPlone.interfaces.siteroot import IPloneSiteRoot
from lxml.html import fromstring
from lxml.html import tostring
from plone.app.textfield.value import RichTextValue
from plone.dexterity.interfaces import IDexterityContent
from plone.uuid.interfaces import IUUID
import transaction
def getUID(ob):
try:
return IUUID(ob)
except TypeError:
return ob.UID()
def findObject(from_obj, path):
while not IPloneSiteRoot.providedBy(from_obj):
found = from_obj.restrictedTraverse(path, None)
if found:
return found
from_obj = aq_parent(from_obj)
def transform_links_to_uuid(ob, html):
if not html:
return html
changes = False
try:
dom = fromstring(html)
except:
print('Error parsing DOM from content: %s' % '/'.join(ob.getPhysicalPath()))
return html
for el in dom.cssselect('a'):
href = el.attrib.get('href', '')
if (not href or href.startswith('http://') or
href.startswith('https://') or href.startswith('/') or
'resolveuid' in href):
continue
link_obj = findObject(ob, href)
if link_obj:
uuid = getUID(link_obj)
el.attrib.update({
'href': 'resolveuid/%s' % uuid,
'data-linktype': 'internal',
'data-val': uuid
})
changes = True
for el in dom.cssselect('img'):
src = el.attrib.get('src', '')
if (not src or src.startswith('http://') or
src.startswith('https://') or src.startswith('/') or
'resolveuid' in src):
continue
src, _, scale = src.partition('/@@images/image/')
if not scale:
src, _, scale = src.partition('/image_')
img_obj = findObject(ob, src)
if img_obj:
uuid = getUID(img_obj)
new_href = 'resolveuid/%s' % uuid
if scale:
new_href += '/@@images/image/' + scale
attribs = {
'src': new_href,
'data-linktype': 'image',
'data-val': uuid
}
if scale:
attribs['data-scale'] = scale
el.attrib.update(attribs)
changes = True
if changes:
return tostring(dom)
else:
return html
def migrate_dexterity_to_uuid_links(ob):
try:
orig = ob.text.raw
new = transform_links_to_uuid(ob, orig)
if new != orig:
ob.text = RichTextValue(
new, mimeType=ob.text.mimeType, outputMimeType=ob.text.outputMimeType)
print('Fixed content not using UUID links: %s' % '/'.join(ob.getPhysicalPath()))
return 1
except AttributeError:
pass
return 0
def migrate_archetypes_to_uuid_links(ob):
for field in ob.Schema().fields():
if type(field) == TextField and field.getContentType(ob) == 'text/html':
orig = field.getRaw(ob)
new = transform_links_to_uuid(ob, orig)
if orig != new:
field.set(ob, new)
print('Fixed content not using UUID links: %s' % '/'.join(ob.getPhysicalPath()))
return 1
return 0
def migrate_to_uuid_links(site):
catalog = site.portal_catalog
count = 0
for brain in catalog():
ob = brain.getObject()
if IDexterityContent.providedBy(ob):
count += migrate_dexterity_to_uuid_links(ob)
else:
count += migrate_archetypes_to_uuid_links(ob)
if count % 100 == 0:
transaction.commit()
print('finished processing %i' % count)
user = app.acl_users.getUser('admin') # noqa
newSecurityManager(None, user.__of__(app.acl_users)) # noqa
for oid in app.objectIds(): # noqa
_obj = app[oid] # noqa
if IPloneSiteRoot.providedBy(_obj):
migrate_to_uuid_links(_obj)
transaction.commit()
This script will run through every Plone site on your instance. If you want to target, just one Plone site, you'll need to modify the bottom of the script.
Then, just run the script against your instance:
./bin/client1 run migrate-to-uuid.py