• 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

49.67
/src/Products/CMFCore/SkinsTool.py
1
##############################################################################
2
#
3
# Copyright (c) 2001 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
"""Portal skins tool.
1✔
14
"""
15

16
from difflib import unified_diff
1✔
17

18
from AccessControl.class_init import InitializeClass
1✔
19
from AccessControl.SecurityInfo import ClassSecurityInfo
1✔
20
from Acquisition import aq_base
1✔
21
from App.special_dtml import DTMLFile
1✔
22
from DateTime import DateTime
1✔
23
from OFS.DTMLMethod import DTMLMethod
1✔
24
from OFS.Folder import Folder
1✔
25
from OFS.Image import Image
1✔
26
from OFS.ObjectManager import REPLACEABLE
1✔
27
from Persistence import PersistentMapping
1✔
28
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
1✔
29
from zope.component import getUtility
1✔
30
from zope.globalrequest import getRequest
1✔
31
from zope.interface import implementer
1✔
32

33
from Products.PythonScripts.PythonScript import PythonScript
1✔
34

35
from .ActionProviderBase import ActionProviderBase
1✔
36
from .DirectoryView import base_ignore
1✔
37
from .DirectoryView import ignore
1✔
38
from .DirectoryView import ignore_re
1✔
39
from .interfaces import IMembershipTool
1✔
40
from .interfaces import ISkinsTool
1✔
41
from .interfaces import IURLTool
1✔
42
from .permissions import AccessContentsInformation
1✔
43
from .permissions import ManagePortal
1✔
44
from .permissions import View
1✔
45
from .SkinsContainer import SkinsContainer
1✔
46
from .utils import UniqueObject
1✔
47
from .utils import _dtmldir
1✔
48
from .utils import registerToolInterface
1✔
49

50

51
def modifiedOptions():
1✔
52
    # Remove the existing "Properties" option and add our own.
53
    rval = []
1✔
54
    for o in Folder.manage_options:
1✔
55
        label = o.get('label', None)
1✔
56
        if label != 'Properties':
1✔
57
            rval.append(o)
1✔
58
    rval[1:1] = [{'label': 'Properties', 'action': 'manage_propertiesForm'}]
1✔
59
    return tuple(rval)
1✔
60

61

62
@implementer(ISkinsTool)
1✔
63
class SkinsTool(UniqueObject, SkinsContainer, Folder, ActionProviderBase):
1✔
64

65
    """ This tool is used to supply skins to a portal.
66
    """
67

68
    id = 'portal_skins'
1✔
69
    meta_type = 'CMF Skins Tool'
1✔
70
    allow_any = 0
1✔
71
    cookie_persistence = 0
1✔
72
    default_skin = ''
1✔
73
    request_varname = 'portal_skin'
1✔
74
    selections = None
1✔
75

76
    security = ClassSecurityInfo()
1✔
77

78
    manage_options = (modifiedOptions() +
1✔
79
                      ({'label': 'Overview', 'action': 'manage_overview'},) +
80
                      ActionProviderBase.manage_options)
81

82
    def __init__(self):
1✔
83
        self.selections = PersistentMapping()
1✔
84

85
    def _getSelections(self):
1✔
86
        sels = self.selections
1✔
87
        if sels is None:
1!
88
            # Backward compatibility.
89
            self.selections = sels = PersistentMapping()
×
90
        return sels
1✔
91

92
    #
93
    #   ZMI methods
94
    #
95
    security.declareProtected(ManagePortal,  # NOQA: flake8: D001
1✔
96
                              'manage_overview')
97
    manage_overview = DTMLFile('explainSkinsTool', _dtmldir)
1✔
98

99
    security.declareProtected(ManagePortal,  # NOQA: flake8: D001
1✔
100
                              'manage_propertiesForm')
101
    manage_propertiesForm = DTMLFile('dtml/skinProps', globals())
1✔
102

103
    # the following two methods override those in FindSupport, to
104
    # support marking of objects used in specific skins
105
    security.declareProtected(ManagePortal,  # NOQA: flake8: D001
1✔
106
                              'manage_findResult')
107
    manage_findResult = DTMLFile('findResult', _dtmldir,
1✔
108
                                 management_view='Find')
109

110
    security.declareProtected(ManagePortal,  # NOQA: flake8: D001
1✔
111
                              'manage_findForm')
112
    manage_findForm = DTMLFile('findForm', _dtmldir,
1✔
113
                               management_view='Find')
114

115
    security.declareProtected(ManagePortal,  # NOQA: flake8: D001
1✔
116
                              'manage_compareResults')
117
    manage_compareResults = DTMLFile('compareResults', _dtmldir,
1✔
118
                                     management_view='Compare')
119

120
    @security.protected(ManagePortal)
1✔
121
    def manage_skinLayers(self, chosen=(), add_skin=0, del_skin=0,
1✔
122
                          skinname='', skinpath='', REQUEST=None):
123
        """ Change the skinLayers.
124
        """
125
        sels = self._getSelections()
×
126
        if del_skin:
×
127
            for name in chosen:
×
128
                del sels[name]
×
129

130
        if REQUEST is not None:
×
131
            for key in sels:
×
132
                fname = 'skinpath_%s' % key
×
133
                val = REQUEST[fname]
×
134

135
                # if val is a list from the new lines field
136
                # then munge it back into a comma delimited list
137
                # for hysterical reasons
138
                if isinstance(val, list):
×
139
                    val = ','.join([layer.strip() for layer in val])
×
140

141
                if sels[key] != val:
×
142
                    self.testSkinPath(val)
×
143
                    sels[key] = val
×
144

145
        if add_skin:
×
146
            skinpath = ','.join([layer.strip() for layer in skinpath])
×
147
            self.testSkinPath(skinpath)
×
148
            sels[str(skinname)] = skinpath
×
149

150
        if REQUEST is not None:
×
151
            msg = 'Skins changed.'
×
152
            return self.manage_propertiesForm(self, REQUEST,
×
153
                                              management_view='Properties',
154
                                              manage_tabs_message=msg)
155

156
    @security.protected(ManagePortal)
1✔
157
    def isFirstInSkin(self, template_path, skin=None):
1✔
158
        """
159
        Is the specified template the one that would get returned
160
        from the current skin?
161
        """
162
        if skin is None or skin == 'None':
×
163
            skin = self.getDefaultSkin()
×
164
        template = self.restrictedTraverse(template_path)
×
165
        name = template.getId()
×
166
        skin_path = self.getSkinPath(skin)
×
167
        if not skin_path:
×
168
            return 0
×
169
        parts = list(skin_path.split(','))
×
170
        found = ''
×
171
        for part in parts:
×
172
            part = part.strip()
×
173
            if part[0] == '_':
×
174
                continue
×
175
            partob = getattr(self, part, None)
×
176
            if partob:
×
177
                skin_template = getattr(partob.aq_base, name, None)
×
178
                if skin_template:
×
179
                    found = skin_template
×
180
                    break
×
181
        if found == template:
×
182
            return 1
×
183
        else:
184
            return 0
×
185

186
    @security.protected(ManagePortal)
1✔
187
    def manage_properties(self, default_skin='', request_varname='',
1✔
188
                          allow_any=0, chosen=(), add_skin=0,
189
                          del_skin=0, skinname='', skinpath='',
190
                          cookie_persistence=0, REQUEST=None):
191
        """ Changes portal_skin properties. """
192
        self.default_skin = str(default_skin)
1✔
193
        self.request_varname = str(request_varname)
1✔
194
        self.allow_any = allow_any and 1 or 0
1✔
195
        self.cookie_persistence = cookie_persistence and 1 or 0
1✔
196
        if REQUEST is not None:
1!
197
            msg = 'Properties changed.'
×
198
            return self.manage_propertiesForm(self, REQUEST,
×
199
                                              management_view='Properties',
200
                                              manage_tabs_message=msg)
201

202
    @security.private
1✔
203
    def PUT_factory(self, name, typ, body):
1✔
204
        """
205
            Dispatcher for PUT requests to non-existent IDs.  Returns
206
            an object of the appropriate type (or None, if we don't
207
            know what to do).
208
        """
209
        major, minor = typ.split('/', 1)
×
210

211
        if major == 'image':
×
212
            return Image(id=name, title='', file='', content_type=typ)
×
213

214
        if major == 'text':
×
215

216
            if minor == 'x-python':
×
217
                return PythonScript(id=name)
×
218

219
            if minor in ('html', 'xml'):
×
220
                return ZopePageTemplate(name)
×
221

222
            return DTMLMethod(__name__=name)
×
223

224
        return None
×
225

226
    # Make the PUT_factory replaceable
227
    PUT_factory__replaceable__ = REPLACEABLE
1✔
228

229
    @security.private
1✔
230
    def testSkinPath(self, p):
1✔
231
        """ Calls SkinsContainer.getSkinByPath().
232
        """
233
        self.getSkinByPath(p, raise_exc=1)
×
234

235
    #
236
    #   'SkinsContainer' interface methods
237
    #
238
    @security.protected(AccessContentsInformation)
1✔
239
    def getSkinPath(self, name):
1✔
240
        """ Convert a skin name to a skin path.
241
        """
242
        sels = self._getSelections()
1✔
243
        p = sels.get(name, None)
1✔
244
        if p is None:
1!
245
            if self.allow_any:
×
246
                return name
×
247
        return p  # Can be None
1✔
248

249
    @security.protected(AccessContentsInformation)
1✔
250
    def getDefaultSkin(self):
1✔
251
        """ Get the default skin name.
252
        """
253
        return self.default_skin
1✔
254

255
    @security.protected(AccessContentsInformation)
1✔
256
    def getRequestVarname(self):
1✔
257
        """ Get the variable name to look for in the REQUEST.
258
        """
259
        return self.request_varname
1✔
260

261
    #
262
    #   UI methods
263
    #
264
    @security.protected(AccessContentsInformation)
1✔
265
    def getAllowAny(self):
1✔
266
        """
267
        Used by the management UI.  Returns a flag indicating whether
268
        users are allowed to use arbitrary skin paths.
269
        """
270
        return self.allow_any
×
271

272
    @security.protected(AccessContentsInformation)
1✔
273
    def getCookiePersistence(self):
1✔
274
        """
275
        Used by the management UI.  Returns a flag indicating whether
276
        the skins cookie is persistent or not.
277
        """
278
        return self.cookie_persistence
×
279

280
    @security.protected(AccessContentsInformation)
1✔
281
    def getSkinPaths(self):
1✔
282
        """
283
        Used by the management UI.  Returns the list of skin name to
284
        skin path mappings as a sorted list of tuples.
285
        """
286
        sels = self._getSelections()
1✔
287
        rval = []
1✔
288
        for key, value in sorted(sels.items()):
1✔
289
            rval.append((key, value))
1✔
290
        return rval
1✔
291

292
    #
293
    #   'portal_skins' interface methods
294
    #
295
    @security.public
1✔
296
    def getSkinSelections(self):
1✔
297
        """ Get the sorted list of available skin names.
298
        """
299
        sels = self._getSelections()
1✔
300
        rval = sorted(sels)
1✔
301
        return rval
1✔
302

303
    @security.protected(View)
1✔
304
    def updateSkinCookie(self):
1✔
305
        """ If needed, updates the skin cookie based on the member preference.
306
        """
307
        mtool = getUtility(IMembershipTool)
×
308
        member = mtool.getAuthenticatedMember()
×
309
        if hasattr(aq_base(member), 'getProperty'):
×
310
            mskin = member.getProperty('portal_skin')
×
311
            if mskin:
×
312
                req = getRequest()
×
313
                cookie = req.cookies.get(self.request_varname, None)
×
314
                if cookie != mskin:
×
315
                    resp = req.RESPONSE
×
316
                    utool = getUtility(IURLTool)
×
317
                    portal_path = req['BASEPATH1'] + '/' + utool(1)
×
318

319
                    if not self.cookie_persistence:
×
320
                        # *Don't* make the cookie persistent!
321
                        resp.setCookie(self.request_varname, mskin,
×
322
                                       path=portal_path)
323
                    else:
324
                        expires = (DateTime('GMT') + 365).rfc822()
×
325
                        resp.setCookie(self.request_varname, mskin,
×
326
                                       path=portal_path, expires=expires)
327
                    # Ensure updateSkinCookie() doesn't try again
328
                    # within this request.
329
                    req.cookies[self.request_varname] = mskin
×
330
                    req[self.request_varname] = mskin
×
331
                    return 1
×
332
        return 0
×
333

334
    @security.protected(View)
1✔
335
    def clearSkinCookie(self):
1✔
336
        """ Expire the skin cookie.
337
        """
338
        req = getRequest()
×
339
        resp = req.RESPONSE
×
340
        utool = getUtility(IURLTool)
×
341
        portal_path = req['BASEPATH1'] + '/' + utool(1)
×
342
        resp.expireCookie(self.request_varname, path=portal_path)
×
343

344
    @security.protected(ManagePortal)
1✔
345
    def addSkinSelection(self, skinname, skinpath, test=0, make_default=0):
1✔
346
        """
347
        Adds a skin selection.
348
        """
349
        sels = self._getSelections()
1✔
350
        skinpath = str(skinpath)
1✔
351

352
        # Basic precaution to make sure the stuff we want to ignore in
353
        # DirectoryViews gets prevented from ending up in a skin path
354
        path_elems = [x.strip() for x in skinpath.split(',')]
1✔
355
        ignored = base_ignore + ignore
1✔
356

357
        for elem in path_elems[:]:
1✔
358
            if elem in ignored or ignore_re.match(elem):
1✔
359
                path_elems.remove(elem)
1✔
360

361
        skinpath = ','.join(path_elems)
1✔
362

363
        if test:
1!
364
            self.testSkinPath(skinpath)
×
365
        sels[str(skinname)] = skinpath
1✔
366
        if make_default:
1!
367
            self.default_skin = skinname
×
368

369
    @security.protected(AccessContentsInformation)
1✔
370
    def getDiff(self, item_one_path, item_two_path, reverse=0):
1✔
371
        """ Return a diff between one and two.
372
        """
373
        if not reverse:
×
374
            item_one = self.unrestrictedTraverse(item_one_path)
×
375
            item_two = self.unrestrictedTraverse(item_two_path)
×
376
        else:
377
            item_one = self.unrestrictedTraverse(item_two_path)
×
378
            item_two = self.unrestrictedTraverse(item_one_path)
×
379

380
        res = unified_diff(item_one.read().splitlines(),
×
381
                           item_two.read().splitlines(),
382
                           item_one_path, item_two_path, '', '', lineterm='')
383
        return res
×
384

385

386
InitializeClass(SkinsTool)
1✔
387
registerToolInterface('portal_skins', ISkinsTool)
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