• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Products.CMFCore / 6246931310

20 Sep 2023 09:54AM UTC coverage: 86.008% (-0.3%) from 86.266%
6246931310

Pull #131

github

mauritsvanrees
gha: don't need setup-python on 27 as we use the 27 container.
Pull Request #131: Make decodeFolderFilter and encodeFolderFilter non-public.

2466 of 3689 branches covered (0.0%)

Branch coverage included in aggregate %.

6 of 6 new or added lines in 1 file covered. (100.0%)

17297 of 19289 relevant lines covered (89.67%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.8
/src/Products/CMFCore/ActionProviderBase.py
1
##############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE.
11
#
12
##############################################################################
13
""" Implement a shared base for tools which provide actions.
1✔
14
"""
15

16
from warnings import warn
1✔
17

18
import six
1✔
19

20
from AccessControl.class_init import InitializeClass
1✔
21
from AccessControl.SecurityInfo import ClassSecurityInfo
1✔
22
from App.special_dtml import DTMLFile
1✔
23
from zope.interface import implementer
1✔
24

25
from .ActionInformation import ActionInfo
1✔
26
from .ActionInformation import ActionInformation
1✔
27
from .ActionInformation import getOAI
1✔
28
from .exceptions import AccessControl_Unauthorized
1✔
29
from .Expression import getExprContext
1✔
30
from .interfaces import IActionProvider
1✔
31
from .permissions import ManagePortal
1✔
32
from .utils import _dtmldir
1✔
33

34

35
@implementer(IActionProvider)
1✔
36
class ActionProviderBase:
1✔
37

38
    """ Provide ActionTabs and management methods for ActionProviders
39
    """
40

41
    security = ClassSecurityInfo()
1✔
42
    _actions = ()
1✔
43
    _actions_form = DTMLFile('editToolsActions', _dtmldir)
1✔
44

45
    manage_options = ({'label': 'Actions', 'action': 'manage_editActionsForm',
1✔
46
                       'help': ('CMFCore', 'Actions.stx')},)
47

48
    #
49
    #   ActionProvider interface
50
    #
51
    @security.private
1✔
52
    def listActions(self, info=None, object=None):
1✔
53
        """ List all the actions defined by a provider.
54
        """
55
        oldstyle_actions = self._actions or ()
1✔
56
        if oldstyle_actions:
1✔
57
            warn('Old-style actions are deprecated and will be removed in CMF '
1✔
58
                 '2.4. Use Action and Action Category objects instead.',
59
                 DeprecationWarning, stacklevel=2)
60

61
        return oldstyle_actions
1✔
62

63
    @security.private
1✔
64
    def getActionObject(self, action):
1✔
65
        """Return the actions object or None if action doesn't exist.
66
        """
67
        # separate cataegory and id from action
68
        sep = action.rfind('/')
1✔
69
        if sep == -1:
1✔
70
            raise ValueError('Actions must have the format <category>/<id>.')
1✔
71
        category, id = action[:sep], action[sep+1:]
1✔
72

73
        # search for action and return first one found
74
        for ai in self.listActions():
1✔
75
            try:
1✔
76
                if id == ai.getId() and category == ai.getCategory():
1✔
77
                    return ai
1✔
78
            except AttributeError:
1✔
79
                continue
1✔
80

81
        # no action found
82
        return None
1✔
83

84
    @security.public
1✔
85
    def listActionInfos(self, action_chain=None, object=None,
1✔
86
                        check_visibility=1, check_permissions=1,
87
                        check_condition=1, max=-1):
88
        # List ActionInfo objects.
89
        # (method is without docstring to disable publishing)
90
        #
91
        ec = self._getExprContext(object)
1✔
92
        actions = self.listActions(object=object)
1✔
93
        actions = [ActionInfo(action, ec) for action in actions]
1✔
94

95
        if action_chain:
1✔
96
            filtered_actions = []
1✔
97
            if isinstance(action_chain, six.string_types):
1✔
98
                action_chain = (action_chain,)
1✔
99
            for action_ident in action_chain:
1✔
100
                sep = action_ident.rfind('/')
1✔
101
                category, id = action_ident[:sep], action_ident[sep+1:]
1✔
102
                for ai in actions:
1✔
103
                    if id == ai['id'] and category == ai['category']:
1✔
104
                        filtered_actions.append(ai)
1✔
105
            actions = filtered_actions
1✔
106

107
        action_infos = []
1✔
108
        for ai in actions:
1✔
109
            if check_visibility and not ai['visible']:
1✔
110
                continue
1✔
111
            if check_permissions and not ai['allowed']:
1!
112
                continue
×
113
            if check_condition and not ai['available']:
1!
114
                continue
×
115
            action_infos.append(ai)
1✔
116
            if max + 1 and len(action_infos) >= max:
1!
117
                break
×
118
        return action_infos
1✔
119

120
    @security.public
1✔
121
    def getActionInfo(self, action_chain, object=None, check_visibility=0,
1✔
122
                      check_condition=0):
123
        """ Get an ActionInfo object specified by a chain of actions.
124
        """
125
        action_infos = self.listActionInfos(action_chain, object,
1✔
126
                                            check_visibility=check_visibility,
127
                                            check_permissions=False,
128
                                            check_condition=check_condition)
129
        if not action_infos:
1✔
130
            if object is None:
1✔
131
                provider = self
1✔
132
            else:
133
                provider = object
1✔
134
            msg = 'Action "%s" not available for %s' % (
1✔
135
                        action_chain, '/'.join(provider.getPhysicalPath()))
136
            raise ValueError(msg)
1✔
137
        for ai in action_infos:
1!
138
            if ai['allowed']:
1!
139
                return ai
1✔
140
        raise AccessControl_Unauthorized('You are not allowed to access any '
×
141
                                         'of the specified Actions.')
142

143
    #
144
    #   ZMI methods
145
    #
146
    @security.protected(ManagePortal)
1✔
147
    def manage_editActionsForm(self, REQUEST, manage_tabs_message=None):
1✔
148

149
        """ Show the 'Actions' management tab.
150
        """
151
        actions = []
×
152
        for action in self.listActions():
×
153
            # The Actions tab currently only shows old-style actions,
154
            # so we need to weed out everything else.
155
            if getattr(action, 'getMapping', None) is not None:
×
156
                actions.append(action.getMapping())
×
157

158
        # possible_permissions is in AccessControl.Role.RoleManager.
159
        pp = self.possible_permissions()
×
160
        return self._actions_form(self, REQUEST, actions=actions,
×
161
                                  possible_permissions=pp,
162
                                  management_view='Actions',
163
                                  manage_tabs_message=manage_tabs_message)
164

165
    @security.protected(ManagePortal)
1✔
166
    def addAction(self, id, name, action, condition, permission, category,
1✔
167
                  visible=1, icon_expr='', link_target='', REQUEST=None):
168
        """ Add an action to our list.
169
        """
170
        if not name:
1!
171
            raise ValueError('A name is required.')
×
172

173
        action = action and str(action) or ''
1✔
174
        condition = condition and str(condition) or ''
1✔
175

176
        if not isinstance(permission, tuple):
1✔
177
            permission = (str(permission),)
1✔
178

179
        new_actions = self._cloneActions()
1✔
180

181
        new_action = ActionInformation(id=str(id), title=str(name),
1✔
182
                                       category=str(category),
183
                                       condition=condition,
184
                                       permissions=permission,
185
                                       visible=bool(visible),
186
                                       action=action,
187
                                       icon_expr=icon_expr,
188
                                       link_target=link_target)
189

190
        new_actions.append(new_action)
1✔
191
        self._actions = tuple(new_actions)
1✔
192

193
        if REQUEST is not None:
1!
194
            return self.manage_editActionsForm(
×
195
                REQUEST, manage_tabs_message='Added.')
196

197
    @security.protected(ManagePortal)
1✔
198
    def changeActions(self, properties=None, REQUEST=None):
1✔
199

200
        """ Update our list of actions.
201
        """
202
        if properties is None:
1!
203
            properties = REQUEST
×
204

205
        actions = []
1✔
206

207
        for index in range(len(self._actions)):
1✔
208
            actions.append(self._extractAction(properties, index))
1✔
209

210
        self._actions = tuple(actions)
1✔
211

212
        if REQUEST is not None:
1!
213
            msg = 'Actions changed.'
×
214
            return self.manage_editActionsForm(REQUEST,
×
215
                                               manage_tabs_message=msg)
216

217
    @security.protected(ManagePortal)
1✔
218
    def deleteActions(self, selections=(), REQUEST=None):
1✔
219

220
        """ Delete actions indicated by indexes in 'selections'.
221
        """
222
        sels = list(map(int, selections))  # Convert to a list of integers.
1✔
223

224
        old_actions = self._cloneActions()
1✔
225
        new_actions = []
1✔
226

227
        for index in range(len(old_actions)):
1✔
228
            if index not in sels:
1✔
229
                new_actions.append(old_actions[index])
1✔
230

231
        self._actions = tuple(new_actions)
1✔
232

233
        if REQUEST is not None:
1!
234
            msg = 'Deleted %d action(s).' % len(sels)
×
235
            return self.manage_editActionsForm(REQUEST,
×
236
                                               manage_tabs_message=msg)
237

238
    @security.protected(ManagePortal)
1✔
239
    def moveUpActions(self, selections=(), REQUEST=None):
1✔
240

241
        """ Move the specified actions up one slot in our list.
242
        """
243
        sels = sorted(map(int, selections))  # Convert to list of ints
×
244

245
        new_actions = self._cloneActions()
×
246

247
        for idx in sels:
×
248
            idx2 = idx - 1
×
249
            if idx2 < 0:
×
250
                # Wrap to the bottom.
251
                idx2 = len(new_actions) - 1
×
252
            # Swap.
253
            a = new_actions[idx2]
×
254
            new_actions[idx2] = new_actions[idx]
×
255
            new_actions[idx] = a
×
256

257
        self._actions = tuple(new_actions)
×
258

259
        if REQUEST is not None:
×
260
            msg = 'Moved up %d action(s).' % len(sels)
×
261
            return self.manage_editActionsForm(REQUEST,
×
262
                                               manage_tabs_message=msg)
263

264
    @security.protected(ManagePortal)
1✔
265
    def moveDownActions(self, selections=(), REQUEST=None):
1✔
266

267
        """ Move the specified actions down one slot in our list.
268
        """
269
        sels = sorted(list(map(int, selections)))  # Convert to list of ints
×
270
        sels.reverse()
×
271

272
        new_actions = self._cloneActions()
×
273

274
        for idx in sels:
×
275
            idx2 = idx + 1
×
276
            if idx2 >= len(new_actions):
×
277
                # Wrap to the top.
278
                idx2 = 0
×
279
            # Swap.
280
            a = new_actions[idx2]
×
281
            new_actions[idx2] = new_actions[idx]
×
282
            new_actions[idx] = a
×
283

284
        self._actions = tuple(new_actions)
×
285

286
        if REQUEST is not None:
×
287
            msg = 'Moved down %d action(s).' % len(sels)
×
288
            return self.manage_editActionsForm(REQUEST,
×
289
                                               manage_tabs_message=msg)
290

291
    #
292
    #   Helper methods
293
    #
294
    @security.private
1✔
295
    def _cloneActions(self):
1✔
296

297
        """ Return a list of actions, cloned from our current list.
298
        """
299
        return [x.clone() for x in list(self._actions)]
1✔
300

301
    @security.private
1✔
302
    def _extractAction(self, properties, index):
1✔
303

304
        """ Extract an ActionInformation from the funky form properties.
305
        """
306
        id = str(properties.get('id_%d' % index, ''))
1✔
307
        title = str(properties.get('name_%d' % index, ''))
1✔
308
        action = str(properties.get('action_%d' % index, ''))
1✔
309
        icon_expr = str(properties.get('icon_expr_%d' % index, ''))
1✔
310
        condition = str(properties.get('condition_%d' % index, ''))
1✔
311
        category = str(properties.get('category_%d' % index, ''))
1✔
312
        visible = bool(properties.get('visible_%d' % index, False))
1✔
313
        permissions = properties.get('permission_%d' % index, ())
1✔
314
        link_target = str(properties.get('link_target_%d' % index, ''))
1✔
315

316
        if not title:
1!
317
            raise ValueError('A title is required.')
×
318

319
        if category == '':
1✔
320
            category = 'object'
1✔
321

322
        if isinstance(permissions, six.string_types):
1!
323
            permissions = (permissions,)
×
324

325
        return ActionInformation(id=id, title=title, action=action,
1✔
326
                                 condition=condition,
327
                                 permissions=permissions,
328
                                 category=category,
329
                                 visible=visible,
330
                                 icon_expr=icon_expr,
331
                                 link_target=link_target)
332

333
    def _getOAI(self, object):
1✔
334
        return getOAI(self, object)
×
335

336
    def _getExprContext(self, object):
1✔
337
        return getExprContext(self, object)
1✔
338

339

340
InitializeClass(ActionProviderBase)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc