We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Can't add an user in UserGroupsControlpanelView to a Group with pas.plugin.ldap with plone.many_users=True
### What I did:

- add plone group `testgroup1` via /@@usergroup-groupprefs
- set registry entries for `plone.many_users` and `plone.many_groups` to `True`
- add a Folder
- go to the folder and call the sharing view /myfolder/@@sharing
- add the group `testgroup1` with reader, editor roles
- click on the group to add some users `/@@usergroup-groupmembership?groupname=testgroup1`
- search a user
- select a user (should be a LDAP User) and save

### What I expect to happen:
- the user is added to the group

### What actually happened:
- endless waiting for response after click on "Add" Button

### What version of Plone/ Addons I am using:

- Plone 6.0.4
- pas.plugin.ldap 1.8.2
- node.ext.ldap 1.2
- node 1.2.1
- LDAP Backend user objects > 10000

### Some investigations

- in the ZMI acl_users -> source_groups the action is possible without errors
- with loglevel DEBUG i see thousands of queries to the LDAP Backend
- the task run many minutes
- its a limitation of [pas.plugin.ldap](https://github.com/collective/pas.plugins.ldap#limitations-and-future-optimizations)
 

the [GroupMembershipControlPanel](https://github.com/plone/Products.CMFPlone/blob/a5b48c0c24e6eebbe01aa2874eaaa9aa3d49f155/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py#L65) clean the searchstring in the request and pass it to the membership search in UsersGroupsControlPanelView.

An empty searchstring is like a intention "list all users" and a violation against the option `many_users = True`. The search with empty searchstring should not be performed.

With the following patch, all is fine ;-)

```
if searchGroups:
  if not self.many_groups or bool(searchString):
    groupResults = searchView.merge(chain(*[searchView.searchGroups(**{field: searchString}) for field in ['id', 'title']]), 'groupid')
    groupResults = [gtool.getGroupById(g['id']) for g in groupResults if g['id'] not in ignore]
    groupResults.sort(key=lambda x: x is not None and normalizeString(x.getGroupTitleOrName()))
        
  if searchUsers:
    if not self.many_users or bool(searchString):
      userResults = searchView.merge(chain(*[searchView.searchUsers(**{field: searchString}) for field in ['login', 'fullname', 'email']]), 'userid')
      userResults = [mtool.getMemberById(u['id']) for u in userResults if u['id'] not in ignore]
      userResults.sort(key=lambda x: x is not None and x.getProperty('fullname') is not None and normalizeString(x.getProperty('fullname')) or '')
```


--- 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/controlpanel/browser/usergroups.py`
Content:
```
1 from AccessControl import getSecurityManager
2 from Acquisition import aq_inner
3 from itertools import chain
4 from plone.autoform.form import AutoExtensibleForm
5 from plone.base import PloneMessageFactory as _
6 from plone.base.interfaces import ISecuritySchema
7 from plone.base.interfaces import IUserGroupsSettingsSchema
8 from plone.z3cform import layout
9 from Products.CMFCore.permissions import ManagePortal
10 from Products.CMFCore.utils import getToolByName
11 from Products.CMFPlone.utils import normalizeString
12 from Products.Five.browser import BrowserView
13 from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
14 from z3c.form import button
15 from z3c.form import form
16 from zope.component import getAdapter
17 from zope.component import getMultiAdapter
18 from ZTUtils import make_query
19 
20 
21 class UserGroupsSettingsControlPanel(AutoExtensibleForm, form.EditForm):
22     schema = IUserGroupsSettingsSchema
23     id = "usergroupsettings-control-panel"
24     label = _("Users and Groups")
25     form_name = _("User/Groups settings")
26     control_panel_view = "usergroups-controlpanel"
27 
28     @button.buttonAndHandler(_("label_save", default="Save"), name="save")
29     def handleApply(self, action):
30         super().handleApply(self, action)
31 
32     def updateActions(self):
33         super().updateActions()
34         if self.actions and "save" in self.actions:
35             self.actions["save"].addClass("btn-primary")
36 
37 
38 class ControlPanelFormWrapper(layout.FormWrapper):
39     """Use this form as the plone.z3cform layout wrapper to get the control
40     panel layout.
41     """
42 
43     index = ViewPageTemplateFile("controlpanel_usergroups_layout.pt")
44 
45 
46 UserGroupsSettingsPanelView = layout.wrap_form(
47     UserGroupsSettingsControlPanel, ControlPanelFormWrapper
48 )
49 
50 
51 class UsersGroupsControlPanelView(BrowserView):
52     @property
53     def portal_roles(self):
54         pmemb = getToolByName(aq_inner(self.context), "portal_membership")
55         return [r for r in pmemb.getPortalRoles() if r != "Owner"]
56 
57     @property
58     def many_users(self):
59         return getAdapter(aq_inner(self.context), IUserGroupsSettingsSchema).many_users
60 
61     @property
62     def many_groups(self):
63         return getAdapter(aq_inner(self.context), IUserGroupsSettingsSchema).many_groups
64 
65     @property
66     def email_as_username(self):
67         return getAdapter(
68             aq_inner(self.context), ISecuritySchema
69         ).get_use_email_as_login()
70 
71     def makeQuery(self, **kw):
72         return make_query(**kw)
73 
74     def membershipSearch(
75         self, searchString="", searchUsers=True, searchGroups=True, ignore=[]
76     ):
77         """Search for users and/or groups, returning actual member and group items
78         Replaces the now-deprecated prefs_user_groups_search.py script"""
79         groupResults = userResults = []
80 
81         gtool = getToolByName(self, "portal_groups")
82         mtool = getToolByName(self, "portal_membership")
83 
84         searchView = getMultiAdapter(
85             (aq_inner(self.context), self.request), name="pas_search"
86         )
87 
88         if searchGroups:
89             groupResults = searchView.merge(
90                 chain(
91                     *[
92                         searchView.searchGroups(**{field: searchString})
93                         for field in ["id", "title"]
94                     ]
95                 ),
96                 "groupid",
97             )
98             groupResults = [
99                 gtool.getGroupById(g["id"])
100                 for g in groupResults
101                 if g["id"] not in ignore
102             ]
103             groupResults.sort(
104                 key=lambda x: x is not None and normalizeString(x.getGroupTitleOrName())
105             )
106 
107         if searchUsers:
108             userResults = searchView.merge(
109                 chain(
110                     *[
111                         searchView.searchUsers(**{field: searchString})
112                         for field in ["login", "fullname", "email"]
113                     ]
114                 ),
115                 "userid",
116             )
117             userResults = [
118                 mtool.getMemberById(u["id"])
119                 for u in userResults
120                 if u["id"] not in ignore
121             ]
122             userResults.sort(
123                 key=lambda x: x is not None
124                 and x.getProperty("fullname") is not None
125                 and normalizeString(x.getProperty("fullname"))
126                 or ""
127             )
128 
129         return groupResults + userResults
130 
131     def atoi(self, s):
132         try:
133             return int(s)
134         except ValueError:
135             return 0
136 
137     @property
138     def is_zope_manager(self):
139         return getSecurityManager().checkPermission(ManagePortal, self.context)
140 
141     # The next two class methods implement the following truth table:
142     #
143     # MANY USERS/GROUPS SEARCHING       CAN LIST USERS/GROUPS   RESULT
144     # False             False           False                   Lists unavailable
145     # False             False           True                    Show all
146     # False             True            False                   Show matching
147     # False             True            True                    Show matching
148     # True              False           False                   Too many to list
149     # True              False           True                    Lists unavailable
150     # True              True            False                   Show matching
151     # True              True            True                    Show matching
152 
153     # TODO: Maybe have these methods return a text message (instead of a bool)
154     # corresponding to the actual result, e.g. "Too many to list", "Lists
155     # unavailable"
156 
157     @property
158     def show_group_listing_warning(self):
159         if not self.searchString:
160             acl = getToolByName(self, "acl_users")
161             if acl.canListAllGroups():
162                 if self.many_groups:
163                     return True
164         return False
165 
166     @property
167     def show_users_listing_warning(self):
168         if not self.searchString:
169             acl = getToolByName(self, "acl_users")
170             # XXX Huh? Is canListAllUsers broken?
171             if not acl.canListAllUsers():
172                 if self.many_users:
173                     return True
174         return False
175
```
--- 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/controlpanel/browser/usergroups.py b/Products/CMFPlone/controlpanel/browser/usergroups.py
--- a/Products/CMFPlone/controlpanel/browser/usergroups.py
+++ b/Products/CMFPlone/controlpanel/browser/usergroups.py
@@ -86,45 +86,50 @@
         )
 
         if searchGroups:
-            groupResults = searchView.merge(
-                chain(
-                    *[
-                        searchView.searchGroups(**{field: searchString})
-                        for field in ["id", "title"]
-                    ]
-                ),
-                "groupid",
-            )
-            groupResults = [
-                gtool.getGroupById(g["id"])
-                for g in groupResults
-                if g["id"] not in ignore
-            ]
-            groupResults.sort(
-                key=lambda x: x is not None and normalizeString(x.getGroupTitleOrName())
-            )
+            # Only search for all ('') if the many_users flag is not set.
+            if not (self.many_groups) or bool(self.searchString):
+                groupResults = searchView.merge(
+                    chain(
+                        *[
+                            searchView.searchGroups(**{field: searchString})
+                            for field in ["id", "title"]
+                        ]
+                    ),
+                    "groupid",
+                )
+                groupResults = [
+                    gtool.getGroupById(g["id"])
+                    for g in groupResults
+                    if g["id"] not in ignore
+                ]
+                groupResults.sort(
+                    key=lambda x: x is not None
+                    and normalizeString(x.getGroupTitleOrName())
+                )
 
         if searchUsers:
-            userResults = searchView.merge(
-                chain(
-                    *[
-                        searchView.searchUsers(**{field: searchString})
-                        for field in ["login", "fullname", "email"]
-                    ]
-                ),
-                "userid",
-            )
-            userResults = [
-                mtool.getMemberById(u["id"])
-                for u in userResults
-                if u["id"] not in ignore
-            ]
-            userResults.sort(
-                key=lambda x: x is not None
-                and x.getProperty("fullname") is not None
-                and normalizeString(x.getProperty("fullname"))
-                or ""
-            )
+            # Only search for all ('') if the many_users flag is not set.
+            if not (self.many_users) or bool(self.searchString):
+                userResults = searchView.merge(
+                    chain(
+                        *[
+                            searchView.searchUsers(**{field: searchString})
+                            for field in ["login", "fullname", "email"]
+                        ]
+                    ),
+                    "userid",
+                )
+                userResults = [
+                    mtool.getMemberById(u["id"])
+                    for u in userResults
+                    if u["id"] not in ignore
+                ]
+                userResults.sort(
+                    key=lambda x: x is not None
+                    and x.getProperty("fullname") is not None
+                    and normalizeString(x.getProperty("fullname"))
+                    or ""
+                )
 
         return groupResults + userResults

{"golden_diff": "diff --git a/Products/CMFPlone/controlpanel/browser/usergroups.py b/Products/CMFPlone/controlpanel/browser/usergroups.py\n--- a/Products/CMFPlone/controlpanel/browser/usergroups.py\n+++ b/Products/CMFPlone/controlpanel/browser/usergroups.py\n@@ -86,45 +86,50 @@\n         )\n \n         if searchGroups:\n-            groupResults = searchView.merge(\n-                chain(\n-                    *[\n-                        searchView.searchGroups(**{field: searchString})\n-                        for field in [\"id\", \"title\"]\n-                    ]\n-                ),\n-                \"groupid\",\n-            )\n-            groupResults = [\n-                gtool.getGroupById(g[\"id\"])\n-                for g in groupResults\n-                if g[\"id\"] not in ignore\n-            ]\n-            groupResults.sort(\n-                key=lambda x: x is not None and normalizeString(x.getGroupTitleOrName())\n-            )\n+            # Only search for all ('') if the many_users flag is not set.\n+            if not (self.many_groups) or bool(self.searchString):\n+                groupResults = searchView.merge(\n+                    chain(\n+                        *[\n+                            searchView.searchGroups(**{field: searchString})\n+                            for field in [\"id\", \"title\"]\n+                        ]\n+                    ),\n+                    \"groupid\",\n+                )\n+                groupResults = [\n+                    gtool.getGroupById(g[\"id\"])\n+                    for g in groupResults\n+                    if g[\"id\"] not in ignore\n+                ]\n+                groupResults.sort(\n+                    key=lambda x: x is not None\n+                    and normalizeString(x.getGroupTitleOrName())\n+                )\n \n         if searchUsers:\n-            userResults = searchView.merge(\n-                chain(\n-                    *[\n-                        searchView.searchUsers(**{field: searchString})\n-                        for field in [\"login\", \"fullname\", \"email\"]\n-                    ]\n-                ),\n-                \"userid\",\n-            )\n-            userResults = [\n-                mtool.getMemberById(u[\"id\"])\n-                for u in userResults\n-                if u[\"id\"] not in ignore\n-            ]\n-            userResults.sort(\n-                key=lambda x: x is not None\n-                and x.getProperty(\"fullname\") is not None\n-                and normalizeString(x.getProperty(\"fullname\"))\n-                or \"\"\n-            )\n+            # Only search for all ('') if the many_users flag is not set.\n+            if not (self.many_users) or bool(self.searchString):\n+                userResults = searchView.merge(\n+                    chain(\n+                        *[\n+                            searchView.searchUsers(**{field: searchString})\n+                            for field in [\"login\", \"fullname\", \"email\"]\n+                        ]\n+                    ),\n+                    \"userid\",\n+                )\n+                userResults = [\n+                    mtool.getMemberById(u[\"id\"])\n+                    for u in userResults\n+                    if u[\"id\"] not in ignore\n+                ]\n+                userResults.sort(\n+                    key=lambda x: x is not None\n+                    and x.getProperty(\"fullname\") is not None\n+                    and normalizeString(x.getProperty(\"fullname\"))\n+                    or \"\"\n+                )\n \n         return groupResults + userResults\n", "issue": "Can't add an user in UserGroupsControlpanelView to a Group with pas.plugin.ldap with plone.many_users=True\n### What I did:\r\n\r\n- add plone group `testgroup1` via /@@usergroup-groupprefs\r\n- set registry entries for `plone.many_users` and `plone.many_groups` to `True`\r\n- add a Folder\r\n- go to the folder and call the sharing view /myfolder/@@sharing\r\n- add the group `testgroup1` with reader, editor roles\r\n- click on the group to add some users `/@@usergroup-groupmembership?groupname=testgroup1`\r\n- search a user\r\n- select a user (should be a LDAP User) and save\r\n\r\n### What I expect to happen:\r\n- the user is added to the group\r\n\r\n### What actually happened:\r\n- endless waiting for response after click on \"Add\" Button\r\n\r\n### What version of Plone/ Addons I am using:\r\n\r\n- Plone 6.0.4\r\n- pas.plugin.ldap 1.8.2\r\n- node.ext.ldap 1.2\r\n- node 1.2.1\r\n- LDAP Backend user objects > 10000\r\n\r\n### Some investigations\r\n\r\n- in the ZMI acl_users -> source_groups the action is possible without errors\r\n- with loglevel DEBUG i see thousands of queries to the LDAP Backend\r\n- the task run many minutes\r\n- its a limitation of [pas.plugin.ldap](https://github.com/collective/pas.plugins.ldap#limitations-and-future-optimizations)\r\n \r\n\r\nthe [GroupMembershipControlPanel](https://github.com/plone/Products.CMFPlone/blob/a5b48c0c24e6eebbe01aa2874eaaa9aa3d49f155/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py#L65) clean the searchstring in the request and pass it to the membership search in UsersGroupsControlPanelView.\r\n\r\nAn empty searchstring is like a intention \"list all users\" and a violation against the option `many_users = True`. The search with empty searchstring should not be performed.\r\n\r\nWith the following patch, all is fine ;-)\r\n\r\n```\r\nif searchGroups:\r\n  if not self.many_groups or bool(searchString):\r\n    groupResults = searchView.merge(chain(*[searchView.searchGroups(**{field: searchString}) for field in ['id', 'title']]), 'groupid')\r\n    groupResults = [gtool.getGroupById(g['id']) for g in groupResults if g['id'] not in ignore]\r\n    groupResults.sort(key=lambda x: x is not None and normalizeString(x.getGroupTitleOrName()))\r\n        \r\n  if searchUsers:\r\n    if not self.many_users or bool(searchString):\r\n      userResults = searchView.merge(chain(*[searchView.searchUsers(**{field: searchString}) for field in ['login', 'fullname', 'email']]), 'userid')\r\n      userResults = [mtool.getMemberById(u['id']) for u in userResults if u['id'] not in ignore]\r\n      userResults.sort(key=lambda x: x is not None and x.getProperty('fullname') is not None and normalizeString(x.getProperty('fullname')) or '')\r\n```\r\n\n", "before_files": [{"content": "from AccessControl import getSecurityManager\nfrom Acquisition import aq_inner\nfrom itertools import chain\nfrom plone.autoform.form import AutoExtensibleForm\nfrom plone.base import PloneMessageFactory as _\nfrom plone.base.interfaces import ISecuritySchema\nfrom plone.base.interfaces import IUserGroupsSettingsSchema\nfrom plone.z3cform import layout\nfrom Products.CMFCore.permissions import ManagePortal\nfrom Products.CMFCore.utils import getToolByName\nfrom Products.CMFPlone.utils import normalizeString\nfrom Products.Five.browser import BrowserView\nfrom Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\nfrom z3c.form import button\nfrom z3c.form import form\nfrom zope.component import getAdapter\nfrom zope.component import getMultiAdapter\nfrom ZTUtils import make_query\n\n\nclass UserGroupsSettingsControlPanel(AutoExtensibleForm, form.EditForm):\n    schema = IUserGroupsSettingsSchema\n    id = \"usergroupsettings-control-panel\"\n    label = _(\"Users and Groups\")\n    form_name = _(\"User/Groups settings\")\n    control_panel_view = \"usergroups-controlpanel\"\n\n    @button.buttonAndHandler(_(\"label_save\", default=\"Save\"), name=\"save\")\n    def handleApply(self, action):\n        super().handleApply(self, action)\n\n    def updateActions(self):\n        super().updateActions()\n        if self.actions and \"save\" in self.actions:\n            self.actions[\"save\"].addClass(\"btn-primary\")\n\n\nclass ControlPanelFormWrapper(layout.FormWrapper):\n    \"\"\"Use this form as the plone.z3cform layout wrapper to get the control\n    panel layout.\n    \"\"\"\n\n    index = ViewPageTemplateFile(\"controlpanel_usergroups_layout.pt\")\n\n\nUserGroupsSettingsPanelView = layout.wrap_form(\n    UserGroupsSettingsControlPanel, ControlPanelFormWrapper\n)\n\n\nclass UsersGroupsControlPanelView(BrowserView):\n    @property\n    def portal_roles(self):\n        pmemb = getToolByName(aq_inner(self.context), \"portal_membership\")\n        return [r for r in pmemb.getPortalRoles() if r != \"Owner\"]\n\n    @property\n    def many_users(self):\n        return getAdapter(aq_inner(self.context), IUserGroupsSettingsSchema).many_users\n\n    @property\n    def many_groups(self):\n        return getAdapter(aq_inner(self.context), IUserGroupsSettingsSchema).many_groups\n\n    @property\n    def email_as_username(self):\n        return getAdapter(\n            aq_inner(self.context), ISecuritySchema\n        ).get_use_email_as_login()\n\n    def makeQuery(self, **kw):\n        return make_query(**kw)\n\n    def membershipSearch(\n        self, searchString=\"\", searchUsers=True, searchGroups=True, ignore=[]\n    ):\n        \"\"\"Search for users and/or groups, returning actual member and group items\n        Replaces the now-deprecated prefs_user_groups_search.py script\"\"\"\n        groupResults = userResults = []\n\n        gtool = getToolByName(self, \"portal_groups\")\n        mtool = getToolByName(self, \"portal_membership\")\n\n        searchView = getMultiAdapter(\n            (aq_inner(self.context), self.request), name=\"pas_search\"\n        )\n\n        if searchGroups:\n            groupResults = searchView.merge(\n                chain(\n                    *[\n                        searchView.searchGroups(**{field: searchString})\n                        for field in [\"id\", \"title\"]\n                    ]\n                ),\n                \"groupid\",\n            )\n            groupResults = [\n                gtool.getGroupById(g[\"id\"])\n                for g in groupResults\n                if g[\"id\"] not in ignore\n            ]\n            groupResults.sort(\n                key=lambda x: x is not None and normalizeString(x.getGroupTitleOrName())\n            )\n\n        if searchUsers:\n            userResults = searchView.merge(\n                chain(\n                    *[\n                        searchView.searchUsers(**{field: searchString})\n                        for field in [\"login\", \"fullname\", \"email\"]\n                    ]\n                ),\n                \"userid\",\n            )\n            userResults = [\n                mtool.getMemberById(u[\"id\"])\n                for u in userResults\n                if u[\"id\"] not in ignore\n            ]\n            userResults.sort(\n                key=lambda x: x is not None\n                and x.getProperty(\"fullname\") is not None\n                and normalizeString(x.getProperty(\"fullname\"))\n                or \"\"\n            )\n\n        return groupResults + userResults\n\n    def atoi(self, s):\n        try:\n            return int(s)\n        except ValueError:\n            return 0\n\n    @property\n    def is_zope_manager(self):\n        return getSecurityManager().checkPermission(ManagePortal, self.context)\n\n    # The next two class methods implement the following truth table:\n    #\n    # MANY USERS/GROUPS SEARCHING       CAN LIST USERS/GROUPS   RESULT\n    # False             False           False                   Lists unavailable\n    # False             False           True                    Show all\n    # False             True            False                   Show matching\n    # False             True            True                    Show matching\n    # True              False           False                   Too many to list\n    # True              False           True                    Lists unavailable\n    # True              True            False                   Show matching\n    # True              True            True                    Show matching\n\n    # TODO: Maybe have these methods return a text message (instead of a bool)\n    # corresponding to the actual result, e.g. \"Too many to list\", \"Lists\n    # unavailable\"\n\n    @property\n    def show_group_listing_warning(self):\n        if not self.searchString:\n            acl = getToolByName(self, \"acl_users\")\n            if acl.canListAllGroups():\n                if self.many_groups:\n                    return True\n        return False\n\n    @property\n    def show_users_listing_warning(self):\n        if not self.searchString:\n            acl = getToolByName(self, \"acl_users\")\n            # XXX Huh? Is canListAllUsers broken?\n            if not acl.canListAllUsers():\n                if self.many_users:\n                    return True\n        return False\n", "path": "Products/CMFPlone/controlpanel/browser/usergroups.py"}], "after_files": [{"content": "from AccessControl import getSecurityManager\nfrom Acquisition import aq_inner\nfrom itertools import chain\nfrom plone.autoform.form import AutoExtensibleForm\nfrom plone.base import PloneMessageFactory as _\nfrom plone.base.interfaces import ISecuritySchema\nfrom plone.base.interfaces import IUserGroupsSettingsSchema\nfrom plone.z3cform import layout\nfrom Products.CMFCore.permissions import ManagePortal\nfrom Products.CMFCore.utils import getToolByName\nfrom Products.CMFPlone.utils import normalizeString\nfrom Products.Five.browser import BrowserView\nfrom Products.Five.browser.pagetemplatefile import ViewPageTemplateFile\nfrom z3c.form import button\nfrom z3c.form import form\nfrom zope.component import getAdapter\nfrom zope.component import getMultiAdapter\nfrom ZTUtils import make_query\n\n\nclass UserGroupsSettingsControlPanel(AutoExtensibleForm, form.EditForm):\n    schema = IUserGroupsSettingsSchema\n    id = \"usergroupsettings-control-panel\"\n    label = _(\"Users and Groups\")\n    form_name = _(\"User/Groups settings\")\n    control_panel_view = \"usergroups-controlpanel\"\n\n    @button.buttonAndHandler(_(\"label_save\", default=\"Save\"), name=\"save\")\n    def handleApply(self, action):\n        super().handleApply(self, action)\n\n    def updateActions(self):\n        super().updateActions()\n        if self.actions and \"save\" in self.actions:\n            self.actions[\"save\"].addClass(\"btn-primary\")\n\n\nclass ControlPanelFormWrapper(layout.FormWrapper):\n    \"\"\"Use this form as the plone.z3cform layout wrapper to get the control\n    panel layout.\n    \"\"\"\n\n    index = ViewPageTemplateFile(\"controlpanel_usergroups_layout.pt\")\n\n\nUserGroupsSettingsPanelView = layout.wrap_form(\n    UserGroupsSettingsControlPanel, ControlPanelFormWrapper\n)\n\n\nclass UsersGroupsControlPanelView(BrowserView):\n    @property\n    def portal_roles(self):\n        pmemb = getToolByName(aq_inner(self.context), \"portal_membership\")\n        return [r for r in pmemb.getPortalRoles() if r != \"Owner\"]\n\n    @property\n    def many_users(self):\n        return getAdapter(aq_inner(self.context), IUserGroupsSettingsSchema).many_users\n\n    @property\n    def many_groups(self):\n        return getAdapter(aq_inner(self.context), IUserGroupsSettingsSchema).many_groups\n\n    @property\n    def email_as_username(self):\n        return getAdapter(\n            aq_inner(self.context), ISecuritySchema\n        ).get_use_email_as_login()\n\n    def makeQuery(self, **kw):\n        return make_query(**kw)\n\n    def membershipSearch(\n        self, searchString=\"\", searchUsers=True, searchGroups=True, ignore=[]\n    ):\n        \"\"\"Search for users and/or groups, returning actual member and group items\n        Replaces the now-deprecated prefs_user_groups_search.py script\"\"\"\n        groupResults = userResults = []\n\n        gtool = getToolByName(self, \"portal_groups\")\n        mtool = getToolByName(self, \"portal_membership\")\n\n        searchView = getMultiAdapter(\n            (aq_inner(self.context), self.request), name=\"pas_search\"\n        )\n\n        if searchGroups:\n            # Only search for all ('') if the many_users flag is not set.\n            if not (self.many_groups) or bool(self.searchString):\n                groupResults = searchView.merge(\n                    chain(\n                        *[\n                            searchView.searchGroups(**{field: searchString})\n                            for field in [\"id\", \"title\"]\n                        ]\n                    ),\n                    \"groupid\",\n                )\n                groupResults = [\n                    gtool.getGroupById(g[\"id\"])\n                    for g in groupResults\n                    if g[\"id\"] not in ignore\n                ]\n                groupResults.sort(\n                    key=lambda x: x is not None\n                    and normalizeString(x.getGroupTitleOrName())\n                )\n\n        if searchUsers:\n            # Only search for all ('') if the many_users flag is not set.\n            if not (self.many_users) or bool(self.searchString):\n                userResults = searchView.merge(\n                    chain(\n                        *[\n                            searchView.searchUsers(**{field: searchString})\n                            for field in [\"login\", \"fullname\", \"email\"]\n                        ]\n                    ),\n                    \"userid\",\n                )\n                userResults = [\n                    mtool.getMemberById(u[\"id\"])\n                    for u in userResults\n                    if u[\"id\"] not in ignore\n                ]\n                userResults.sort(\n                    key=lambda x: x is not None\n                    and x.getProperty(\"fullname\") is not None\n                    and normalizeString(x.getProperty(\"fullname\"))\n                    or \"\"\n                )\n\n        return groupResults + userResults\n\n    def atoi(self, s):\n        try:\n            return int(s)\n        except ValueError:\n            return 0\n\n    @property\n    def is_zope_manager(self):\n        return getSecurityManager().checkPermission(ManagePortal, self.context)\n\n    # The next two class methods implement the following truth table:\n    #\n    # MANY USERS/GROUPS SEARCHING       CAN LIST USERS/GROUPS   RESULT\n    # False             False           False                   Lists unavailable\n    # False             False           True                    Show all\n    # False             True            False                   Show matching\n    # False             True            True                    Show matching\n    # True              False           False                   Too many to list\n    # True              False           True                    Lists unavailable\n    # True              True            False                   Show matching\n    # True              True            True                    Show matching\n\n    # TODO: Maybe have these methods return a text message (instead of a bool)\n    # corresponding to the actual result, e.g. \"Too many to list\", \"Lists\n    # unavailable\"\n\n    @property\n    def show_group_listing_warning(self):\n        if not self.searchString:\n            acl = getToolByName(self, \"acl_users\")\n            if acl.canListAllGroups():\n                if self.many_groups:\n                    return True\n        return False\n\n    @property\n    def show_users_listing_warning(self):\n        if not self.searchString:\n            acl = getToolByName(self, \"acl_users\")\n            # XXX Huh? Is canListAllUsers broken?\n            if not acl.canListAllUsers():\n                if self.many_users:\n                    return True\n        return False\n", "path": "Products/CMFPlone/controlpanel/browser/usergroups.py"}]}