# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


__doc__ = """Glasnost Cards Web"""

__version__ = '$Revision$'[11:-2]


import cPickle

import glasnost.common.context as context
import glasnost.common.faults as faults
from glasnost.common.ObjectsCommon import ObjectCommon
import glasnost.common.slots as slots
import glasnost.common.system as system
import glasnost.common.tools_new as commonTools
import glasnost.common.xhtmlgenerator as X

from glasnost.proxy.CardsProxy import *

from ObjectsWeb import register, AdminMixin, BaseObjectWebMixin, ObjectsWebMixin
import properties
import things
from tools import *


class Command:
    action = None
    nextModeName = None
    nextObjectId = None

    def getNextUrl(self):
        return X.idUrl(self.nextObjectId, action = self.nextModeName)
    

class AdminCards(AdminMixin, AdminCards):
    pass
register(AdminCards)


class AbstractCard(BaseObjectWebMixin, Card):
    def getEditModeUsersSet(self):
        if self.editModeUsersSet is not None:
            return self.editModeUsersSet
        prototypes = self.getC3PrototypeLinearization()
        for prototype in prototypes:
            if prototype.editModeUsersSet is not None:
                return prototype.editModeUsersSet
        return [system.generalPublicId]

    def getRequiredFieldSlotNames(self, fields, parentSlot = None):
        allSlotNames = ObjectCommon.getSlotNames(self, parentSlot = parentSlot)
        orderedSlotNames = self.getOrderedFieldSlotNames(
                fields, parentSlot = parentSlot)
        slotNames = []
        for slotName in orderedSlotNames:
            if not slotName in allSlotNames:
                continue
            slotNames.append(slotName)
        for slotName in allSlotNames:
            if slotName in slotNames:
                continue
            slotNames.append(slotName)
        return slotNames

    def getRequiredLayoutSlotNames(self, fields, parentSlot = None):
        # Skip properties slots.
        allSlotNames = ObjectCommon.getSlotNames(self, parentSlot = parentSlot)
        orderedSlotNames = self.getOrderedLayoutSlotNames(
                parentSlot = parentSlot)
        slotNames = []
        for slotName in orderedSlotNames:
            if not slotName in allSlotNames:
                continue
            slotNames.append(slotName)
        for slotName in allSlotNames:
            if slotName in slotNames:
                continue
            slotNames.append(slotName)
        return slotNames

    def getViewModeUsersSet(self):
        if self.viewModeUsersSet is not None:
            return self.viewModeUsersSet
        prototypes = self.getC3PrototypeLinearization()
        for prototype in prototypes:
            if prototype.viewModeUsersSet is not None:
                return prototype.viewModeUsersSet
        return [system.generalPublicId]


class Card(AbstractCard):
    pass
register(Card)


class CardForDefinition(AbstractCard):
    thingName = 'Card'

    def getFieldSlotNames(self, fields, parentSlot = None):
        allSlotNames = ObjectCommon.getSlotNames(self, parentSlot = parentSlot)
        orderedSlotNames = self.getOrderedFieldSlotNames(
                fields, parentSlot = parentSlot)
        slotNames = []
        for slotName in orderedSlotNames:
            if not slotName in allSlotNames:
                continue
            slotNames.append(slotName)
        for slotName in allSlotNames:
            if slotName in slotNames:
                continue
            slotNames.append(slotName)
        return slotNames

    def getLayoutSlotNames(self, fields, parentSlot = None):
        # Skip properties slots.
        allSlotNames = ObjectCommon.getSlotNames(self, parentSlot = parentSlot)
        orderedSlotNames = self.getOrderedLayoutSlotNames(
            parentSlot = parentSlot)
        slotNames = []
        for slotName in orderedSlotNames:
            if not slotName in allSlotNames:
                continue
            slotNames.append(slotName)
        for slotName in allSlotNames:
            if slotName in slotNames:
                continue
            slotNames.append(slotName)
        return slotNames

##     def getSlotNames(self, parentSlot = None):
##         return ObjectCommon.getSlotNames(self, parentSlot = parentSlot)

    def getSlotToModifyNames(self, parentSlot = None):
        return ObjectCommon.getSlotNames(self, parentSlot = parentSlot)

    def makeContentTitle(self, contentSlot, contentLabel):
        return X.a(href = X.idUrl(
                self.id, 'definition/%s' % contentSlot.getPath()))(
                        contentLabel)


class CardForUse(AbstractCard):
    _c3PrototypeLinearization = None

    class defaultImplementModeName_kindClass(
            AbstractCard.defaultImplementModeName_kindClass):
        importExport = 'from-server-only'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    class defaultListModeName_kindClass(
            AbstractCard.defaultListModeName_kindClass):
        importExport = 'from-server-only'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    class defaultModeName_kindClass(AbstractCard.defaultModeName_kindClass):
        importExport = 'from-server-only'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    draftKey = None
    class draftKey_kindClass:
        _kindName = 'PythonIdentifier'
        importExport = 'private'
        isRequiredInEditMode = 1
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        
    class editMode_kindClass(AbstractCard.editMode_kindClass):
        importExport = 'from-server-only'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    class editModeUsersSet_kindClass(AbstractCard.editModeUsersSet_kindClass):
        importExport = 'from-server-only'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    class modes_kindClass(AbstractCard.modes_kindClass):
        importExport = 'from-server-only'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    properties_kind_importExport = 'from-server-only'
    properties_kind_stateInEditMode = 'hidden'
    properties_kind_stateInViewMode = 'hidden'

    # prototypeIds is modified for new implementations.
    # prototypeIds_kind_importExport = 'from-server-only'
    prototypeIds_kind_stateInEditMode = 'hidden'
    prototypeIds_kind_stateInViewMode = 'hidden'

    thingName = 'Card'

    class viewMode_kindClass(AbstractCard.viewMode_kindClass):
        importExport = 'from-server-only'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    class viewModeUsersSet_kindClass(AbstractCard.viewModeUsersSet_kindClass):
        importExport = 'from-server-only'
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'

    def getC3PrototypeLinearization(self):
        if self._c3PrototypeLinearization is None:
            self._c3PrototypeLinearization = \
                    AbstractCard.getC3PrototypeLinearization(self)
        return self._c3PrototypeLinearization

    def getSlotToModifyNames(self, parentSlot = None):
        return self.getPropertyNames()

    # To do later...
##     def makeContentTitle(self, contentSlot, contentLabel):
##         return X.a(href = X.idUrl(
##                 self.id, 'content/%s' % contentSlot.getPath()))(
##                         contentLabel)


class CardsWeb(ObjectsWebMixin, CardsProxy):
    def definition(self, id, contentPath = ''):
        if not self.hasObject(id):
            return pageNotFound()

        object = self.getObject(id)
        object.__class__ = CardForDefinition # Important.
        rememberObject(id)

        try:
            contentSlot = object.getSlotByPath(contentPath)
        except: # FIXME.
            if context.getVar('debug'):
                raise
            return pageNotFound()
        if contentSlot is None:
            contentSlot = slots.Root(object)
        content = contentSlot.getValue()
        contentKind = contentSlot.getKind()
        contentWidget = contentKind.getModelWidget(
                contentSlot, forceEmbedding = 1)

        layout = X.array()

        if contentSlot.parent is not None:
            # Add the public path to the layout.
            # FIXME: quick & dirty.
            contentPublicPath = []
            slot = contentSlot
            while slot.parent is not None and slot.parent.parent is not None:
                if slot.parent.parent.parent is None:
                    contentPublicPath.append(object.makeContentTitle(
                            slot, slot.parent.getLabel()))
                else:
                    contentPublicPath.append(
                            object.makeContentTitle(slot, slot.getLabel()))
                slot = slot.parent
            contentPublicPath.reverse()
            publicPath = X.array()
            publicPath.append(object.makeContentTitle(
                    slots.Root(object), object.getLabel()))
            for itemTitle in contentPublicPath:
                publicPath.append(' / ')
                publicPath.append(itemTitle)
            layout += X.div(_class = 'public-path')(publicPath)

        layout += contentWidget.getModelPageBodyLayout(contentSlot, None)

        buttonsBar = X.div(_class = 'buttons-bar')
        layout += buttonsBar
        navigationButtonsBar = X.span(_class = 'navigation-buttons-bar')
        buttonsBar += navigationButtonsBar
        if object.getDefaultModeName() != 'view':
            navigationButtonsBar += X.buttonStandalone('use', X.idUrl(id))
        navigationButtonsBar += X.buttonStandalone('view', X.idUrl(id, 'view'))
        if self.canModifyObject(object.id):
            buttonsBar += X.span(_class = 'action-buttons-bar')(
                    X.buttonStandalone(
                            'edit',
                            X.idUrl(id, 'editDefinition/%s' % contentPath)))

        return writePageLayout(
                layout, _('Definition of Card "%s"') % object.getLabel())
    definition.isPublicForWeb = 1

    def edit(self, *arguments, **keywords):
        pass
    edit.isPublicForWeb = 0 # Important, so that self.parseHttpPathAction is
                            # called.

    def editDefinition(self, id, contentPath = ''):
        if id and not self.hasObject(id):
            return pageNotFound()

        if id:
            if not self.canModifyObject(id):
                return accessForbidden()
            object = self.getObject(id)
            rememberObject(id)
        else:
            if not self.canAddObject():
                return accessForbidden()
            object = self.newObject(None)
        
        object.__class__ = CardForDefinition # Important.
        
        return self.editDefinitionObject(object, id, contentPath)
    editDefinition.isPublicForWeb = 1

    def editDefinitionObject(self, object, id = '', contentPath = ''):
        try:
            contentSlot = object.getSlotByPath(contentPath)
        except: # FIXME.
            if context.getVar('debug'):
                raise
            return pageNotFound()
        if contentSlot is None:
            contentSlot = slots.Root(object)
        content = contentSlot.getValue()
        contentKind = contentSlot.getKind()
        contentWidget = contentKind.getModelWidget(
                contentSlot, forceEmbedding = 1)

        if not id:
            headerTitle = self.getTranslatedNewObjectNameCapitalized()
        else:
            headerTitle = _('Editing Definition of Card "%s"') \
                          % object.getLabel()

        context.push(_level = 'editDefinition',
                     isCreateEditMode = not id,
                     layoutMode = 'edit')
        try:
            layout = X.array()

            if contentSlot.parent is not None:
                # Add the public path to the layout.
                # FIXME: quick & dirty.
                contentPublicPath = []
                slot = contentSlot
                while slot.parent is not None \
                          and slot.parent.parent is not None:
                    if slot.parent.parent.parent is None:
                        contentPublicPath.append(slot.parent.getLabel())
                    else:
                        contentPublicPath.append(slot.getLabel())
                    slot = slot.parent
                contentPublicPath.reverse()
                publicPath = X.array()
                publicPath.append(object.getLabel())
                for itemTitle in contentPublicPath:
                    publicPath.append(' / ')
                    publicPath.append(itemTitle)
                layout += X.div(_class = 'public-path')(publicPath)

            if context.getVar('error'):
                layout += object.getErrorLayout()
            form = X.form(action = X.actionUrl('submitDefinition'),
                          enctype= 'multipart/form-data', method = 'post')
            layout += form
            if content != object:
                form += X.input(name = 'id', type = 'hidden', value = id)
            form += X.input(name = 'contentPath', type = 'hidden',
                            value = contentPath)

            form += contentWidget.getModelPageBodyLayout(contentSlot, None)

            buttonsBar = X.div(_class = 'buttons-bar')
            form += buttonsBar
            actionButtonsBar = X.span(_class = 'action-buttons-bar')
            buttonsBar += actionButtonsBar
            if not id:
                actionButtonsBar += X.buttonInForm('create', 'createButton')
            else:
                actionButtonsBar += X.buttonInForm('modify', 'modifyButton')

            return writePageLayout(layout, headerTitle)
        finally:
            context.pull(_level = 'editDefinition')

    def getViewAllActionButtonsBarLayout(self):
        layout = X.array()
        if self.canAddObject():
            layout += X.buttonStandalone(
                    'new', X.actionUrl('editDefinition').add('id', ''))
        return layout

    def getViewNavigationButtonsBarLayout(self, object, fields):
        layout = X.array()
        if object.getDefaultModeName() != 'view':
            layout += X.buttonStandalone('use', X.idUrl(object.id))
        layout += ObjectsWebMixin.getViewNavigationButtonsBarLayout(
            self, object, fields)
        userToken = context.getVar('userToken', default = '')
        if userToken:
            layout += X.buttonStandalone(
                    'view-definition', X.idUrl(object.id, 'definition'))
        return layout

    def getViewOtherActionButtonsBarLayout(self, object, fields):
        layout = X.array()
        layout += ObjectsWebMixin.getViewOtherActionButtonsBarLayout(
            self, object, fields)
        if self.canAddObject():
            layout += X.buttonStandalone(
                    'implement', X.idUrl(object.id, 'new'))
        return layout


    def implementations(self, id, slotNames = None):
        if type(slotNames) is not types.ListType:
            slotNames = None

        if not self.hasObject(id):
            return pageNotFound()
        if not self.canGetObject(id):
            return accessForbidden()
        object = self.getObject(id)
        object.__class__ = CardForUse # Important.
        rememberObject(id)
        #object.execute('onImplementations')

        if not self.canGetObjects():
            return accessForbidden()
        objects = self.getImplementations(id)
        layout = X.array()
        leadIn = self.getViewAllLeadIn()
        if leadIn:
            layout += X.enclose(leadIn, _class = 'lead-in')
        ids = self.getSortedIds(objects)
        layout += self.getObjectsLayout(objects, ids, slotNames = slotNames)

        buttonsBar = X.div(_class = 'buttons-bar')
        layout += buttonsBar
        buttonsBar += X.span(_class = 'action-buttons-bar')(
                X.buttonStandalone('new', X.idUrl(id, 'new')))

        headerTitle = _('"%s" Implementations' % object.getLabelTranslated(
                context.getVar('readLanguages')))
        return writePageLayout(layout, headerTitle)
    implementations.isPublicForWeb = 1

    def index(self, id, modeName = '', *arguments, **keywords):
        localId = commonTools.extractLocalId(id)
        if localId.startswith('draft/'):
            draftKey = localId
            session = context.getVar('session')
            if session is None or not session.has_key(draftKey):
                return pageNotFound()
            object = cPickle.loads(session[draftKey])
        else:
            if not self.hasObject(id):
                return pageNotFound()
            if not self.canGetObject(id):
                return accessForbidden()
            object = self.getObject(id)
            object.__class__ = CardForUse # Important.

        if not modeName:
            modeName = object.getDefaultModeName()
        elif not object.hasMode(modeName):
            # modeName is not a mode name, but an action.
            arguments = [modeName] + list(arguments)
            modeName = object.getDefaultModeName()
        if not object.hasMode(modeName):
            return pageNotFound()
        mode = object.getMode(modeName)
        identitiesProxy = getProxyForServerRole('identities')
        if not identitiesProxy.setContainsUser(
                mode.getModelUsersSet(object)):
            return accessForbidden()

        command = None
        if arguments:
            action = arguments[0]
            if action == 'new':
                prototypeId = id
                id = ''
                if not self.canAddObject():
                    return accessForbidden()
                object = self.newObject(keywords)
                object.__class__ = CardForUse # Important.
                object.prototypeIds = [prototypeId]
                command = Command()
                command.action = 'create'

        if id:
            rememberObject(id)

        return self.indexObject(object, mode, command)
    index.isPublicForWeb = 1

    def indexObject(self, object, mode, command):
        modeName = mode.getName()
        if modeName == 'edit':
            layoutMode = 'edit'
        elif modeName == 'view':
            layoutMode = 'view'
        else:
            layoutMode = 'use'

        create = 0
        if command and (command.action == 'create' or
                        command.action == 'submit' and not object.id):
            create = 1
            prototypeId = object.prototypeIds[0]
            
        context.push(_level = 'index', layoutMode = layoutMode)
        try:
            headerTitle, pageBodyLayout, buttonsBarLayout, inForm = \
                    mode.getModelLayoutInfos(object, None, create)
            layout = X.array()
            if inForm:
                if context.getVar('error'):
                    layout += object.getErrorLayout()
                form = X.form(action = X.actionUrl('submit'),
                              enctype = 'multipart/form-data', method = 'post')
                layout += form
                if modeName:
                    form += X.input(name = 'modeName', type = 'hidden',
                                    value = modeName)
                if create:
                    form += X.input(name = 'prototypeId', type = 'hidden',
                                    value = prototypeId)
                form += pageBodyLayout
                if buttonsBarLayout:
                    form += buttonsBarLayout
            else:
                layout += pageBodyLayout
                if buttonsBarLayout:
                    layout += buttonsBarLayout

            context.push(
                    currentObject = WebAPI.GlasnostObject(object = object))
            layout = writePageLayout(layout, headerTitle)
            context.pull()
            return layout
        finally:
            context.pull(_level = 'index')

    def parseHttpPathAction(self, remaining):
        context.push()
        try:
            if not remaining:
                remaining = [] # Duplicate it for the insert below.
                modeName = None
            else:
                modeName = remaining[0]
                remaining = remaining[1:]
            id = context.getVar('objectId')
            localId = commonTools.extractLocalId(id)
            if localId.startswith('draft/'):
                draftKey = localId
                session = context.getVar('session')
                if session is None or not session.has_key(draftKey):
                    return pageNotFound()
                object = cPickle.loads(session[draftKey])
            else:
                if not self.hasObject(id):
                    return None
                if not self.canGetObject(id):
                    return None
                object = self.getObject(id)
                object.__class__ = CardForUse # Important.
            if not modeName:
                modeName = object.getDefaultModeName()
            elif modeName == 'new':
                modeName = object.getDefaultImplementModeName()
                remaining.insert(0, 'new')
            if not object.hasMode(modeName):
                return None
            context.setVar('function', self.index)
            remaining.insert(0, modeName)
            return context.getVar('application').parseHttpPathArguments(
                    remaining)
        finally:
            context.pull()

    def parseHttpPathId(self, remaining):
        context.push()
        try:
            assert context.getVar('objectId') is None
            assert context.getVar('serverRole') is not None
            if not remaining:
                return None
            if remaining[0] == 'draft':
                if len(remaining) < 2:
                    return None
                localId = '%s/%s' % (remaining[0], remaining[1])
                remaining = remaining[2:]
            else:
                localId = remaining[0]
                remaining = remaining[1:]
            objectId = '%s/%s/%s' % (
                    context.getVar('dispatcherId'),
                    context.getVar('serverRole'),
                    localId)
            context.setVar('objectId', objectId)
            result = context.getVar('application').parseHttpPathAction(
                    remaining)
            if result is not None:
                return result
            return None
        finally:
            context.pull()

    def submit(self, id = '', modeName = '', prototypeId = '', **keywords):
        if keywords is None:
            keywords = {}
        keywords['id'] = id # FIXME: not sure it is still useful
        if isButtonSelected('applyButton', keywords):
            context.setVar('again', 1)
            context.setVar('hideErrors', 1)

        removeDraft = 0
        if id:
            localId = commonTools.extractLocalId(id)
            if localId.startswith('draft/'):
                draftKey = localId
                session = context.getVar('session')
                if session is None or not session.has_key(draftKey):
                    if context.getVar('debug'):
                        raise
                    return accessForbidden()
                object = cPickle.loads(session[draftKey])
                removeDraft = 1
            else:
                object = self.getObject(id)
                object.__class__ = CardForUse # Important.
        else:
            if not prototypeId:
                return accessForbidden()
            object = self.newObject(keywords)
            object.prototypeIds = [prototypeId]
            object.__class__ = CardForUse # Important.
        mode = object.getMode(modeName)
        identitiesProxy = getProxyForServerRole('identities')
        if not identitiesProxy.setContainsUser(
                mode.getModelUsersSet(object)):
            return accessForbidden()

        command = Command()
        command.action = 'submit'
        command.nextObjectId = id
        context.push(_level = 'submit',
                     command  = command)
        try:
            mode.submitModelFields(object, keywords)
        finally:
            # I don't like this; why is the submit in its own context ?
            again = context.getVar('again')
            error = context.getVar('error')
            command = context.getVar('command')
            context.pull(_level = 'submit')
            if again:
                context.setVar('again', 1)
            if error:
                context.setVar('error', 1)

        if context.getVar('again'):
            return self.indexObject(object, mode, command)
        mode.executeModel(object, 'onSubmit', command)
        action = command.action
        if action == 'create' or action == 'submit' and not id:
            try:
                id = self.addObject(object)
            except faults.Fault:
                if context.getVar('debug'):
                    raise
                return accessForbidden()
        elif action == 'modify' or action == 'submit' and id:
            slotToModifyNames = mode.getModelSlotToModifyNames(object)
            if slotToModifyNames:
                # The previous test is needed because modifyPartialObject
                # modifies every slot when slotToModifyNames is empty.
                try:
                    self.modifyPartialObject(object, slotToModifyNames)
                except faults.WrongVersion:
                    context.setVar('again', 1)
                    context.setVar('error', 1)
                    object.setError('version', 1)
                    return self.indexObject(object, mode, command)
                except:
                    if context.getVar('debug'):
                        raise
                    return accessForbidden()
        elif action == 'delete':
            try:
                self.deleteObject(id)
            except faults.Fault:
                if context.getVar('debug'):
                    raise
                return accessForbidden()
        elif action == 'draft':
            session = context.getVar('session')
            if session is None:
                if context.getVar('debug'):
                    raise
                return accessForbidden()
            if object.draftKey is not None \
                   and session.has_key(object.draftKey):
                draftKey = object.draftKey
            else:
                if session.has_key('lastDraftNumber'):
                    # A session can store only two drafts.
                    draftNumber = (session['lastDraftNumber'] + 1) % 2
                else:
                    draftNumber = 0
                session['lastDraftNumber'] = draftNumber
                draftKey = 'draft/%d' % draftNumber
            session[draftKey] = cPickle.dumps(object)
            session['isDirty'] = 1
            defaultDispatcherId = context.getVar('defaultDispatcherId')
            id = '%s/%s' % (commonTools.makeApplicationId(
                                    defaultDispatcherId, self.serverRole),
                            draftKey)
            removeDraft = 0
        elif action:
            if context.getVar('debug'):
                raise faults.UnknownCommandAction(action)
            return accessForbidden()
        else:
            # Accept empty action.
            pass

        if removeDraft and object.draftKey is not None:
            session = context.getVar('session')
            if session is not None and session.has_key(object.draftKey):
                del session[draftKey]
                session['isDirty'] = 1

        # FIXME: To improve.
        if not command.nextObjectId:
            command.nextObjectId = id
        return redirect(command.getNextUrl())
    submit.isPublicForWeb = 1

    def submitDefinition(self, id, contentPath = '', **keywords):
        if keywords is None:
            keywords = {}
        keywords['id'] = id
        keywords['contentPath'] = contentPath
        if isButtonSelected('applyButton', keywords):
            context.setVar('again', 1)
            context.setVar('hideErrors', 1)

        if id:
            object = self.getObject(id)
        else:
            object = self.newObject(keywords)
        object.__class__ = CardForDefinition # Important.
        try:
            contentSlot = object.getSlotByPath(contentPath)
        except: # FIXME.
            if context.getVar('debug'):
                raise
            return pageNotFound()
        if contentSlot is None:
            contentSlot = slots.Root(object)
        content = contentSlot.getValue()
        contentKind = contentSlot.getKind()
        contentWidget = contentSlot.getWidget()

        contentWidget.submitEmbedded(contentSlot, keywords)

        if context.getVar('again'):
            return self.editDefinitionObject(object, contentPath)

        if not id:
            try:
                id = self.addObject(object)
            except faults.Fault:
                if context.getVar('debug'):
                    raise
                return accessForbidden()
        else:
            try:
                self.modifyPartialObject(object, object.getSlotToModifyNames())
            except faults.WrongVersion:
                context.setVar('again', 1)
                context.setVar('error', 1)
                object.setError('version', 1)
                return self.editDefinitionObject(object, contentPath)
            except:
                if context.getVar('debug'):
                    raise
                return accessForbidden()
        return redirect(X.idUrl(id, 'definition/%s' % contentPath))
    submitDefinition.isPublicForWeb = 1

    def use(self, *arguments, **keywords):
        pass
    use.isPublicForWeb = 0 # Important, so that self.parseHttpPathAction is
                           # called.

    def view(self, *arguments, **keywords):
        pass
    view.isPublicForWeb = 0 # Important, so that self.parseHttpPathAction is
                            # called.

