We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
relationhelpers have hard dependency on plone.app.iterate
The newly introduced relationhelpers are having `plone.app.iterate` as a hard dependency.

`plone.app.iterate` is a dependency of the `Plone` package, but not of `Products.CMFPlone`. See:

https://github.com/plone/Products.CMFPlone/blob/d07a74b6f7d944a08563c479c68c76fe6bd3b260/Products/CMFPlone/relationhelper.py#L4-L5

This need to be changed into a soft dependency at least.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Products/CMFPlone/relationhelper.py`
Content:
```
1 from collections import Counter
2 from collections import defaultdict
3 from five.intid.intid import addIntIdSubscriber
4 from plone.app.iterate.dexterity import ITERATE_RELATION_NAME
5 from plone.app.iterate.dexterity.relation import StagingRelationValue
6 from plone.app.linkintegrity.handlers import modifiedContent
7 from plone.app.linkintegrity.utils import referencedRelationship
8 from plone.app.relationfield.event import update_behavior_relations
9 from plone.app.uuid.utils import uuidToObject
10 from plone.dexterity.interfaces import IDexterityContent
11 from plone.dexterity.utils import iterSchemataForType
12 from Products.CMFCore.interfaces import IContentish
13 from Products.CMFPlone import PloneMessageFactory as _
14 from z3c.relationfield import event
15 from z3c.relationfield import RelationValue
16 from z3c.relationfield.event import updateRelations
17 from z3c.relationfield.schema import Relation
18 from z3c.relationfield.schema import RelationChoice
19 from z3c.relationfield.schema import RelationList
20 from zc.relation.interfaces import ICatalog
21 from zope.annotation.interfaces import IAnnotations
22 from zope.component import getUtility
23 from zope.component import queryUtility
24 from zope.component.hooks import getSite
25 from zope.intid.interfaces import IIntIds
26 from zope.intid.interfaces import ObjectMissingError
27 
28 import logging
29 
30 logger = logging.getLogger(__name__)
31 
32 RELATIONS_KEY = 'ALL_REFERENCES'
33 
34 
35 def rebuild_relations(context=None, flush_and_rebuild_intids=False):
36     store_relations()
37     purge_relations()
38     if flush_and_rebuild_intids:
39         flush_intids()
40         rebuild_intids()
41     else:
42         cleanup_intids()
43     restore_relations()
44 
45 
46 def get_relations_stats():
47     info = defaultdict(int)
48     broken = defaultdict(int)
49     relation_catalog = getUtility(ICatalog)
50     for token in relation_catalog.findRelationTokens():
51         try:
52             rel = relation_catalog.resolveRelationToken(token)
53         except ObjectMissingError:
54             broken['Object is missing'] += 1
55             logger.info('Token {} has no object.'.format(token))
56             continue
57 
58         if rel.isBroken():
59             broken[rel.from_attribute] += 1
60         else:
61             info[rel.from_attribute] += 1
62     return info, broken
63 
64 
65 def get_all_relations():
66     """Get all data from zc.relation catalog.
67     Logs some useful statistics.
68     """
69     results = []
70     info = defaultdict(int)
71 
72     relation_catalog = getUtility(ICatalog)
73     for token in relation_catalog.findRelationTokens():
74         try:
75             rel = relation_catalog.resolveRelationToken(token)
76         except ObjectMissingError:
77             logger.info('Token {} has no object.'.format(token))
78             continue
79 
80         if rel.from_object and rel.to_object:
81             try:
82                 results.append({
83                     'from_uuid': rel.from_object.UID(),
84                     'to_uuid': rel.to_object.UID(),
85                     'from_attribute': rel.from_attribute,
86                 })
87                 info[rel.from_attribute] += 1
88             except AttributeError as ex:
89                 logger.info(f'Something went wrong while storing {rel}: \n {ex}')
90         else:
91             logger.info(f'Dropping relation {rel.from_attribute} from {rel.from_object} to {rel.to_object}')
92     msg = ''
93     for key, value in info.items():
94         msg += f'{key}: {value}\n'
95     logger.info(f'\nFound the following relations:\n{msg}')
96     return results
97 
98 
99 def store_relations(context=None):
100     """Store all relations in a annotation on the portal.
101     """
102     all_relations = get_all_relations()
103     portal = getSite()
104     IAnnotations(portal)[RELATIONS_KEY] = all_relations
105     logger.info(f'Stored {len(all_relations)} relations on the portal')
106 
107 
108 def purge_relations(context=None):
109     """Removes all entries form zc.relation catalog.
110     RelationValues that were set as attribute on content are still there!
111     These are removed/overwritten when restoring the relations.
112     """
113     rel_catalog = getUtility(ICatalog)
114     rel_catalog.clear()
115     logger.info('Purged zc.relation catalog')
116 
117 
118 def restore_relations(context=None, all_relations=None):
119     """Restore relations from a annotation on the portal.
120     """
121 
122     portal = getSite()
123     if all_relations is None:
124         all_relations = IAnnotations(portal)[RELATIONS_KEY]
125     logger.info(f'Loaded {len(all_relations)} relations to restore')
126     update_linkintegrity = set()
127     modified_items = set()
128     modified_relation_lists = defaultdict(list)
129 
130     # remove duplicates but keep original order
131     unique_relations = []
132     seen = set()
133     seen_add = seen.add
134     for rel in all_relations:
135         hashable = tuple(rel.items())
136         if hashable not in seen:
137             unique_relations.append(rel)
138             seen_add(hashable)
139         else:
140             logger.info(f'Dropping duplicate: {hashable}')
141 
142     if len(unique_relations) < len(all_relations):
143         logger.info(f'Dropping {len(all_relations) - len(unique_relations)} duplicates')
144         all_relations = unique_relations
145 
146     intids = getUtility(IIntIds)
147     for index, item in enumerate(all_relations, start=1):
148         if not index % 500:
149             logger.info(f'Restored {index} of {len(all_relations)} relations...')
150 
151         try:
152             source_obj = uuidToObject(item['from_uuid'])
153         except KeyError:
154             # brain exists but no object
155             source_obj = None
156         try:
157             target_obj = uuidToObject(item['to_uuid'])
158         except KeyError:
159             # brain exists but no object
160             target_obj = None
161 
162         if not source_obj:
163             logger.info(f'{item["from_uuid"]} is missing')
164             continue
165 
166         if not target_obj:
167             logger.info(f'{item["to_uuid"]} is missing')
168             continue
169 
170         if not IDexterityContent.providedBy(source_obj):
171             logger.info(f'{source_obj} is no dexterity content')
172             continue
173 
174         if not IDexterityContent.providedBy(target_obj):
175             logger.info(f'{target_obj} is no dexterity content')
176             continue
177 
178         from_attribute = item['from_attribute']
179         to_id = intids.getId(target_obj)
180 
181         if from_attribute == referencedRelationship:
182             # Ignore linkintegrity for now. We'll rebuilt it at the end!
183             update_linkintegrity.add(item['from_uuid'])
184             continue
185 
186         if from_attribute == ITERATE_RELATION_NAME:
187             # Iterate relations are not set as values of fields
188             relation = StagingRelationValue(to_id)
189             event._setRelation(source_obj, ITERATE_RELATION_NAME, relation)
190             continue
191 
192         field_and_schema = get_field_and_schema_for_fieldname(from_attribute, source_obj.portal_type)
193         if field_and_schema is None:
194             # the from_attribute is no field
195             logger.info(f'No field. Setting relation: {item}')
196             event._setRelation(source_obj, from_attribute, RelationValue(to_id))
197             continue
198 
199         field, schema = field_and_schema
200         relation = RelationValue(to_id)
201 
202         if isinstance(field, RelationList):
203             logger.info(f'Add relation to relationslist {from_attribute} from {source_obj.absolute_url()} to {target_obj.absolute_url()}')
204             if item['from_uuid'] in modified_relation_lists.get(from_attribute, []):
205                 # Do not purge relations
206                 existing_relations = getattr(source_obj, from_attribute, [])
207             else:
208                 # First touch. Make sure we purge!
209                 existing_relations = []
210             existing_relations.append(relation)
211             setattr(source_obj, from_attribute, existing_relations)
212             modified_items.add(item['from_uuid'])
213             modified_relation_lists[from_attribute].append(item['from_uuid'])
214             continue
215 
216         elif isinstance(field, (Relation, RelationChoice)):
217             logger.info(f'Add relation {from_attribute} from {source_obj.absolute_url()} to {target_obj.absolute_url()}')
218             setattr(source_obj, from_attribute, relation)
219             modified_items.add(item['from_uuid'])
220             continue
221 
222         else:
223             # we should never end up here!
224             logger.warn(f'Unexpected relation {from_attribute} from {source_obj.absolute_url()} to {target_obj.absolute_url()}')
225 
226     update_linkintegrity = set(update_linkintegrity)
227     logger.info(f'Updating linkintegrity for {len(update_linkintegrity)} items')
228     for uuid in sorted(update_linkintegrity):
229         modifiedContent(uuidToObject(uuid), None)
230     logger.info(f'Updating relations for {len(modified_items)} items')
231     for uuid in sorted(modified_items):
232         obj = uuidToObject(uuid)
233         # updateRelations from z3c.relationfield does not properly update relations in behaviors
234         # that are registered with a marker-interface.
235         # update_behavior_relations (from plone.app.relationfield) does that but does not update
236         # those in the main schema. Duh!
237         updateRelations(obj, None)
238         update_behavior_relations(obj, None)
239 
240     # purge annotation from portal if they exist
241     if RELATIONS_KEY in IAnnotations(portal):
242         del IAnnotations(portal)[RELATIONS_KEY]
243     logger.info('Done!')
244 
245 
246 def get_intid(obj):
247     """Intid from intid-catalog"""
248     intids = queryUtility(IIntIds)
249     if intids is None:
250         return
251     # check that the object has an intid, otherwise there's nothing to be done
252     try:
253         return intids.getId(obj)
254     except KeyError:  # noqa
255         # The object has not been added to the ZODB yet
256         return
257 
258 
259 def get_field_and_schema_for_fieldname(field_id, portal_type):
260     """Get field and its schema from a portal_type.
261     """
262     # Turn form.widgets.IDublinCore.title into title
263     field_id = field_id.split('.')[-1]
264     for schema in iterSchemataForType(portal_type):
265         field = schema.get(field_id, None)
266         if field is not None:
267             return (field, schema)
268 
269 
270 def cleanup_intids(context=None):
271     intids = getUtility(IIntIds)
272     all_refs = [f'{i.object.__class__.__module__}.{i.object.__class__.__name__}'
273                 for i in intids.refs.values()]
274     logger.info(Counter(all_refs))
275 
276     count = 0
277     refs = [i for i in intids.refs.values() if isinstance(i.object, RelationValue)]
278     for ref in refs:
279         intids.unregister(ref)
280         count += 1
281     logger.info(f'Removed all {count} RelationValues from IntId-tool')
282 
283     count = 0
284     for ref in intids.refs.values():
285         if 'broken' in repr(ref.object):
286             intids.unregister(ref)
287     logger.info(f'Removed {count} broken refs from IntId-tool')
288     all_refs = ['{i.object.__class__.__module__}.{i.object.__class__.__name__}'
289                 for i in intids.refs.values()]
290     logger.info(Counter(all_refs))
291 
292 
293 def flush_intids():
294     """ Flush all intids
295     """
296     intids = getUtility(IIntIds)
297     intids.ids = intids.family.OI.BTree()
298     intids.refs = intids.family.IO.BTree()
299 
300 
301 def rebuild_intids():
302     """ Create new intids
303     """
304     def add_to_intids(obj, path):
305         if IContentish.providedBy(obj):
306             logger.info(f'Added {obj} at {path} to intid')
307             addIntIdSubscriber(obj, None)
308     portal = getSite()
309     portal.ZopeFindAndApply(portal,
310                             search_sub=True,
311                             apply_func=add_to_intids)
312
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/Products/CMFPlone/relationhelper.py b/Products/CMFPlone/relationhelper.py
--- a/Products/CMFPlone/relationhelper.py
+++ b/Products/CMFPlone/relationhelper.py
@@ -1,8 +1,6 @@
 from collections import Counter
 from collections import defaultdict
 from five.intid.intid import addIntIdSubscriber
-from plone.app.iterate.dexterity import ITERATE_RELATION_NAME
-from plone.app.iterate.dexterity.relation import StagingRelationValue
 from plone.app.linkintegrity.handlers import modifiedContent
 from plone.app.linkintegrity.utils import referencedRelationship
 from plone.app.relationfield.event import update_behavior_relations
@@ -26,6 +24,18 @@
 from zope.intid.interfaces import ObjectMissingError
 
 import logging
+import pkg_resources
+
+try:
+    # "iterate" is not a dependency of CMFPlone, but a consumer of it
+    pkg_resources.get_distribution("plone.app.iterate")
+except pkg_resources.DistributionNotFound:
+    HAS_ITERATE = False
+else:
+    HAS_ITERATE = True
+    from plone.app.iterate.dexterity import ITERATE_RELATION_NAME
+    from plone.app.iterate.dexterity.relation import StagingRelationValue
+
 
 logger = logging.getLogger(__name__)
 
@@ -183,7 +193,7 @@
             update_linkintegrity.add(item['from_uuid'])
             continue
 
-        if from_attribute == ITERATE_RELATION_NAME:
+        if HAS_ITERATE and from_attribute == ITERATE_RELATION_NAME:
             # Iterate relations are not set as values of fields
             relation = StagingRelationValue(to_id)
             event._setRelation(source_obj, ITERATE_RELATION_NAME, relation)

{"golden_diff": "diff --git a/Products/CMFPlone/relationhelper.py b/Products/CMFPlone/relationhelper.py\n--- a/Products/CMFPlone/relationhelper.py\n+++ b/Products/CMFPlone/relationhelper.py\n@@ -1,8 +1,6 @@\n from collections import Counter\n from collections import defaultdict\n from five.intid.intid import addIntIdSubscriber\n-from plone.app.iterate.dexterity import ITERATE_RELATION_NAME\n-from plone.app.iterate.dexterity.relation import StagingRelationValue\n from plone.app.linkintegrity.handlers import modifiedContent\n from plone.app.linkintegrity.utils import referencedRelationship\n from plone.app.relationfield.event import update_behavior_relations\n@@ -26,6 +24,18 @@\n from zope.intid.interfaces import ObjectMissingError\n \n import logging\n+import pkg_resources\n+\n+try:\n+    # \"iterate\" is not a dependency of CMFPlone, but a consumer of it\n+    pkg_resources.get_distribution(\"plone.app.iterate\")\n+except pkg_resources.DistributionNotFound:\n+    HAS_ITERATE = False\n+else:\n+    HAS_ITERATE = True\n+    from plone.app.iterate.dexterity import ITERATE_RELATION_NAME\n+    from plone.app.iterate.dexterity.relation import StagingRelationValue\n+\n \n logger = logging.getLogger(__name__)\n \n@@ -183,7 +193,7 @@\n             update_linkintegrity.add(item['from_uuid'])\n             continue\n \n-        if from_attribute == ITERATE_RELATION_NAME:\n+        if HAS_ITERATE and from_attribute == ITERATE_RELATION_NAME:\n             # Iterate relations are not set as values of fields\n             relation = StagingRelationValue(to_id)\n             event._setRelation(source_obj, ITERATE_RELATION_NAME, relation)\n", "issue": "relationhelpers have hard dependency on plone.app.iterate\nThe newly introduced relationhelpers are having `plone.app.iterate` as a hard dependency.\r\n\r\n`plone.app.iterate` is a dependency of the `Plone` package, but not of `Products.CMFPlone`. See:\r\n\r\nhttps://github.com/plone/Products.CMFPlone/blob/d07a74b6f7d944a08563c479c68c76fe6bd3b260/Products/CMFPlone/relationhelper.py#L4-L5\r\n\r\nThis need to be changed into a soft dependency at least.\n", "before_files": [{"content": "from collections import Counter\nfrom collections import defaultdict\nfrom five.intid.intid import addIntIdSubscriber\nfrom plone.app.iterate.dexterity import ITERATE_RELATION_NAME\nfrom plone.app.iterate.dexterity.relation import StagingRelationValue\nfrom plone.app.linkintegrity.handlers import modifiedContent\nfrom plone.app.linkintegrity.utils import referencedRelationship\nfrom plone.app.relationfield.event import update_behavior_relations\nfrom plone.app.uuid.utils import uuidToObject\nfrom plone.dexterity.interfaces import IDexterityContent\nfrom plone.dexterity.utils import iterSchemataForType\nfrom Products.CMFCore.interfaces import IContentish\nfrom Products.CMFPlone import PloneMessageFactory as _\nfrom z3c.relationfield import event\nfrom z3c.relationfield import RelationValue\nfrom z3c.relationfield.event import updateRelations\nfrom z3c.relationfield.schema import Relation\nfrom z3c.relationfield.schema import RelationChoice\nfrom z3c.relationfield.schema import RelationList\nfrom zc.relation.interfaces import ICatalog\nfrom zope.annotation.interfaces import IAnnotations\nfrom zope.component import getUtility\nfrom zope.component import queryUtility\nfrom zope.component.hooks import getSite\nfrom zope.intid.interfaces import IIntIds\nfrom zope.intid.interfaces import ObjectMissingError\n\nimport logging\n\nlogger = logging.getLogger(__name__)\n\nRELATIONS_KEY = 'ALL_REFERENCES'\n\n\ndef rebuild_relations(context=None, flush_and_rebuild_intids=False):\n    store_relations()\n    purge_relations()\n    if flush_and_rebuild_intids:\n        flush_intids()\n        rebuild_intids()\n    else:\n        cleanup_intids()\n    restore_relations()\n\n\ndef get_relations_stats():\n    info = defaultdict(int)\n    broken = defaultdict(int)\n    relation_catalog = getUtility(ICatalog)\n    for token in relation_catalog.findRelationTokens():\n        try:\n            rel = relation_catalog.resolveRelationToken(token)\n        except ObjectMissingError:\n            broken['Object is missing'] += 1\n            logger.info('Token {} has no object.'.format(token))\n            continue\n\n        if rel.isBroken():\n            broken[rel.from_attribute] += 1\n        else:\n            info[rel.from_attribute] += 1\n    return info, broken\n\n\ndef get_all_relations():\n    \"\"\"Get all data from zc.relation catalog.\n    Logs some useful statistics.\n    \"\"\"\n    results = []\n    info = defaultdict(int)\n\n    relation_catalog = getUtility(ICatalog)\n    for token in relation_catalog.findRelationTokens():\n        try:\n            rel = relation_catalog.resolveRelationToken(token)\n        except ObjectMissingError:\n            logger.info('Token {} has no object.'.format(token))\n            continue\n\n        if rel.from_object and rel.to_object:\n            try:\n                results.append({\n                    'from_uuid': rel.from_object.UID(),\n                    'to_uuid': rel.to_object.UID(),\n                    'from_attribute': rel.from_attribute,\n                })\n                info[rel.from_attribute] += 1\n            except AttributeError as ex:\n                logger.info(f'Something went wrong while storing {rel}: \\n {ex}')\n        else:\n            logger.info(f'Dropping relation {rel.from_attribute} from {rel.from_object} to {rel.to_object}')\n    msg = ''\n    for key, value in info.items():\n        msg += f'{key}: {value}\\n'\n    logger.info(f'\\nFound the following relations:\\n{msg}')\n    return results\n\n\ndef store_relations(context=None):\n    \"\"\"Store all relations in a annotation on the portal.\n    \"\"\"\n    all_relations = get_all_relations()\n    portal = getSite()\n    IAnnotations(portal)[RELATIONS_KEY] = all_relations\n    logger.info(f'Stored {len(all_relations)} relations on the portal')\n\n\ndef purge_relations(context=None):\n    \"\"\"Removes all entries form zc.relation catalog.\n    RelationValues that were set as attribute on content are still there!\n    These are removed/overwritten when restoring the relations.\n    \"\"\"\n    rel_catalog = getUtility(ICatalog)\n    rel_catalog.clear()\n    logger.info('Purged zc.relation catalog')\n\n\ndef restore_relations(context=None, all_relations=None):\n    \"\"\"Restore relations from a annotation on the portal.\n    \"\"\"\n\n    portal = getSite()\n    if all_relations is None:\n        all_relations = IAnnotations(portal)[RELATIONS_KEY]\n    logger.info(f'Loaded {len(all_relations)} relations to restore')\n    update_linkintegrity = set()\n    modified_items = set()\n    modified_relation_lists = defaultdict(list)\n\n    # remove duplicates but keep original order\n    unique_relations = []\n    seen = set()\n    seen_add = seen.add\n    for rel in all_relations:\n        hashable = tuple(rel.items())\n        if hashable not in seen:\n            unique_relations.append(rel)\n            seen_add(hashable)\n        else:\n            logger.info(f'Dropping duplicate: {hashable}')\n\n    if len(unique_relations) < len(all_relations):\n        logger.info(f'Dropping {len(all_relations) - len(unique_relations)} duplicates')\n        all_relations = unique_relations\n\n    intids = getUtility(IIntIds)\n    for index, item in enumerate(all_relations, start=1):\n        if not index % 500:\n            logger.info(f'Restored {index} of {len(all_relations)} relations...')\n\n        try:\n            source_obj = uuidToObject(item['from_uuid'])\n        except KeyError:\n            # brain exists but no object\n            source_obj = None\n        try:\n            target_obj = uuidToObject(item['to_uuid'])\n        except KeyError:\n            # brain exists but no object\n            target_obj = None\n\n        if not source_obj:\n            logger.info(f'{item[\"from_uuid\"]} is missing')\n            continue\n\n        if not target_obj:\n            logger.info(f'{item[\"to_uuid\"]} is missing')\n            continue\n\n        if not IDexterityContent.providedBy(source_obj):\n            logger.info(f'{source_obj} is no dexterity content')\n            continue\n\n        if not IDexterityContent.providedBy(target_obj):\n            logger.info(f'{target_obj} is no dexterity content')\n            continue\n\n        from_attribute = item['from_attribute']\n        to_id = intids.getId(target_obj)\n\n        if from_attribute == referencedRelationship:\n            # Ignore linkintegrity for now. We'll rebuilt it at the end!\n            update_linkintegrity.add(item['from_uuid'])\n            continue\n\n        if from_attribute == ITERATE_RELATION_NAME:\n            # Iterate relations are not set as values of fields\n            relation = StagingRelationValue(to_id)\n            event._setRelation(source_obj, ITERATE_RELATION_NAME, relation)\n            continue\n\n        field_and_schema = get_field_and_schema_for_fieldname(from_attribute, source_obj.portal_type)\n        if field_and_schema is None:\n            # the from_attribute is no field\n            logger.info(f'No field. Setting relation: {item}')\n            event._setRelation(source_obj, from_attribute, RelationValue(to_id))\n            continue\n\n        field, schema = field_and_schema\n        relation = RelationValue(to_id)\n\n        if isinstance(field, RelationList):\n            logger.info(f'Add relation to relationslist {from_attribute} from {source_obj.absolute_url()} to {target_obj.absolute_url()}')\n            if item['from_uuid'] in modified_relation_lists.get(from_attribute, []):\n                # Do not purge relations\n                existing_relations = getattr(source_obj, from_attribute, [])\n            else:\n                # First touch. Make sure we purge!\n                existing_relations = []\n            existing_relations.append(relation)\n            setattr(source_obj, from_attribute, existing_relations)\n            modified_items.add(item['from_uuid'])\n            modified_relation_lists[from_attribute].append(item['from_uuid'])\n            continue\n\n        elif isinstance(field, (Relation, RelationChoice)):\n            logger.info(f'Add relation {from_attribute} from {source_obj.absolute_url()} to {target_obj.absolute_url()}')\n            setattr(source_obj, from_attribute, relation)\n            modified_items.add(item['from_uuid'])\n            continue\n\n        else:\n            # we should never end up here!\n            logger.warn(f'Unexpected relation {from_attribute} from {source_obj.absolute_url()} to {target_obj.absolute_url()}')\n\n    update_linkintegrity = set(update_linkintegrity)\n    logger.info(f'Updating linkintegrity for {len(update_linkintegrity)} items')\n    for uuid in sorted(update_linkintegrity):\n        modifiedContent(uuidToObject(uuid), None)\n    logger.info(f'Updating relations for {len(modified_items)} items')\n    for uuid in sorted(modified_items):\n        obj = uuidToObject(uuid)\n        # updateRelations from z3c.relationfield does not properly update relations in behaviors\n        # that are registered with a marker-interface.\n        # update_behavior_relations (from plone.app.relationfield) does that but does not update\n        # those in the main schema. Duh!\n        updateRelations(obj, None)\n        update_behavior_relations(obj, None)\n\n    # purge annotation from portal if they exist\n    if RELATIONS_KEY in IAnnotations(portal):\n        del IAnnotations(portal)[RELATIONS_KEY]\n    logger.info('Done!')\n\n\ndef get_intid(obj):\n    \"\"\"Intid from intid-catalog\"\"\"\n    intids = queryUtility(IIntIds)\n    if intids is None:\n        return\n    # check that the object has an intid, otherwise there's nothing to be done\n    try:\n        return intids.getId(obj)\n    except KeyError:  # noqa\n        # The object has not been added to the ZODB yet\n        return\n\n\ndef get_field_and_schema_for_fieldname(field_id, portal_type):\n    \"\"\"Get field and its schema from a portal_type.\n    \"\"\"\n    # Turn form.widgets.IDublinCore.title into title\n    field_id = field_id.split('.')[-1]\n    for schema in iterSchemataForType(portal_type):\n        field = schema.get(field_id, None)\n        if field is not None:\n            return (field, schema)\n\n\ndef cleanup_intids(context=None):\n    intids = getUtility(IIntIds)\n    all_refs = [f'{i.object.__class__.__module__}.{i.object.__class__.__name__}'\n                for i in intids.refs.values()]\n    logger.info(Counter(all_refs))\n\n    count = 0\n    refs = [i for i in intids.refs.values() if isinstance(i.object, RelationValue)]\n    for ref in refs:\n        intids.unregister(ref)\n        count += 1\n    logger.info(f'Removed all {count} RelationValues from IntId-tool')\n\n    count = 0\n    for ref in intids.refs.values():\n        if 'broken' in repr(ref.object):\n            intids.unregister(ref)\n    logger.info(f'Removed {count} broken refs from IntId-tool')\n    all_refs = ['{i.object.__class__.__module__}.{i.object.__class__.__name__}'\n                for i in intids.refs.values()]\n    logger.info(Counter(all_refs))\n\n\ndef flush_intids():\n    \"\"\" Flush all intids\n    \"\"\"\n    intids = getUtility(IIntIds)\n    intids.ids = intids.family.OI.BTree()\n    intids.refs = intids.family.IO.BTree()\n\n\ndef rebuild_intids():\n    \"\"\" Create new intids\n    \"\"\"\n    def add_to_intids(obj, path):\n        if IContentish.providedBy(obj):\n            logger.info(f'Added {obj} at {path} to intid')\n            addIntIdSubscriber(obj, None)\n    portal = getSite()\n    portal.ZopeFindAndApply(portal,\n                            search_sub=True,\n                            apply_func=add_to_intids)\n", "path": "Products/CMFPlone/relationhelper.py"}], "after_files": [{"content": "from collections import Counter\nfrom collections import defaultdict\nfrom five.intid.intid import addIntIdSubscriber\nfrom plone.app.linkintegrity.handlers import modifiedContent\nfrom plone.app.linkintegrity.utils import referencedRelationship\nfrom plone.app.relationfield.event import update_behavior_relations\nfrom plone.app.uuid.utils import uuidToObject\nfrom plone.dexterity.interfaces import IDexterityContent\nfrom plone.dexterity.utils import iterSchemataForType\nfrom Products.CMFCore.interfaces import IContentish\nfrom Products.CMFPlone import PloneMessageFactory as _\nfrom z3c.relationfield import event\nfrom z3c.relationfield import RelationValue\nfrom z3c.relationfield.event import updateRelations\nfrom z3c.relationfield.schema import Relation\nfrom z3c.relationfield.schema import RelationChoice\nfrom z3c.relationfield.schema import RelationList\nfrom zc.relation.interfaces import ICatalog\nfrom zope.annotation.interfaces import IAnnotations\nfrom zope.component import getUtility\nfrom zope.component import queryUtility\nfrom zope.component.hooks import getSite\nfrom zope.intid.interfaces import IIntIds\nfrom zope.intid.interfaces import ObjectMissingError\n\nimport logging\nimport pkg_resources\n\ntry:\n    # \"iterate\" is not a dependency of CMFPlone, but a consumer of it\n    pkg_resources.get_distribution(\"plone.app.iterate\")\nexcept pkg_resources.DistributionNotFound:\n    HAS_ITERATE = False\nelse:\n    HAS_ITERATE = True\n    from plone.app.iterate.dexterity import ITERATE_RELATION_NAME\n    from plone.app.iterate.dexterity.relation import StagingRelationValue\n\n\nlogger = logging.getLogger(__name__)\n\nRELATIONS_KEY = 'ALL_REFERENCES'\n\n\ndef rebuild_relations(context=None, flush_and_rebuild_intids=False):\n    store_relations()\n    purge_relations()\n    if flush_and_rebuild_intids:\n        flush_intids()\n        rebuild_intids()\n    else:\n        cleanup_intids()\n    restore_relations()\n\n\ndef get_relations_stats():\n    info = defaultdict(int)\n    broken = defaultdict(int)\n    relation_catalog = getUtility(ICatalog)\n    for token in relation_catalog.findRelationTokens():\n        try:\n            rel = relation_catalog.resolveRelationToken(token)\n        except ObjectMissingError:\n            broken['Object is missing'] += 1\n            logger.info('Token {} has no object.'.format(token))\n            continue\n\n        if rel.isBroken():\n            broken[rel.from_attribute] += 1\n        else:\n            info[rel.from_attribute] += 1\n    return info, broken\n\n\ndef get_all_relations():\n    \"\"\"Get all data from zc.relation catalog.\n    Logs some useful statistics.\n    \"\"\"\n    results = []\n    info = defaultdict(int)\n\n    relation_catalog = getUtility(ICatalog)\n    for token in relation_catalog.findRelationTokens():\n        try:\n            rel = relation_catalog.resolveRelationToken(token)\n        except ObjectMissingError:\n            logger.info('Token {} has no object.'.format(token))\n            continue\n\n        if rel.from_object and rel.to_object:\n            try:\n                results.append({\n                    'from_uuid': rel.from_object.UID(),\n                    'to_uuid': rel.to_object.UID(),\n                    'from_attribute': rel.from_attribute,\n                })\n                info[rel.from_attribute] += 1\n            except AttributeError as ex:\n                logger.info(f'Something went wrong while storing {rel}: \\n {ex}')\n        else:\n            logger.info(f'Dropping relation {rel.from_attribute} from {rel.from_object} to {rel.to_object}')\n    msg = ''\n    for key, value in info.items():\n        msg += f'{key}: {value}\\n'\n    logger.info(f'\\nFound the following relations:\\n{msg}')\n    return results\n\n\ndef store_relations(context=None):\n    \"\"\"Store all relations in a annotation on the portal.\n    \"\"\"\n    all_relations = get_all_relations()\n    portal = getSite()\n    IAnnotations(portal)[RELATIONS_KEY] = all_relations\n    logger.info(f'Stored {len(all_relations)} relations on the portal')\n\n\ndef purge_relations(context=None):\n    \"\"\"Removes all entries form zc.relation catalog.\n    RelationValues that were set as attribute on content are still there!\n    These are removed/overwritten when restoring the relations.\n    \"\"\"\n    rel_catalog = getUtility(ICatalog)\n    rel_catalog.clear()\n    logger.info('Purged zc.relation catalog')\n\n\ndef restore_relations(context=None, all_relations=None):\n    \"\"\"Restore relations from a annotation on the portal.\n    \"\"\"\n\n    portal = getSite()\n    if all_relations is None:\n        all_relations = IAnnotations(portal)[RELATIONS_KEY]\n    logger.info(f'Loaded {len(all_relations)} relations to restore')\n    update_linkintegrity = set()\n    modified_items = set()\n    modified_relation_lists = defaultdict(list)\n\n    # remove duplicates but keep original order\n    unique_relations = []\n    seen = set()\n    seen_add = seen.add\n    for rel in all_relations:\n        hashable = tuple(rel.items())\n        if hashable not in seen:\n            unique_relations.append(rel)\n            seen_add(hashable)\n        else:\n            logger.info(f'Dropping duplicate: {hashable}')\n\n    if len(unique_relations) < len(all_relations):\n        logger.info(f'Dropping {len(all_relations) - len(unique_relations)} duplicates')\n        all_relations = unique_relations\n\n    intids = getUtility(IIntIds)\n    for index, item in enumerate(all_relations, start=1):\n        if not index % 500:\n            logger.info(f'Restored {index} of {len(all_relations)} relations...')\n\n        try:\n            source_obj = uuidToObject(item['from_uuid'])\n        except KeyError:\n            # brain exists but no object\n            source_obj = None\n        try:\n            target_obj = uuidToObject(item['to_uuid'])\n        except KeyError:\n            # brain exists but no object\n            target_obj = None\n\n        if not source_obj:\n            logger.info(f'{item[\"from_uuid\"]} is missing')\n            continue\n\n        if not target_obj:\n            logger.info(f'{item[\"to_uuid\"]} is missing')\n            continue\n\n        if not IDexterityContent.providedBy(source_obj):\n            logger.info(f'{source_obj} is no dexterity content')\n            continue\n\n        if not IDexterityContent.providedBy(target_obj):\n            logger.info(f'{target_obj} is no dexterity content')\n            continue\n\n        from_attribute = item['from_attribute']\n        to_id = intids.getId(target_obj)\n\n        if from_attribute == referencedRelationship:\n            # Ignore linkintegrity for now. We'll rebuilt it at the end!\n            update_linkintegrity.add(item['from_uuid'])\n            continue\n\n        if HAS_ITERATE and from_attribute == ITERATE_RELATION_NAME:\n            # Iterate relations are not set as values of fields\n            relation = StagingRelationValue(to_id)\n            event._setRelation(source_obj, ITERATE_RELATION_NAME, relation)\n            continue\n\n        field_and_schema = get_field_and_schema_for_fieldname(from_attribute, source_obj.portal_type)\n        if field_and_schema is None:\n            # the from_attribute is no field\n            logger.info(f'No field. Setting relation: {item}')\n            event._setRelation(source_obj, from_attribute, RelationValue(to_id))\n            continue\n\n        field, schema = field_and_schema\n        relation = RelationValue(to_id)\n\n        if isinstance(field, RelationList):\n            logger.info(f'Add relation to relationslist {from_attribute} from {source_obj.absolute_url()} to {target_obj.absolute_url()}')\n            if item['from_uuid'] in modified_relation_lists.get(from_attribute, []):\n                # Do not purge relations\n                existing_relations = getattr(source_obj, from_attribute, [])\n            else:\n                # First touch. Make sure we purge!\n                existing_relations = []\n            existing_relations.append(relation)\n            setattr(source_obj, from_attribute, existing_relations)\n            modified_items.add(item['from_uuid'])\n            modified_relation_lists[from_attribute].append(item['from_uuid'])\n            continue\n\n        elif isinstance(field, (Relation, RelationChoice)):\n            logger.info(f'Add relation {from_attribute} from {source_obj.absolute_url()} to {target_obj.absolute_url()}')\n            setattr(source_obj, from_attribute, relation)\n            modified_items.add(item['from_uuid'])\n            continue\n\n        else:\n            # we should never end up here!\n            logger.warn(f'Unexpected relation {from_attribute} from {source_obj.absolute_url()} to {target_obj.absolute_url()}')\n\n    update_linkintegrity = set(update_linkintegrity)\n    logger.info(f'Updating linkintegrity for {len(update_linkintegrity)} items')\n    for uuid in sorted(update_linkintegrity):\n        modifiedContent(uuidToObject(uuid), None)\n    logger.info(f'Updating relations for {len(modified_items)} items')\n    for uuid in sorted(modified_items):\n        obj = uuidToObject(uuid)\n        # updateRelations from z3c.relationfield does not properly update relations in behaviors\n        # that are registered with a marker-interface.\n        # update_behavior_relations (from plone.app.relationfield) does that but does not update\n        # those in the main schema. Duh!\n        updateRelations(obj, None)\n        update_behavior_relations(obj, None)\n\n    # purge annotation from portal if they exist\n    if RELATIONS_KEY in IAnnotations(portal):\n        del IAnnotations(portal)[RELATIONS_KEY]\n    logger.info('Done!')\n\n\ndef get_intid(obj):\n    \"\"\"Intid from intid-catalog\"\"\"\n    intids = queryUtility(IIntIds)\n    if intids is None:\n        return\n    # check that the object has an intid, otherwise there's nothing to be done\n    try:\n        return intids.getId(obj)\n    except KeyError:  # noqa\n        # The object has not been added to the ZODB yet\n        return\n\n\ndef get_field_and_schema_for_fieldname(field_id, portal_type):\n    \"\"\"Get field and its schema from a portal_type.\n    \"\"\"\n    # Turn form.widgets.IDublinCore.title into title\n    field_id = field_id.split('.')[-1]\n    for schema in iterSchemataForType(portal_type):\n        field = schema.get(field_id, None)\n        if field is not None:\n            return (field, schema)\n\n\ndef cleanup_intids(context=None):\n    intids = getUtility(IIntIds)\n    all_refs = [f'{i.object.__class__.__module__}.{i.object.__class__.__name__}'\n                for i in intids.refs.values()]\n    logger.info(Counter(all_refs))\n\n    count = 0\n    refs = [i for i in intids.refs.values() if isinstance(i.object, RelationValue)]\n    for ref in refs:\n        intids.unregister(ref)\n        count += 1\n    logger.info(f'Removed all {count} RelationValues from IntId-tool')\n\n    count = 0\n    for ref in intids.refs.values():\n        if 'broken' in repr(ref.object):\n            intids.unregister(ref)\n    logger.info(f'Removed {count} broken refs from IntId-tool')\n    all_refs = ['{i.object.__class__.__module__}.{i.object.__class__.__name__}'\n                for i in intids.refs.values()]\n    logger.info(Counter(all_refs))\n\n\ndef flush_intids():\n    \"\"\" Flush all intids\n    \"\"\"\n    intids = getUtility(IIntIds)\n    intids.ids = intids.family.OI.BTree()\n    intids.refs = intids.family.IO.BTree()\n\n\ndef rebuild_intids():\n    \"\"\" Create new intids\n    \"\"\"\n    def add_to_intids(obj, path):\n        if IContentish.providedBy(obj):\n            logger.info(f'Added {obj} at {path} to intid')\n            addIntIdSubscriber(obj, None)\n    portal = getSite()\n    portal.ZopeFindAndApply(portal,\n                            search_sub=True,\n                            apply_func=add_to_intids)\n", "path": "Products/CMFPlone/relationhelper.py"}]}