import urllib
import re
import json
 
from Acquisition import aq_inner
from DateTime import DateTime
 
from zope.interface import implements
from zope.component import getMultiAdapter
 
# BBB: compatibility with older plone versions
try:
    # Plone < 4.3
    from zope.app.container import interfaces
    INameChooser = interfaces.INameChooser
except ImportError:
    # Plone >= 4.3
    from zope.container.interfaces import INameChooser
 
from zope.viewlet.interfaces import IViewletManager, IViewlet
 
from plone.app.layout.navigation.root import getNavigationRoot
 
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.interfaces import IAction, IActionCategory
from Products.CMFCore.ActionInformation import Action, ActionCategory
from Products.CMFCore.Expression import Expression
from Products.CMFPlone import utils
from Products.CMFPlone.browser.navigation import get_view_url
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.Five.browser import BrowserView
from Products.statusmessages.interfaces import IStatusMessage
 
from quintagroup.plonetabs.config import PROPERTY_SHEET, FIELD_NAME
from quintagroup.plonetabs.utils import setupViewletByName
from quintagroup.plonetabs import messageFactory as _
from interfaces import IPloneTabsControlPanel
 
ACTION_ATTRS = ["id", "title", "description", "url_expr",
                "icon_expr", "available_expr", "visible"]
UI_ATTRS = {"id": "id",
            "title": "name",
            "url_expr": "action",
            "icon_expr": "icon_expr",
            "description": "description",
            "available_expr": "condition",
            "visible": "visible"}
GENERATED_TABS_FIELDS = {
    'disable_folder_sections': [
        _(u"Generated tabs switched on."),
        _(u"Generated tabs switched off.")
    ],
    'disable_nonfolderish_sections': [
        _(u"Generated tabs for items other than folders switched on."),
        _(u"Generated tabs for items other than folders switched off.")
    ]
}
 
bad_id = re.compile(r'[^a-zA-Z0-9-_~,.$\(\)# @]').search
 
 
class PloneTabsControlPanel():
 
    implements(IPloneTabsControlPanel)
 
    template = ViewPageTemplateFile("templates/plonetabs.pt")
    actionslist_template = ViewPageTemplateFile("templates/actionslist.pt")
    autogenerated_template = ViewPageTemplateFile("templates/autogenerated.pt")
    autogenerated_list = ViewPageTemplateFile("templates/autogeneratedlist.pt")
 
    # custom templates used to update page sections
    sections_template = ViewPageTemplateFile("templates/sections.pt")
 
    # configuration variables
    prefix = "tabslist_"
    sufix = ""
 
    def __call__(self):
        """Perform the update and redirect if necessary, or render the page"""
        postback = True
        ajaxback = False
        errors = {}
 
        form = self.request.form
        submitted = form.get('form.submitted', False)
        ajax_request = form.get('ajax_request', False)
 
        # action handler def handler(self, form)
        if ajax_request:
            ajaxback = self.ajax_postback(form)
        if submitted:
            postback = self.submitted_postback(form, errors)
 
        if ajaxback:
            self.request.response.setHeader("Content-type", "application/json")
            return json.dumps(ajaxback)
        elif postback:
            return self.template(errors=errors)
 
    def translate(self, message):
        """translate message"""
        ts = getToolByName(self.context, 'translation_service')
        return ts.translate(message, context=self.context)
 
    def submitted_postback(self, form, errors):
        """submitted postback"""
        if 'add.add' in form.keys():
            postback = self.manage_addAction(form, errors)
        elif "edit.save" in form.keys():
            postback = self.manage_editAction(form, errors)
        elif "edit.delete" in form.keys():
            postback = self.manage_deleteAction(form, errors)
        elif "edit.moveup" in form.keys():
            postback = self.manage_moveUpAction(form, errors)
        elif "edit.movedown" in form.keys():
            postback = self.manage_moveDownAction(form, errors)
        elif "autogenerated.save" in form.keys():
            postback = self.manage_setAutogeneration(form, errors)
        else:
            postback = True
        return postback
 
    def ajax_postback(self, form):
        """ajax_postback ajaxback"""
        conv_dict = {
            "edit_moveact": "manage_ajax_moveAction",
            "category_change": "manage_ajax_changeCategory",
            "edit_delete": "manage_ajax_deleteAction",
            "edit_save": "manage_ajax_saveAction",
            "edit_cancel": "manage_ajax_cancelEditting",
            "tabslist_visible": "manage_ajax_toggleActionsVisibility",
            "roottabs_visible": "manage_ajax_toggleRootsVisibility",
            "generated_tabs": "manage_ajax_toggleGeneratedTabs",
            "add_add": "manage_ajax_addAction",
        }
 
        for method in conv_dict.keys():
            if method in form.keys():
                method_val = conv_dict.get(method)
                return getattr(self, method_val)(form)
 
        return False
 
    @property
    def plone_portal_state(self):
        """plone_portal_state"""
        return getMultiAdapter((aq_inner(self.context), self.request),
                               name='plone_portal_state')
 
    @property
    def portal_properties(self):
        """portal_properties"""
        return getToolByName(self.context, 'portal_properties', None)
 
    @property
    def portal_actions(self):
        """portal_actions"""
        return getToolByName(self.context, 'portal_actions')
 
    #
    # AJAX Methods
    #
 
    def manage_ajax_addAction(self, form):
        # extract posted data
        resp_dict = {}
        cat_name = form['category']
        id, ie7bad_category, data = self.parseAddForm(form)
 
        # validate posted data
        errors = self.validateActionFields(cat_name, data)
 
        if not errors:
            action = self.addAction(cat_name, data)
            # add one more action to actions list
            content = self.getActionsList(category=cat_name, tabs=[action, ])
            resp_dict['content'] = content
            resp_dict['status_code'] = 200
            resp_dict['status_message'] = self.translate(
                _(u"'${id}' action successfully added.",
                  mapping={'id': action.id}))
        else:
            resp_dict['status_message'] = self.translate(
                _("Please correct the indicated errors."))
            resp_dict['status_code'] = 500
            resp_dict['content'] = errors
        return resp_dict
 
    def manage_ajax_toggleGeneratedTabs(self, form):
        """Toggle autogenaration setting on configlet"""
        resp_dict = {}
        errors = []
 
        # TODO: parse, validate form
        checked = form['generated_tabs']
        field = form['field']
 
        if field in GENERATED_TABS_FIELDS:
            if checked == 'true':
                self.setSiteProperties(**{field: False})
                message = self.translate(GENERATED_TABS_FIELDS[field][0])
            else:
                self.setSiteProperties(**{field: True})
                message = self.translate(GENERATED_TABS_FIELDS[field][1])
            content = self.getGeneratedTabs()
            resp_dict['content'] = content
            resp_dict['status_code'] = 200
            resp_dict['status_message'] = message
        else:
            resp_dict['status_message'] = self.translate(
                _("Invalid property name."))
            resp_dict['status_code'] = 500
        return resp_dict
 
    def manage_ajax_toggleRootsVisibility(self, form):
        # Toggle visibility for portal actions
        resp_dict = {}
        errors = []
 
        # TODO: parse, validate form
        id = form['orig_id']
        checked = form['visibility']
 
        portal = getMultiAdapter((aq_inner(self.context), self.request),
                                 name='plone_portal_state').portal()
 
        # remove prefix, added for making ids on configlet unique ("roottabs_")
        obj_id = id[len("roottabs_"):]
 
        if obj_id not in portal.objectIds():
            errors.append(self.translate(
                _("Object with '${id}' id doesn't exist in portal root.",
                  mapping={'id': obj_id})))
 
        checked = True if checked == 'true' else False
 
        if not errors:
            portal[obj_id].update(excludeFromNav=not checked)
 
            if checked:
                message = self.translate(
                    _(u"'${id}' object was included into navigation.",
                      mapping={'id': obj_id}))
            else:
                message = self.translate(
                    _(u"'${id}' object was excluded from navigation.",
                      mapping={'id': obj_id}))
            resp_dict['status_message'] = message
            resp_dict['status_code'] = 200
        else:
            resp_dict['status_message'] = errors
            resp_dict['status_code'] = 500
        return resp_dict
 
    def manage_ajax_toggleActionsVisibility(self, form):
        # Toggle visibility for portal actions
        resp_dict = {}
 
        # TODO: parse, validate form
        id = form['orig_id']
        cat_name = form['category']
        checked = form['visibility']
 
        act_id, category, action, errors = self.manage_validateAction(
            id, cat_name)
 
        if not errors:
            self.updateAction(act_id, cat_name, {
                'id': act_id,
                'visible': (checked == 'true') or False
            })
            if checked == 'true':
                message = self.translate(
                    _(u"'${id}' action is now visible.",
                      mapping={'id': act_id}))
            else:
                message = self.translate(
                    _(u"'${id}' action is now invisible.",
                      mapping={'id': act_id}))
            resp_dict['status_message'] = message
            resp_dict['status_code'] = 200
        else:
            resp_dict['status_message'] = errors
            resp_dict['status_code'] = 500
        return resp_dict
 
    def manage_ajax_deleteAction(self, form):
        """Delete portal action with given id & category"""
        resp_dict = {}
 
        # TODO: parse, validate form
        id = form['orig_id']
        cat_name = form['category']
 
        act_id, category, action, errors = self.manage_validateAction(
            id, cat_name)
        if not errors:
            self.deleteAction(act_id, cat_name)
            resp_dict['status_message'] = self.translate(
                _(u"'${id}' action deleted.",
                  mapping={'id': act_id}))
            resp_dict['status_code'] = 200
        else:
            resp_dict['status_message'] = errors
            resp_dict['status_code'] = 500
        return resp_dict
 
    def manage_ajax_cancelEditting(self, form):
        """Hide edit form for given action"""
        resp_dict = {}
        # TODO: parse, validate form
        id = form['orig_id']
        cat_name = form['category']
 
        act_id, category, action, errors = self.manage_validateAction(
            id, cat_name)
 
        if not errors:
            content = self.getActionsList(category=cat_name, tabs=[action, ])
            resp_dict['content'] = content
            resp_dict['status_message'] = self.translate(
                _(u"Changes discarded."))
            resp_dict['status_code'] = 200
        else:
            resp_dict['status_message'] = errors
            resp_dict['status_code'] = 500
        return resp_dict
 
    def manage_validateAction(self, action_id, cat_name):
        """Check whether action with given id exists in cat_name category"""
        errors = []
        act_id = ""
        category = ""
        action = ""
 
        act_id = action_id
 
        try:
            category = self.getActionCategory(cat_name)
        except Exception:
            errors.append(
                self.translate(
                    _(u"'${cat_name}' action category does not exist.",
                      mapping={'cat_name': cat_name})))
 
        try:
            action = category[act_id]
        except Exception:
            errors.append(
                self.translate(
                    _(u"No '${id}' action in '${cat_name}' category.",
                      mapping={'id': act_id, 'cat_name': cat_name})))
        return (act_id, category, action, errors)
 
    def manage_ajax_saveAction(self, form):
        """Manage Method to update action"""
        # extract posted data
        id, cat_name, data = self.parseEditForm(form)
 
        # get category and action to edit
        category = self.getActionCategory(cat_name)
        action = category[id]
 
        # validate posted data
        errors = self.validateActionFields(
            cat_name, data, allow_dup=(id == data['id']))
 
        if not errors:
            action = self.updateAction(id, cat_name, data)
            content = self.getActionsList(category=cat_name, tabs=[action, ])
            resp_dict = {
                'content': content,
                'status_code': 200,
                'status_message': self.translate(
                    _(u"'${id}' action successfully updated.",
                      mapping={'id': action.id}))
            }
        else:
            resp_dict = {
                'content': errors,
                'status_code': 500,
                'status_message': self.translate(
                    _("Please correct the indicated errors."))
            }
        return resp_dict
 
    def manage_ajax_moveAction(self, form):
        cat_name = form['category']
        category = self.getActionCategory(cat_name)
        components = urllib.unquote(form['actions']).split('&')
        if self.sufix == '':
            ids = [component[len(self.prefix):] for component in components]
        else:
            ids = [component[len(self.prefix):-len(self.sufix)]
                   for component in components]
        # do actual sorting
        resp = category.moveObjectsByDelta(ids, -len(category.objectIds()))
 
        if resp:
            resp_dict = {
                'status_code': 200,
                'status_message': self.translate(
                    _("Actions successfully sorted."))
            }
        else:
            resp_dict = {
                'status_code': 500,
                'status_message': self.translate(
                    _("There was error while sorting, or list not changed"))
            }
        return resp_dict
 
    def manage_ajax_changeCategory(self, form):
        resp_dict = {}
 
        """Change action category to manage"""
        cat_name = form['category']
 
        if cat_name in self.portal_actions.objectIds():
            # update actions list
            resp_dict['actionslist'] = self.getActionsList(category=cat_name)
            # update autogenerated sections
            resp_dict['section'] = self.getAutoGenereatedSection(cat_name)
            # and title
            resp_dict['title'] = self.translate(self.getPageTitle(cat_name))
            resp_dict['status_code'] = 200
            resp_dict['status_message'] = self.translate(
                _("Category changed successfully."))
        else:
            resp_dict['status_code'] = 500
            resp_dict['status_message'] = self.translate(
                _(u"'${cat_name}' action category does not exist.",
                mapping={'cat_name': cat_name}))
 
        return resp_dict
 
    #
    # Methods for processing configlet posts
    #
    def manage_setAutogeneration(self, form, errors):
        """Process managing autogeneration settings"""
 
        # set excludeFromNav property for root objects
        portal = self.plone_portal_state.portal()
        generated_tabs = form.get("generated_tabs", '0')
        nonfolderish_tabs = form.get("nonfolderish_tabs", '0')
 
        for item in self.getRootTabs():
            obj = getattr(portal, item['id'], None)
            if obj is not None:
                checked = form.get(item['id'], None)
                if checked == '1':
                    obj.update(excludeFromNav=False)
                else:
                    obj.update(excludeFromNav=True)
 
        # set disable_folder_sections property
        if int(generated_tabs) == 1:
            self.setSiteProperties(disable_folder_sections=False)
        else:
            self.setSiteProperties(disable_folder_sections=True)
 
        # set disable_nonfolderish_sections property
        if int(nonfolderish_tabs) == 1:
            self.setSiteProperties(disable_nonfolderish_sections=False)
        else:
            self.setSiteProperties(disable_nonfolderish_sections=True)
 
        # after successfull form processing make redirect with good message
        IStatusMessage(self.request).addStatusMessage(_(u"Changes saved!"),
                                                      type="info")
        self.redirect()
        return False
 
    def manage_addAction(self, form, errs):
        """Manage method to add a new action to given category,
        if category doesn't exist, create it
        """
        # extract posted data
        id, cat_name, data = self.parseAddForm(form)
 
        # validate posted data
        errors = self.validateActionFields(cat_name, data)
 
        # if not errors find (or create) category and set action to it
        if not errors:
            action = self.addAction(cat_name, data)
            IStatusMessage(self.request).addStatusMessage(
                _(u"'${id}' action successfully added.",
                  mapping={'id': action.id}), type="info")
            self.redirect(search="category=%s" % cat_name)
            return False
        else:
            errs.update(errors)
            IStatusMessage(self.request).addStatusMessage(
                _(u"Please correct the indicated errors."), type="error")
            return True
 
    def manage_editAction(self, form, errs):
        """Manage Method to update action"""
        # extract posted data
        id, cat_name, data = self.parseEditForm(form)
 
        # get category and action to edit
        category = self.getActionCategory(cat_name)
        action = category[id]
 
        # validate posted data
        errors = self.validateActionFields(
            cat_name, data, allow_dup=(id == data['id']))
 
        if not errors:
            action = self.updateAction(id, cat_name, data)
            IStatusMessage(self.request).addStatusMessage(
                _(u"'${id}' action saved.", mapping={'id': action.id}),
                type="info")
            self.redirect(search="category=%s" % cat_name)
            return False
        else:
            errs.update(self.processErrors(
                errors, sufix='_%s' % id))  # add edit form sufix to error ids
            IStatusMessage(self.request).addStatusMessage(_(
                u"Please correct the indicated errors."), type="error")
            return True
 
    def manage_deleteAction(self, form, errs):
        """Manage Method to delete action"""
        # extract posted data
        id, cat_name, data = self.parseEditForm(form)
 
        # get category and action to delete
        category = self.getActionCategory(cat_name)
        if id in category.objectIds():
            self.deleteAction(id, cat_name)
            IStatusMessage(self.request).addStatusMessage(
                _(u"'${id}' action deleted.", mapping={'id': id}), type="info")
            self.redirect(search="category=%s" % cat_name)
            return False
        else:
            IStatusMessage(self.request).addStatusMessage(
                _(u"No '${id}' action in '${cat_name}' category.",
                  mapping={'id': id, 'cat_name': cat_name}),
                type="error")
            return True
 
    def manage_moveUpAction(self, form, errs):
        """Manage Method for moving up given action by one position"""
        # extract posted data
        id, cat_name, data = self.parseEditForm(form)
 
        # get category and action to move
        category = self.getActionCategory(cat_name)
        if id in category.objectIds():
            self.moveAction(id, cat_name, steps=1)
            IStatusMessage(self.request).addStatusMessage(
                _(u"'${id}' action moved up.", mapping={'id': id}),
                type="info")
            self.redirect(search="category=%s" % cat_name)
            return False
        else:
            IStatusMessage(self.request).addStatusMessage(
                _(u"No '${id}' action in '${cat_name}' category.",
                  mapping={'id': id, 'cat_name': cat_name}), type="error")
            return True
 
    def manage_moveDownAction(self, form, errs):
        """Manage Method for moving down given action by one position"""
        # extract posted data
        id, cat_name, data = self.parseEditForm(form)
 
        # get category and action to move
        category = self.getActionCategory(cat_name)
        if id in category.objectIds():
            self.moveAction(id, cat_name, steps=-1)
            IStatusMessage(self.request).addStatusMessage(
                _(u"'${id}' action moved down.", mapping={'id': id}),
                type="info")
            self.redirect(search="category=%s" % cat_name)
            return False
        else:
            IStatusMessage(self.request).addStatusMessage(
                _(u"No '${id}' action in '${cat_name}' category.",
                  mapping={'id': id, 'cat_name': cat_name}),
                type="error")
            return True
 
    def redirect(self, url="", search="", url_hash=""):
        """Redirect to @@plonetabs-controlpanel configlet"""
        if not url:
            portal_url = self.plone_portal_state.portal_url()
            url = '%s/%s' % (portal_url, "@@plonetabs-controlpanel")
        if search:
            search = '?%s' % search
        if url_hash:
            url_hash = '#%s' % url_hash
        self.request.response.redirect("%s%s%s" % (url, search, url_hash))
 
    #
    #
    #  Methods - providers for templates
    #
    #
 
    def _charset(self):
        pp = self.portal_properties
        if pp is not None:
            site_properties = getattr(pp, 'site_properties', None)
            if site_properties is not None:
                return site_properties.getProperty('default_charset', 'utf-8')
        return 'utf-8'
 
    def getPageTitle(self, category="portal_tabs"):
        """See interface"""
        portal_props = self.portal_properties
        default_title = _(u"Plone '${cat_name}' Configuration",
                          mapping={'cat_name': category})
 
        if not hasattr(portal_props, PROPERTY_SHEET):
            return default_title
 
        sheet = getattr(portal_props, PROPERTY_SHEET)
        if not hasattr(sheet, FIELD_NAME):
            return default_title
 
        field = sheet.getProperty(FIELD_NAME)
        dict = {}
        for line in field:
            cat, title = line.split("|", 2)
            dict[cat] = title
 
        title = dict.get(category, None)
        if title is None:
            return default_title
 
        charset = self._charset()
        if not isinstance(title, unicode):
            title = title.decode(charset)
 
        return _(title)
 
    def hasActions(self, category="portal_tabs"):
        """See interface"""
        tool = self.portal_actions
        return len(tool.listActions(categories=[category, ])) > 0
 
    def getPortalActions(self, category="portal_tabs"):
        """See interface"""
        portal_actions = self.portal_actions
 
        if category not in portal_actions.objectIds():
            return []
 
        actions = []
        for item in portal_actions[category].objectValues():
            if IAction.providedBy(item):
                actions.append(item)
 
        return actions
 
    def isGeneratedTabs(self):
        """See interface"""
        site_properties = self.portal_properties.site_properties
        return not site_properties.getProperty("disable_folder_sections",
                                               False)
 
    def isNotFoldersGenerated(self):
        """See interface"""
        site_properties = self.portal_properties.site_properties
        prop = site_properties.getProperty("disable_nonfolderish_sections",
                                           False)
        return not prop
 
    def getActionsList(self, category="portal_tabs", errors={}, tabs=[]):
        """See interface"""
        kw = {'category': category, 'errors': errors}
        if tabs:
            kw['tabs'] = tabs
        return self.actionslist_template(**kw)
 
    def getAutoGenereatedSection(self, cat_name, errors={}):
        """See interface"""
        return self.autogenerated_template(category=cat_name, errors=errors)
 
    def getGeneratedTabs(self):
        """See interface"""
        return self.autogenerated_list()
 
    def _add_sort_option(self, query):
        """ add sort option """
        navtree_props = getattr(self.portal_properties, 'navtree_properties')
        sortAttribute = navtree_props.getProperty('sortAttribute', None)
        if sortAttribute is not None:
            query['sort_on'] = sortAttribute
 
            sortOrder = navtree_props.getProperty('sortOrder', None)
            if sortOrder is not None:
                query['sort_order'] = sortOrder
 
    def getRootTabs(self):
        """See interface"""
        context = aq_inner(self.context)
 
        portal_catalog = getToolByName(context, 'portal_catalog')
        portal_properties = self.portal_properties
        navtree_properties = getattr(portal_properties, 'navtree_properties')
 
        # Build result dict
        result = []
 
        # check whether tabs autogeneration is turned on
        if not self.isGeneratedTabs():
            return result
 
        query = {}
        rootPath = getNavigationRoot(context)
        query['path'] = {'query': rootPath, 'depth': 1}
        query['portal_type'] = utils.typesToList(context)
 
        self._add_sort_option(query)
 
        if navtree_properties.getProperty('enable_wf_state_filtering', False):
            query['review_state'] = navtree_properties.getProperty(
                'wf_states_to_show', [])
 
        query['is_default_page'] = False
 
        if not self.isNotFoldersGenerated():
            query['is_folderish'] = True
 
        # Get ids not to list and make a dict to make the search fast
        idsNotToList = navtree_properties.getProperty('idsNotToList', ())
        excludedIds = {}
        for id in idsNotToList:
            excludedIds[id] = 1
 
        rawresult = portal_catalog.searchResults(**query)
 
        # now add the content to results
        for item in rawresult:
            if item.getId not in excludedIds:
                result.append(self.getItem(item))
 
        return result
 
    def getItem(self, item):
        """get item"""
        context = aq_inner(self.context)
        id_, item_url = get_view_url(item)
        data = {
            'name': utils.pretty_title_or_id(context, item),
            'id': id_,
            'url': item_url,
            'description': item.Description,
            'exclude_from_nav': item.exclude_from_nav,
        }
        return data
 
    def getCategories(self):
        """See interface"""
        portal_actions = self.portal_actions
        return portal_actions.objectIds()
 
    #
    # Methods to make this class looks like global sections viewlet
    #
 
    def test(self, condition, ifTrue, ifFalse):
        """See interface"""
        if condition:
            return ifTrue
        else:
            return ifFalse
 
    # methods for rendering global-sections viewlet via kss,
    # due to bug in macroContent when global-section list is empty,
    # ul have condition
    def portal_tabs(self):
        """See global-sections viewlet"""
        actions = getMultiAdapter((self.context, self.request),
                                  name=u'plone_context_state').actions()
        actions_tabs = []
        try:
            # Plone 4 and higher
            import plone.app.upgrade
            plone.app.upgrade  # pyflakes
        except ImportError:
            actions_tabs = actions
        if not actions_tabs and 'portal_tabs' in actions:
            actions_tabs = actions['portal_tabs']
 
        portal_tabs_view = getMultiAdapter((self.context, self.request),
                                           name="portal_tabs_view")
        return portal_tabs_view.topLevelTabs(actions=actions_tabs)
 
    def selected_portal_tab(self):
        """See global-sections viewlet"""
        # BBB: compatibility with older plone versions.
        # ``selectedTabs`` Python script was merged into the
        # GlobalSectionsViewlet.
        section_viewlet = setupViewletByName(self,
                                             self.context,
                                             self.request,
                                             'plone.global_sections')
        if hasattr(section_viewlet, 'selectedTabs'):
            # Plone >= 4.3
            selected_tabs = section_viewlet.selectedTabs(
                default_tab='index_html',
                portal_tabs=self.portal_tabs())
        else:
            # Plone < 4.3
            selectedTabs = self.context.restrictedTraverse('selectedTabs')
            selected_tabs = selectedTabs('index_html', self.context,
                                         self.portal_tabs())
 
        return selected_tabs['portal']
 
    #
    # Utility Methods
    #
 
    def fixExpression(self, expr):
        """Fix expression appropriately for tal format"""
        if expr.find('/') == 0:
            return 'string:${portal_url}%s' % expr
        elif re.compile('^(ht|f)tps?\:', re.I).search(expr):
            return 'string:%s' % expr
        # elif re.compile('^(python:|string:|not:|exists:|nocall:|path:)',
                        # re.I).search(expr):
            # return expr
        elif expr.find(':') != -1:
            return expr
        else:
            return 'string:${object_url}/%s' % expr
 
    def copyAction(self, action):
        """Copy action to dictionary"""
        action_info = {'description': action.description}
        for attr in ACTION_ATTRS:
            action_info[attr] = getattr(action, attr)
        return action_info
 
    def _validate_expression(self, name, data, errors):
        """ validate expression """
        if data[name]:
            try:
                Expression(data[name])
            except Exception, e:
                mapping = {'expr': data[name]}
                idx = data[name].find(':')
                if idx != -1:
                    mapping['expr_type'] = data[name][:idx]
                errors[name] = self._formatError(e, **mapping)
 
    def validateActionFields(self, cat_name, data, allow_dup=False):
        """Check action fields on validity"""
        errors = {}
 
        if allow_dup:
            # create dummy category to avoid id
            # duplication during action update
            category = ActionCategory(cat_name)
        else:
            # get or create (if necessary) actions category
            category = self.getOrCreateCategory(cat_name)
 
        # validate action id
        chooser = INameChooser(category)
        try:
            chooser.checkName(data['id'], self.context)
        except Exception, e:
            errors['id'] = self._formatError(e, **{'id': data['id']})
 
        # validate action name
        if not data['title'].strip():
            errors['title'] = self.translate(
                _(u"Empty or invalid title specified"))
 
        # validate condition expression
        self._validate_expression('available_expr', data, errors)
 
        # validate action expression
        self._validate_expression('url_expr', data, errors)
 
        # validate icon expression
        self._validate_expression('icon_expr', data, errors)
 
        return errors
 
    def _formatError(self, message, **kw):
        """Make error message a little bit prettier to ease translation"""
        charset = self._charset()
        message = str(message)
        message = message.replace('"', "'")
        mapping = {}
        for key, value in kw.items():
            message = message.replace("'%s'" % value, "'${%s}'" % key)
            # trying to work around zope.i18n issue
            mapping[key] = unicode(value, charset)
        message = message.strip()
        return self.translate(_(unicode(message, charset), mapping=mapping))
 
    def processErrors(self, errors, prefix='', sufix=''):
        """Add prefixes, sufixes to error ids
        This is necessary during edit form validation,
        because every edit form on the page has it's own sufix (id)
        """
        if not (prefix or sufix):
            return errors
 
        result = {}
        for key, value in errors.items():
            result['%s%s%s' % (prefix, key, sufix)] = value
 
        return result
 
    def parseEditForm(self, form):
        """Extract all needed fields from edit form"""
        # get original id and category
        info = {}
        id = form['orig_id']
        category = form['category']
 
        # preprocess 'visible' field (checkbox needs special checking)
        if 'visible_%s' % id in form:
            form['visible_%s' % id] = True
        else:
            form['visible_%s' % id] = False
 
        # collect all action fields
        for attr in ACTION_ATTRS:
            info[attr] = form['%s_%s' % (attr, id)]
 
        return (id, category, info)
 
    def parseAddForm(self, form):
        """Extract all needed fields from add form"""
        info = {}
        id = form['id']
        category = form['category']
 
        # preprocess 'visible' field (checkbox needs special checking)
        if 'visible' in form and form['visible']:
            form['visible'] = True
        else:
            form['visible'] = False
 
        # fix expression fields
        form['url_expr'] = self.fixExpression(form['url_expr'])
 
        # collect all action fields
        for attr in ACTION_ATTRS:
            info[attr] = form[attr]
 
        return (id, category, info)
 
    def getActionCategory(self, name):
        portal_actions = self.portal_actions
        return portal_actions[name]
 
    def getOrCreateCategory(self, name):
        """Get or create (if necessary) category"""
        portal_actions = self.portal_actions
        if name not in map(lambda x: x.id,
                           filter(lambda x: IActionCategory.providedBy(x),
                                  portal_actions.objectValues())):
            portal_actions._setObject(name, ActionCategory(name))
        return self.getActionCategory(name)
 
    def setSiteProperties(self, **kw):
        """Change site_properties"""
        site_properties = self.portal_properties.site_properties
        site_properties.manage_changeProperties(**kw)
        return True
 
    def renderViewlet(self, manager, name):
        if isinstance(manager, basestring):
            manager = getMultiAdapter((self.context, self.request, self,),
                                      IViewletManager, name=manager)
        renderer = getMultiAdapter((self.context, self.request, self, manager),
                                   IViewlet, name=name)
        renderer = renderer.__of__(self.context)
        renderer.update()
        return renderer.render()
    #
    # Basic API to work with portal actions tool in a more pleasent way
    #
 
    def addAction(self, cat_name, data):
        """Create and add new action to category with given name"""
        id = data.pop('id')
        category = self.getOrCreateCategory(cat_name)
        action = Action(id, **data)
        category._setObject(id, action)
        return action
 
    def updateAction(self, id, cat_name, data):
        """Update action with given id and category"""
        new_id = data.pop('id')
        category = self.getActionCategory(cat_name)
 
        # rename action if necessary
        if id != new_id:
            category.manage_renameObject(id, new_id)
 
        # get action
        action = category[new_id]
 
        # update action properties
        for attr in data.keys():
            if attr in data:
                action._setPropValue(attr, data[attr])
 
        return action
 
    def deleteAction(self, id, cat_name):
        """Delete action with given id from given category"""
        category = self.getActionCategory(cat_name)
        category.manage_delObjects(ids=[id, ])
        return True
 
    def moveAction(self, id, cat_name, steps=0):
        """Move action by a given steps"""
        if steps != 0:
            category = self.getActionCategory(cat_name)
            if steps > 0:
                category.moveObjectsUp([id, ], steps)
            else:
                category.moveObjectsDown([id, ], abs(steps))
            return True
        return False