# -*- 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 Translations Web"""

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


import copy
import difflib
import md5

import glasnost.common.faults as faults
import glasnost.common.slots as slots
import glasnost.common.context as context
import glasnost.common.translation as translation

from glasnost.proxy.TranslationsProxy import *

from ObjectsWeb import register, AdminWithoutWritersMixin, \
        ObjectWebMixin, ObjectsWebMixin
import kinds
from tools import *
import widgets


class SimilarStringWidget(widgets.TextArea):
    thingName = 'SimilarString'

    def getHtmlValue(self, slot, fields, **keywords):
        fieldName = slot.getFieldName()
        similarString = slot.getValue() or ''
        assert self.isInForm()
        assert self.isReadOnly(slot)
        sourceStringSlot = slot.getContainer().getSlot('sourceString')
        sourceString = sourceStringSlot.getValue() or ''

        layout = X.array(
            self.getModelHiddenLayout(slot, fields),
            )
        tt = X.tt(_class = 'diff')
        layout += tt
        sequenceMatcher = difflib.SequenceMatcher(
            None, similarString, sourceString)
        for tag, i1, i2, j1, j2 in sequenceMatcher.get_opcodes():
            if tag == 'delete':
                tt += X.span(_class = 'diff-old')(
                    similarString[i1:i2])
            elif tag == 'equal':
                tt += X.span(_class = 'diff-equal')(
                    similarString[i1:i2])
            elif tag == 'insert':
                tt += X.span(_class = 'diff-new')(
                    sourceString[j1:j2])
            elif tag == 'replace':
                tt += X.span(_class = 'diff-old')(
                    similarString[i1:i2])
                tt += X.span(_class = 'diff-new')(
                    sourceString[j1:j2])
        return layout
widgets.register(SimilarStringWidget)

class TranslationToAddWidget(widgets.BaseWidget):
    thingName = 'TranslationToAdd'

    def getHtmlValue(self, slot, fields, **keywords):
        if not self.isInForm():
            return
        fromFieldName = slot.getFieldOptionName('from')
        toFieldName = slot.getFieldOptionName('to')
        values = translation.languageKeys
        labels = translation.languageLabels
        layout = X.array()
        layout += self.getModelErrorLayout(slot, fields)
        fromSelect = X.select(name = fromFieldName)
        for value in values:
            optionAttributes = {}
            fromSelect += X.option(value = value, **optionAttributes)(
                _(labels[value]))
        toSelect = X.select(name = toFieldName)
        for value in values:
            optionAttributes = {}
            toSelect += X.option(value = value, **optionAttributes)(
                _(labels[value]))
        layout += X.array( _('From '), fromSelect, _(' to '), toSelect, ' ')
        layout += X.buttonInForm('add', slot.getFieldOptionName('addButton'))
        return layout

    def submit(self, slot, fields):
        addButtonName = slot.getFieldOptionName('addButton')
        if not isButtonSelected(addButtonName, fields):
            return None

        fromLanguage = slot.getFieldOption(fields, 'from') or ''
        toLanguage = slot.getFieldOption(fields, 'to') or ''
        if len(fromLanguage) != 2 or len(toLanguage) != 2:
            return None

        object = slot.getObject()
        object.translationLanguages.append('%s%s' % (fromLanguage, toLanguage))
        slotKey = 'translatorsSet_%s_%s' % (fromLanguage, toLanguage)
        setattr(object, slotKey, None)
        kind = commonTools.newThing('kind', 'UsersSet')
        kind.widget = TranslatorsSetWidget()
        kind.buildKinds()
        setattr(object, slotKey + '_kind', kind)

        context.setVar('again', 1)
        context.setVar('hideErrors', 1)
        return None
widgets.register(TranslationToAddWidget)


class TranslatorsSetWidget(widgets.Multi):
    def getHtmlFieldLabel(self, slot):
        fieldName = slot.getFieldName()
        fromLanguage = fieldName[-5:-3]
        toLanguage = fieldName[-2:]
        fieldLabelLocalized = _('%(from)s to %(to)s Translators') % {
            'from': _(translation.languageLabels[fromLanguage]),
            'to': _(translation.languageLabels[toLanguage]),
            }
        labelAttributes = {'for': fieldName}
        return X.span(_class = 'label')(
                fieldLabelLocalized, X.asIs(_(':')))
widgets.register(TranslatorsSetWidget)


class AdminTranslations(AdminWithoutWritersMixin, AdminTranslations):
    translationLanguages = None

    translationToAdd = None
    translationToAdd_kindName = 'TranslationToAdd'
    translationToAdd_kind_widget_fieldLabel = N_('Translation')
    translationToAdd_kind_widgetName = 'TranslationToAdd'

    translatorsSets_widgetName = 'TranslatorsSetsWidget'

    def getLayoutSlotNames(self, fields, parentSlot = None):
        slotNames = AdminWithoutWritersMixin.getLayoutSlotNames(self, fields, parentSlot)
        slotNames.remove('translatorsSets')
        slotNames.remove('translationToAdd')
        return slotNames

    def getEditLayoutSlotNames(self, fields, parentSlot = None):
        slotNames = AdminWithoutWritersMixin.getViewLayoutSlotNames(self,
                fields, parentSlot)
        slotNames.append('translationToAdd')
        return slotNames
register(AdminTranslations)


class Localization(ObjectWebMixin, Localization):
    destinationLanguage_kind_defaultValue = 'VoteRanking' # FIXME: wtf???
    destinationLanguage_kind_stateInEditMode = 'hidden'
    destinationLanguage_kind_stateInViewMode = 'hidden'
    destinationLanguage_kind_widget_fieldLabel = N_('Translation Language')
    destinationLanguage_kind_widget_labels = translation.languageLabels
    destinationLanguage_kind_widgetName = 'Select'

    destinationString_kind_widget_fieldLabel = N_('Translated String')
    destinationString_kind_widget_colSpan = 2
    destinationString_kind_widgetName = 'TextArea'

    isFuzzy_kind_defaultValue = 0
    isFuzzy_kind_widget_fieldLabel = N_('Translation State')
    isFuzzy_kind_widget_labels = {
        '0': N_('Good'),
        '1': N_('Fuzzy'),
        }
    isFuzzy_kind_widgetName = 'InputCheckBox'

    isTranslatable_kind_defaultValue = 1
    isTranslatable_kind_widget_apply = 1
    isTranslatable_kind_widget_fieldLabel = N_('Translation')
    isTranslatable_kind_widget_labels = {
        '0': N_('Unneeded'),
        '1': N_('Needed'),
        }
    isTranslatable_kind_widgetName = 'InputCheckBox'

    similarString_kind_stateInEditMode = 'hidden'
    similarString_kind_stateInViewMode = 'hidden'
    similarString_kind_widget_fieldLabel = N_('Similar String')
    similarString_kind_widget_colSpan = 2
    similarString_kind_widget_viewInTextArea = 1
    similarString_kind_widgetName = 'SimilarString'

    sourceIds_kind_hasToSubmitField = 0
    sourceIds_kind_stateInEditMode = 'read-only'
    sourceIds_kind_itemKind_value_widgetName = 'SelectId'
    sourceIds_kind_widget_fieldLabel = N_('Sources')
    sourceIds_kind_widgetName = 'Multi'

    sourceLanguage_kind_defaultValue = 'VoteRanking'
    sourceLanguage_kind_stateInEditMode = 'hidden'
    sourceLanguage_kind_stateInViewMode = 'hidden'
    sourceLanguage_kind_widget_fieldLabel = N_('Original Language')
    sourceLanguage_kind_widget_labels = translation.languageLabels
    sourceLanguage_kind_widgetName = 'Select'

    sourceString_kind_stateInEditMode = 'read-only'
    sourceString_kind_widgetName = 'TextArea'
    sourceString_kind_widget_fieldLabel = N_('Original String')
    sourceString_kind_widget_colSpan = 2
    sourceString_kind_widget_viewInTextArea = 1

    sourceStringDigest_kind_stateInEditMode = 'hidden'
    sourceStringDigest_kind_stateInViewMode = 'hidden'

    translatorsSet_kind_stateInEditMode = 'read-only'
    translatorsSet_kind_widget_fieldLabel = N_('Translators')

    def getEditLayout(self, fields, parentSlot = None):
        layout = X.array()
        nbLines = len([
            x
            for x in (self.getSlot(
                'sourceString', parentSlot = parentSlot).getValue() or '')
            if x == '\n'])
        if nbLines > 18:
            nbLinesDestination = 20
            nbLinesSource = 20
        else:
            nbLinesDestination = nbLines + (nbLines+9)/10
            nbLinesSource = nbLines
        self.getSlot('sourceString', parentSlot = parentSlot).getWidget(
            ).rows = nbLinesSource
        self.getSlot('destinationString', parentSlot = parentSlot).getWidget(
            ).rows = nbLinesDestination
        layout += ObjectWebMixin.getEditLayout(
            self, fields, parentSlot = parentSlot)
        return layout

    def getEditLayoutHiddenSlotNames(self, fields, parentSlot = None):
        hiddenSlotNames = ObjectWebMixin.getEditLayoutHiddenSlotNames(
            self, fields, parentSlot = parentSlot)
        hiddenSlotNames = hiddenSlotNames[:]
        if self.getSlot('isTranslatable', parentSlot = parentSlot).getValue():
            isTranslatable = 1
        else:
            isTranslatable = 0
        if not isTranslatable:
            for slotName in ['destinationString', 'isFuzzy']:
                if not slotName in hiddenSlotNames:
                    hiddenSlotNames.append(slotName)
        return hiddenSlotNames

    def getEditLayoutSlotNames(self, fields, parentSlot = None):
        slotNames = ObjectWebMixin.getEditLayoutSlotNames(
            self, fields, parentSlot = parentSlot)
        slotNames = slotNames[:]
        if self.getSlot('isTranslatable', parentSlot = parentSlot).getValue():
            isTranslatable = 1
        else:
            isTranslatable = 0
        slotName = 'similarString'
        slot = self.getSlot(slotName, parentSlot = parentSlot)
        if not isTranslatable or not slot.getValue():
            if slotName in slotNames:
                slotNames.remove(slotName)
        return slotNames

    def getViewLayoutSlotNames(self, fields, parentSlot = None):
        slotNames = ObjectWebMixin.getViewLayoutSlotNames(
            self, fields, parentSlot = parentSlot)
        slotNames = slotNames[:]
        for slotName in ['similarString']:
            if slotName in slotNames:
                slotNames.remove(slotName)
        return slotNames

    def submitFields(self, fields, parentSlot = None):
        ObjectWebMixin.submitFields(self, fields, parentSlot = parentSlot)
        if not self.isTranslatable and self.destinationString:
            context.setVar('again', 1)
            context.setVar('error', 1)
            slot = self.getSlot('isTranslatable')
            # TODO: fault with correct description
            _('Destination string set while translation '\
                      'was marked as not necessary')
            self.setError(slot.getPath(), faults.BadValue())
            self.isTranslatable = 1
register(Localization)


class TranslationsWeb(ObjectsWebMixin, TranslationsProxy):
    
    def canViewAll(self, serverId = None):
        userToken = context.getVar('userToken', default = '')
        if userToken:
            return 1
        else:
            return 0

    def delete(self, localizationKey, sourceStringDigest):
        localization = Localization()
        localization.destinationLanguage = localizationKey[2:]
        localization.isTranslatable = 1
        localization.sourceLanguage = localizationKey[:2]
        localization.sourceStringDigest = sourceStringDigest
        try:
            self.modifyLocalization(localization)
        except faults.Fault:
            if context.getVar('debug'):
                raise
            return accessForbidden()
        return redirect(X.actionUrl('viewAll/%s' % localizationKey))
    delete.isPublicForWeb = 1

    def edit(self, localizationKey, sourceStringDigest):
        if not self.hasSourceString(localizationKey, sourceStringDigest):
            return pageNotFound()
        if not self.canModifyLocalization(localizationKey, sourceStringDigest):
            return accessForbidden()
        localization = self.getLocalization(localizationKey,
                sourceStringDigest)
        return self.editObject(localization, localizationKey, sourceStringDigest)
    edit.isPublicForWeb = 1

    def editObject(self, localization, localizationKey, sourceStringDigest):
        headerTitle = _('Editing Translation')
        headerTitle += ' (%s -> %s)' % (
                _(translation.languageLabels[localization.sourceLanguage]),
                _(translation.languageLabels[
                        localization.destinationLanguage]))

        context.push(_level = 'edit', layoutMode = 'edit')
        try:
            layout = X.array()
            if context.getVar('error'):
                layout += localization.getErrorLayout()
            form = X.form(action = X.actionUrl('submit'),
                          enctype= 'multipart/form-data', method = 'post')
            layout += form

            if context.getVar('nextUri'):
                form += X.input(name = 'nextUri', type = 'hidden',
                                value = context.getVar('nextUri'))

            slot = slots.Root(localization)
            widget = slot.getWidget()
            form += widget.getModelPageBodyLayout(slot, None)
            form += X.input(name = 'localizationKey', type = 'hidden',
                            value = localizationKey)

            buttonsBar = X.div(_class = 'buttons-bar')
            form += buttonsBar
            actionButtonsBar = X.span(_class = 'action-buttons-bar')
            buttonsBar += actionButtonsBar
            if self.canDeleteLocalization(localizationKey, sourceStringDigest):
                actionButtonsBar += X.buttonInForm('delete', 'deleteButton')
            actionButtonsBar += X.buttonInForm('modify', 'modifyButton')

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

    def everyString(self, localizationKey):
        fromLanguage = localizationKey[:2]
        toLanguage = localizationKey[2:4]
        pageTitle = '%s: %s' % (
                _('Translations'),
                _('%(from)s to %(to)s') % {
                    'from': _(translation.languageLabels[
                            fromLanguage]),
                    'to': _(translation.languageLabels[toLanguage]),
                    })

        digestsAndLabels = self.getSomeDigestsAndLabels(
                localizationKey, -1, ['untranslated'])
        layout = self.getObjectsSectionLayout(
                localizationKey, digestsAndLabels,
                _("""Strings yet to translate"""))
        
        return writePageLayout(layout, pageTitle)
    everyString.isPublicForWeb = 1

    def getAdmin(self, serverId = None):
        admin = TranslationsProxy.getAdmin(self, serverId = serverId)
        admin.translationLanguages = []
        if admin.translatorsSets:
            for languages, translators in admin.translatorsSets.items():
                admin.translationLanguages.append(languages)
                fromLanguage = languages[:2]
                toLanguage = languages[2:4]
                slotKey = 'translatorsSet_%s_%s' % (fromLanguage, toLanguage)
                setattr(admin, slotKey, translators)
                kind = commonTools.newThing('kind', 'UsersSet')
                kind.widget = TranslatorsSetWidget()
                kind.buildKinds()
                setattr(admin, slotKey + '_kind', kind)
        return admin

    def modifyAdmin(self, admin, serverId = None):
        admin.translatorsSets = {}
        for languages in admin.translationLanguages or []:
            fromLanguage = languages[:2]
            toLanguage = languages[2:4]
            slotKey = 'translatorsSet_%s_%s' % (fromLanguage, toLanguage)
            admin.translatorsSets[languages] = admin.getSlot(slotKey).getValue()
            if not admin.translatorsSets[languages]:
                del admin.translatorsSets[languages]
        return TranslationsProxy.modifyAdmin(self, admin, serverId = serverId)

    def newAdmin(self, keywords):
        ks = [x[15:20] for x in keywords.keys() if 
                x.startswith('translatorsSet_') and len(x) == 22]
        admin = AdminTranslations()
        admin.translationLanguages = []
        for k in ks:
            fromLanguage = k[:2]
            toLanguage = k[3:5]
            admin.translationLanguages.append('%s%s' % (fromLanguage, toLanguage))
            slotKey = 'translatorsSet_%s_%s' % (fromLanguage, toLanguage)
            setattr(admin, slotKey, None)
            kind = commonTools.newThing('kind', 'UsersSet')
            kind.widget = TranslatorsSetWidget()
            kind.buildKinds()
            setattr(admin, slotKey + '_kind', kind)
        return admin

    def getObjectsSectionLayout(self, key, digestsAndLabels, intertitle):
        layout = None
        if len(digestsAndLabels) > 0:
            layout = X.array( X.h2()(intertitle) )
            ul = X.ul()
            layout += ul
            for digest, label, wordsCount in digestsAndLabels:
                link = X.a(href = X.actionUrl(
                        'edit/%s/%s' % (key, digest)))(label)
                li = X.li()
                li += link
                if label[-3:] == '...':
                    li += X.asIs( (_('(%d words)') % wordsCount).replace(
                            ' ', '&nbsp;'))
                ul += li
        return layout

    def status(self, serverRole):
        objectRole = getWebForServerRole(serverRole)
        if not objectRole:
            return pageNotFound()
        localizationKeys = self.getTranslatorLocalizationKeys()
        if not localizationKeys and not self.isAdmin():
            return accessForbidden()
        objectIds = objectRole.getObjectIds()
        possibleLanguages = self.getPossibleLanguages()
        possibleLanguages.sort()
        table = X.table(_class = 'translation-status')
        thead = X.thead(X.tr( [X.th()] + [
                X.th(_(translation.languageLabels[x]))
                        for x in possibleLanguages] ))
        table += thead
        tbody = X.tbody()
        table += tbody

        for objectId in objectIds:
            languages = self.getLanguagesForObjectId(objectId)
            tr = X.tr()
            tr += X.td(X.objectHypertextLabel(objectId))
            for lang in possibleLanguages:
                if lang in languages:
                    tr += X.td(_class = 'ok')('x')
                else:
                    tr += X.td(_class = 'missing')(' ')
            tbody += tr

        return writePageLayout(table, _('Translation Status'))
    status.isPublicForWeb = 1

    def submit(self, localizationKey, sourceStringDigest, **keywords):
        if keywords is None:
            keywords = {}
        if isButtonSelected('deleteButton', keywords):
            return confirmDelete(
                X.actionUrl('delete').add(
                    'localizationKey', localizationKey).add(
                    'sourceStringDigest', sourceStringDigest),
                    objectName = self.objectName)
        if isButtonSelected('applyButton', keywords):
            context.setVar('again', 1)
            context.setVar('hideErrors', 1)
        localization = Localization()
        localization.submitFields(keywords)
        localization.sourceStringDigest = sourceStringDigest
        if context.getVar('again'):
            return self.editObject(localization, localizationKey,
                    sourceStringDigest)
        try:
            self.modifyLocalization(localization)
        except faults.WrongVersion:
            context.setVar('error', 1)
            localization.setError('version', 1)
            return self.editObject(localization, localizationKey,
                    sourceStringDigest)
        except:
            if context.getVar('debug'):
                raise
            return accessForbidden()
        if context.getVar('nextUri'):
            return redirect(context.getVar('nextUri'))
        return redirect(X.actionUrl('viewAll/%s' % localizationKey))
    submit.isPublicForWeb = 1

    def viewAll(self, localizationKey = ''):
        userToken = context.getVar('userToken', default = '')
        if not userToken:
            return accessForbidden()
        context.push(_level = 'viewAll',
                     defaultDispatcherId = context.getVar('dispatcherId'))
        try:
            isAdmin = self.isAdmin()
            localizationKeys = self.getTranslatorLocalizationKeys()
            if not isAdmin:
                if not localizationKeys:
                    return accessForbidden()
                if localizationKey and not localizationKey in localizationKeys:
                    return accessForbidden()
            if localizationKeys:
                if not localizationKey \
                      or not localizationKey in localizationKeys:
                    localizationKey = localizationKeys[0]
                    return redirect(X.actionUrl('viewAll/%s' % localizationKey))
            else:
                localizationKey = ''
            if localizationKey:
                fromLanguage = localizationKey[:2]
                toLanguage = localizationKey[2:4]
                pageTitle = '%s: %s' % (
                        _('Translations'),
                        _('%(from)s to %(to)s') % {
                            'from': _(translation.languageLabels[
                                    fromLanguage]),
                            'to': _(translation.languageLabels[toLanguage]),
                            })
            else:
                pageTitle = _('Translations')

            otherLocalizations = []
            for otherLocalizationKey in localizationKeys:
                if otherLocalizationKey == localizationKey:
                    continue
                fromLanguage = otherLocalizationKey[:2]
                toLanguage = otherLocalizationKey[2:4]
                labelLocalized = _('%(from)s to %(to)s') % {
                    'from': _(translation.languageLabels[fromLanguage]),
                    'to': _(translation.languageLabels[toLanguage]),
                    }
                otherLocalizations.append({
                        'label': labelLocalized,
                        'link': X.actionUrl(
                                'viewAll/%s' % otherLocalizationKey),
                        })

            layout = X.array()

            if localizationKey:
                digestsAndLabels = self.getSomeDigestsAndLabels(
                    localizationKey, 50, ['untranslated'])
                layout += self.getObjectsSectionLayout(
                    localizationKey, digestsAndLabels,
                    _("""The last strings to translate"""))

                digestsAndLabels = self.getSomeDigestsAndLabels(
                    localizationKey, 50, ['fuzzy'])
                layout += self.getObjectsSectionLayout(
                    localizationKey, digestsAndLabels,
                    _("""The last fuzzy translations"""))

                digestsAndLabels = self.getSomeDigestsAndLabels(
                    localizationKey, 50, ['translated'])
                layout += self.getObjectsSectionLayout(
                    localizationKey, digestsAndLabels,
                    _("""The last translations"""))

                digestsAndLabels = self.getSomeDigestsAndLabels(
                    localizationKey, 50, ['untranslatable'])
                layout += self.getObjectsSectionLayout(
                    localizationKey, digestsAndLabels,
                    _("""The last strings to leave untranslated"""))

                if isAdmin:
                    digestsAndLabels = self.getSomeDigestsAndLabels(
                        localizationKey, 50, ['obsolete'])
                    layout += self.getObjectsSectionLayout(
                        localizationKey, digestsAndLabels,
                        _("""The last obsolete translations"""))

            if otherLocalizations:
                div = X.div()
                layout += div
                div += X.strong(_('Other Translations'))
                div += X.asIs(_(':'))
                div += X.nbsp
                for otherLocalization in otherLocalizations:
                    div += X.a(href = otherLocalization['link'])(
                        otherLocalization['label'])
                layout += X.br()

            buttonsBar = X.div(_class = 'buttons-bar')()
            if localizationKey:
                buttonsBar += X.buttonStandalone(
                        _('Every untranslated string'),
                        X.actionUrl('everyString/%s' % localizationKey))
            if self.canGetAdmin():
                buttonsBar += X.buttonStandalone(
                        'settings', X.actionUrl('admin'))
            if buttonsBar.children:
                layout += buttonsBar
        finally:
            context.pull(_level = 'viewAll')
        return writePageLayout(layout, pageTitle)
    viewAll.isPublicForWeb = 1

