class DiscussionItemContainer(Persistent, Implicit, Traversable):

    """
        Store DiscussionItem objects. Discussable content that
        has DiscussionItems associated with it will have an
        instance of DiscussionItemContainer injected into it to
        hold the discussion threads.
    """

    implements(IDiscussable, ICallableOpaqueItemEvents)

    # for the security machinery to allow traversal
    #__roles__ = None

    security = ClassSecurityInfo()

    def __init__(self):
        self.id = 'talkback'
        self._container = PersistentMapping()

    security.declareProtected(View, 'getId')
    def getId(self):
        return self.id

    security.declareProtected(View, 'getReply')
    def getReply(self, reply_id):
        """
            Return a discussion item, given its ID;  raise KeyError
            if not found.
        """
        return self._container.get(reply_id).__of__(self)

    # Is this right?
    security.declareProtected(View, '__bobo_traverse__')
    def __bobo_traverse__(self, REQUEST, name):
        """
        This will make this container traversable
        """
        target = getattr(self, name, None)
        if target is not None:
            return target

        else:
            try:
                return self.getReply(name)
            except:
                parent = aq_parent(aq_inner(self))
                if parent.getId() == name:
                    return parent
                else:
                    REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, ''))

    security.declarePrivate('manage_afterAdd')
    def manage_afterAdd(self, item, container):
        """
            We have juste been added or moved.
            Add the contained items to the catalog.
        """
        if aq_base(container) is not aq_base(self):
            for obj in self.objectValues():
                obj.__of__(self).indexObject()

    security.declarePrivate('manage_afterClone')
    def manage_afterClone(self, item):
        """
            We have just been cloned.
            Notify the workflow about the contained items.
        """
        for obj in self.objectValues():
            obj.__of__(self).notifyWorkflowCreated()

    security.declarePrivate('manage_beforeDelete')
    def manage_beforeDelete(self, item, container):
        """
            Remove the contained items from the catalog.
        """
        if aq_base(container) is not aq_base(self):
            for obj in self.objectValues():
                obj.__of__(self).unindexObject()

    #
    #   OFS.ObjectManager query interface.
    #
    security.declareProtected(AccessContentsInformation, 'objectIds')
    def objectIds(self, spec=None):
        """
            Return a list of the ids of our DiscussionItems.
        """
        if spec and spec is not DiscussionItem.meta_type:
            return []
        return self._container.keys()

    security.declareProtected(AccessContentsInformation, 'objectItems')
    def objectItems(self, spec=None):
        """
            Return a list of (id, subobject) tuples for our DiscussionItems.
        """
        r = []
        a = r.append
        g = self._container.get
        for id in self.objectIds(spec):
            a((id, g(id)))
        return r

    security.declareProtected(AccessContentsInformation, 'objectValues')
    def objectValues(self):
        """
            Return a list of our DiscussionItems.
        """
        return self._container.values()

    #
    #   IDiscussable interface
    #
    security.declareProtected(ReplyToItem, 'createReply')
    def createReply(self, title, text, Creator=None,
                    text_format='structured-text'):
        """
            Create a reply in the proper place
        """
        id = int(DateTime().timeTime())
        while self._container.get(str(id), None) is not None:
            id = id + 1
        id = str(id)

        item = DiscussionItem(id, title=title, description=title)
        self._container[id] = item
        item = item.__of__(self)

        item.setFormat(text_format)
        item._edit(text)
        item.addCreator(Creator)
        item.setReplyTo(self._getDiscussable())

        item.indexObject()
        item.notifyWorkflowCreated()

        return id

    security.declareProtected(ManagePortal, 'deleteReply')
    def deleteReply(self, reply_id):
        """ Remove a reply from this container """
        if reply_id in self._container:
            reply = self._container.get(reply_id).__of__(self)
            my_replies = reply.talkback.getReplies()
            for my_reply in my_replies:
                my_reply_id = my_reply.getId()
                self.deleteReply(my_reply_id)

            if hasattr(reply, 'unindexObject'):
                reply.unindexObject()

            del self._container[reply_id]

    security.declareProtected(View, 'hasReplies')
    def hasReplies(self, content_obj):
        """
            Test to see if there are any dicussion items
        """
        outer = self._getDiscussable(outer=1)
        if content_obj == outer:
            return bool(len(self._container))
        else:
            return bool(len(content_obj.talkback._getReplyResults()))

    security.declareProtected(View, 'replyCount')
    def replyCount(self, content_obj):
        """ How many replies do i have? """
        outer = self._getDiscussable(outer=1)
        if content_obj == outer:
            return len(self._container)
        else:
            replies = content_obj.talkback.getReplies()
            return self._repcount(replies)

    security.declarePrivate('_repcount')
    def _repcount(self, replies):
        """counts the total number of replies

        by recursing thru the various levels
        """
        count = 0

        for reply in replies:
            count = count + 1

            #if there is at least one reply to this reply
            replies = reply.talkback.getReplies()
            if replies:
                count = count + self._repcount(replies)

        return count

    security.declareProtected(View, 'getReplies')
    def getReplies(self):
        """ Return a sequence of the IDiscussionResponse objects which are
            associated with this Discussable
        """
        objects = []
        a = objects.append
        result_ids = self._getReplyResults()

        for id in result_ids:
            a(self._container.get(id).__of__(self))

        return objects

    security.declareProtected(View, 'quotedContents')
    def quotedContents(self):
        """
            Return this object's contents in a form suitable for inclusion
            as a quote in a response.
        """

        return ""

    #
    #   Utility methods
    #
    security.declarePrivate('_getReplyParent')
    def _getReplyParent(self, in_reply_to):
        """
            Return the object indicated by the 'in_reply_to', where
            'None' represents the "outer" content object.
        """
        outer = self._getDiscussable(outer=1)
        if not in_reply_to:
            return outer
        parent = self._container[in_reply_to].__of__(aq_inner(self))
        return parent.__of__(outer)

    security.declarePrivate('_getDiscussable')
    def _getDiscussable(self, outer=0):
        """
        """
        tb = outer and aq_inner(self) or self
        return getattr(tb, 'aq_parent', None)

    security.declarePrivate('_getReplyResults')
    def _getReplyResults(self):
        """
           Get a list of ids of DiscussionItems which are replies to
           our Discussable.
        """
        discussable = self._getDiscussable()
        outer = self._getDiscussable(outer=1)

        if discussable == outer:
            in_reply_to = ''
        else:
            in_reply_to = discussable.getId()

        result = []
        a = result.append
        for key, value in self._container.items():
            if value.in_reply_to == in_reply_to:
                a((key, value))

        result.sort(lambda a, b: cmp(a[1].creation_date, b[1].creation_date))

        return [ x[0] for x in result ]
Esempio n. 2
0
class UserFolder(BasicUserFolder):
    """Standard UserFolder object

    A UserFolder holds User objects which contain information
    about users including name, password domain, and roles.
    UserFolders function chiefly to control access by authenticating
    users and binding them to a collection of roles."""

    implements(IStandardUserFolder)

    meta_type = 'User Folder'
    id = 'acl_users'
    title = 'User Folder'

    def __init__(self):
        self.data=PersistentMapping()

    def getUserNames(self):
        """Return a list of usernames"""
        names=self.data.keys()
        names.sort()
        return names

    def getUsers(self):
        """Return a list of user objects"""
        data=self.data
        names=data.keys()
        names.sort()
        return [data[n] for n in names]

    def getUser(self, name):
        """Return the named user object or None"""
        return self.data.get(name, None)

    def hasUsers(self):
        """ This is not a formal API method: it is used only to provide
        a way for the quickstart page to determine if the default user
        folder contains any users to provide instructions on how to
        add a user for newbies.  Using getUserNames or getUsers would have
        posed a denial of service risk."""
        return not not len(self.data)

    def _doAddUser(self, name, password, roles, domains, **kw):
        """Create a new user"""
        if password is not None and self.encrypt_passwords \
                                and not self._isPasswordEncrypted(password):
            password = self._encryptPassword(password)
        self.data[name] = User(name, password, roles, domains)

    def _doChangeUser(self, name, password, roles, domains, **kw):
        user=self.data[name]
        if password is not None:
            if (self.encrypt_passwords
                and not self._isPasswordEncrypted(password)):
                password = self._encryptPassword(password)
            user.__ = password
        user.roles = roles
        user.domains = domains

    def _doDelUsers(self, names):
        for name in names:
            del self.data[name]
Esempio n. 3
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """Stores messages and their translations...
    """

    meta_type = 'MessageCatalog'
    implements(IMessageCatalog)


    security = ClassSecurityInfo()


    def __init__(self, id, title, sourcelang, languages):
        self.id = id

        self.title = title

        # Language Manager data
        self._languages = tuple(languages)
        self._default_language = sourcelang

        # Here the message translations are stored
        self._messages = PersistentMapping()

        # Data for the PO files headers
        self._po_headers = PersistentMapping()
        for lang in self._languages:
            self._po_headers[lang] = empty_po_header


    #######################################################################
    # ITranslationDomain interface
    # zope.i18n.interfaces.ITranslationDomain
    #######################################################################
    @property
    def domain(self):
        """ """
        return unicode(self.id)


    def translate(self, msgid, mapping=None, context=None,
                  target_language=None, default=None):
        """ """
        msgstr = self.gettext(msgid, lang=target_language, default=default)
        return interpolate(msgstr, mapping)


    #######################################################################
    # Private API
    #######################################################################
    def get_message_key(self, message):
        if message in self._messages:
            return message
        # A message may be stored as unicode or byte string
        encoding = HTTPRequest.default_encoding
        if isinstance(message, unicode):
            message = message.encode(encoding)
        else:
            message = unicode(message, encoding)
        if message in self._messages:
            return message


    def get_translations(self, message):
        message = self.get_message_key(message)
        return self._messages[message]


    def get_tabs_message(self, REQUEST):
        message = REQUEST.get('manage_tabs_message')
        if message is None:
            return None
        return unicode(message, 'utf-8')


    #######################################################################
    # Public API
    #######################################################################
    security.declarePublic('message_exists')
    def message_exists(self, message):
        """ """
        return self._messages.has_key(message)


    security.declareProtected('Manage messages', 'message_edit')
    def message_edit(self, message, language, translation, note):
        """ """
        self._messages[message][language] = translation
        self._messages[message]['note'] = note


    security.declareProtected('Manage messages', 'message_del')
    def message_del(self, message):
        """ """
        del self._messages[message]


    security.declarePublic('gettext')
    def gettext(self, message, lang=None, add=1, default=None):
        """Returns the message translation from the database if available.

        If add=1, add any unknown message to the database.
        If a default is provided, use it instead of the message id
        as a translation for unknown messages.
        """
        if not isinstance(message, (str, unicode)):
            raise TypeError, 'only strings can be translated.'

        message = message.strip()

        if default is None:
            default = message

        # Add it if it's not in the dictionary
        if add and not self._messages.has_key(message) and message:
            self._messages[message] = PersistentMapping()

        # Get the string
        if self._messages.has_key(message):
            m = self._messages[message]

            if lang is None:
                # Builds the list of available languages
                # should the empty translations be filtered?
                available_languages = list(self._languages)

                # Imagine that the default language is 'en'. There is no
                # translation from 'en' to 'en' in the message catalog
                # The user has the preferences 'en' and 'nl' in that order
                # The next two lines make certain 'en' is shown, not 'nl'
                if not self._default_language in available_languages:
                    available_languages.append(self._default_language)

                # Get the language!
                lang = lang_negotiator(available_languages)

                # Is it None? use the default
                if lang is None:
                    lang = self._default_language

            if lang is not None:
                return m.get(lang) or default

        return default


    __call__ = gettext


    #######################################################################
    # Management screens
    #######################################################################
    manage_options = (
        {'label': u'Messages', 'action': 'manage_messages',
         'help': ('Localizer', 'MC_messages.stx')},
        {'label': u'Properties', 'action': 'manage_propertiesForm'},
        {'label': u'Import', 'action': 'manage_Import_form',
         'help': ('Localizer', 'MC_importExport.stx')},
        {'label': u'Export', 'action': 'manage_Export_form',
         'help': ('Localizer', 'MC_importExport.stx')}) \
        + LanguageManager.manage_options \
        + SimpleItem.manage_options


    #######################################################################
    # Management screens -- Messages
    #######################################################################
    security.declareProtected('Manage messages', 'manage_messages')
    manage_messages = LocalDTMLFile('ui/MC_messages', globals())


    security.declarePublic('get_namespace')
    def get_namespace(self, REQUEST):
        """For the management interface, allows to filter the messages to
        show.
        """
        # Check whether there are languages or not
        languages = self.get_languages_mapping()
        if not languages:
            return {}

        # Input
        batch_start = REQUEST.get('batch_start', 0)
        batch_size = REQUEST.get('batch_size', 15)
        empty = REQUEST.get('empty', 0)
        regex = REQUEST.get('regex', '')
        message = REQUEST.get('msg', None)

        # Build the namespace
        namespace = {}
        namespace['batch_size'] = batch_size
        namespace['empty'] = empty
        namespace['regex'] = regex

        # The language
        lang = REQUEST.get('lang', None) or languages[0]['code']
        namespace['language'] = lang

        # Filter the messages
        query = regex.strip()
        try:
            query = compile(query)
        except:
            query = compile('')

        messages = []
        for m, t in self._messages.items():
            if query.search(m) and (not empty or not t.get(lang, '').strip()):
                messages.append(m)
        messages.sort(filter_sort)
        # How many messages
        n = len(messages)
        namespace['n_messages'] = n

        # Calculate the start
        while batch_start >= n:
            batch_start = batch_start - batch_size
        if batch_start < 0:
            batch_start = 0
        namespace['batch_start'] = batch_start
        # Select the batch to show
        batch_end = batch_start + batch_size
        messages = messages[batch_start:batch_end]
        # Batch links
        namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size,
            batch_size, regex, lang, empty)
        namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size,
            batch_size, regex, lang, empty)

        # Get the message
        message_encoded = None
        translations = {}
        if message is None:
            if messages:
                message = messages[0]
                translations = self.get_translations(message)
                message = to_unicode(message)
                message_encoded = message_encode(message)
        else:
            message_encoded = message
            message = message_decode(message_encoded)
            translations = self.get_translations(message)
            message = to_unicode(message)
        namespace['message'] = message
        namespace['message_encoded'] = message_encoded
        namespace['translations'] = translations
        namespace['translation'] = translations.get(lang, '')
        namespace['note'] = translations.get('note', '')

        # Calculate the current message
        namespace['messages'] = []
        for x in messages:
            x = to_unicode(x)
            x_encoded = message_encode(x)
            url = get_url(
                REQUEST.URL, batch_start, batch_size, regex, lang, empty,
                msg=x_encoded)
            namespace['messages'].append({
                'message': x,
                'message_encoded': x_encoded,
                'current': x == message,
                'url': url})

        # The languages
        for language in languages:
            code = language['code']
            language['name'] = _(language['name'], language=code)
            language['url'] = get_url(REQUEST.URL, batch_start, batch_size,
                regex, code, empty, msg=message_encoded)
        namespace['languages'] = languages

        return namespace


    security.declareProtected('Manage messages', 'manage_editMessage')
    def manage_editMessage(self, message, language, translation, note,
                           REQUEST, RESPONSE):
        """Modifies a message.
        """
        message_encoded = message
        message = message_decode(message_encoded)
        message_key = self.get_message_key(message)
        self.message_edit(message_key, language, translation, note)

        url = get_url(REQUEST.URL1 + '/manage_messages',
                      REQUEST['batch_start'], REQUEST['batch_size'],
                      REQUEST['regex'], REQUEST.get('lang', ''),
                      REQUEST.get('empty', 0),
                      msg=message_encoded,
                      manage_tabs_message=_(u'Saved changes.'))
        RESPONSE.redirect(url)


    security.declareProtected('Manage messages', 'manage_delMessage')
    def manage_delMessage(self, message, REQUEST, RESPONSE):
        """ """
        message = message_decode(message)
        message_key = self.get_message_key(message)
        self.message_del(message_key)

        url = get_url(REQUEST.URL1 + '/manage_messages',
                      REQUEST['batch_start'], REQUEST['batch_size'],
                      REQUEST['regex'], REQUEST.get('lang', ''),
                      REQUEST.get('empty', 0),
                      manage_tabs_message=_(u'Saved changes.'))
        RESPONSE.redirect(url)


    #######################################################################
    # Management screens -- Properties
    # Management screens -- Import/Export
    # FTP access
    #######################################################################
    security.declareProtected('View management screens',
                              'manage_propertiesForm')
    manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals())


    security.declareProtected('View management screens', 'manage_properties')
    def manage_properties(self, title, REQUEST=None, RESPONSE=None):
        """Change the Message Catalog properties.
        """
        self.title = title

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')


    # Properties management screen
    security.declareProtected('View management screens', 'get_po_header')
    def get_po_header(self, lang):
        """ """
        # For backwards compatibility
        if not hasattr(aq_base(self), '_po_headers'):
            self._po_headers = PersistentMapping()

        return self._po_headers.get(lang, empty_po_header)


    security.declareProtected('View management screens', 'update_po_header')
    def update_po_header(self, lang,
                         last_translator_name=None,
                         last_translator_email=None,
                         language_team=None,
                         charset=None,
                         REQUEST=None, RESPONSE=None):
        """ """
        header = self.get_po_header(lang)

        if last_translator_name is None:
            last_translator_name = header['last_translator_name']

        if last_translator_email is None:
            last_translator_email = header['last_translator_email']

        if language_team is None:
            language_team = header['language_team']

        if charset is None:
            charset = header['charset']

        header = {'last_translator_name': last_translator_name,
                  'last_translator_email': last_translator_email,
                  'language_team': language_team,
                  'charset': charset}

        self._po_headers[lang] = header

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')



    security.declareProtected('View management screens', 'manage_Import_form')
    manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals())


    security.declarePublic('get_charsets')
    def get_charsets(self):
        """ """
        return charsets[:]


    security.declarePublic('manage_export')
    def manage_export(self, x, REQUEST=None, RESPONSE=None):
        """Exports the content of the message catalog either to a template
        file (locale.pot) or to an language specific PO file (<x>.po).
        """
        # Get the PO header info
        header = self.get_po_header(x)
        last_translator_name = header['last_translator_name']
        last_translator_email = header['last_translator_email']
        language_team = header['language_team']
        charset = header['charset']

        # PO file header, empty message.
        po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time()))
        pot_creation_date = po_revision_date
        last_translator = '%s <%s>' % (last_translator_name,
                                       last_translator_email)

        if x == 'locale.pot':
            language_team = 'LANGUAGE <*****@*****.**>'
        else:
            language_team = '%s <%s>' % (x, language_team)

        r = ['msgid ""',
             'msgstr "Project-Id-Version: %s\\n"' % self.title,
             '"POT-Creation-Date: %s\\n"' % pot_creation_date,
             '"PO-Revision-Date: %s\\n"' % po_revision_date,
             '"Last-Translator: %s\\n"' % last_translator,
             '"Language-Team: %s\\n"' % language_team,
             '"MIME-Version: 1.0\\n"',
             '"Content-Type: text/plain; charset=%s\\n"' % charset,
             '"Content-Transfer-Encoding: 8bit\\n"',
             '', '']


        # Get the messages, and perhaps its translations.
        d = {}
        if x == 'locale.pot':
            filename = x
            for k in self._messages.keys():
                d[k] = ""
        else:
            filename = '%s.po' % x
            for k, v in self._messages.items():
                try:
                    d[k] = v[x]
                except KeyError:
                    d[k] = ""

        # Generate the file
        def backslashescape(x):
            quote_esc = compile(r'"')
            x = quote_esc.sub('\\"', x)

            trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')]
            for a, b in trans:
                x = x.replace(a, b)

            return x

        # Generate sorted msgids to simplify diffs
        dkeys = d.keys()
        dkeys.sort()
        for k in dkeys:
            r.append('msgid "%s"' % backslashescape(k))
            v = d[k]
            r.append('msgstr "%s"' % backslashescape(v))
            r.append('')

        if RESPONSE is not None:
            RESPONSE.setHeader('Content-type','application/data')
            RESPONSE.setHeader('Content-Disposition',
                               'inline;filename=%s' % filename)

        r2 = []
        for x in r:
            if isinstance(x, unicode):
                r2.append(x.encode(charset))
            else:
                r2.append(x)

        return '\n'.join(r2)


    security.declareProtected('Manage messages', 'po_import')
    def po_import(self, lang, data):
        """ """
        messages = self._messages

        # Load the data
        po = itools.gettext.POFile(string=data)
        for msgid in po.get_msgids():
            # TODO Keep the context if any
            _context, msgid = msgid
            if msgid:
                msgstr = po.get_msgstr(msgid) or ''
                if not messages.has_key(msgid):
                    messages[msgid] = PersistentMapping()
                messages[msgid][lang] = msgstr

        # Set the encoding (the full header should be loaded XXX)
        self.update_po_header(lang, charset=po.get_encoding())


    security.declareProtected('Manage messages', 'manage_import')
    def manage_import(self, lang, file, REQUEST=None, RESPONSE=None):
        """ """
        # XXX For backwards compatibility only, use "po_import" instead.
        if isinstance(file, str):
            content = file
        else:
            content = file.read()

        self.po_import(lang, content)

        if RESPONSE is not None:
            RESPONSE.redirect('manage_messages')


    def objectItems(self, spec=None):
        """ """
        for lang in self._languages:
            if not hasattr(aq_base(self), lang):
                self._setObject(lang, POFile(lang))

        r = MessageCatalog.inheritedAttribute('objectItems')(self, spec)
        return r


    #######################################################################
    # TMX support
    security.declareProtected('View management screens', 'manage_Export_form')
    manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals())


    security.declareProtected('Manage messages', 'tmx_export')
    def tmx_export(self, REQUEST, RESPONSE=None):
        """Exports the content of the message catalog to a TMX file
        """
        src_lang = self._default_language

        # Get the header info
        header = self.get_po_header(src_lang)
        charset = header['charset']

        # Init the TMX handler
        tmx = TMXFile()
        tmx.header['creationtool'] = u'Localizer'
        tmx.header['creationtoolversion'] = u'1.x'
        tmx.header['datatype'] = u'plaintext'
        tmx.header['segtype'] = u'paragraph'
        tmx.header['adminlang'] = src_lang
        tmx.header['srclang'] = src_lang
        tmx.header['o-encoding'] = u'%s' % charset.lower()

        # handle messages
        for msgkey, transunit in self._messages.items():
            unit = TMXUnit({})
            for lang in transunit.keys():
                if lang != 'note':
                    sentence = Sentence({'lang': lang})
                    sentence.text = transunit[lang]
                    unit.msgstr[lang] = sentence

            if src_lang not in transunit.keys():
                sentence = Sentence({'lang': src_lang})
                sentence.text = msgkey
                unit.msgstr[src_lang] = sentence

            if transunit.has_key('note'):
                note = TMXNote(transunit.get('note'))
                unit.notes.append(note)
            tmx.messages[msgkey] = unit

        if RESPONSE is not None:
            RESPONSE.setHeader('Content-type','application/data')
            RESPONSE.setHeader('Content-Disposition',
                               'attachment; filename="%s.tmx"' % self.id)

        return tmx.to_str()



    security.declareProtected('Manage messages', 'tmx_import')
    def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None):
        """Imports a TMX level 1 file.
        """
        try:
            data = file.read()
            tmx = TMXFile(string=data)
        except:
            return MessageDialog(title = 'Parse error',
                                 message = _('impossible to parse the file') ,
                                 action = 'manage_Import_form',)

        num_notes = 0
        num_trans = 0

        if howmuch == 'clear':
            # Clear the message catalogue prior to import
            self._messages = {}
            self._languages = ()
            self._default_language = tmx.get_srclang()

        for id, msg in tmx.messages.items():
            if not self._messages.has_key(id) and howmuch == 'existing':
                continue
            msg.msgstr.pop(self._default_language)
            if not self._messages.has_key(id):
                self._messages[id] = {}
            for lang in msg.msgstr.keys():
                # normalize the languageTag and extract the core
                (core, local) = LanguageTag.decode(lang)
                lang = LanguageTag.encode((core, local))
                if lang not in self._languages:
                    self._languages += (lang,)
                if msg.msgstr[lang].text:
                    self._messages[id][lang] = msg.msgstr[lang].text
                    if core != lang and core != self._default_language:
                        if core not in self._languages:
                            self._languages += (core,)
                        if not msg.msgstr.has_key(core):
                            self._messages[id][core] = msg.msgstr[lang].text
            if msg.notes:
                ns = [m.text for m in msg.notes]
                self._messages[id]['note'] = u' '.join(ns)
                num_notes += 1
            num_trans += 1

        if REQUEST is not None:
            message = _(u'Imported %d messages and %d notes')
            return MessageDialog(
                title = _(u'Messages imported'),
                message = message % (num_trans, num_notes),
                action = 'manage_messages')



    #######################################################################
    # Backwards compatibility (XXX)
    #######################################################################

    hasmsg = message_exists
    hasLS = message_exists  # CMFLocalizer uses it

    security.declareProtected('Manage messages', 'xliff_export')
    def xliff_export(self, dst_lang, export_all=1, REQUEST=None,
                     RESPONSE=None):
        """Exports the content of the message catalog to an XLIFF file
        """
        from DateTime import DateTime

        src_lang = self._default_language
        export_all = int(export_all)

        # Init the XLIFF handler
        xliff = XLFFile()
        # Add the translation units
        original = '/%s' % self.absolute_url(1)
        for msgkey, transunit in self._messages.items():
            target = transunit.get(dst_lang, '')
            # If 'export_all' is true export all messages, otherwise export
            # only untranslated messages
            if export_all or not target:
                unit = xliff.add_unit(original, msgkey, None)
                unit.attributes['id'] = md5text(msgkey)
                if target:
                    unit.target = target
                # Add note
                note = transunit.get('note')
                if note:
                    unit.notes.append(XLFNote(note))

        # build the data-stucture for the File tag
        file = xliff.files[original]
        attributes = file.attributes
        attributes['original'] = original
        attributes['product-name'] = u'Localizer'
        attributes['product-version'] = u'1.1.x'
        attributes['data-type'] = u'plaintext'
        attributes['source-language'] = src_lang
        attributes['target-language'] = dst_lang
        attributes['date'] = DateTime().HTML4()

        # Serialize
        xliff = xliff.to_str()
        # Set response headers
        RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8')
        filename = '%s_%s_%s.xlf' % (self.id, src_lang, dst_lang)
        RESPONSE.setHeader('Content-Disposition',
           'attachment; filename="%s"' % filename)
        # Ok
        return xliff


    security.declareProtected('Manage messages', 'xliff_import')
    def xliff_import(self, howmuch, file, REQUEST=None):
        """XLIFF is the XML Localization Interchange File Format designed by a
        group of software providers.  It is specified by www.oasis-open.org
        """
        try:
            data = file.read()
            xliff = XLFFile(string=data)
        except:
            return MessageDialog(title = 'Parse error',
                                 message = _('impossible to parse the file') ,
                                 action = 'manage_Import_form',)

        num_notes = 0
        num_trans = 0
        (file_ids, sources, targets) = xliff.get_languages()

        if howmuch == 'clear':
            # Clear the message catalogue prior to import
            self._messages = {}
            self._languages = ()
            self._default_language = sources[0]

        # update languages
        if len(sources) > 1 or sources[0] != self._default_language:
            return MessageDialog(title = 'Language error',
                                 message = _('incompatible language sources') ,
                                 action = 'manage_Import_form',)
        for lang in targets:
            if lang != self._default_language and lang not in self._languages:
                self._languages += (lang,)

        # get messages
        for file in xliff.files:
            cur_target = file.attributes.get('target-language', '')
            for msg in file.body.keys():
                if not self._messages.has_key(msg) and howmuch == 'existing':
                    pass
                else:
                    if not self._messages.has_key(msg):
                        self._messages[msg] = {}

                    if cur_target and file.body[msg].target:
                        self._messages[msg][cur_target] = file.body[msg].target
                        num_trans += 1
                    if file.body[msg].notes:
                        ns = [n.text for n in file.body[msg].notes]
                        comment = ' '.join(ns)
                        self._messages[msg]['note'] = comment
                        num_notes += 1

        if REQUEST is not None:
            return MessageDialog(
                title = _(u'Messages imported'),
                message = (_(u'Imported %d messages and %d notes to %s') % \
                           (num_trans, num_notes, ' '.join(targets))),
                action = 'manage_messages')
Esempio n. 4
0
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
    """Stores messages and their translations...
    """

    meta_type = 'MessageCatalog'
    implements(IMessageCatalog)


    security = ClassSecurityInfo()


    POLICY_ADD_FALSE = 0
    POLICY_ADD_TRUE = 1
    POLICY_ADD_LOG = 2


    def __init__(self, id, title, sourcelang, languages):
        self.id = id

        self.title = title
        self.policy = self.POLICY_ADD_TRUE

        # Language Manager data
        self._languages = tuple(languages)
        self._default_language = sourcelang

        # Here the message translations are stored
        self._messages = PersistentMapping()

        # Data for the PO files headers
        self._po_headers = PersistentMapping()
        for lang in self._languages:
            self._po_headers[lang] = empty_po_header


    #######################################################################
    # ITranslationDomain interface
    # zope.i18n.interfaces.ITranslationDomain
    #######################################################################
    @property
    def domain(self):
        """ """
        return unicode(self.id)


    def translate(self, msgid, mapping=None, context=None,
                  target_language=None, default=None):
        """ """
        msgstr = self.gettext(msgid, lang=target_language, default=default)
        # BBB support str in mapping by converting to unicode for
        # backward compatibility.
        if mapping:
            mapping = dict([to_unicode(k), to_unicode(v)]
                            for k, v in mapping.iteritems())
        return interpolate(msgstr, mapping)


    #######################################################################
    # Private API
    #######################################################################
    def get_message_key(self, message):
        if message in self._messages:
            return message
        # A message may be stored as unicode or byte string
        encoding = HTTPRequest.default_encoding
        if isinstance(message, unicode):
            message = message.encode(encoding)
        else:
            message = unicode(message, encoding)
        if message in self._messages:
            return message


    def get_translations(self, message):
        message = self.get_message_key(message)
        return self._messages[message]


    def get_tabs_message(self, REQUEST):
        message = REQUEST.get('manage_tabs_message')
        if message is None:
            return None
        return unicode(message, 'utf-8')


    #######################################################################
    # Public API
    #######################################################################
    security.declarePublic('message_exists')
    def message_exists(self, message):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        return bool(self.get_message_key(message))


    security.declareProtected('Manage messages', 'message_edit')
    def message_edit(self, message, language, translation, note):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or message
        self._messages[message][language] = translation
        self._messages[message]['note'] = note


    security.declareProtected('Manage messages', 'message_del')
    def message_del(self, message):
        """ """
        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or message
        del self._messages[message]


    security.declarePublic('gettext')
    def gettext(self, message, lang=None, add=None, default=None):
        """Returns the message translation from the database if available.

        If add=1, add any unknown message to the database.
        If a default is provided, use it instead of the message id
        as a translation for unknown messages.
        """
        if not isinstance(message, basestring):
            raise TypeError, 'only strings can be translated.'

        if default is None:
            default = message

        message = message.strip()

        # BBB call get_message_key to support both (old) str key and
        # (new) unicode key.
        message = self.get_message_key(message) or to_unicode(message)

        # Add it if it's not in the dictionary
        if add is None:
            add = getattr(self, 'policy', self.POLICY_ADD_TRUE)
        if add != self.POLICY_ADD_FALSE and not self._messages.has_key(message) and message:
            if add == self.POLICY_ADD_LOG:
                LOG('New entry added to message catalog %s :' % self.id,  INFO, '%s\n%s' % (message, ''.join(format_list(extract_stack()[:-1]))))
            self._messages[message] = PersistentMapping()

        # Get the string
        if self._messages.has_key(message):
            m = self._messages[message]

            if lang is None:
                # Builds the list of available languages
                # should the empty translations be filtered?
                available_languages = list(self._languages)

                # Imagine that the default language is 'en'. There is no
                # translation from 'en' to 'en' in the message catalog
                # The user has the preferences 'en' and 'nl' in that order
                # The next two lines make certain 'en' is shown, not 'nl'
                if not self._default_language in available_languages:
                    available_languages.append(self._default_language)

                # Get the language!
                lang = lang_negotiator(available_languages)

                # Is it None? use the default
                if lang is None:
                    lang = self._default_language

            if lang is not None:
                return m.get(lang) or default

        return default


    __call__ = gettext


    #######################################################################
    # Management screens
    #######################################################################
    manage_options = (
        {'label': u'Messages', 'action': 'manage_messages',
         'help': ('Localizer', 'MC_messages.stx')},
        {'label': u'Properties', 'action': 'manage_propertiesForm'},
        {'label': u'Import', 'action': 'manage_Import_form',
         'help': ('Localizer', 'MC_importExport.stx')},
        {'label': u'Export', 'action': 'manage_Export_form',
         'help': ('Localizer', 'MC_importExport.stx')}) \
        + LanguageManager.manage_options \
        + SimpleItem.manage_options


    #######################################################################
    # Management screens -- Messages
    #######################################################################
    security.declareProtected('Manage messages', 'manage_messages')
    manage_messages = LocalDTMLFile('ui/MC_messages', globals())


    security.declarePublic('get_namespace')
    def get_namespace(self, REQUEST):
        """For the management interface, allows to filter the messages to
        show.
        """
        # Check whether there are languages or not
        languages = self.get_languages_mapping()
        if not languages:
            return {}

        # Input
        batch_start = REQUEST.get('batch_start', 0)
        batch_size = REQUEST.get('batch_size', 15)
        empty = REQUEST.get('empty', 0)
        regex = REQUEST.get('regex', '')
        message = REQUEST.get('msg', None)

        # Build the namespace
        namespace = {}
        namespace['batch_size'] = batch_size
        namespace['empty'] = empty
        namespace['regex'] = regex

        # The language
        lang = REQUEST.get('lang', None) or languages[0]['code']
        namespace['language'] = lang

        # Filter the messages
        query = regex.strip()
        try:
            query = compile(query)
        except:
            query = compile('')

        messages = []
        for m, t in self._messages.items():
            if query.search(m) and (not empty or not t.get(lang, '').strip()):
                messages.append(m)
        messages.sort(filter_sort)
        # How many messages
        n = len(messages)
        namespace['n_messages'] = n

        # Calculate the start
        while batch_start >= n:
            batch_start = batch_start - batch_size
        if batch_start < 0:
            batch_start = 0
        namespace['batch_start'] = batch_start
        # Select the batch to show
        batch_end = batch_start + batch_size
        messages = messages[batch_start:batch_end]
        # Batch links
        namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size,
            batch_size, regex, lang, empty)
        namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size,
            batch_size, regex, lang, empty)

        # Get the message
        message_encoded = None
        translations = {}
        if message is None:
            if messages:
                message = messages[0]
                translations = self.get_translations(message)
                message = to_unicode(message)
                message_encoded = message_encode(message)
        else:
            message_encoded = message
            message = message_decode(message_encoded)
            translations = self.get_translations(message)
            message = to_unicode(message)
        namespace['message'] = message
        namespace['message_encoded'] = message_encoded
        namespace['translations'] = translations
        namespace['translation'] = translations.get(lang, '')
        namespace['note'] = translations.get('note', '')

        # Calculate the current message
        namespace['messages'] = []
        for x in messages:
            x = to_unicode(x)
            x_encoded = message_encode(x)
            url = get_url(
                REQUEST.URL, batch_start, batch_size, regex, lang, empty,
                msg=x_encoded)
            namespace['messages'].append({
                'message': x,
                'message_encoded': x_encoded,
                'current': x == message,
                'url': url})

        # The languages
        for language in languages:
            code = language['code']
            language['name'] = _(language['name'], language=code)
            language['url'] = get_url(REQUEST.URL, batch_start, batch_size,
                regex, code, empty, msg=message_encoded)
        namespace['languages'] = languages

        return namespace


    security.declareProtected('Manage messages', 'manage_editMessage')
    def manage_editMessage(self, message, language, translation, note,
                           REQUEST, RESPONSE):
        """Modifies a message.
        """
        message_encoded = message
        message = message_decode(message_encoded)
        message_key = self.get_message_key(message)
        self.message_edit(message_key, language, translation, note)

        url = get_url(REQUEST.URL1 + '/manage_messages',
                      REQUEST['batch_start'], REQUEST['batch_size'],
                      REQUEST['regex'], REQUEST.get('lang', ''),
                      REQUEST.get('empty', 0),
                      msg=message_encoded,
                      manage_tabs_message=_(u'Saved changes.'))
        RESPONSE.redirect(url)


    security.declareProtected('Manage messages', 'manage_delMessage')
    def manage_delMessage(self, message, REQUEST, RESPONSE):
        """ """
        message = message_decode(message)
        message_key = self.get_message_key(message)
        self.message_del(message_key)

        url = get_url(REQUEST.URL1 + '/manage_messages',
                      REQUEST['batch_start'], REQUEST['batch_size'],
                      REQUEST['regex'], REQUEST.get('lang', ''),
                      REQUEST.get('empty', 0),
                      manage_tabs_message=_(u'Saved changes.'))
        RESPONSE.redirect(url)


    #######################################################################
    # Management screens -- Properties
    # Management screens -- Import/Export
    # FTP access
    #######################################################################
    security.declareProtected('View management screens',
                              'manage_propertiesForm')
    manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals())


    security.declareProtected('View management screens', 'manage_properties')
    def manage_properties(self, title, policy, REQUEST=None, RESPONSE=None):
        """Change the Message Catalog properties.
        """
        self.title = title
        self.policy = int(policy)

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')


    # Properties management screen
    security.declareProtected('View management screens', 'get_po_header')
    def get_po_header(self, lang):
        """ """
        # For backwards compatibility
        if not hasattr(aq_base(self), '_po_headers'):
            self._po_headers = PersistentMapping()

        return self._po_headers.get(lang, empty_po_header)


    security.declareProtected('View management screens', 'update_po_header')
    def update_po_header(self, lang,
                         last_translator_name=None,
                         last_translator_email=None,
                         language_team=None,
                         charset=None,
                         REQUEST=None, RESPONSE=None):
        """ """
        header = self.get_po_header(lang)

        if last_translator_name is None:
            last_translator_name = header['last_translator_name']

        if last_translator_email is None:
            last_translator_email = header['last_translator_email']

        if language_team is None:
            language_team = header['language_team']

        if charset is None:
            charset = header['charset']

        header = {'last_translator_name': last_translator_name,
                  'last_translator_email': last_translator_email,
                  'language_team': language_team,
                  'charset': charset}

        self._po_headers[lang] = header

        if RESPONSE is not None:
            RESPONSE.redirect('manage_propertiesForm')



    security.declareProtected('View management screens', 'manage_Import_form')
    manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals())


    security.declarePublic('get_policies')
    def get_policies(self):
        """ """
        if not hasattr(self, 'policy'):
            self.policy = self.POLICY_ADD_TRUE
        policies = [
            [self.POLICY_ADD_FALSE, "Never add new entries automatically"],
            [self.POLICY_ADD_TRUE, "Add new entries automatically if missing"],
            [self.POLICY_ADD_LOG, "Add new entries automatically if missing and log the backtrace"],
        ]
        return policies


    security.declarePublic('get_charsets')
    def get_charsets(self):
        """ """
        return charsets[:]


    security.declarePublic('manage_export')
    def manage_export(self, x, REQUEST=None, RESPONSE=None):
        """Exports the content of the message catalog either to a template
        file (locale.pot) or to an language specific PO file (<x>.po).
        """
        # Get the PO header info
        header = self.get_po_header(x)
        last_translator_name = header['last_translator_name']
        last_translator_email = header['last_translator_email']
        language_team = header['language_team']
        charset = header['charset']

        # PO file header, empty message.
        po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time()))
        pot_creation_date = po_revision_date
        last_translator = '%s <%s>' % (last_translator_name,
                                       last_translator_email)

        if x == 'locale.pot':
            language_team = 'LANGUAGE <*****@*****.**>'
        else:
            language_team = '%s <%s>' % (x, language_team)

        r = ['msgid ""',
             'msgstr "Project-Id-Version: %s\\n"' % self.title,
             '"POT-Creation-Date: %s\\n"' % pot_creation_date,
             '"PO-Revision-Date: %s\\n"' % po_revision_date,
             '"Last-Translator: %s\\n"' % last_translator,
             '"Language-Team: %s\\n"' % language_team,
             '"MIME-Version: 1.0\\n"',
             '"Content-Type: text/plain; charset=%s\\n"' % charset,
             '"Content-Transfer-Encoding: 8bit\\n"',
             '', '']


        # Get the messages, and perhaps its translations.
        # Convert keys to unicode for proper sorting.
        d = {}
        if x == 'locale.pot':
            filename = x
            for k in self._messages.keys():
                d[to_unicode(k, encoding=charset)] = u""
        else:
            filename = '%s.po' % x
            for k, v in self._messages.items():
                k = to_unicode(k, encoding=charset)
                d[k] = to_unicode(v.get(x, ""), encoding=charset)

        # Generate the file
        def backslashescape(x):
            x = to_str(x)
            quote_esc = compile(r'"')
            x = quote_esc.sub('\\"', x)

            trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')]
            for a, b in trans:
                x = x.replace(a, b)

            return x

        # Generate sorted msgids to simplify diffs
        dkeys = d.keys()
        dkeys.sort()
        for k in dkeys:
            r.append('msgid "%s"' % backslashescape(k))
            v = d[k]
            r.append('msgstr "%s"' % backslashescape(v))
            r.append('')

        if RESPONSE is not None:
            RESPONSE.setHeader('Content-type','application/data')
            RESPONSE.setHeader('Content-Disposition',
                               'inline;filename=%s' % filename)

        return '\n'.join(r)


    security.declareProtected('Manage messages', 'po_import')
    def po_import(self, lang, data):
        """ """
        messages = self._messages

        # Load the data
        po = polib.pofile(data)
        encoding = to_str(po.encoding)
        for entry in po:
            msgid = to_unicode(entry.msgid, encoding=encoding)
            if msgid:
                msgstr = to_unicode(entry.msgstr or '', encoding=encoding)
                translation_map = messages.get(msgid)
                if translation_map is None:
                    # convert old non-unicode translations if they exist:
                    translation_map = messages.pop(self.get_message_key(msgid),
                                                   None)
                    if translation_map is None:
                        translation_map = PersistentMapping()
                    messages[msgid] = translation_map
                translation_map[lang] = msgstr

        # Set the encoding (the full header should be loaded XXX)
        self.update_po_header(lang, charset=encoding)


    security.declareProtected('Manage messages', 'manage_import')
    def manage_import(self, lang, file, REQUEST=None, RESPONSE=None):
        """ """
        # XXX For backwards compatibility only, use "po_import" instead.
        if isinstance(file, str):
            content = file
        else:
            content = file.read()

        self.po_import(lang, content)

        if RESPONSE is not None:
            RESPONSE.redirect('manage_messages')


    def objectItems(self, spec=None):
        """ """
        for lang in self._languages:
            if not hasattr(aq_base(self), lang):
                self._setObject(lang, POFile(lang))

        r = MessageCatalog.inheritedAttribute('objectItems')(self, spec)
        return r


    #######################################################################
    # TMX support
    security.declareProtected('View management screens', 'manage_Export_form')
    manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals())


    #######################################################################
    # Backwards compatibility (XXX)
    #######################################################################

    hasmsg = message_exists
    hasLS = message_exists  # CMFLocalizer uses it
Esempio n. 5
0
class HBTreeFolder2Base (Persistent):
    """Base for BTree-based folders.
    """

    security = ClassSecurityInfo()

    manage_options=(
        ({'label':'Contents', 'action':'manage_main',},
         ) + Folder.manage_options[1:]
        )

    security.declareProtected(view_management_screens,
                              'manage_main')
    manage_main = DTMLFile('contents', globals())

    _htree = None      # OOBTree: { id -> object }
    _count = None     # A BTrees.Length
    _v_nextid = 0     # The integer component of the next generated ID
    title = ''
    _tree_list = None


    def __init__(self, id=None):
        if id is not None:
            self.id = id
        self._initBTrees()

    def _initBTrees(self):
        self._htree = OOBTree()
        self._count = Length()
        self._tree_list = PersistentMapping()

    def _populateFromFolder(self, source):
        """Fill this folder with the contents of another folder.
        """
        for name in source.objectIds():
            value = source._getOb(name, None)
            if value is not None:
                self._setOb(name, aq_base(value))


    security.declareProtected(view_management_screens, 'manage_fixCount')
    def manage_fixCount(self):
        """Calls self._fixCount() and reports the result as text.
        """
        old, new = self._fixCount()
        path = '/'.join(self.getPhysicalPath())
        if old == new:
            return "No count mismatch detected in HBTreeFolder2 at %s." % path
        else:
            return ("Fixed count mismatch in HBTreeFolder2 at %s. "
                    "Count was %d; corrected to %d" % (path, old, new))


    def _fixCount(self):
        """Checks if the value of self._count disagrees with
        len(self.objectIds()). If so, corrects self._count. Returns the
        old and new count values. If old==new, no correction was
        performed.
        """
        old = self._count()
        new = len(self.objectIds())
        if old != new:
            self._count.set(new)
        return old, new


    security.declareProtected(view_management_screens, 'manage_cleanup')
    def manage_cleanup(self):
        """Calls self._cleanup() and reports the result as text.
        """
        v = self._cleanup()
        path = '/'.join(self.getPhysicalPath())
        if v:
            return "No damage detected in HBTreeFolder2 at %s." % path
        else:
            return ("Fixed HBTreeFolder2 at %s.  "
                    "See the log for more details." % path)


    def _cleanup(self):
        """Cleans up errors in the BTrees.

        Certain ZODB bugs have caused BTrees to become slightly insane.
        Fortunately, there is a way to clean up damaged BTrees that
        always seems to work: make a new BTree containing the items()
        of the old one.

        Returns 1 if no damage was detected, or 0 if damage was
        detected and fixed.
        """
        def hCheck(htree):
          """
              Recursively check the btree
          """
          check(htree)
          for key in htree.keys():
              if not htree.has_key(key):
                  raise AssertionError(
                      "Missing value for key: %s" % repr(key))
              else:
                ob = htree[key]
                if isinstance(ob, OOBTree):
                  hCheck(ob)
          return 1
        
        from BTrees.check import check
        path = '/'.join(self.getPhysicalPath())
        try:
            return hCheck(self._htree)
        except AssertionError:            
            LOG('HBTreeFolder2', WARNING,
                'Detected damage to %s. Fixing now.' % path,
                error=sys.exc_info())
            try:
                self._htree = OOBTree(self._htree) # XXX hFix needed
            except:
                LOG('HBTreeFolder2', ERROR, 'Failed to fix %s.' % path,
                    error=sys.exc_info())
                raise
            else:
                LOG('HBTreeFolder2', INFO, 'Fixed %s.' % path)
            return 0

    def hashId(self, id):
        """Return a tuple of ids
        """
        # XXX: why tolerate non-string ids ?
        id_list = str(id).split(H_SEPARATOR)     # We use '-' as the separator by default
        if len(id_list) > 1:
          return tuple(id_list)
        else:
          return [id,]
    
#         try:                             # We then try int hashing
#           id_int = int(id)
#         except ValueError:
#           return id_list
#         result = []
#         while id_int:
#           result.append(id_int % MAX_OBJECT_PER_LEVEL)
#           id_int = id_int / MAX_OBJECT_PER_LEVEL
#         result.reverse()
#         return tuple(result)

    def _getOb(self, id, default=_marker):
        """
            Return the named object from the folder.
        """
        htree = self._htree
        ob = htree
        id_list = self.hashId(id)
        for sub_id in id_list[0:-1]:
          if default is _marker:
            ob = ob[sub_id]
          else:
            ob = ob.get(sub_id, _marker)
            if ob is _marker:
              return default
        if default is _marker:
          ob = ob[id]
        else:
          ob = ob.get(id, _marker)
          if ob is _marker:
            return default
        return ob.__of__(self)

    def _setOb(self, id, object):
        """Store the named object in the folder.
        """
        htree = self._htree
        id_list = self.hashId(id)
        for idx in xrange(len(id_list) - 1):
          sub_id = id_list[idx]
          if not htree.has_key(sub_id):
            # Create a new level
            htree[sub_id] = OOBTree()
            if isinstance(sub_id, (int, long)):
              tree_id = 0
              for id in id_list[:idx+1]:
                  tree_id = tree_id + id * MAX_OBJECT_PER_LEVEL
            else:
              tree_id = H_SEPARATOR.join(id_list[:idx+1])
            # Index newly created level
            self._tree_list[tree_id] = None
            
          htree = htree[sub_id]

        if len(id_list) == 1 and not htree.has_key(None):
            self._tree_list[None] = None
        # set object in subtree            
        ob_id = id_list[-1]
        if htree.has_key(id):
            raise KeyError('There is already an item named "%s".' % id)
        htree[id] = object
        self._count.change(1)

    def _delOb(self, id):
        """Remove the named object from the folder.
        """
        htree = self._htree
        id_list = self.hashId(id)
        for sub_id in id_list[0:-1]:
          htree = htree[sub_id]
        del htree[id]
        self._count.change(-1)

    security.declareProtected(view_management_screens, 'getBatchObjectListing')
    def getBatchObjectListing(self, REQUEST=None):
        """Return a structure for a page template to show the list of objects.
        """
        if REQUEST is None:
            REQUEST = {}
        pref_rows = int(REQUEST.get('dtpref_rows', 20))
        b_start = int(REQUEST.get('b_start', 1))
        b_count = int(REQUEST.get('b_count', 1000))
        b_end = b_start + b_count - 1
        url = self.absolute_url() + '/manage_main'
        count = self.objectCount()

        if b_end < count:
            next_url = url + '?b_start=%d' % (b_start + b_count)
        else:
            b_end = count
            next_url = ''

        if b_start > 1:
            prev_url = url + '?b_start=%d' % max(b_start - b_count, 1)
        else:
            prev_url = ''

        formatted = [listtext0 % pref_rows]
        for optID in islice(self.objectIds(), b_start - 1, b_end):
            optID = escape(optID)
            formatted.append(listtext1 % (escape(optID, quote=1), optID))
        formatted.append(listtext2)
        return {'b_start': b_start, 'b_end': b_end,
                'prev_batch_url': prev_url,
                'next_batch_url': next_url,
                'formatted_list': ''.join(formatted)}


    security.declareProtected(view_management_screens,
                              'manage_object_workspace')
    def manage_object_workspace(self, ids=(), REQUEST=None):
        '''Redirects to the workspace of the first object in
        the list.'''
        if ids and REQUEST is not None:
            REQUEST.RESPONSE.redirect(
                '%s/%s/manage_workspace' % (
                self.absolute_url(), quote(ids[0])))
        else:
            return self.manage_main(self, REQUEST)


    security.declareProtected(access_contents_information,
                              'tpValues')
    def tpValues(self):
        """Ensures the items don't show up in the left pane.
        """
        return ()


    security.declareProtected(access_contents_information,
                              'objectCount')
    def objectCount(self):
        """Returns the number of items in the folder."""
        return self._count()


    security.declareProtected(access_contents_information, 'has_key')
    def has_key(self, id):
        """Indicates whether the folder has an item by ID.
        """
        htree = self._htree
        id_list = self.hashId(id)
        for sub_id in id_list[0:-1]:
          if not isinstance(htree, OOBTree):
            return 0
          if not htree.has_key(sub_id):
            return 0
          htree = htree[sub_id]
        if not htree.has_key(id):
          return 0
        return 1

    # Work around for the performance regression introduced in Zope 2.12.23.
    # Otherwise, we use superclass' __contains__ implementation, which uses
    # objectIds, which is inefficient in HBTreeFolder2 to lookup a single key.
    __contains__ = has_key

    def _htree_iteritems(self, min=None):
        # BUG: Due to bad design of HBTreeFolder2, buckets other than the root
        #      one must not contain both buckets & leafs. Otherwise, this method
        #      fails.
        h = self._htree
        recurse_stack = []
        try:
          for sub_id in min and self.hashId(min) or ('',):
            if recurse_stack:
              i.next()
              if type(h) is not OOBTree:
                break
              id += H_SEPARATOR + sub_id
              if type(h.itervalues().next()) is not OOBTree:
                sub_id = id
            else:
              id = sub_id
            i = h.iteritems(sub_id)
            recurse_stack.append(i)
            h = h[sub_id]
        except (KeyError, StopIteration):
          pass
        while recurse_stack:
          i = recurse_stack.pop()
          try:
            while 1:
              id, h = i.next()
              if type(h) is OOBTree:
                recurse_stack.append(i)
                i = h.iteritems()
              else:
                yield id, h
          except StopIteration:
            pass

    security.declareProtected(access_contents_information,
                              'treeIds')
    def treeIds(self, base_id=None):
        """ Return a list of subtree ids
        """
        tree = self._getTree(base_id=base_id)
        return [k for k, v in self._htree.items() if isinstance(v, OOBTree)]


    def _getTree(self, base_id):
        """ Return the tree wich has the base_id
        """
        htree = self._htree
        id_list = self.hashId(base_id)
        for sub_id in id_list:            
          if not isinstance(htree, OOBTree):
            return None
          if not htree.has_key(sub_id):
            raise IndexError, base_id
          htree = htree[sub_id]
        return htree

    def _getTreeIdList(self, htree=None):
        """ recursively build a list of btree ids
        """
        if htree is None:
          htree = self._htree
          btree_list = []
        else:
          btree_list = []
        for obj_id in htree.keys():
          obj = htree[obj_id]
          if isinstance(obj, OOBTree):
            btree_list.extend(["%s-%s"%(obj_id, x) for x in self._getTreeIdList(htree=obj)])
            btree_list.append(obj_id)

        return btree_list 

    security.declareProtected(access_contents_information,
                              'getTreeIdList')
    def getTreeIdList(self, htree=None):
        """ Return list of all tree ids
        """
        if self._tree_list is None or len(self._tree_list.keys()) == 0:
            tree_list = self._getTreeIdList(htree=htree)
            self._tree_list = PersistentMapping()
            for tree in tree_list:                
                self._tree_list[tree] = None
        return sorted(self._tree_list.keys())

    def _checkObjectId(self, ids):
        """ test id is not in btree id list
        """
        base_id, obj_id = ids
        if base_id is not None:
            obj_id = "%s%s%s" %(base_id, H_SEPARATOR, obj_id)
        return not self._tree_list.has_key(obj_id)
        
    security.declareProtected(access_contents_information,
                              'objectValues')
    def objectValues(self, base_id=_marker):
        return HBTreeObjectValues(self, base_id)

    security.declareProtected(access_contents_information,
                              'objectIds')
    def objectIds(self, base_id=_marker):
        return HBTreeObjectIds(self, base_id)

    security.declareProtected(access_contents_information,
                              'objectItems')
    def objectItems(self, base_id=_marker):
        # Returns a list of (id, subobject) tuples of the current object.
        # If 'spec' is specified, returns only objects whose meta_type match
        # 'spec'
        return HBTreeObjectItems(self, base_id)

    # superValues() looks for the _objects attribute, but the implementation
    # would be inefficient, so superValues() support is disabled.
    _objects = ()


    security.declareProtected(access_contents_information,
                              'objectIds_d')
    def objectIds_d(self, t=None):
        return dict.fromkeys(self.objectIds(t), 1)

    def _checkId(self, id, allow_dup=0):
        if not allow_dup and self.has_key(id):
            raise BadRequestException, ('The id "%s" is invalid--'
                                        'it is already in use.' % id)


    def _setObject(self, id, object, roles=None, user=None, set_owner=1):
        v=self._checkId(id)
        if v is not None: id=v

        # If an object by the given id already exists, remove it.
        if self.has_key(id):
            self._delObject(id)

        self._setOb(id, object)
        object = self._getOb(id)

        if set_owner:
            object.manage_fixupOwnershipAfterAdd()

            # Try to give user the local role "Owner", but only if
            # no local roles have been set on the object yet.
            if hasattr(object, '__ac_local_roles__'):
                if object.__ac_local_roles__ is None:
                    user=getSecurityManager().getUser()
                    if user is not None:
                        userid=user.getId()
                        if userid is not None:
                            object.manage_setLocalRoles(userid, ['Owner'])

        object.manage_afterAdd(object, self)
        return id


    def _delObject(self, id, dp=1):
        object = self._getOb(id)
        try:
            object.manage_beforeDelete(object, self)
        except BeforeDeleteException, ob:
            raise
        except ConflictError:
            raise
class DuplicatesCriteriaManager(Implicit):
    """ An object that contains :
    _criteria {bib_type : [criteria_name]}:
              contains all possible criteria (meta-data) for each bibliography type

    duplicates_criteria {bib_type : [criteria] } :
          contains criteria to be checked
          for duplication while importing for each bibliography type

    _nonmeta_criteria = ( 'reference_type', ) : contains criteria not
         comprised in meta_data

    _criteria_names = {'publication_authors' : 'authors'} : contains
         mappings for meta_data that need a special treatement like 'authors'

    _ignored_criteria = (critrias) : contains the names of  same meta_data
         like 'id' or 'allowDiscussion' to be filtered

    IMPORTANT : only 'authors','reference_type', 'publication_year',
      'title' are fully functional for now
    """

    _nonmeta_criteria = ( 'reference_type', )

    _default_duplicates_criteria  = (
        #FIXME - we may need a default value for these
                          'authors',
                          'reference_type',
                          'publication_year',
                          'title',)

    _criteria_names = {'publication_authors' : 'authors'}

    _ignored_criteria = ('allowDiscussion','id', )

    _www = os.path.join(os.path.dirname(__file__), 'www')

    security = ClassSecurityInfo()

    manage_options = (
        { 'label'  : 'Criteria',
          'action' : 'manageImportCriteria',
          },
        )

    security.declareProtected(ManagePortal,
                              'manageImportCriteria')
    manageImportCriteria = PageTemplateFile('manage_criteria', _www)

    def __init__(self):
        self._criteria = PersistentMapping()
        self.duplicates_criteria = PersistentMapping()
        self.criteriaUpdated = False

    def allCriteria(self, bib_type=None):

        # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas
        if shasattr(self, '_criterias'):
            print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (allCriteria of %s)' % '/'.join(self.getId())
            self._criteria = PersistentMapping()
            self.initCriteria()
            try: delattr(self, '_criterias')
            except: pass

        # this should have been performed by __init__ but after some product migrations we might want to check it here again
        if not shasattr(self, '_criteria'):
            self._criteria = PersistentMapping()
        if not shasattr(self, 'duplicates_criteria'):
            self.duplicates_criteria = PersistentMapping()

        # first call? initialize self._criteria (available duplicates criteria per reference type)
        if not self._criteria:
            self.initCriteria()

        # always init criteria, during development, schema changes, etc.
        self.initCriteria()
        if bib_type:
            try:
                self._criteria[bib_type]
            except KeyError:
                return False

            return self._criteria[bib_type]
        else:
            critKeys = self._criteria.keys()
            critKeys.sort()
            return [(key, self._criteria[key]) for key in critKeys]

    # fixing a linguistic fault-pas
    allcriterias = allCriteria

    def sortCriteriaByTitles(self, criteria, i18n_domain='cmfbibliographyat'):
        """ take a list of criteria tuples (as returned by allCriteria)
            and sort it according to their i18n names. used for nice display on screen
        """
        # sorting reference types by their i18n titles
        ref_types_i18n = [ (self.translate(domain='plone', msgid=criterion[0], default=criterion[0]), criterion[0]) for criterion in criteria ]
        ref_types_i18n.sort()
        ref_types = [ t[1] for t in ref_types_i18n ]

        # turning criteria into a dictionary
        criteria_dict = {}
        for ref_type in ref_types:
            criteria_dict[ref_type] = [ t[1] for t in criteria if t[0] == ref_type ][0]

        # sorting fields for each ref_type, standard fields always preceed non-standard fields
        standard_fields = ['authors', 'publication_year', 'title', 'reference_type',]
        for ref_type in ref_types:
            nonstandard_fields_i18n = [ (self.translate(domain=i18n_domain, msgid='label_%s' % field, default=field), field) for field in criteria_dict[ref_type] if field not in standard_fields ]
            nonstandard_fields_i18n.sort()
            nonstandard_fields = [ f[1] for f in nonstandard_fields_i18n ]
            criteria_dict[ref_type] = standard_fields + nonstandard_fields

        return [ (t, tuple(criteria_dict[t])) for t in ref_types ]

    def initCriteria(self):
        """ initialize the dictionary of import criteria
        for each bibliography type
        """
        # this is a migration 0.8 -> 0.9 fix:
        if not shasattr(self, '_criteria'):
            self._criteria = PersistentMapping()
        bib_tool = getToolByName(self, 'portal_bibliography')
        has = self._criteria_names.has_key
        for bib_type in bib_tool.getBibliographyContentTypes():
            bibname = bib_type['name']
            self._criteria[bibname] = [criteria for criteria in self._nonmeta_criteria]
            #adds all meta_data as criteria for each bibliography type
            for field in bib_type['schema'].fields():
                field_name = field.getName()
                if field_name in self._ignored_criteria:
                    continue
                if not shasattr(field, 'is_duplicates_criterion'):
                    continue
                if not field.is_duplicates_criterion:
                    continue
                if has(field_name):
                    self._criteria[bibname].append(self._criteria_names[field_name])
                else :
                    self._criteria[bibname].append(field_name)
            self._criteria[bibname].sort()
            self._criteria[bibname] = tuple(self._criteria[bibname])

    # fixing a linguistic fault-pas
    initCriterias = initCriteria

    def manage_changeCriteria(self, REQUEST):
        """Changes all criteria for a bibliography type given,
           called by management screen
         """
        reference_type = REQUEST.get('bibtype')
        has = REQUEST.has_key
        if reference_type != 'all':
            reference_types = [reference_type]
        else:
            bib_tool = getToolByName(self, 'portal_bibliography')
            reference_types = bib_tool.getReferenceTypes()

        for reference_type in reference_types:
            criteria = []
            for criterion in self._criteria[reference_type]:
                # the form bibtype_criterion is not useful for now, but it was set up
                # in case we want to use one submit input for all the bibliography types
                if has("%s_%s" % (reference_type, criterion)):
                    criteria.append(criterion)
            try: self.setCriteriaForType(reference_type, criteria)
            except:
                return MessageDialog(
                    title  ='Warning!',
                    message='Your changes have not been saved',
                    action ='manageImportCriteria')

        return MessageDialog(
                title  ='Success!',
                message='Your changes have been saved',
                action ='manageImportCriteria'
                )

    # fixing a linguistic fault-pas
    manage_changeCriterias = manage_changeCriteria

    def setCriteriaForType(self, bib_type, criteria):
        """update criteria for a bibliography type given"""
        self.duplicates_criteria[bib_type] = PersistentList(criteria)
        #FIXME may need to check if any change has been done
        self.criteriaUpdated = True
        return True

    # fixing a linguistic fault-pas
    setCriteriasForType = setCriteriaForType

    def setCriteria(self, duplicates_criteria):
        """update criteria for all bibliography types"""
        if not duplicates_criteria:
            duplicates_criteria = {}
            bib_tool = getToolByName(self, 'portal_bibliography')
            for key in bib_tool.getReferenceTypes():
                duplicates_criteria[key] = []
        self.duplicates_criteria = PersistentMapping(duplicates_criteria)
        self.criteriaUpdated = True

    # fixing a linguistic fault-pas
    setCriterias = setCriteria

    def isCriterionSelected(self, bib_type, criterion):
        return (criterion in self.getSelectedCriteria(bib_type)) and True or False

    def isNonMetaCriterion(self, criterion):
        return criterion in self._nonmeta_criteria

    # fixing a linguistic fault-pas
    isCriteriaSelected = isCriterionSelected

    def getSelectedCriteria(self, bib_type = None):

        # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas
        if shasattr(self, 'imp_criterias'):
            print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (getSelectedCriteria of %s)' % '/'.join(self.getId())
            self.duplicates_criteria = PersistentMapping()
            self.duplicates_criteria = copy.deepcopy(self.imp_criterias)
            self.criteriaUpdated = self.criteriasUpdated
            try: delattr(self, 'imp_criterias')
            except: pass
            try: delattr(self, 'criteriasUpdated')
            except: pass

        # first call? initialize self.duplicates_criteria
        bib_tool = getToolByName(self, 'portal_bibliography')
        if not shasattr(self, 'duplicates_criteria') or not self.duplicates_criteria:

            if self.getId() == bib_tool.getId():
                for reference_type in bib_tool.getReferenceTypes():
                    self.duplicates_criteria[reference_type] = PersistentList(self._default_duplicates_criteria)
                self.criteriaUpdated = True
            else:
                self.duplicates_criteria = bib_tool.getSelectedCriteria()
                self.criteriaUpdated = True

        if not shasattr(self, '_criteria') or not self._criteria:
            self.initCriteria()

        # make sure, our selected criteria are in sync with available criteria
        duplicates_criteria = {}
        for key in self._criteria.keys():
            duplicates_criteria[key] = [ criterion for criterion in self._criteria[key] if self.duplicates_criteria.has_key(key) and (criterion in self.duplicates_criteria[key]) ]

        if bib_type:
            try:
                duplicates_criteria[bib_type]
            except KeyError:
                return False
            return duplicates_criteria[bib_type]
        else:
            return duplicates_criteria

    # fixing a linguistic fault-pas
    getSelectedCriterias = getSelectedCriteria
Esempio n. 7
0
class UserFolder(BasicUserFolder):
    """Standard UserFolder object

    A UserFolder holds User objects which contain information
    about users including name, password domain, and roles.
    UserFolders function chiefly to control access by authenticating
    users and binding them to a collection of roles."""

    meta_type = 'User Folder'
    id = 'acl_users'
    title = 'User Folder'

    def __init__(self):
        self.data = PersistentMapping()

    def getUserNames(self):
        """Return a list of usernames"""
        names = self.data.keys()
        return sorted(names)

    def getUsers(self):
        """Return a list of user objects"""
        data = self.data
        names = data.keys()
        return [data[n] for n in sorted(names)]

    def getUser(self, name):
        """Return the named user object or None"""
        return self.data.get(name, None)

    def hasUsers(self):
        """ This is not a formal API method: it is used only to provide
        a way for the quickstart page to determine if the default user
        folder contains any users to provide instructions on how to
        add a user for newbies.  Using getUserNames or getUsers would have
        posed a denial of service risk."""
        return not not len(self.data)

    def _doAddUser(self, name, password, roles, domains, **kw):
        """Create a new user

        Note that an existing user of this name is simply overwritten."""
        if password is not None and self.encrypt_passwords and \
                not self._isPasswordEncrypted(password):
            password = self._encryptPassword(password)
        self.data[name] = User(name, password, roles, domains)
        return self.data[name]

    def _doChangeUser(self, name, password, roles, domains, **kw):
        user = self.data[name]
        if password is not None:
            if (self.encrypt_passwords
                    and not self._isPasswordEncrypted(password)):
                password = self._encryptPassword(password)
            user.__ = password
        user.roles = roles
        user.domains = domains

    def _doDelUsers(self, names):
        for name in names:
            del self.data[name]
Esempio n. 8
0
class HBTreeFolder2Base(Persistent):
    """Base for BTree-based folders.
    """

    security = ClassSecurityInfo()

    manage_options = (({
        'label': 'Contents',
        'action': 'manage_main',
    }, ) + Folder.manage_options[1:])

    security.declareProtected(view_management_screens, 'manage_main')
    manage_main = DTMLFile('contents', globals())

    _htree = None  # OOBTree: { id -> object }
    _count = None  # A BTrees.Length
    _v_nextid = 0  # The integer component of the next generated ID
    title = ''
    _tree_list = None

    def __init__(self, id=None):
        if id is not None:
            self.id = id
        self._initBTrees()

    def _initBTrees(self):
        self._htree = OOBTree()
        self._count = Length()
        self._tree_list = PersistentMapping()

    def _populateFromFolder(self, source):
        """Fill this folder with the contents of another folder.
        """
        for name in source.objectIds():
            value = source._getOb(name, None)
            if value is not None:
                self._setOb(name, aq_base(value))

    security.declareProtected(view_management_screens, 'manage_fixCount')

    def manage_fixCount(self):
        """Calls self._fixCount() and reports the result as text.
        """
        old, new = self._fixCount()
        path = '/'.join(self.getPhysicalPath())
        if old == new:
            return "No count mismatch detected in HBTreeFolder2 at %s." % path
        else:
            return ("Fixed count mismatch in HBTreeFolder2 at %s. "
                    "Count was %d; corrected to %d" % (path, old, new))

    def _fixCount(self):
        """Checks if the value of self._count disagrees with
        len(self.objectIds()). If so, corrects self._count. Returns the
        old and new count values. If old==new, no correction was
        performed.
        """
        old = self._count()
        new = len(self.objectIds())
        if old != new:
            self._count.set(new)
        return old, new

    security.declareProtected(view_management_screens, 'manage_cleanup')

    def manage_cleanup(self):
        """Calls self._cleanup() and reports the result as text.
        """
        v = self._cleanup()
        path = '/'.join(self.getPhysicalPath())
        if v:
            return "No damage detected in HBTreeFolder2 at %s." % path
        else:
            return ("Fixed HBTreeFolder2 at %s.  "
                    "See the log for more details." % path)

    def _cleanup(self):
        """Cleans up errors in the BTrees.

        Certain ZODB bugs have caused BTrees to become slightly insane.
        Fortunately, there is a way to clean up damaged BTrees that
        always seems to work: make a new BTree containing the items()
        of the old one.

        Returns 1 if no damage was detected, or 0 if damage was
        detected and fixed.
        """
        def hCheck(htree):
            """
              Recursively check the btree
          """
            check(htree)
            for key in htree.keys():
                if not htree.has_key(key):
                    raise AssertionError("Missing value for key: %s" %
                                         repr(key))
                else:
                    ob = htree[key]
                    if isinstance(ob, OOBTree):
                        hCheck(ob)
            return 1

        from BTrees.check import check
        path = '/'.join(self.getPhysicalPath())
        try:
            return hCheck(self._htree)
        except AssertionError:
            LOG('HBTreeFolder2',
                WARNING,
                'Detected damage to %s. Fixing now.' % path,
                error=sys.exc_info())
            try:
                self._htree = OOBTree(self._htree)  # XXX hFix needed
            except:
                LOG('HBTreeFolder2',
                    ERROR,
                    'Failed to fix %s.' % path,
                    error=sys.exc_info())
                raise
            else:
                LOG('HBTreeFolder2', INFO, 'Fixed %s.' % path)
            return 0

    def hashId(self, id):
        """Return a tuple of ids
        """
        # XXX: why tolerate non-string ids ?
        id_list = str(id).split(
            H_SEPARATOR)  # We use '-' as the separator by default
        if len(id_list) > 1:
            return tuple(id_list)
        else:
            return [
                id,
            ]


#         try:                             # We then try int hashing
#           id_int = int(id)
#         except ValueError:
#           return id_list
#         result = []
#         while id_int:
#           result.append(id_int % MAX_OBJECT_PER_LEVEL)
#           id_int = id_int / MAX_OBJECT_PER_LEVEL
#         result.reverse()
#         return tuple(result)

    def _getOb(self, id, default=_marker):
        """
            Return the named object from the folder.
        """
        htree = self._htree
        ob = htree
        id_list = self.hashId(id)
        for sub_id in id_list[0:-1]:
            if default is _marker:
                ob = ob[sub_id]
            else:
                ob = ob.get(sub_id, _marker)
                if ob is _marker:
                    return default
        if default is _marker:
            ob = ob[id]
        else:
            ob = ob.get(id, _marker)
            if ob is _marker:
                return default
        return ob.__of__(self)

    def _setOb(self, id, object):
        """Store the named object in the folder.
        """
        htree = self._htree
        id_list = self.hashId(id)
        for idx in xrange(len(id_list) - 1):
            sub_id = id_list[idx]
            if not htree.has_key(sub_id):
                # Create a new level
                htree[sub_id] = OOBTree()
                if isinstance(sub_id, (int, long)):
                    tree_id = 0
                    for id in id_list[:idx + 1]:
                        tree_id = tree_id + id * MAX_OBJECT_PER_LEVEL
                else:
                    tree_id = H_SEPARATOR.join(id_list[:idx + 1])
                # Index newly created level
                self._tree_list[tree_id] = None

            htree = htree[sub_id]

        if len(id_list) == 1 and not htree.has_key(None):
            self._tree_list[None] = None
        # set object in subtree
        ob_id = id_list[-1]
        if htree.has_key(id):
            raise KeyError('There is already an item named "%s".' % id)
        htree[id] = object
        self._count.change(1)

    def _delOb(self, id):
        """Remove the named object from the folder.
        """
        htree = self._htree
        id_list = self.hashId(id)
        for sub_id in id_list[0:-1]:
            htree = htree[sub_id]
        del htree[id]
        self._count.change(-1)

    security.declareProtected(view_management_screens, 'getBatchObjectListing')

    def getBatchObjectListing(self, REQUEST=None):
        """Return a structure for a page template to show the list of objects.
        """
        if REQUEST is None:
            REQUEST = {}
        pref_rows = int(REQUEST.get('dtpref_rows', 20))
        b_start = int(REQUEST.get('b_start', 1))
        b_count = int(REQUEST.get('b_count', 1000))
        b_end = b_start + b_count - 1
        url = self.absolute_url() + '/manage_main'
        count = self.objectCount()

        if b_end < count:
            next_url = url + '?b_start=%d' % (b_start + b_count)
        else:
            b_end = count
            next_url = ''

        if b_start > 1:
            prev_url = url + '?b_start=%d' % max(b_start - b_count, 1)
        else:
            prev_url = ''

        formatted = [listtext0 % pref_rows]
        for optID in islice(self.objectIds(), b_start - 1, b_end):
            optID = escape(optID)
            formatted.append(listtext1 % (escape(optID, quote=1), optID))
        formatted.append(listtext2)
        return {
            'b_start': b_start,
            'b_end': b_end,
            'prev_batch_url': prev_url,
            'next_batch_url': next_url,
            'formatted_list': ''.join(formatted)
        }

    security.declareProtected(view_management_screens,
                              'manage_object_workspace')

    def manage_object_workspace(self, ids=(), REQUEST=None):
        '''Redirects to the workspace of the first object in
        the list.'''
        if ids and REQUEST is not None:
            REQUEST.RESPONSE.redirect('%s/%s/manage_workspace' %
                                      (self.absolute_url(), quote(ids[0])))
        else:
            return self.manage_main(self, REQUEST)

    security.declareProtected(access_contents_information, 'tpValues')

    def tpValues(self):
        """Ensures the items don't show up in the left pane.
        """
        return ()

    security.declareProtected(access_contents_information, 'objectCount')

    def objectCount(self):
        """Returns the number of items in the folder."""
        return self._count()

    security.declareProtected(access_contents_information, 'has_key')

    def has_key(self, id):
        """Indicates whether the folder has an item by ID.
        """
        htree = self._htree
        id_list = self.hashId(id)
        for sub_id in id_list[0:-1]:
            if not isinstance(htree, OOBTree):
                return 0
            if not htree.has_key(sub_id):
                return 0
            htree = htree[sub_id]
        if not htree.has_key(id):
            return 0
        return 1

    # Work around for the performance regression introduced in Zope 2.12.23.
    # Otherwise, we use superclass' __contains__ implementation, which uses
    # objectIds, which is inefficient in HBTreeFolder2 to lookup a single key.
    __contains__ = has_key

    def _htree_iteritems(self, min=None):
        # BUG: Due to bad design of HBTreeFolder2, buckets other than the root
        #      one must not contain both buckets & leafs. Otherwise, this method
        #      fails.
        h = self._htree
        recurse_stack = []
        try:
            for sub_id in min and self.hashId(min) or ('', ):
                if recurse_stack:
                    i.next()
                    if type(h) is not OOBTree:
                        break
                    id += H_SEPARATOR + sub_id
                    if type(h.itervalues().next()) is not OOBTree:
                        sub_id = id
                else:
                    id = sub_id
                i = h.iteritems(sub_id)
                recurse_stack.append(i)
                h = h[sub_id]
        except (KeyError, StopIteration):
            pass
        while recurse_stack:
            i = recurse_stack.pop()
            try:
                while 1:
                    id, h = i.next()
                    if type(h) is OOBTree:
                        recurse_stack.append(i)
                        i = h.iteritems()
                    else:
                        yield id, h
            except StopIteration:
                pass

    security.declareProtected(access_contents_information, 'treeIds')

    def treeIds(self, base_id=None):
        """ Return a list of subtree ids
        """
        tree = self._getTree(base_id=base_id)
        return [k for k, v in self._htree.items() if isinstance(v, OOBTree)]

    def _getTree(self, base_id):
        """ Return the tree wich has the base_id
        """
        htree = self._htree
        id_list = self.hashId(base_id)
        for sub_id in id_list:
            if not isinstance(htree, OOBTree):
                return None
            if not htree.has_key(sub_id):
                raise IndexError, base_id
            htree = htree[sub_id]
        return htree

    def _getTreeIdList(self, htree=None):
        """ recursively build a list of btree ids
        """
        if htree is None:
            htree = self._htree
            btree_list = []
        else:
            btree_list = []
        for obj_id in htree.keys():
            obj = htree[obj_id]
            if isinstance(obj, OOBTree):
                btree_list.extend([
                    "%s-%s" % (obj_id, x)
                    for x in self._getTreeIdList(htree=obj)
                ])
                btree_list.append(obj_id)

        return btree_list

    security.declareProtected(access_contents_information, 'getTreeIdList')

    def getTreeIdList(self, htree=None):
        """ Return list of all tree ids
        """
        if self._tree_list is None or len(self._tree_list.keys()) == 0:
            tree_list = self._getTreeIdList(htree=htree)
            self._tree_list = PersistentMapping()
            for tree in tree_list:
                self._tree_list[tree] = None
        return sorted(self._tree_list.keys())

    def _checkObjectId(self, ids):
        """ test id is not in btree id list
        """
        base_id, obj_id = ids
        if base_id is not None:
            obj_id = "%s%s%s" % (base_id, H_SEPARATOR, obj_id)
        return not self._tree_list.has_key(obj_id)

    security.declareProtected(access_contents_information, 'objectValues')

    def objectValues(self, base_id=_marker):
        return HBTreeObjectValues(self, base_id)

    security.declareProtected(access_contents_information, 'objectIds')

    def objectIds(self, base_id=_marker):
        return HBTreeObjectIds(self, base_id)

    security.declareProtected(access_contents_information, 'objectItems')

    def objectItems(self, base_id=_marker):
        # Returns a list of (id, subobject) tuples of the current object.
        # If 'spec' is specified, returns only objects whose meta_type match
        # 'spec'
        return HBTreeObjectItems(self, base_id)

    # superValues() looks for the _objects attribute, but the implementation
    # would be inefficient, so superValues() support is disabled.
    _objects = ()

    security.declareProtected(access_contents_information, 'objectIds_d')

    def objectIds_d(self, t=None):
        return dict.fromkeys(self.objectIds(t), 1)

    def _checkId(self, id, allow_dup=0):
        if not allow_dup and self.has_key(id):
            raise BadRequestException, ('The id "%s" is invalid--'
                                        'it is already in use.' % id)

    def _setObject(self, id, object, roles=None, user=None, set_owner=1):
        v = self._checkId(id)
        if v is not None: id = v

        # If an object by the given id already exists, remove it.
        if self.has_key(id):
            self._delObject(id)

        self._setOb(id, object)
        object = self._getOb(id)

        if set_owner:
            object.manage_fixupOwnershipAfterAdd()

            # Try to give user the local role "Owner", but only if
            # no local roles have been set on the object yet.
            if hasattr(object, '__ac_local_roles__'):
                if object.__ac_local_roles__ is None:
                    user = getSecurityManager().getUser()
                    if user is not None:
                        userid = user.getId()
                        if userid is not None:
                            object.manage_setLocalRoles(userid, ['Owner'])

        object.manage_afterAdd(object, self)
        return id

    def _delObject(self, id, dp=1):
        object = self._getOb(id)
        try:
            object.manage_beforeDelete(object, self)
        except BeforeDeleteException, ob:
            raise
        except ConflictError:
            raise