Skip to content

Commit

Permalink
Merge pull request #834 from plone/linkintegrity
Browse files Browse the repository at this point in the history
Linkintegrity
  • Loading branch information
vangheem committed Aug 24, 2015
2 parents c0e6b36 + 70d03f5 commit a41980e
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 54 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Expand Up @@ -14,6 +14,10 @@ Changelog
5.0b4 (2015-08-23)
------------------

- remove Products.CMFPlone.utils.isLinked function. Switch to using
plone.app.linkintegrity's variant
[vangheem]

- fix #350: "plone.app.content circular dependency on Products.CMFPlone" - this
fixes the imports only, not on zcml/genericsetup level.
[jensens]
Expand Down
6 changes: 4 additions & 2 deletions Products/CMFPlone/PloneTool.py
Expand Up @@ -33,6 +33,7 @@
from Products.CMFPlone.interfaces import ISearchSchema
from Products.CMFPlone.interfaces import ISecuritySchema
from Products.CMFPlone.interfaces import ISiteSchema
from Products.CMFPlone.log import log_deprecated
from Products.CMFPlone.PloneBaseTool import PloneBaseTool
from Products.CMFPlone.PloneFolder import ReplaceableWrapper
from Products.CMFPlone.utils import base_hasattr
Expand Down Expand Up @@ -1139,6 +1140,8 @@ def getMethodAliases(self, typeInfo):
# manage_delObjects calls should handle permission checks for us.
@security.public
def deleteObjectsByPaths(self, paths, handle_errors=True, REQUEST=None):
log_deprecated("deleteObjectsByPaths is deprecated, you should use. "
"plone.api.content.delete. This method no longer does link integrity checks") # noqa
failure = {}
success = []
# use the portal for traversal in case we have relative paths
Expand All @@ -1155,8 +1158,6 @@ def deleteObjectsByPaths(self, paths, handle_errors=True, REQUEST=None):
success.append('%s (%s)' % (obj.getId(), path))
except ConflictError:
raise
except LinkIntegrityNotificationException:
raise
except Exception, e:
if handle_errors:
sp.rollback()
Expand All @@ -1173,6 +1174,7 @@ def transitionObjectsByPaths(self, workflow_action, paths, comment='',
expiration_date=None, effective_date=None,
include_children=False, handle_errors=True,
REQUEST=None):
log_deprecated("transitionObjectsByPaths is deprecated")
failure = {}
# use the portal for traversal in case we have relative paths
portal = getToolByName(self, 'portal_url').getPortalObject()
Expand Down
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from Products.CMFPlone.interfaces import IEditingSchema
from Products.CMFPlone.testing import PRODUCTS_CMFPLONE_FUNCTIONAL_TESTING
from plone.app.linkintegrity.interfaces import ILinkIntegrityInfo
from plone.app.linkintegrity.utils import linkintegrity_enabled
from plone.app.testing import SITE_OWNER_NAME
from plone.app.testing import SITE_OWNER_PASSWORD
from plone.registry.interfaces import IRegistry
Expand Down Expand Up @@ -109,9 +109,7 @@ def test_enable_link_integrity_checks_active(self):
self.browser.getControl("Enable link integrity checks")\
.selected = True
self.browser.getControl('Save').click()

self.assertTrue(
ILinkIntegrityInfo(self.request).integrityCheckingEnabled())
self.assertTrue(linkintegrity_enabled())

def test_lock_on_ttw_edit(self):
self.browser.open(
Expand Down
1 change: 1 addition & 0 deletions Products/CMFPlone/profiles/dependencies/metadata.xml
Expand Up @@ -8,6 +8,7 @@
<dependency>profile-Products.CMFEditions:CMFEditions</dependency>
<dependency>profile-Products.PlonePAS:PlonePAS</dependency>
<dependency>profile-plone.app.discussion:default</dependency>
<dependency>profile-plone.app.linkintegrity:default</dependency>
<dependency>profile-plone.app.registry:default</dependency>
<dependency>profile-plone.app.theming:default</dependency>
<dependency>profile-plone.app.users:default</dependency>
Expand Down
138 changes: 138 additions & 0 deletions Products/CMFPlone/tests/robot/test_linkintegrity.robot
@@ -0,0 +1,138 @@
# ============================================================================
# Tests for the Plone Link Integrity Support
# ============================================================================
#
# $ bin/robot-server --reload-path src/Products.CMFPlone/Products/CMFPlone/ Products.CMFPlone.testing.PRODUCTS_CMFPLONE_ROBOT_TESTING
#
# $ bin/robot src/Products.CMFPlone/Products/CMFPlone/tests/robot/test_controlpanel_usergroups.robot
#
# ============================================================================

*** Settings *****************************************************************

Resource plone/app/robotframework/keywords.robot
Resource plone/app/robotframework/saucelabs.robot

Library Remote ${PLONE_URL}/RobotRemote

Resource keywords.robot

Test Setup Open SauceLabs test browser
Test Teardown Run keywords Report test status Close all browsers


*** Test Cases ***************************************************************

Scenario: When page is linked show warning
Given a logged-in site administrator
a page to link to
and a page to edit
and a link in rich text
should show warning when deleting page


Scenario: After you fix linked page no longer show warning
Given a logged-in site administrator
a page to link to
and a page to edit
and a link in rich text
should show warning when deleting page
remove link to page
should not show warning when deleting page


Scenario: Show warning when deleting linked item from folder_contents
Given a logged-in site administrator
a page to link to
and a page to edit
and a link in rich text
should show warning when deleting page from folder_contents
remove link to page
should not show warning when deleting page from folder_contents


*** Keywords *****************************************************************

# --- GIVEN ------------------------------------------------------------------

a logged-in site administrator
Enable autologin as Site Administrator


a page to edit
Go To ${PLONE_URL}
Click Link css=#plone-contentmenu-factories a
Click Link css=.plonetoolbar-contenttype .contenttype-document
Input Text css=#formfield-form-widgets-IDublinCore-title input Bar


a page to link to
Go To ${PLONE_URL}
Click Link css=#plone-contentmenu-factories a
Click Link css=.plonetoolbar-contenttype .contenttype-document
Input Text css=#formfield-form-widgets-IDublinCore-title input Foo
Click Button css=#form-buttons-save


should show warning when deleting page
Go To ${PLONE_URL}/foo
Click Link css=#plone-contentmenu-actions a
Click Link css=#plone-contentmenu-actions-delete
Page should contain element css=.breach-container .breach-item


should show warning when deleting page from folder_contents
Go To ${PLONE_URL}/folder_contents
Wait until keyword succeeds 30 1 Page should contain element css=tr[data-id="foo"] input
Click Element css=tr[data-id="foo"] input
Checkbox Should Be Selected css=tr[data-id="foo"] input
Wait until keyword succeeds 30 1 Page should not contain element css=#btn-delete.disabled
Click Link Delete
Wait until page contains element css=.popover-content .btn-danger
Page should contain element css=.breach-container .breach-item
Click Button No
Checkbox Should Be Selected css=tr[data-id="foo"] input


should not show warning when deleting page from folder_contents
Go To ${PLONE_URL}/folder_contents
Click Element css=tr[data-id="foo"] input
Checkbox Should Be Selected css=tr[data-id="foo"] input
Wait until keyword succeeds 30 1 Page should not contain element css=#btn-delete.disabled
Click Link Delete
Wait until page contains element css=.popover-content .btn-danger
Page should not contain element css=.breach-container .breach-item
Click Button Yes
Wait until page contains Successfully delete items
Wait until keyword succeeds 30 1 Page should not contain Element css=tr[data-id="foo"] input


should not show warning when deleting page
Go To ${PLONE_URL}/foo
Click Link css=#plone-contentmenu-actions a
Click Link css=#plone-contentmenu-actions-delete
Page should not contain element css=.breach-container .breach-item


a link in rich text
Select Frame css=.mce-edit-area iframe
Input text css=.mce-content-body foo
Execute Javascript function selectElementContents(el) {var range = document.createRange(); range.selectNodeContents(el); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range);} var el = document.getElementById("tinymce"); selectElementContents(el);
UnSelect Frame
Click Button css=div[aria-label="Insert/edit link"] button
Click Element css=.select2-input.select2-default
Input text css=.select2-dropdown-open .select2-input foo
Click Link css=.pattern-relateditems-result-select.selectable
Click Button css=.plone-modal-footer .plone-btn-primary
Click Button css=#form-buttons-save


remove link to page
Go To ${PLONE_URL}/bar
Click Link css=#contentview-edit a
Select Frame css=.mce-edit-area iframe
Input text css=.mce-content-body foo
Execute Javascript function selectElementContents(el) {var range = document.createRange(); range.selectNodeContents(el); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range);} var el = document.getElementById("tinymce"); selectElementContents(el);
UnSelect Frame
Click Button css=div[aria-label="Remove link(s)"] button
Click Button css=#form-buttons-save
40 changes: 33 additions & 7 deletions Products/CMFPlone/tests/robot/test_tinymce.robot
Expand Up @@ -23,15 +23,15 @@ Scenario: A page is opened to edit
Given a logged-in site administrator
and an uploaded image
and an edited page
Click Button css=#mceu_15 button
Click Element css=.select2-input.select2-default
Click Link css=.pattern-relateditems-result-select.selectable
Input Text css=.plone-modal-body [name="title"] SomeTitle
Input Text css=.plone-modal-body [name="alt"] SomeAlt
Click Button css=.plone-modal-footer .plone-btn-primary
and text inserted into wysiwyg
and insert link
and insert image

Click Button css=#form-buttons-save
Element Should Be Visible css=#parent-fieldname-text img[alt="SomeAlt"]
Element Should Be Visible css=#parent-fieldname-text img[title="SomeTitle"]
Element Should Be Visible css=#parent-fieldname-text a


*** Keywords *****************************************************************

Expand All @@ -42,4 +42,30 @@ an edited page
Go to ${PLONE_URL}/${PAGE_ID}/edit

an uploaded image
Create content type=Image title=an-mage
Create content type=Image title=an-image

text inserted into wysiwyg
Select Frame css=.mce-edit-area iframe
Input text css=.mce-content-body foobar
UnSelect Frame

insert link
Select Frame css=.mce-edit-area iframe
Execute Javascript function selectElementContents(el) {var range = document.createRange(); range.selectNodeContents(el); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range);} var el = document.getElementById("tinymce"); selectElementContents(el);
UnSelect Frame
Click Button css=div[aria-label="Insert/edit link"] button
Click Element css=.select2-input.select2-default
Click Link css=.pattern-relateditems-result-select.selectable
Input Text css=.plone-modal-body [name="title"] SomeTitle
Click Button css=.plone-modal-footer .plone-btn-primary
Select Frame css=.mce-edit-area iframe
Execute Javascript window.getSelection().removeAllRanges()
UnSelect Frame

insert image
Click Button css=div[aria-label="Insert/edit image"] button
Click Element css=.select2-input.select2-default
Click Link css=.pattern-relateditems-result-select.selectable
Input Text css=.plone-modal-body [name="title"] SomeTitle
Input Text css=.plone-modal-body [name="alt"] SomeAlt
Click Button css=.plone-modal-footer .plone-btn-primary
2 changes: 1 addition & 1 deletion Products/CMFPlone/tests/testCutPasteSecurity.py
Expand Up @@ -37,7 +37,7 @@ def testRenameOtherMemberContentFails(self):

self.login('user2')
folder = self.membership.getHomeFolder('user1')
self.assertRaises(CopyError, folder.manage_renameObject,
self.assertRaises(Unauthorized, folder.manage_renameObject,
'testrename', 'bad')

def testCopyMemberContent(self):
Expand Down
44 changes: 4 additions & 40 deletions Products/CMFPlone/utils.py
Expand Up @@ -592,46 +592,10 @@ def _getSecurity(klass, create=True):


def isLinked(obj):
""" check if the given content object is linked from another one
WARNING: this function can be time consuming !!
It deletes the object in a subtransaction that is rollbacked.
In other words, the object is kept safe.
Nevertheless, this implies that it also deletes recursively
all object's subobjects and references, which can be very
expensive.
"""
# first check to see if link integrity handling has been enabled at all
# and if so, if the removal of the object was already confirmed, i.e.
# while replaying the request; unfortunately this makes it necessary
# to import from plone.app.linkintegrity here, hence the try block...
try:
from plone.app.linkintegrity.interfaces import ILinkIntegrityInfo
info = ILinkIntegrityInfo(obj.REQUEST)
except (ImportError, TypeError):
# if p.a.li isn't installed the following check can be cut short...
return False
if not info.integrityCheckingEnabled():
return False
if info.isConfirmedItem(obj):
return True
# otherwise, when not replaying the request already, it is tried to
# delete the object, making it possible to find out if it was referenced,
# i.e. in case a link integrity exception was raised
linked = False
parent = obj.aq_inner.aq_parent
try:
savepoint = transaction.savepoint()
parent.manage_delObjects(obj.getId())
except OFS.ObjectManager.BeforeDeleteException:
linked = True
except: # ignore other exceptions, not useful to us at this point
pass
finally:
savepoint.rollback()
return linked
"""Check if the given content object is linked from another one."""
log_deprecated("utils.isLinked is deprecated, you should use plone.app.linkintegrity.utils.hasIncomingLinks") # noqa
from plone.app.linkintegrity.utils import hasIncomingLinks
return hasIncomingLinks(obj)


def set_own_login_name(member, loginname):
Expand Down

0 comments on commit a41980e

Please sign in to comment.