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
RevLine 
[1194]1# -*- coding: utf-8 -*-
2from Acquisition import aq_inner
[1182]3
[3153]4from zope.component import getMultiAdapter
[1194]5
[1182]6from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
[1194]7from Products.CMFCore.utils import getToolByName
8from Products.CMFCore.interfaces import IAction, IActionCategory
9from Products.CMFCore.ActionInformation import ActionInfo
[2657]10from Products.CMFPlone.utils import versionTupleFromString
[1182]11
[1194]12from plone.memoize.instance import memoize
[3234]13from plone.memoize.compress import xhtmlslimmer
[1182]14from plone.app.layout.viewlets import common
[1197]15from plone.app.layout.navigation.navtree import buildFolderTree
16from plone.app.layout.navigation.interfaces import INavtreeStrategy
[1182]17
[1197]18from quintagroup.dropdownmenu.browser.menu import DropDownMenuQueryBuilder
[1319]19from quintagroup.dropdownmenu.util import getDropDownMenuSettings
[1182]20
[2775]21from time import time
22from plone.memoize import ram
23import copy
[1194]24
[3348]25
[3339]26def menu_cache_key(f, view):
27    # menu cache key conssits of:
[3461]28    # - path to selected item
[3347]29    # - site can be accessed on different domains
[3339]30    # - language is important for multilingua sites
[3235]31
[3239]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]
[3339]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.
[3234]44    return view.__name__ + \
[3514]45        path_key + \
46        language + \
47        str(time() // (60 * 5))
[2775]48
[3347]49
50# we are caching the menu structure built out of portal_actions tool
[3339]51# this cache key does not take in account expressions and roles settings
[3234]52def tabs_cache_key(f, view, site_url):
53    return site_url + str(time() // (60 * 60))
54
[3347]55
[3239]56def dropdowncache(f):
57    def func(view):
[3339]58        portal_state = getMultiAdapter((view.context, view.request),
[3514]59                                       name=u'plone_portal_state')
[3347]60        # it is impossible to reliably cache entire rendered menu generated
[3339]61        # with potral actions strategy.
62        if not view.conf.enable_caching or view.conf.show_actions_tabs or \
[3514]63            (not portal_state.anonymous() and
[3339]64                view.conf.caching_strategy == 'anonymous'):
[3239]65            return f(view)
66        return ram.cache(menu_cache_key)(f)(view)
67    return func
[3234]68
[3239]69
[3347]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
[1182]78class GlobalSectionsViewlet(common.GlobalSectionsViewlet):
79    index = ViewPageTemplateFile('templates/sections.pt')
[1194]80    recurse = ViewPageTemplateFile('templates/sections_recurse.pt')
[3491]81    mobile = ViewPageTemplateFile('templates/sections_mobile.pt')
[1182]82
83    def update(self):
[1194]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)
[3236]89        self.conf = self._settings()
[1194]90        self.tool = getToolByName(context, 'portal_actions')
[2700]91        self.site_url = getToolByName(context, 'portal_url')()
[3151]92        self.context_state = getMultiAdapter((self.context, self.request),
[3514]93                                             name="plone_context_state")
[3151]94        self.context_url = self.context_state.is_default_page() and \
[2768]95            '/'.join(self.context.absolute_url().split('/')[:-1]) or \
96            self.context.absolute_url()
[3151]97
98        self.cat_sufix = self.conf.nested_category_sufix or ''
[2772]99        self.cat_prefix = self.conf.nested_category_prefix or ''
[3234]100
101    def portal_tabs(self):
102        tabs = []
103
[1194]104        # fetch actions-based tabs?
[3234]105        if self.conf.show_actions_tabs:
[1194]106            tabs.extend(self._actions_tabs())
107
108        # fetch content structure-based tabs?
[3234]109        if self.conf.show_content_tabs:
[1194]110            # put content-based actions before content structure-based ones?
[3234]111            if self.conf.content_before_actions_tabs:
[1194]112                tabs = self._content_tabs() + tabs
113            else:
114                tabs.extend(self._content_tabs())
115
[3234]116        return tabs
[1194]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
[3514]122        url = self.context_url
123        starts = url.startswith
[1194]124
125        # check if we have required root actions category inside tool
126        if conf.actions_category not in tool.objectIds():
127            return []
[2775]128        listtabs = []
129        res, listtabs = self.prepare_tabs(self.site_url)
130        res = copy.deepcopy(res)
131        self.tabs = listtabs
[3151]132
[2775]133        # if there is no custom menu in portal tabs return
134        if not listtabs:
135            return []
[3151]136
[2774]137        current_item = -1
[2772]138        delta = 1000
[2775]139        for info in listtabs:
[3514]140            if starts(info['url']) and len(url) - len(info['url']) < delta:
[3151]141                delta = len(self.context_url) - len(info['url'])
142                current_item = listtabs.index(info)
143        self.id_chain = []
144
[3514]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:
[3151]149            self.mark_active(listtabs[current_item]['id'],
150                             listtabs[current_item]['url'])
[2775]151        self._activate(res)
152        return res
[3151]153
[3347]154    @tabscache
[2775]155    def prepare_tabs(self, site_url):
[3151]156        def normalize_actions(category, object, level, parent_url=None):
[2775]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
[3514]166                    in_category = subcat_id in category.objectIds()
167                    if subcat_id != info['id'] and in_category:
[2775]168                        subcat = category._getOb(subcat_id)
169                        if IActionCategory.providedBy(subcat):
[3151]170                            children = normalize_actions(subcat, object,
171                                                         level + 1,
172                                                         info['url'])
[2775]173
[3514]174                parent_id = category.getId()
175                parent_id = parent_id.replace(self.cat_prefix, '')
176                parent_id = parent_id.replace(self.cat_sufix, '')
[3151]177                tab = {'id': info['id'],
[3514]178                       'title': info['title'],
179                       'url': info['url'],
180                       'parent': (parent_id, parent_url)}
[2775]181                tabslist.append(tab)
[3151]182
183                tab = {'id': info['id'],
[2775]184                       'Title': info['title'],
185                       'Description': info['description'],
186                       'getURL': info['url'],
187                       'show_children': len(children) > 0,
188                       'children': children,
[3151]189                       'currentItem': False,
[2775]190                       'currentParent': False,
191                       'item_icon': {'html_tag': icon},
192                       'normalized_review_state': 'visible'}
193                tabs.append(tab)
[3151]194            return tabs
[2775]195        tabslist = []
[3151]196        tabs = normalize_actions(self.tool._getOb(self.conf.actions_category),
197                                 aq_inner(self.context), 0)
[2775]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):
[2772]210        for info in self.tabs:
[2775]211            if info['id'] == current_id and info['url'] == url:
[2773]212                self.mark_active(info['parent'][0], info['parent'][1])
[2775]213                self.id_chain.append(info['url'])
[1194]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()
[3514]220                   if IAction.providedBy(action)]
[1194]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"""
[1197]237        context = aq_inner(self.context)
[1194]238
[1197]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
[1194]252    @memoize
253    def _settings(self):
254        """Fetch dropdown menu settings registry"""
[1319]255        return getDropDownMenuSettings(self.context)
[1194]256
[3239]257    @dropdowncache
[3491]258    def createMenuMobile(self):
259        html = self.mobile(children=self.portal_tabs(), level=1)
[3628]260        return xhtmlslimmer.compress(html).strip(' \n')
[3491]261
262    @dropdowncache
[1194]263    def createMenu(self):
[3461]264        html = self.recurse(children=self.portal_tabs(), level=1)
[3628]265        return xhtmlslimmer.compress(html).strip(' \n')
[1194]266
[2658]267    @memoize
[2657]268    def is_plone_four(self):
269        """Indicates if we are in plone 4.
[3151]270
[2657]271        """
[3151]272        pm = getToolByName(aq_inner(self.context), 'portal_migration')
[2658]273        try:
274            version = versionTupleFromString(pm.getSoftwareVersion())
275        except AttributeError:
276            version = versionTupleFromString(pm.getFileSystemVersion())
277
[2657]278        if version:
279            return version[0] == 4
Note: See TracBrowser for help on using the repository browser.