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

Last change on this file since 3628 was 3628, checked in by kroman0, 11 years ago

Fixed html validation of mobile layout, Cleanup templates

  • Property svn:eol-style set to native
File size: 10.9 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    mobile = ViewPageTemplateFile('templates/sections_mobile.pt')
82
83    def update(self):
84        # we may need some previously defined variables
85        #super(GlobalSectionsViewlet, self).update()
86
87        # prepare to gather portal tabs
88        context = aq_inner(self.context)
89        self.conf = self._settings()
90        self.tool = getToolByName(context, 'portal_actions')
91        self.site_url = getToolByName(context, 'portal_url')()
92        self.context_state = getMultiAdapter((self.context, self.request),
93                                             name="plone_context_state")
94        self.context_url = self.context_state.is_default_page() and \
95            '/'.join(self.context.absolute_url().split('/')[:-1]) or \
96            self.context.absolute_url()
97
98        self.cat_sufix = self.conf.nested_category_sufix or ''
99        self.cat_prefix = self.conf.nested_category_prefix or ''
100
101    def portal_tabs(self):
102        tabs = []
103
104        # fetch actions-based tabs?
105        if self.conf.show_actions_tabs:
106            tabs.extend(self._actions_tabs())
107
108        # fetch content structure-based tabs?
109        if self.conf.show_content_tabs:
110            # put content-based actions before content structure-based ones?
111            if self.conf.content_before_actions_tabs:
112                tabs = self._content_tabs() + tabs
113            else:
114                tabs.extend(self._content_tabs())
115
116        return tabs
117
118    def _actions_tabs(self):
119        """Return tree of tabs based on portal_actions tool configuration"""
120        conf = self.conf
121        tool = self.tool
122        url = self.context_url
123        starts = url.startswith
124
125        # check if we have required root actions category inside tool
126        if conf.actions_category not in tool.objectIds():
127            return []
128        listtabs = []
129        res, listtabs = self.prepare_tabs(self.site_url)
130        res = copy.deepcopy(res)
131        self.tabs = listtabs
132
133        # if there is no custom menu in portal tabs return
134        if not listtabs:
135            return []
136
137        current_item = -1
138        delta = 1000
139        for info in listtabs:
140            if starts(info['url']) and len(url) - len(info['url']) < delta:
141                delta = len(self.context_url) - len(info['url'])
142                current_item = listtabs.index(info)
143        self.id_chain = []
144
145        active = listtabs[current_item]['url'] == self.site_url
146        active = active and self.context_url == self.site_url
147        active = listtabs[current_item]['url'] != self.site_url or active
148        if  current_item > -1 and current_item < len(listtabs) and active:
149            self.mark_active(listtabs[current_item]['id'],
150                             listtabs[current_item]['url'])
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                    in_category = subcat_id in category.objectIds()
167                    if subcat_id != info['id'] and in_category:
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()
175                parent_id = parent_id.replace(self.cat_prefix, '')
176                parent_id = parent_id.replace(self.cat_sufix, '')
177                tab = {'id': info['id'],
178                       'title': info['title'],
179                       'url': info['url'],
180                       'parent': (parent_id, parent_url)}
181                tabslist.append(tab)
182
183                tab = {'id': info['id'],
184                       'Title': info['title'],
185                       'Description': info['description'],
186                       'getURL': info['url'],
187                       'show_children': len(children) > 0,
188                       'children': children,
189                       'currentItem': False,
190                       'currentParent': False,
191                       'item_icon': {'html_tag': icon},
192                       'normalized_review_state': 'visible'}
193                tabs.append(tab)
194            return tabs
195        tabslist = []
196        tabs = normalize_actions(self.tool._getOb(self.conf.actions_category),
197                                 aq_inner(self.context), 0)
198        return tabs, tabslist
199
200    def _activate(self, res):
201        """Mark selected chain in the tabs dictionary"""
202        for info in res:
203            if info['getURL'] in self.id_chain:
204                info['currentItem'] = True
205                info['currentParent'] = True
206                if info['children']:
207                    self._activate(info['children'])
208
209    def mark_active(self, current_id, url):
210        for info in self.tabs:
211            if info['id'] == current_id and info['url'] == url:
212                self.mark_active(info['parent'][0], info['parent'][1])
213                self.id_chain.append(info['url'])
214
215    def _actionInfos(self, category, object, check_visibility=1,
216                     check_permissions=1, check_condition=1, max=-1):
217        """Return action infos for a given category"""
218        ec = self.tool._getExprContext(object)
219        actions = [ActionInfo(action, ec) for action in category.objectValues()
220                   if IAction.providedBy(action)]
221
222        action_infos = []
223        for ai in actions:
224            if check_visibility and not ai['visible']:
225                continue
226            if check_permissions and not ai['allowed']:
227                continue
228            if check_condition and not ai['available']:
229                continue
230            action_infos.append(ai)
231            if max + 1 and len(action_infos) >= max:
232                break
233        return action_infos
234
235    def _content_tabs(self):
236        """Return tree of tabs based on content structure"""
237        context = aq_inner(self.context)
238
239        queryBuilder = DropDownMenuQueryBuilder(context)
240        strategy = getMultiAdapter((context, None), INavtreeStrategy)
241        # XXX This works around a bug in plone.app.portlets which was
242        # fixed in http://dev.plone.org/svn/plone/changeset/18836
243        # When a release with that fix is made this workaround can be
244        # removed and the plone.app.portlets requirement in setup.py
245        # be updated.
246        if strategy.rootPath is not None and strategy.rootPath.endswith("/"):
247            strategy.rootPath = strategy.rootPath[:-1]
248
249        return buildFolderTree(context, obj=context, query=queryBuilder(),
250                               strategy=strategy).get('children', [])
251
252    @memoize
253    def _settings(self):
254        """Fetch dropdown menu settings registry"""
255        return getDropDownMenuSettings(self.context)
256
257    @dropdowncache
258    def createMenuMobile(self):
259        html = self.mobile(children=self.portal_tabs(), level=1)
260        return xhtmlslimmer.compress(html).strip(' \n')
261
262    @dropdowncache
263    def createMenu(self):
264        html = self.recurse(children=self.portal_tabs(), level=1)
265        return xhtmlslimmer.compress(html).strip(' \n')
266
267    @memoize
268    def is_plone_four(self):
269        """Indicates if we are in plone 4.
270
271        """
272        pm = getToolByName(aq_inner(self.context), 'portal_migration')
273        try:
274            version = versionTupleFromString(pm.getSoftwareVersion())
275        except AttributeError:
276            version = versionTupleFromString(pm.getFileSystemVersion())
277
278        if version:
279            return version[0] == 4
Note: See TracBrowser for help on using the repository browser.