source: products/quintagroup.dropdownmenu/trunk/quintagroup/dropdownmenu/browser/viewlets.py @ 3461

Last change on this file since 3461 was 3461, checked in by chervol, 12 years ago

minor fixes code clean up

  • Property svn:eol-style set to native
File size: 10.6 KB
Line 
1# -*- coding: utf-8 -*-
2from Acquisition import aq_inner
3
4from zope.component import getMultiAdapter
5
6from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
7from Products.CMFCore.utils import getToolByName
8from Products.CMFCore.interfaces import IAction, IActionCategory
9from Products.CMFCore.ActionInformation import ActionInfo
10from Products.CMFPlone.utils import versionTupleFromString
11
12from plone.memoize.instance import memoize
13from plone.memoize.compress import xhtmlslimmer
14from plone.app.layout.viewlets import common
15from plone.app.layout.navigation.navtree import buildFolderTree
16from plone.app.layout.navigation.interfaces import INavtreeStrategy
17
18from quintagroup.dropdownmenu.browser.menu import DropDownMenuQueryBuilder
19from quintagroup.dropdownmenu.util import getDropDownMenuSettings
20
21from time import time
22from plone.memoize import ram
23import copy
24
25
26def menu_cache_key(f, view):
27    # menu cache key conssits of:
28    # - path to selected item
29    # - site can be accessed on different domains
30    # - language is important for multilingua sites
31
32    portal_state = getMultiAdapter((view.context, view.request),
33                                   name=u'plone_portal_state')
34    site_len = len(portal_state.navigation_root_path().split('/'))
35    content_path = view.context.getPhysicalPath()[site_len:]
36    if view.conf.content_tabs_level > 0:
37        content_path = content_path[:view.conf.content_tabs_level]
38    path_key = view.request.physicalPathToURL(content_path)
39
40    language = portal_state.locale().getLocaleID()
41
42    # Cache for five minutes. Note that the HTTP RAM-cache
43    # typically purges entries after 60 minutes.
44    return view.__name__ + \
45           path_key + \
46           language + \
47           str(time() // (60 * 5))
48
49
50# we are caching the menu structure built out of portal_actions tool
51# this cache key does not take in account expressions and roles settings
52def tabs_cache_key(f, view, site_url):
53    return site_url + str(time() // (60 * 60))
54
55
56def dropdowncache(f):
57    def func(view):
58        portal_state = getMultiAdapter((view.context, view.request),
59                                        name=u'plone_portal_state')
60        # it is impossible to reliably cache entire rendered menu generated
61        # with potral actions strategy.
62        if not view.conf.enable_caching or view.conf.show_actions_tabs or \
63           (not portal_state.anonymous() and \
64                view.conf.caching_strategy == 'anonymous'):
65            return f(view)
66        return ram.cache(menu_cache_key)(f)(view)
67    return func
68
69
70def tabscache(f):
71    def func(view, site_url):
72        if not view.conf.enable_caching:
73            return f(view, site_url)
74        return ram.cache(tabs_cache_key)(f)(view, site_url)
75    return func
76
77
78class GlobalSectionsViewlet(common.GlobalSectionsViewlet):
79    index = ViewPageTemplateFile('templates/sections.pt')
80    recurse = ViewPageTemplateFile('templates/sections_recurse.pt')
81
82    def update(self):
83        # we may need some previously defined variables
84        #super(GlobalSectionsViewlet, self).update()
85
86        # prepare to gather portal tabs
87        context = aq_inner(self.context)
88        self.conf = self._settings()
89        self.tool = getToolByName(context, 'portal_actions')
90        self.site_url = getToolByName(context, 'portal_url')()
91        self.context_state = getMultiAdapter((self.context, self.request),
92                                              name="plone_context_state")
93        self.context_url = self.context_state.is_default_page() and \
94            '/'.join(self.context.absolute_url().split('/')[:-1]) or \
95            self.context.absolute_url()
96
97        self.cat_sufix = self.conf.nested_category_sufix or ''
98        self.cat_prefix = self.conf.nested_category_prefix or ''
99
100    def portal_tabs(self):
101        tabs = []
102
103        # fetch actions-based tabs?
104        if self.conf.show_actions_tabs:
105            tabs.extend(self._actions_tabs())
106
107        # fetch content structure-based tabs?
108        if self.conf.show_content_tabs:
109            # put content-based actions before content structure-based ones?
110            if self.conf.content_before_actions_tabs:
111                tabs = self._content_tabs() + tabs
112            else:
113                tabs.extend(self._content_tabs())
114
115        return tabs
116
117    def _actions_tabs(self):
118        """Return tree of tabs based on portal_actions tool configuration"""
119        conf = self.conf
120        tool = self.tool
121
122        # check if we have required root actions category inside tool
123        if conf.actions_category not in tool.objectIds():
124            return []
125        listtabs = []
126        res, listtabs = self.prepare_tabs(self.site_url)
127        res = copy.deepcopy(res)
128        self.tabs = listtabs
129
130        # if there is no custom menu in portal tabs return
131        if not listtabs:
132            return []
133
134        current_item = -1
135        delta = 1000
136        for info in listtabs:
137            if  self.context_url.startswith(info['url']) and \
138                len(self.context_url) - len(info['url']) < delta:
139                delta = len(self.context_url) - len(info['url'])
140                current_item = listtabs.index(info)
141        self.id_chain = []
142
143        if current_item > -1 and current_item < len(listtabs) and \
144            (listtabs[current_item]['url'] != self.site_url or \
145            listtabs[current_item]['url'] == self.site_url and \
146            self.context_url == self.site_url):
147
148            self.mark_active(listtabs[current_item]['id'],
149                             listtabs[current_item]['url'])
150
151        self._activate(res)
152        return res
153
154    @tabscache
155    def prepare_tabs(self, site_url):
156        def normalize_actions(category, object, level, parent_url=None):
157            """walk through the tabs dictionary and build list of all tabs"""
158            tabs = []
159            for info in self._actionInfos(category, object):
160                icon = info['icon'] and '<img src="%s" />' % info['icon'] or ''
161                children = []
162                bottomLevel = self.conf.actions_tabs_level
163                if bottomLevel < 1 or level < bottomLevel:
164                    # try to find out appropriate subcategory
165                    subcat_id = self.cat_prefix + info['id'] + self.cat_sufix
166                    if subcat_id != info['id'] and \
167                       subcat_id in category.objectIds():
168                        subcat = category._getOb(subcat_id)
169                        if IActionCategory.providedBy(subcat):
170                            children = normalize_actions(subcat, object,
171                                                         level + 1,
172                                                         info['url'])
173
174                parent_id = category.getId().replace(self.cat_prefix,
175                                '').replace(self.cat_sufix, '')
176                tab = {'id': info['id'],
177                   'title': info['title'],
178                   'url': info['url'],
179                   'parent': (parent_id, parent_url)}
180                tabslist.append(tab)
181
182                tab = {'id': info['id'],
183                       'Title': info['title'],
184                       'Description': info['description'],
185                       'getURL': info['url'],
186                       'show_children': len(children) > 0,
187                       'children': children,
188                       'currentItem': False,
189                       'currentParent': False,
190                       'item_icon': {'html_tag': icon},
191                       'normalized_review_state': 'visible'}
192                tabs.append(tab)
193            return tabs
194        tabslist = []
195        tabs = normalize_actions(self.tool._getOb(self.conf.actions_category),
196                                 aq_inner(self.context), 0)
197        return tabs, tabslist
198
199    def _activate(self, res):
200        """Mark selected chain in the tabs dictionary"""
201        for info in res:
202            if info['getURL'] in self.id_chain:
203                info['currentItem'] = True
204                info['currentParent'] = True
205                if info['children']:
206                    self._activate(info['children'])
207
208    def mark_active(self, current_id, url):
209        for info in self.tabs:
210            if info['id'] == current_id and info['url'] == url:
211                self.mark_active(info['parent'][0], info['parent'][1])
212                self.id_chain.append(info['url'])
213
214    def _actionInfos(self, category, object, check_visibility=1,
215                     check_permissions=1, check_condition=1, max=-1):
216        """Return action infos for a given category"""
217        ec = self.tool._getExprContext(object)
218        actions = [ActionInfo(action, ec) for action in category.objectValues()
219                    if IAction.providedBy(action)]
220
221        action_infos = []
222        for ai in actions:
223            if check_visibility and not ai['visible']:
224                continue
225            if check_permissions and not ai['allowed']:
226                continue
227            if check_condition and not ai['available']:
228                continue
229            action_infos.append(ai)
230            if max + 1 and len(action_infos) >= max:
231                break
232        return action_infos
233
234    def _content_tabs(self):
235        """Return tree of tabs based on content structure"""
236        context = aq_inner(self.context)
237
238        queryBuilder = DropDownMenuQueryBuilder(context)
239        strategy = getMultiAdapter((context, None), INavtreeStrategy)
240        # XXX This works around a bug in plone.app.portlets which was
241        # fixed in http://dev.plone.org/svn/plone/changeset/18836
242        # When a release with that fix is made this workaround can be
243        # removed and the plone.app.portlets requirement in setup.py
244        # be updated.
245        if strategy.rootPath is not None and strategy.rootPath.endswith("/"):
246            strategy.rootPath = strategy.rootPath[:-1]
247
248        return buildFolderTree(context, obj=context, query=queryBuilder(),
249                               strategy=strategy).get('children', [])
250
251    @memoize
252    def _settings(self):
253        """Fetch dropdown menu settings registry"""
254        return getDropDownMenuSettings(self.context)
255
256    @dropdowncache
257    def createMenu(self):
258        html = self.recurse(children=self.portal_tabs(), level=1)
259        return xhtmlslimmer.compress(html).strip('\n')
260
261    @memoize
262    def is_plone_four(self):
263        """Indicates if we are in plone 4.
264
265        """
266        pm = getToolByName(aq_inner(self.context), 'portal_migration')
267        try:
268            version = versionTupleFromString(pm.getSoftwareVersion())
269        except AttributeError:
270            version = versionTupleFromString(pm.getFileSystemVersion())
271
272        if version:
273            return version[0] == 4
Note: See TracBrowser for help on using the repository browser.