class MemberData(BaseMemberData):

    security = AccessControl.ClassSecurityInfo()

    security.declarePrivate('setMemberProperties')

    def setMemberProperties(self, mapping):
        """Overridden to store a copy of certain user data fields in an SQL database"""
        # Only pass relevant fields to the database
        db_args = dict([(f, v) for (f, v) in mapping.items()
                        if f in DB_FIELDS and self.getProperty(f) != v])
        dbtool = getToolByName(self, 'portal_moduledb')
        if dbtool and db_args:
            # We have to pass in aq_parent to be our own parent,
            # otheriwse the ZSQL method will acquire blank arguments
            # from the property sheet
            if not self.member_catalog(getUserName=self.getId()):
                zLOG.LOG(
                    "MemberData", zLOG.INFO,
                    "INSERT memberdata for %s: %s" % (self.getId(), db_args))
                dbtool.sqlInsertMember(aq_parent=self.aq_parent,
                                       id=self.getId(),
                                       **db_args)
            else:
                zLOG.LOG(
                    "MemberData", zLOG.INFO,
                    "UPDATE memberdata for %s: %s" % (self.getId(), db_args))
                dbtool.sqlUpdateMember(aq_parent=self.aq_parent,
                                       id=self.getId(),
                                       **db_args)

        BaseMemberData.setMemberProperties(self, mapping)
示例#2
0
class CatalogTool(base.CatalogTool):

    security = AccessControl.ClassSecurityInfo()
    
    def reindexComments(self):
        pass
    
    def clearFindAndRebuild(self):
        """Empties catalog, then finds all contentish objects (i.e. objects
           with an indexObject method), and reindexes them.
           This may take a long time.
        """
        
        def indexObject(obj, path):	    
            if (base_hasattr(obj, 'indexObject') and
                safe_callable(obj.indexObject)):
                try:
                    obj.indexObject()
                    pdtool = obj.portal_discussion
                    if pdtool.isDiscussionAllowedFor(obj):                        
                        tb = pdtool.getDiscussionFor(obj)
                        for ob in tb.getReplies():
                                ob.indexObject()
                except TypeError:
                    # Catalogs have 'indexObject' as well, but they
                    # take different args, and will fail
                    pass
        self.manage_catalogClear()
        
        portal = aq_parent(aq_inner(self))
        portal.ZopeFindAndApply(portal, search_sub=True, apply_func=indexObject)
示例#3
0
class Plugin(PAS.plugins.BasePlugin.BasePlugin):
    security = AccessControl.ClassSecurityInfo()
    meta_type = 'LDAPAlchemy Plugin'

    # Tell PAS not to swallow our exceptions, do not use for production
    _dont_swallow_my_exceptions = True

    @security.public
    def authenticateCredentials(self, credentials):
        """see PAS.interfaces.plugins.IAuthenticationPlugin
        """
        login = credentials.get('login')
        pw = credentials.get('password')
        if not (login and pw):
            return None

        # XXX: no real ldapalchemy, yet, but barebone ldapy
        # also for now, we open/close the ldap connection for each call
        ld = ldapy.initialize('ldapi://ldapi')
        login_dn = 'cn=%s,o=o' % login

        try:
            ldapy.simple_bind_s(ld, login_dn, pw)
        except ldapy.InvalidCredentials:
            authinfo = None
        else:
            uid = login
            authinfo = (uid, login)
            ldapy.unbind(ld)

        return authinfo
示例#4
0
class HasProtectedMethods(SimpleItem):

    security = AccessControl.ClassSecurityInfo()

    def __init__(self, id):
        self.id = id

    @security.public
    def public_method(self):
        pass

    @security.protected('ppp')
    def pp_method(self):
        pass

    @security.protected('qqq')
    def qq_method(self):
        pass

    @security.protected('rrr')
    def rr_method(self):
        pass

    @security.private
    def private_method(self):
        pass
示例#5
0
class HasProtectedMethods(SimpleItem):

    security = AccessControl.ClassSecurityInfo()

    security.declarePublic('public_method')
    security.declareProtected('ppp', 'pp_method')
    security.declareProtected('qqq', 'qq_method')
    security.declareProtected('rrr', 'rr_method')
    security.declarePrivate('private_method')

    def __init__(self, id):
        self.id = id

    def public_method(self):
        pass

    def pp_method(self):
        pass

    def qq_method(self):
        pass

    def rr_method(self):
        pass

    def private_method(self):
        pass
示例#6
0
class MemberData(BaseMemberData):

    __implements__ = IMemberData

    security = AccessControl.ClassSecurityInfo()

    def __init__(self, id):
        self.id = id

    def getId(self):
        """Override to return the id we've stored"""
        return self.id

    security.declarePrivate('notifyModified')

    def notifyModified(self):
        # Recatalog this member
        cat = getToolByName(self, MEMBER_CATALOG)
        cat.catalog_object(self)

    security.declarePublic('getUser')

    def getUser(self):
        # First try using the acqusition context
        user = aq_parent(self)
        bcontext = aq_base(user)
        bcontainer = aq_base(aq_parent(aq_inner(self)))
        if bcontext is bcontainer or not hasattr(bcontext, 'getUserName'):
            # OK, the wrapper didn't work so let's try looking up the user by ID
            user_folder = getToolByName(self, 'acl_users')
            user = user_folder.getUser(self.getId())

        if user:
            return user
        else:
            raise 'MemberDataError', "Can't find user data for %s" % self.getId(
            )

    def getTool(self):
        return getToolByName(self, 'portal_memberdata')

    security.declarePublic('getMemberId')

    def getMemberId(self):
        return self.getId()

    ### GRUF interface

    security.declarePublic('getGroups')

    def getGroups(self):
        """Check to see if a user has a given role or roles."""
        try:
            return self.getUser().getGroups()
        except AttributeError:
            # Cope with users from non-GRUF user folders
            return ()
示例#7
0
class EMailAspect(Products.AlphaFlow.aspect.Aspect):

    zope.interface.implements(IEMailAspect)

    security = AccessControl.ClassSecurityInfo()

    aspect_type  = "email"

    security.declarePrivate('__call__')
    def __call__(self):
        """Send email."""
        work_items = [self.getWorkItem()]
        Products.AlphaFlow.activities.notify._send_email(
            self, self.getDefinition(), work_items)
示例#8
0
class ZopeVersion(Version.Version, AccessControl.Role.RoleManager,
                  OFS.SimpleItem.Item):
    """The ZopeVersion class builds on the core Version class to provide
       the Zope management interface and other product trappings."""

    security = AccessControl.ClassSecurityInfo()
    security.setDefaultAccess('deny')

    meta_type = 'Version'

    manage_options = ((
        {
            'label': 'Information',
            'action': 'manage_main',
            'help': ('ZopeVersionControl', 'Version-Manage.stx')
        },
        {
            'label': 'Properties',
            'action': 'manage_properties_form',
            'help': ('ZopeVersionControl', 'Version-Properties.stx')
        },
    ) + AccessControl.Role.RoleManager.manage_options +
                      OFS.SimpleItem.Item.manage_options)

    icon = 'misc_/ZopeVersionControl/Version.gif'

    security.declareProtected('View management screens', 'manage_main')
    manage_main = DTMLFile('dtml/VersionManageMain', globals())
    manage_main._setName('manage_main')
    manage = manage_main

    security.declareProtected('View management screens',
                              'manage_properties_form')
    manage_properties_form = DTMLFile('dtml/VersionProperties', globals())

    security.declareProtected('Manage repositories', 'manage_edit')

    def manage_edit(self, REQUEST=None):
        """Change object properties."""
        if REQUEST is not None:
            message = "Saved changes."
            return self.manage_properties_form(self,
                                               REQUEST,
                                               manage_tabs_message=message)
class UserFeedback(BaseContent):
    """
    Feedback from Connexions user
    """

    schema = schema
    security = AccessControl.ClassSecurityInfo()
    actions = (
        {
            'id': 'view',
            'title': 'View',
            'action': 'feedback_view',
            'permissions': (CMFCorePermissions.View, )
        },
        {
            'id': 'edit',
            'title': 'Edit',
            'action': 'base_edit',
            'permissions': (CMFCorePermissions.ModifyPortalContent, )
        },
        {
            'id': 'metadata',
            'title': 'Properties',
            'action': 'base_metadata',
            'permissions': (CMFCorePermissions.ModifyPortalContent, )
        },
        {
            'id': 'references',
            'title': 'References',
            'action': 'reference_edit',
            'permissions': (CMFCorePermissions.ModifyPortalContent, )
        },
    )

    security.declareProtected(CMFCorePermissions.View, 'CookedBody')

    def CookedBody(self, stx_level='ignored'):
        """CMF compatibility method
        """
        return self.getText()
示例#10
0
class LensRedirectContainer(LensMajorContainer):
    """Like a Major Container, but looks for its contents in the real one.
    """
    archetype_name = "Lens Redirect Container"
    allowed_content_types = ()

    security = AccessControl.ClassSecurityInfo()

    def __bobo_traverse__(self, request, key):
        """If we find child objects in the real lenses folder, return those instead.
        Ideally, I'd like to make them think they were in this folder, but I can't seem to figure out how.
        Instead, push the category value into the request, where the dispatcher script can look.
        """
        stack = request.get('TraversalRequestNameStack')
        lens_tool = getToolByName(self, 'lens_tool')
        canonical = lens_tool.getMajorContainer()

        # first getattr is just a test (which should not be wrapped, acquisition being too broad),
        # second is actual object (which must be wrapped, or else later acquisition fails)
        userfolder = getattr(canonical.aq_base, key, None) and getattr(
            canonical, key, None)

        if userfolder:
            if type(
                    request
            ) != dict:  # a dict when key is template, and we don't want to get normal lenses_listing
                request.set('getCategory', self.getCategory())
                return userfolder
        return LensMajorContainer.__bobo_traverse__(
            self, request,
            key)  # uses Five; getattr(self, key, None) doesn't work anymore

    # A PUT on the folder is intentionally a no-op
    security.declareProtected(ModifyPortalContent, 'PUT')

    def PUT(self, REQUEST):
        return
示例#11
0
class LensTool(UniqueObject, SimpleItem):
    """Globally available methods for interacting with the lens system."""

    id = 'lens_tool'
    meta_type = 'Lens Tool'
    security = AccessControl.ClassSecurityInfo()

    manage_options = (({
        'label': 'Overview',
        'action': 'manage_overview'
    }, ) + SimpleItem.manage_options)
    ##   ZMI methods
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = PageTemplateFile('zpt/overview', globals())

    def __init__(self):
        """Setup tool data."""
        self.lenstypes = KNOWN_LENS_TYPES

    security.declareProtected(ManagePermission, 'manage_setConfig')

    def manage_setConfig(self, lenstypes, REQUEST=None):
        """Post-creation config; see overview.zpt."""
        if type(lenstypes) in StringTypes:
            lenstypes = lenstypes.split()
        self.lenstypes = lenstypes
        if REQUEST is not None:
            REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_overview')

    security.declareProtected(ManagePermission, 'getLensTypes')

    def getLensTypes(self):
        """List of content types considered lenses."""
        lenstypes = getattr(self, 'lenstypes', None)
        if lenstypes is None:  # auto-upgrade step
            self.lenstypes = lenstypes = KNOWN_LENS_TYPES
        return lenstypes

    def searchResults(self, REQUEST=None, **kw):
        """Calls lenses_catalog.searchResults with extra arguments that
        limit the results to what the user is allowed to see.
        """
        portal_catalog = getToolByName(self, 'portal_catalog')
        lens_catalog = getToolByName(self, 'lens_catalog')
        user = _getAuthenticatedUser(self)
        kw['allowedRolesAndUsers'] = portal_catalog._listAllowedRolesAndUsers(
            user)

        # portal_catalog checks expiry here, but we don't care

        return lens_catalog.searchResults(REQUEST=REQUEST, **kw)

    security.declarePrivate('_getEntriesFor')

    def _getEntriesFor(self,
                       contentId,
                       version=None,
                       implicit=True,
                       inside=None):
        """Internal method to return all entries pointing to a specified item. If a version is provided,
        only entries with a start version less than and a stop version greater than that version will be
        provided.

        params:
          contentId: key for item. string.
          version: version of item from 'contentId'. string in the form '1.10' or list of ints like [1,10].
              optional; if not provided, no version filtering. List param is a bit faster.
          implicit: get implicitly included content, being content inside an included collection. boolean.
          inside: the collection we are inside, if any, so that entries for that collection can be returned.
              tuple: (contentId, version). version is as above.
        returns:
          (global entries, contextual entries), where each is a (lazy) list of SelectedContent brains
        """
        insideId = inside and inside[0] or None
        insideVersion = inside and inside[1] or None
        if version:
            if type(version) in StringTypes:
                version = [int(x) for x in version.split('.')
                           ]  # must provide list, not tuple!
            if type(version) is not ListType:
                version = list(version)
        if inside:
            if type(insideVersion) in StringTypes:
                try:
                    insideVersion = [int(x) for x in insideVersion.split('.')
                                     ]  # must provide list, not tuple!
                except ValueError:  # not an int, probably an unpublished course
                    if insideVersion == "**new**":
                        insideVersion = [
                            1, 1
                        ]  # just set dummy version, no course entries will be found
                    else:
                        raise
            if type(insideVersion) is not ListType:
                insideVersion = list(insideVersion)

        query = {}
        query['portal_type'] = 'SelectedContent'
        query['id'] = contentId
        if version:
            query['getVersionStart'] = {'query': [version], 'range': 'max'}
            query['getVersionStop'] = {'query': [version], 'range': 'min'}
            # Note: the version stop here is supposed to be open-ended; we do this by relying on endless entries
            # being a different type to sort to the end instead of the beginning. See VersionField for details.

        directentries = self.searchResults(**query)

        content_catalog = getToolByName(self, 'content').catalog
        collections = [
            c for c in content_catalog(containedModuleIds=contentId)
            if c.objectId != insideId
        ]
        latest_version_str = content_catalog(
            objectId=contentId)[0].version  # TODO: only works for published
        # leaving here to draw out errors
        if version:
            # Filter out any collections that don't contain this version of the module.
            collectionIds = []
            for c in collections:
                pcp = c.getObject().getContainedObject(contentId)
                if pcp.version == 'latest':
                    pcp_version_str = latest_version_str
                else:
                    pcp_version_str = pcp.version
                pcp_version_tup = [int(x) for x in pcp_version_str.split('.')]
                if pcp_version_tup == version:
                    collectionIds.append(c.objectId)
        else:
            collectionIds = [c.objectId for c in collections]
        containingentries = self.searchResults({
            'portal_type': 'SelectedContent',
            'id': collectionIds,
            'getImplicit': True
        })
        allentries = directentries + containingentries

        if inside:
            query['id'] = insideId
            query['getVersionStart'] = {
                'query': [insideVersion],
                'range': 'max'
            }
            query['getVersionStop'] = {
                'query': [insideVersion],
                'range': 'min'
            }
            contextual = self.searchResults(**query)
        else:
            contextual = LazyCat([])

        return (allentries, contextual)

    security.declarePublic('getListsIncluding')

    def getListsIncluding(self,
                          contentId,
                          version=None,
                          categories=[],
                          implicit=True,
                          inside=None):
        """Return data about known lens content (per getLensTypes) which include entries for specified item.
        If a version is provided, only entries with a start version less than and a stop version greater
        than that version will be provided.

        params:
          contentId: key for item. string.
          version: version of item from 'contentId'. string.
          categories: "lens" types (endorsement, review, etc) to return. list of strings. Empty/None means all.
          implicit: get implicitly included content, being content inside an included collection. boolean.
          inside: the collection we are inside, if any, so that entries for that collection can be returned.
              tuple: (contentId, version)
          return {category: {listlocation:(listinfo,entryinfo)}}
            where listinfo={name:value} from CSL.listDict(), including lens id, names, category, state, location, et al.
            where entryinfo={name:value} including entry id, Title, tags, comment, fromImplicit
        """
        lens_catalog = getToolByName(self, 'lens_catalog')
        catdict = {}

        entries = self._getEntriesFor(contentId, version, implicit, inside)
        # each list can contain only one entry, so we don't need a set...
        for entry in entries[1] + entries[0]:
            # get parent info, which among other things will tell us about implicitness
            parentpath = entry.getPath().split('/')[:-1]
            parent = self.restrictedTraverse(
                parentpath
            )  # TODO : might put this in a CSL metadata slot and get from catalog
            listinfo = parent.listDict()
            listloc = listinfo['location']

            edict = {}
            #we're not us
            if entry.getId != contentId:
                if inside and entry.getId == inside[0]:
                    edict['fromImplicit'] = 'contextual'
                else:
                    edict['fromImplicit'] = 'implicit'
            else:
                edict['fromImplicit'] = False
            edict['id'] = entry.getId
            edict['Title'] = entry.Title
            edict['tags'] = ' '.join(entry.getTags)

            if 'getNamespaceTags' in lens_catalog.indexes():
                # Legacy data with essentially unintialized metadata
                # will not return the desired iterablke type.
                if isinstance(entry.getNamespaceTags, (ListType, TupleType)):
                    edict['namespaceTags'] = entry.getNamespaceTags
                else:
                    edict['namespaceTags'] = []

            edict['comment'] = entry.getPlainComment
            edict['approved'] = getattr(entry, 'getApproved', lambda: False)

            #separate lists by type (or only the specified type, if specified)
            cat = listinfo['category']
            if not catdict.has_key(cat):
                catdict[cat] = {}
            if not catdict[cat].has_key(listloc):
                catdict[cat][listloc] = (listinfo, [])
            catdict[cat][listloc][1].append(edict)

        return catdict

    security.declarePublic('getListsOwned')

    def getListsOwned(self, memberid=None, **kwargs):
        """Return known lens content (per getLensTypes) owned by member 'memberid', as a catalog list.
        
        If memberid is None, default to authenticated user, if any.
        """
        if not memberid:
            mship = getToolByName(self, 'portal_membership')
            m = mship.getAuthenticatedMember()
            if len(
                    m.getRoles()
            ) > 1:  # only one role == Anonymous; should be less fragile than name checks
                memberid = m.getId()

        if memberid:
            contentFilter = dict(portal_type=self.getLensTypes(),
                                 Creator=memberid)
            if kwargs:
                contentFilter.update(kwargs)
            pwned = self.searchResults(**contentFilter)
            # allowedRolesAndUsers is for viewing, but there's no similar index for editing,
            # which would be more appropriate...
        else:
            pwned = []

        return pwned

    security.declarePublic('getRecentlyModifiedListsOwned')

    def getRecentlyModifiedListsOwned(self, memberid=None):
        """Return known lens content owned by member 'memberid' and sorted via modification date,
        as a catalog list.
        
        If memberid is None, default to authenticated user, if any.
        """
        if not memberid:
            mship = getToolByName(self, 'portal_membership')
            m = mship.getAuthenticatedMember()
            if len(
                    m.getRoles()
            ) > 1:  # only one role == Anonymous; should be less fragile than name checks
                memberid = m.getId()

        if memberid:
            pwned = self.searchResults(portal_type='ContentSelectionLens',
                                       Creator=memberid,
                                       sort_on='modified',
                                       sort_order='descending')
            # allowedRolesAndUsers is for viewing, but there's no similar index for editing,
            # which would be more appropriate...
        else:
            pwned = []

        return pwned

    security.declarePublic('getListsBy')

    def getListsBy(self,
                   category=None,
                   path=None,
                   all=None,
                   memberid=None,
                   inclzero=True,
                   incl_organised=False):
        """Return public lensish content (per getLensTypes) meeting certain criteria. Light catalog wrapper.
        Returns catalog results list.
        If 'path' is provided, restrict to that path. (Used for showing only those inside a folder.)
        If 'category' is None, all are provided. Otherwise, all in that category.
        If 'all' is true, will not restrict to public.
        If 'inclzero' if true, empty lenses will be included. This is the default, being the old behavior.
        Path should be as acceptable by lens_catalog; like that provided by '/'.join(context.getPhysicalPath())
        """
        # get the lenses
        args = {'portal_type': self.getLensTypes()}
        if category: args['getCategory'] = category
        if path: args['path'] = path
        if not all:
            args['review_state'] = ('published', 'published_open',
                                    'private_open')
        if memberid: args['Creator'] = memberid
        results = self.searchResults(**args)
        if results:
            if not inclzero: results = [x for x in results if x.getCount > 0]

            if not incl_organised:
                # find all lenses referenced by a lensorganiser
                rc = getToolByName(self, 'reference_catalog')
                targetUIDs = [
                    b.targetUID
                    for b in rc(relationship='lenses_lensorganizers')
                ]

                # filter out the lenses in lensorganisers
                if targetUIDs:
                    results = [x for x in results if x.UID not in targetUIDs]

        return results

    security.declarePublic('getTagsForContent')

    def getTagsForContent(self,
                          contentId,
                          version=None,
                          inside=None,
                          tag_type='tags'):
        """Return the tags on any entries that point to specified item. If version is supplied, only return
        tags on entries that include the version in their range.
        params:
          contentId: key for item. string.
          version: version of item from 'contentId'. string.
        return:
          list of strings

        Theoretical companion would be 'getTagCloudForContent' which would return
        list of {tagname:number/percentage})

        tag_type is one of ('tags', 'namespaceTags')
        """
        insideId = inside and inside[0] or None

        #TODO : switched filter by version
        directentries = self.searchResults(portal_type='SelectedContent',
                                           id=contentId)

        content_catalog = getToolByName(self, 'content').catalog
        collectionIds = [
            c.objectId for c in content_catalog(containedModuleIds=contentId)
        ]
        containingentries = self.searchResults({
            'portal_type': 'SelectedContent',
            'id': collectionIds,
            'getImplicit': True
        })
        contextualentry = self.searchResults({
            'portal_type': 'SelectedContent',
            'id': insideId
        })
        allentries = directentries + containingentries + contextualentry

        #aggregate comments for each entry into set
        #  (for a tag cloud, we could aggregate into a {tagname:number of occurences})
        tagset = Set()
        for brain in allentries:
            if tag_type == 'tags':
                tags = brain.getTags
            elif tag_type == 'namespaceTags':
                # Legacy data with essentially uninitialized metadata
                # will not return the desired iterable type.
                if isinstance(brain.getNamespaceTags, (ListType, TupleType)):
                    tags = brain.getNamespaceTags
                else:
                    tags = []
            else:
                tags = []
            for tag in tags:
                tagset.add(tag)

        if tag_type == 'tags':
            return tuple(tagset)

        if tag_type == 'namespaceTags':
            return tuple(tagset)

        return []

    security.declarePublic('getContentForTag')

    def getContentForTag(self, tag):
        """Return content that has this on any entries that point to that content.
        params:
          tag: string
        returns:
          list of entry brains (urls? id/version? objects?)
        """
        contentFilter = dict(portal_type='SelectedContent')
        lens_catalog = getToolByName(self, 'lens_catalog')

        if 'getNamespaceTags' in lens_catalog.indexes():
            if tag.find(TAGNAMESPACE_DELIMITER) != -1:
                contentFilter['getNamespaceTags'] = tag
            else:
                contentFilter['getTags'] = tag
        else:
            contentFilter['getTags'] = tag

        taggedentries = self.searchResults(**contentFilter)
        return taggedentries

    security.declarePublic('entriesToContent')

    def entriesToContent(self, entries):
        """Return list of result objects (catalog brains) for content referred to in list of entries
        passed in with 'entrylist'.
        Used to convert something like the output of 'getContentForTag' into a list of content brains
        for use by a results macro.
        
        params:
          entries: list of entry (SelectedContent) brains
        returns:
          list of content brains
        """
        ids = [elt.getId for elt in entries]
        return self.content.catalog(objectId=ids)

    security.declarePrivate('getMajorContainer')

    def getMajorContainer(self):
        """Get the folder that contains all individual folders. Private because one shouldn't
        rely on there being a major container.
        """
        toplevelcontainer = "lenses"
        portal = getToolByName(self, 'portal_url').getPortalObject()
        lenses = getattr(portal.aq_explicit, toplevelcontainer, None)
        if lenses is None:  # make one...
            portal.invokeFactory('LensMajorContainer',
                                 id=toplevelcontainer,
                                 title="Lenses")
            lenses = getattr(portal, toplevelcontainer)
        return lenses

    security.declarePublic('getIndividualFolder')

    def getIndividualFolder(self, userid=None, create=True):
        """Return the folder that contains lenses for the current user. None for Anonymous.
        Pass 'userid' for a specific non-current user.
        Pass boolean value for 'create' to create folder if it doens't exist. Default: true.
        """
        mship = getToolByName(self, 'portal_membership')
        if userid is None:
            m = mship.getAuthenticatedMember()
        else:
            m = mship.getMemberById(userid)
        if len(
                m.getRoles()
        ) > 1:  # only one role == Anonymous; should be less fragile than name checks
            memberid = m.getId()
            lenses = self.getMajorContainer()
            namedfolder = getattr(lenses, memberid, None)
            if namedfolder is None and create:  # make one...
                namedfolder = _createObjectByType('LensFolder', lenses,
                                                  memberid)

                # set ownership
                namedfolder.__ac_local_roles__ = None
                namedfolder.manage_setLocalRoles(memberid, ['Owner'])

            return namedfolder
        return None

    security.declarePublic('getMemberFolder')

    def getMemberFolder(self, context):
        """
        Traverse upwards from context until the member's folder is found
        """
        folder = None
        parent = getattr(context, 'aq_parent', None)
        previous = context
        while parent is not None:
            if getattr(parent, 'portal_type', '') == 'LensMajorContainer':
                folder = previous
                break
            previous = parent
            parent = getattr(parent, 'aq_parent', None)

        return folder

    security.declarePublic('getOpenLenses')

    def getOpenLenses(self, full_objects=False, omit_contained_ids=None):
        """
        Return open lenses

        params:
            full_objects: 
                return full objects
            omit_contained_ids:
                do not include a lens in the result if an id in omit_contained_ids
                is contained by the lens
        """

        if omit_contained_ids is not None:
            # Make iterable
            if type(omit_contained_ids) != type([]):
                omit_contained_ids = [omit_contained_ids]

            # Remove the paths for the ids to be omitted from result_paths
            result_paths = [
                b.getPath() for b in self.searchResults(
                    portal_type=self.getLensTypes(),
                    review_state=['published_open', 'private_open'])
            ]
            remove_paths = [
                '/'.join(b.getPath().split('/')[:-1])
                for b in self.searchResults(path=result_paths,
                                            id=omit_contained_ids)
            ]
            new_paths = []
            for pth in result_paths:
                if pth not in remove_paths:
                    new_paths.append(pth)

            # Do the final query
            results = self.searchResults(
                portal_type=self.getLensTypes(),
                path=new_paths,
                review_state=['published_open', 'private_open'])

        else:
            # Nothing to omit
            results = self.searchResults(
                portal_type=self.getLensTypes(),
                review_state=['published_open', 'private_open'])

        if full_objects:
            return [r.getObject() for r in results]
        return results

    security.declarePublic('catalogQueueInfo')

    def catalogQueueInfo(self):
        """Fairly general method for indicating time left for queue catalog to process something just added.
        Put here because the lenses facility uses this, and it's convenient.
        Return tuple of raw size and estimated time in minutes.
        """
        rate = 150.0  # per minute
        qcat = getToolByName(self, 'lens_catalog')
        if getattr(qcat, 'manage_size', None) is not None:
            size = qcat.manage_size()
            return (size, size / rate)
        return None

    security.declarePublic('catalogQueueProcess')

    def catalogQueueProcess(self):
        """Poke otherwise-resticted process method of queue catalog.
        """
        qcat = getToolByName(self, 'lens_catalog')
        process = getattr(qcat, 'process', None)
        if process is not None:
            return process(25)

    security.declarePrivate('notify')

    def notify(self, lenses, object, template, **kwargs):
        """
        Render the mail template provided using the object as context and email it to the adress associated with the lens.
        """

        host = self.MailHost
        for lens in lenses:
            #           try:
            # we cannot pass 'lens' as it might be private and thus unauthorized to the current user
            lensTitle = lens.Title()
            lensURL = lens.absolute_url()
            included = not lens[object.objectId].getVersionStop()
            lensCreator = lens.Creator()

            if lens.notifyOfChanges:
                mail_text = template(self,
                                     lensTitle=lensTitle,
                                     lensURL=lensURL,
                                     included=included,
                                     lensCreator=lensCreator,
                                     object=object,
                                     **kwargs)
                try:
                    host.send(mail_text)
                except ConflictError:
                    raise
                except Exception, e:
                    import zLOG
                    zLOG.LOG("Lensmaker", zLOG.ERROR,
                             "Error sending mail: " + str(e))
示例#12
0
class ZopeVersionHistory(
        VersionHistory.VersionHistory,
        AccessControl.Role.RoleManager,
        OFS.SimpleItem.Item,
):
    """The ZopeVersionHistory build on the core VersionHistory class to 
       provide the Zope management interface and other product trappings."""

    security = AccessControl.ClassSecurityInfo()
    security.setDefaultAccess('deny')

    meta_type = 'Version History'

    manage_options = ((
        {
            'label': 'Contents',
            'action': 'manage_main',
            'help': ('ZopeVersionControl', 'VersionHistory-Manage.stx')
        },
        {
            'label': 'Properties',
            'action': 'manage_properties_form',
            'help': ('ZopeVersionControl', 'VersionHistory-Properties.stx')
        },
    ) + AccessControl.Role.RoleManager.manage_options +
                      OFS.SimpleItem.Item.manage_options)

    icon = 'misc_/ZopeVersionControl/VersionHistory.gif'

    security.declareProtected('View management screens', 'manage_main')
    manage_main = DTMLFile('dtml/VersionHistoryManageMain', globals())
    manage_main._setName('manage_main')
    manage = manage_main

    security.declareProtected('View management screens',
                              'manage_properties_form')
    manage_properties_form = DTMLFile('dtml/VersionHistoryProperties',
                                      globals())

    security.declareProtected('Manage repositories', 'manage_edit')

    def manage_edit(self, REQUEST=None):
        """Change object properties."""
        if REQUEST is not None:
            message = "Saved changes."
            return self.manage_properties_form(self,
                                               REQUEST,
                                               manage_tabs_message=message)

    def __getitem__(self, name):
        activity = self._branches.get(name)
        if activity is not None:
            return activity.__of__(self)
        raise KeyError, name

    security.declarePrivate('objectIds')

    def objectIds(self, spec=None):
        return self._branches.keys()

    security.declarePrivate('objectValues')

    def objectValues(self, spec=None):
        return self._branches.values()

    security.declarePrivate('objectItems')

    def objectItems(self, spec=None):
        return self._branches.items()
class VersionSupport(ExtensionClass.Base):
    """Mixin class to support version-controllable resources."""

    manage_options=(
        {'label': 'Version Control', 'action':'versionControlMain',
         'help':  ('ZopeVersionControl', 'VersionControl.stx'),
         'filter': isAVersionableResource,
         },
        )

    security = AccessControl.ClassSecurityInfo()

    security.declareProtected('View management screens', 'versionControlMain')
    versionControlMain = DTMLFile('dtml/VersionControlMain', globals())

    security.declareProtected('View management screens', 'versionControlLog')
    versionControlLog = DTMLFile('dtml/VersionControlLog', globals())

    security.declarePrivate('haveRepository')
    def haveRepository(self):
        try: result = self.getRepository()
        except VersionControlError:
            return 0
        return 1

    security.declarePrivate('getRepository')
    def getRepository(self):
        # We currently only allow a single repository in a given context.
        if hasattr(self, '_v_repository'):
            return self._v_repository
        try:    items = self.superValues('Repository')
        except: items = self.aq_inner.aq_parent.superValues('Repository')
        result = items and items[0] or None
        if result is None:
            raise VersionControlError(
                'No versioning repository was found.'
                )
        self._v_repository = result
        return result


    security.declarePublic('isAVersionableResource')
    def isAVersionableResource(self, object):
        return self.getRepository().isAVersionableResource(self)

    security.declarePublic('isUnderVersionControl')
    def isUnderVersionControl(self):
        return hasattr(self, '__vc_info__')

    security.declarePublic('isResourceUpToDate')
    def isResourceUpToDate(self):
        return self.getRepository().isResourceUpToDate(self)

    security.declarePublic('isResourceChanged')
    def isResourceChanged(self):
        return self.getRepository().isResourceChanged(self)

    security.declarePublic('getVersionInfo')
    def getVersionInfo(self):
        return self.getRepository().getVersionInfo(self)

    security.declareProtected(use_vc_permission, 'applyVersionControl')
    def applyVersionControl(self, REQUEST=None):
        """Place a resource under version control."""
        repository = self.getRepository()
        object = repository.applyVersionControl(self)
        if REQUEST is not None:
            message="The resource has been placed under version control."
            return object.versionControlMain(
                object, REQUEST,
                manage_tabs_message=message
                )

    security.declareProtected(use_vc_permission, 'checkoutResource')
    def checkoutResource(self, REQUEST=None):
        """Checkout a version-controlled resource."""
        repository = self.getRepository()
        object = repository.checkoutResource(self)
        if REQUEST is not None:
            message="The resource has been checked out."
            return object.versionControlMain(
                object, REQUEST,
                manage_tabs_message=message
                )

    security.declareProtected(use_vc_permission, 'checkinResource')
    def checkinResource(self, message='', REQUEST=None):
        """Checkout a version-controlled resource."""
        repository = self.getRepository()
        object = repository.checkinResource(self, message)
        version = object.getVersionInfo().version_id
        if REQUEST is not None:
            message="The resource has been checked in [version %s]." % version
            return object.versionControlMain(
                object, REQUEST,
                manage_tabs_message=message
                )

    security.declareProtected(use_vc_permission, 'uncheckoutResource')
    def uncheckoutResource(self, REQUEST=None):
        """Uncheckout a version-controlled resource."""
        repository = self.getRepository()
        object = repository.uncheckoutResource(self)
        version = object.getVersionInfo().version_id
        if REQUEST is not None:
            message="The resource has been reverted to version %s." % version
            return object.versionControlMain(
                object, REQUEST,
                manage_tabs_message=message
                )

    security.declareProtected(use_vc_permission, 'updateResource')
    def updateResource(self, selector, REQUEST=None):
        """Update a version-controlled resource."""
        repository = self.getRepository()
        if selector == 'LATEST_VERSION':
            selector = None
        object = repository.updateResource(self, selector)
        version = object.getVersionInfo().version_id
        if REQUEST is not None:
            message="The resource has been updated to version %s." % version
            return object.versionControlMain(
                object, REQUEST,
                manage_tabs_message=message
                )

    security.declareProtected(use_vc_permission, 'labelResource')
    def labelResource(self, label, force=0, REQUEST=None):
        """Label a version-controlled resource."""
        repository = self.getRepository()
        object = repository.labelResource(self, label, force)
        if REQUEST is not None:
            message="The label has been applied to this resource."
            return object.versionControlMain(
                object, REQUEST,
                manage_tabs_message=message
                )

    security.declareProtected(use_vc_permission, 'getVersionIds')
    def getVersionIds(self):
        return self.getRepository().getVersionIds(self)

    security.declareProtected(use_vc_permission, 'getLabelsForHistory')
    def getLabelsForHistory(self):
        return self.getRepository().getLabelsForHistory(self)

    security.declareProtected(use_vc_permission, 'getLabelsForVersion')
    def getLabelsForVersion(self):
        return self.getRepository().getLabelsForVersion(self)

    security.declareProtected(use_vc_permission, 'getLogEntries')
    def getLogEntries(self):
        return self.getRepository().getLogEntries(self)
示例#14
0
class ZopeSimplate(Script, Simplate, Historical, Cacheable, Traversable,
                   PropertyManager):
    "Zope wrapper for Simplate using python string replacement"

    if SUPPORTS_WEBDAV_LOCKS:
        __implements__ = (WriteLockInterface, )

    meta_type = 'Simplate'

    func_defaults = None
    func_code = FuncCode((), 0)

    _default_bindings = {'name_subpath': 'traverse_subpath'}
    _default_content_fn = os.path.join(package_home(globals()), 'www',
                                       'default.html')

    manage_options = (
        {'label':'Edit', 'action':'simplate_editForm',
         'help': ('Simplates', 'Simplate_Edit.stx')},
        {'label':'Test', 'action':'ZScriptHTML_tryForm'},
        ) + PropertyManager.manage_options \
        + Historical.manage_options \
        + SimpleItem.manage_options \
        + Cacheable.manage_options

    _properties = (
        {
            'id': 'title',
            'type': 'string',
            'mode': 'w'
        },
        {
            'id': 'content_type',
            'type': 'string',
            'mode': 'w'
        },
        #{'id':'expand', 'type':'boolean', 'mode': 'w'},
        {
            'id': 'value_paths',
            'type': 'lines',
            'mode': 'w'
        },
    )

    def __init__(self, id, text='(empty)', content_type=None, value_paths=[]):
        self.id = str(id)
        self.ZBindings_edit(self._default_bindings)
        if text is None:
            text = open(self._default_content_fn).read()
        self.simplate_edit(text, content_type)
        self.simplate_setValue_paths(value_paths)

    def _setPropValue(self, id, value):
        PropertyManager._setPropValue(self, id, value)
        self.ZCacheable_invalidate()

    security = AccessControl.ClassSecurityInfo()

    security.declareObjectProtected('View')
    security.declareProtected('View', '__call__')

    security.declareProtected(
        'View management screens',
        'simplate_editForm',
        'manage_main',
        'read',
        'ZScriptHTML_tryForm',
        'PrincipiaSearchSource',
        'document_src',
        #                              'source.html',
        #                              'source.xml',
    )

    security.declareProtected(
        'FTP access',
        'manage_FTPstat',
        'manage_FTPget',
        'manage_FTPlist',
    )

    simplate_editForm = PageTemplateFile('www/simplateEdit.pt',
                                         globals(),
                                         __name__='simplate_editForm')
    simplate_editForm._owner = None
    manage = manage_main = simplate_editForm

    security.declareProtected(
        'Change simplates',
        'simplate_editAction',
        'simplate_setTitle',
        'simplate_setValue_paths',
        'simplate_edit',
        'simplate_upload',
        'simplate_changePrefs',
    )

    def simplate_editAction(self, REQUEST, title, text, content_type,
                            value_paths):
        """Change the title and document."""
        if SUPPORTS_WEBDAV_LOCKS and self.wl_isLocked():
            raise ResourceLockedError, "File is locked via WebDAV"
        self.simplate_setTitle(title)
        self.simplate_setValue_paths(value_paths)
        self.simplate_edit(text, content_type)
        REQUEST.set('text', self.read())  # May not equal 'text'!
        message = "Saved changes."
        if getattr(self, '_v_warnings', None):
            message = ("<strong>Warning:</strong> <i>%s</i>" %
                       '<br>'.join(self._v_warnings))
        return self.simplate_editForm(manage_tabs_message=message)

    def simplate_setTitle(self, title):
        self._setPropValue('title', str(title))

    def simplate_setValue_paths(self, value_paths):
        self._setPropValue('value_paths', list(value_paths))
        self._cook()

    def simplate_upload(self, REQUEST, file=''):
        """Replace the document with the text in file."""
        if SUPPORTS_WEBDAV_LOCKS and self.wl_isLocked():
            raise ResourceLockedError, "File is locked via WebDAV"

        if type(file) is not StringType:
            if not file: raise ValueError, 'File not specified'
            file = file.read()

        self.write(file)
        message = 'Saved changes.'
        return self.simplate_editForm(manage_tabs_message=message)

    def simplate_changePrefs(self,
                             REQUEST,
                             height=None,
                             width=None,
                             dtpref_cols="100%",
                             dtpref_rows="20"):
        """Change editing preferences."""
        dr = {"Taller": 5, "Shorter": -5}.get(height, 0)
        dc = {"Wider": 5, "Narrower": -5}.get(width, 0)
        if isinstance(height, int): dtpref_rows = height
        if isinstance(width, int) or \
           isinstance(width, str) and width.endswith('%'):
            dtpref_cols = width
        rows = str(max(1, int(dtpref_rows) + dr))
        cols = str(dtpref_cols)
        if cols.endswith('%'):
            cols = str(min(100, max(25, int(cols[:-1]) + dc))) + '%'
        else:
            cols = str(max(35, int(cols) + dc))
        e = (DateTime("GMT") + 365).rfc822()
        setCookie = REQUEST["RESPONSE"].setCookie
        setCookie("dtpref_rows", rows, path='/', expires=e)
        setCookie("dtpref_cols", cols, path='/', expires=e)
        REQUEST.other.update({"dtpref_cols": cols, "dtpref_rows": rows})
        return self.manage_main()

    def ZScriptHTML_tryParams(self):
        """Parameters to test the script with."""
        return []

    def manage_historyCompare(self,
                              rev1,
                              rev2,
                              REQUEST,
                              historyComparisonResults=''):
        return ZopeSimplate.inheritedAttribute('manage_historyCompare')(
            self,
            rev1,
            rev2,
            REQUEST,
            historyComparisonResults=html_diff(rev1._text, rev2._text))

    def simplate_getContext(self):
        root = self.getPhysicalRoot()
        context = self._getContext()
        c = {
            'template': self,
            'here': context,
            'context': context,
            'container': self._getContainer(),
            'nothing': None,
            'options': {},
            'root': root,
            'request': getattr(root, 'REQUEST', None),
            'modules': SecureModuleImporter,
        }
        return c

    def write(self, text):
        self.ZCacheable_invalidate()
        ZopeSimplate.inheritedAttribute('write')(self, text)

    def _exec(self, bound_names, args, kw):
        """Call a simplate"""
        if not kw.has_key('args'):
            kw['args'] = args
        bound_names['options'] = kw

        try:
            response = self.REQUEST.RESPONSE
            if not response.headers.has_key('content-type'):
                response.setHeader('content-type', self.content_type)
        except AttributeError:
            pass

        security = getSecurityManager()
        bound_names['user'] = security.getUser()

        # Retrieve the value from the cache.
        keyset = None
        if self.ZCacheable_isCachingEnabled():
            # Prepare a cache key.
            keyset = {'here': self._getContext(), 'bound_names': bound_names}
            result = self.ZCacheable_get(keywords=keyset)
            if result is not None:
                # Got a cached value.
                return result

        # Execute the template in a new security context.
        security.addContext(self)
        try:
            result = self.simplate_render(extra_context=bound_names)
            if keyset is not None:
                # Store the result in the cache.
                self.ZCacheable_set(result, keywords=keyset)
            return result
        finally:
            security.removeContext(self)

    security.declareProtected(
        'Change simplates',
        'PUT',
        'manage_FTPput',
        'write',
        'manage_historyCopy',
        'manage_beforeHistoryCopy',
        'manage_afterHistoryCopy',
    )

    def PUT(self, REQUEST, RESPONSE):
        """ Handle HTTP PUT requests """
        self.dav__init(REQUEST, RESPONSE)
        if SUPPORTS_WEBDAV_LOCKS:
            self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
        self.write(REQUEST.get('BODY', ''))
        RESPONSE.setStatus(204)
        return RESPONSE

    manage_FTPput = PUT

    def manage_FTPget(self):
        "Get source for FTP download"
        self.REQUEST.RESPONSE.setHeader('Content-Type', self.content_type)
        return self.read()

    def get_size(self):
        return len(self.read())

    getSize = get_size

    def PrincipiaSearchSource(self):
        "Support for searching - the document's contents are searched."
        return self.read()

    def document_src(self, REQUEST=None, RESPONSE=None):
        """Return expanded document source."""

        if RESPONSE is not None:
            RESPONSE.setHeader('Content-Type', 'text/plain')
        if REQUEST is not None and REQUEST.get('raw'):
            return self._text
        return self.read()

    def om_icons(self):
        """Return a list of icon URLs to be displayed by an ObjectManager"""
        icons = ({
            'path': 'misc_/Simplates/simplate.png',
            'alt': self.meta_type,
            'title': self.meta_type
        }, )
        if not self._v_cooked:
            self._cook()
        if self._v_errors:
            icons = icons + ({
                'path': 'misc_/Simplates/exclamation.gif',
                'alt': 'Error',
                'title': 'This simplate has an error'
            }, )
        return icons

    def __setstate__(self, state):
        # This is here for backward compatibility. :-(
        ZopeSimplate.inheritedAttribute('__setstate__')(self, state)

    def simplate_source_file(self):
        """Returns a file name to be compiled into the TAL code."""
        try:
            return '/'.join(self.getPhysicalPath())
        except:
            # This simplate is being compiled without an
            # acquisition context, so we don't know where it is. :-(
            return None

    if not SUPPORTS_WEBDAV_LOCKS:

        def wl_isLocked(self):
            return 0
class HitCountTool(UniqueObject, SimpleItem):

    __implements__ = (IHitCountTool)

    id = 'portal_hitcount'
    meta_type = 'HitCount Tool'
    security = AccessControl.ClassSecurityInfo()

    manage_options = (({
        'label': 'Overview',
        'action': 'manage_overview'
    }, ) + SimpleItem.manage_options)

    def __init__(self):
        self._hits = {}
        self._recent_hit_counts = []
        self._hit_counts = []
        self._recent_daily_averages = []
        self._daily_averages = []
        self._startdate = self._inc_begin = self._inc_end = DateTime.DateTime()

    ##   ZMI methods
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = PageTemplateFile('zpt/explainHitCountTool', globals())

    # IHitCountTool Interface fulfillment

    def resetHitCounts(self):
        """
        Reset all hit counts to 0 and set the start date to the current date and time
        """
        if hasattr(self, '_hits'):
            self._hits.clear()
        else:
            self._hits = {}
        self._recent_hit_counts = []
        self._hit_counts = []
        self._recent_daily_averages = []
        self._daily_averages = []
        self._startdate = self._inc_begin = self._inc_end = DateTime.DateTime()
        try:
            objs = self.content.objectValues(
                ['Version Folder', 'Module Version Folder'])
            for ob in objs:
                self.registerObject(ob.id, ob.created())
        except AttributeError:
            pass

    def registerObject(self, objectId, published_date):
        """Register an object with the HitCountTool"""

        self._hits[objectId] = Hits(published_date, self)
        self._recent_hit_counts.append((objectId, 0))
        self._hit_counts.append((objectId, 0))
        self._recent_daily_averages.append((objectId, 0))
        self._daily_averages.append((objectId, 0))
        self._p_changed = 1

    def listRegisteredObjects(self):
        """Return a list of identifiers for registered objects"""
        return self._hits.keys()

    def incrementCounts(self, mapping, begin_date=None, end_date=None):
        """
        Increment the hit counter for content objects.  mapping must
        be a mapping from objectID to an integer number of hit counts.
        begin_date and end_date specify the range of time covered by
        this increment.  If end_date is None, the current timestamp
        will be used.
        """
        if not type(mapping) is DictType:
            raise TypeError

        self.recentHits = mapping
        self._inc_begin = begin_date or self._inc_end
        self._inc_end = end_date or DateTime.DateTime()

        #If the increment begin date is earlier than the start date, update it
        if begin_date and (begin_date < self._startdate):
            self._startdate = begin_date

        dateRange = self.getIncrementDateRange()
        end = dateRange[1]
        inc_start = dateRange[0]
        tool_start = self.getStartDate()

        for objectId, count in mapping.items():
            try:
                hits = self._hits[objectId]
            except KeyError:
                raise ValueError, "Object %s not registered" % objectId

            hits.recent = count
            hits.total += count

            recent_age = end - (hits.published > inc_start and hits.published
                                or inc_start)
            # If there is no age, just use the count as the average
            if recent_age:
                recent_avg = count * 1.0 / recent_age
            else:
                recent_avg = count

            full_age = end - (hits.published > tool_start and hits.published
                              or tool_start)
            # If there is no age, just use the count as the average
            if full_age:
                avg = hits.total * 1.0 / full_age
            else:
                avg = hits.total

            hits._daily_average = (avg, recent_avg)

        rec_items = [(id, hits.recent) for (id, hits) in self._hits.items()]
        rec_items.sort(lambda a, b: cmp(b[1], a[1]))
        self._recent_hit_counts = rec_items

        items = [(id, hits.total) for (id, hits) in self._hits.items()]
        items.sort(lambda a, b: cmp(b[1], a[1]))
        self._hit_counts = items

        rec_items = [(id, hits.avgPerDay(True))
                     for (id, hits) in self._hits.items()]
        rec_items.sort(lambda a, b: cmp(b[1], a[1]))
        self._recent_daily_averages = rec_items

        items = [(id, hits.avgPerDay(False))
                 for (id, hits) in self._hits.items()]
        items.sort(lambda a, b: cmp(b[1], a[1]))
        self._daily_averages = items

        total_objects = len(self.listRegisteredObjects())
        if total_objects:
            object_ranks, counts = zip(*items)
            object_ranks = list(object_ranks)
            recent_object_ranks, recent_counts = zip(*rec_items)
            recent_object_ranks = list(recent_object_ranks)
        else:
            object_ranks = []
            counts = []
            recent_object_ranks = []
            recent_counts = []

        for o_id, o_hits in self._hits.items():
            try:
                index = object_ranks.index(o_id) + 1
            except ValueError:
                # Object not ranked, place after ranked objects
                index = total_objects
            try:
                recent_index = recent_object_ranks.index(o_id) + 1
            except ValueError:
                # Object not ranked, place after ranked objects
                recent_index = total_objects
            o_hits._percentile = ((total_objects - index) * 100.0 /
                                  total_objects,
                                  (total_objects - recent_index) * 100.0 /
                                  total_objects)

        self._p_changed = 1

    def getHitCounts(self, recent=False):
        """
        Return hit counts for all content objects.  If recent is True,
        return the count for the previous increment, otherwise returns
        counts for all time

        The return value will be a list of (objectId, count) tuples in
        descending hit count order.
        """

        if recent:
            return self._recent_hit_counts
        else:
            return self._hit_counts

    def getHitCountForObject(self, objectId, recent=False):
        """
        Return the hit counts for the specified object ID.  If recent
        is True, return the count for the previous increment,
        otherwise returns counts for all time
        """
        try:
            hits = self._hits[objectId]
        except KeyError:
            return 0

        if recent:
            return hits.recent
        else:
            return hits.total

    def getDailyAverages(self, recent=False):
        """
        Return the average daily hit count for all registered objects.
        If recent=True, return the average for the previous increment,
        otherwise returns averages for all time.
        """

        if recent:
            return self._recent_daily_averages
        else:
            return self._daily_averages

    def getDailyAverageForObject(self, objectId, recent=False):
        """
        Return the average daily hit count for the specified object
        from its date of publication to the present (for the date
        range statistics are available).  If recent=True, return the
        average for the previous increment, otherwise returns averge
        for all time.
        """
        try:
            return self._hits[objectId].avgPerDay(recent)
        except KeyError:
            return 0

    def getPercentileForObject(self, objectId, recent=False):
        """
        Return the average daily hit count for the specified object
        from its date of publication to the present (for the date
        range statistics are available).  If recent=True, return the
        average for the previous increment, otherwise returns averge
        for all time.
        """
        try:
            return self._hits[objectId].getPercentile(recent)
        except KeyError:
            return 0

    def setStartDate(self, date):
        """
        Set the initial date from which hits are counted
        """
        self._startdate = date

    def getStartDate(self):
        """
        Get the initial date from which hits are counted
        """
        return self._startdate

    def getIncrementDateRange(self):
        """
        Return the date range covered by the most recent increment as
        a tuple (begin_date, end_date)
        """
        return (self._inc_begin, self._inc_end)

    def dumpHitCounts(self):
        """Returns a transferable string object of all the hit count data. 
        For use with restoreHitCounts"""
        pstr = StringIO.StringIO()
        kosher = pickle.Pickler(pstr)
        for attr in (self._hits, self._recent_hit_counts, self._hit_counts,
                     self._recent_daily_averages, self._daily_averages,
                     self._startdate, self._inc_begin, self._inc_end):
            kosher.dump(attr)
        pdata = pstr.getvalue()
        pstr.close()
        return pdata

    def restoreHitCounts(self, dumpstr):
        """Destructively overrides the hit count data with that provided 
        in the dump string, which was previously generated by dumpHitCounts"""
        pstr = StringIO.StringIO(dumpstr)
        dill = pickle.Unpickler(pstr)
        self.resetHitCounts()
        self._hits.update(dill.load())
        self._recent_hit_counts.extend(dill.load())
        self._hit_counts.extend(dill.load())
        self._recent_daily_averages.extend(dill.load())
        self._daily_averages.extend(dill.load())
        self._startdate = dill.load()
        self._inc_begin = dill.load()
        self._inc_end = dill.load()
示例#16
0
class QueueTool(UniqueObject, SimpleItem):
    """
    Tool for storage of Print files
    """

    id = 'queue_tool'
    meta_type = 'Queue Tool'

    manage_options = (({'label':'Overview', 'action':'manage_overview'},
                       {'label':'Configure', 'action':'manage_configure'}
                      )+ SimpleItem.manage_options
                     )
    manage_overview = PageTemplateFile('zpt/manage_overview_queue_tool.zpt', globals())
    manage_configure = PageTemplateFile('zpt/manage_configure_queue_tool.zpt', globals())

    security = AccessControl.ClassSecurityInfo()

            
    # set default time window to 1 hour
    DEFAULT_PROCESS_TIME_WINDOW = 3600


    def __init__(self):
        """Initialize (singleton!) tool object."""
        # user configurable
        self.dictServers        = {}
        self.secProcessWindows  = {}
        self.secDefaultProcessWindow   = self.DEFAULT_PROCESS_TIME_WINDOW
        # unknown to the user
        self.pendingRequests    = PersistentList([])
        self.processingRequests = PersistentList([])
        log('__init__ completed.') # this no workeee???

        # Dependency checkers

    def printtool_dep(self, request, dep):
        """check for the existence of the given filetype instance (objectId, Version, extenstion) newer than the given timestamp"""
        pt = getToolByName(self,'rhaptos_print')
        depkey,deptype,depaction,depdetail = dep
        ptf_params  = (depdetail['objectId'],depdetail['version'],depdetail['extension'])
        if pt.doesFileExist(*ptf_params):
            mod_date  = DateTime(pt.getModificationDate(*ptf_params))
            if mod_date > depdetail['newer']:
                return None
        return dep

    def url_dep(self, request, dep):
        """check for bits available from the given url, with a given mimetype and newer than the given timestamp"""
        pass

    def file_dep(self, request, dep):
        """check for the existence if a file at the given path, that is newer than the given timestamp"""
        depkey,deptype,depaction,depdetail = dep
        try:
            fstat = os.stat(depdetail['path'])
        except OSError:
            return dep
        if DateTime(fstat.st_ctime) > depdetail['newer']:
            return None
        return dep
        

    security.declareProtected(ManagePermission, 'manage_queue_tool')
    def manage_queue_tool(self, dictServers={}, dictProcessWindows={}, secDefaultProcessWindow=DEFAULT_PROCESS_TIME_WINDOW):
        """
        Post creation configuration.  See manage_configure_queue_tool.zpt
        """
        self.dictServers  = eval(dictServers) # string not dictionary returned
        self.secProcessWindows  = eval(dictProcessWindows) # string not dictionary returned
        try:
            self.secDefaultProcessWindow = int(secDefaultProcessWindow)
        except ValueError:
            self.secDefaultProcessWindow = self.DEFAULT_PROCESS_TIME_WINDOW


    security.declareProtected(ManagePermission, 'add')
    def add(self, key, dictParams, callbackrequesthandler, priority=1):
        """
        Add a request to the Pending Queue.
        """
        # add() acquires mutex lock.  caller is responsible for commiting the transaction.
        mutex.acquire()
        try:
            iListIndex = self.find(key, self.pendingRequests)
            if iListIndex is not None:
                # remove duplicate
                del self.pendingRequests[iListIndex]

            dictRequest = dictParams.copy()

            dictRequest['key'] = key
            dictRequest['requestHandler'] = callbackrequesthandler
            dictRequest['timeRequestMade'] = datetime.now()
            dictRequest['priority'] = priority
            # Walk list, insert immediately before first entry that is lower priority (higher value)
            for i,req in enumerate(self.pendingRequests):
                if req['priority'] > priority:
                    self.pendingRequests.insert(i,req)
                    break
            else: 
                # didn't find one, tack on the end
                self.pendingRequests.append(dictRequest)
            # note that we explicitly and purposely do not commit the transaction here.
            # the caller is likely to be in a module/collection publish transaction.
            # when the caller's transaction commits, the QueueTool change (i.e. adding
            # a request) will also commit.  thus, the request can not be processed until
            # after the publish transaction has completed (i.e. no race condition).
        finally:
            mutex.release()

    security.declarePrivate('find')
    def find(self, strKey, listRequests):
        """
        Find key in persistent list, looking for duplicates.  Return index if found.
        """
        for i in range(0,len(listRequests)):
            if listRequests[i]['key'] == strKey:
                return i
        return None


    security.declarePrivate('gotRequest')
    def gotRequest(self):
        """
        Determine if any requests are ready for processing. Return key of first one, if any, else False.
        Does dependency checking
        """
        # requests must be pending ...
        if len(self.pendingRequests) == 0:
            return False

        # our host server can not be maxed out ...
        hostName = getfqdn()
        listHostProcess = [entry for entry in self.processingRequests if 'hostName' in entry and entry['hostName'] == hostName]
        if listHostProcess is not None:
            iProcessMax = hostName in self.dictServers and self.dictServers[hostName] or 1
            if len(listHostProcess) >= iProcessMax:
                return False

        portal = self.portal_url.getPortalObject()
        # check requests for dependencies, bail out at first success
        for dictRequest in self.pendingRequests:
            key = dictRequest['key']
            unmetDepends = self.checkDepends(dictRequest)
            if unmetDepends:
                if unmetDepends[2] == 'reenqueue':
                    iListIndex = self.find(key, self.pendingRequests)
                    portal.plone_log("... reenqueue request for key '%s'at index '%s' ..." % (dictRequest['key'],iListIndex))
                    if iListIndex is not None:
                        portal.plone_log("... QueueTool.clockTick() has skipped request for key '%s' for failed dependency '%s' ..." % (dictRequest['key'],str(unmetDepends)))
                elif unmetDepends[2] == 'fail':
                    self._email_depend_fail(dictRequest,unmetDepends) # removes req from pending as well
                else:
                    portal.plone_log("... unknown failed dependency action for key '%s' ... skipping." % (dictRequest['key'],))
            else:
                return key
            
        # fell through loop: no requests w/o unmet dependencies
        return False

    security.declarePrivate('getRequest')
    def getRequest(self, key=None):
        """
        Get a request from the pending queue and put it in the processing queue or
        get an request already in the processing queue via key.
        """
        # caller must acquire mutex lock.  caller is responsible for commiting the transaction.
        if key is None:
            # get a request from the pending queue and put it in the processing queue
            reqKey = self.gotRequest()
            if reqKey:
                iListIndex = self.find(reqKey, self.pendingRequests)
                if iListIndex is not None:
                    dictRequest = self.pendingRequests[iListIndex]
                    del self.pendingRequests[iListIndex]
                    self.processingRequests.append(dictRequest)
                    return dictRequest
                else:
                    return None
            else:
                return None
        else:
            # get an request already in the processing queue
            iListIndex = self.find(key, self.processingRequests)
            if iListIndex is not None:
                dictRequest = self.processingRequests[iListIndex]
                return dictRequest
            else:
                return None


    security.declarePrivate('start')
    def start(self, key):
        """
        Start processing the queued request.
        """
        # start() acquires mutex lock and is responsible for commiting the transaction.
        return


    security.declarePrivate('stop')
    def stop(self, key):
        """
        Stop processing the queued request.
        """
        # stop() acquires mutex lock and is responsible for commiting the transaction.
        mutex.acquire()
        try:
            self.removeRequest(key)
            # should only commit the above change
            transaction.commit()
            #import pdb; pdb.set_trace()
            #savepoint = transaction.savepoint()
            #for i in range(0,3):
            #    try:
            #        self.removeRequest(key)
            #        # should only commit the above change
            #        transaction.commit()
            #    except Exception, e:
            #        print "failure:", e
            #        self.plone_log("... QueueTool.stop() failed to commit removing the reuest from the processing queue ... %s ..." % str(e))
            #        savepoint.rollback()
            #        pass
        finally:
            mutex.release()


    security.declarePrivate('removeRequest')
    def removeRequest(self, key, fromList=None):
        """
        Remove a request from the processing queue.
        """
        # caller must acquire mutex lock.  caller is responsible for commiting the transaction.
        if not fromList:
            fromList = self.processingRequests
        iListIndex = self.find(key, fromList)
        if iListIndex is not None:
            del fromList[iListIndex]


    security.declarePrivate('cleanupProcessing')
    def cleanupProcessing(self):
        """
        Cleanup the processingRequests queue.  Assume that processing entries that
        do not complete in a defined time window cored and did not call removeRequest().
        """
        # cleanupProcessing() acquires mutex lock and is responsible for commiting the transaction.
        mutex.acquire()
        try:
            timeNow = datetime.now()
            bChanged = False
            for request in self.processingRequests:
                timeProcessStarted = 'timeRequestProcessed' in request and request['timeRequestProcessed'] or None
                childPid = request.get('pid') 
                reqHostName = request.get('hostName')
                myHostName = getfqdn()
                if childPid and reqHostName == myHostName:
                    try:
                        os.kill(childPid, 0)
                    except OSError:
                        if timeProcessStarted is not None:
                            self._email_cleanup_message(request,0)
                            bChanged = True

                if timeProcessStarted is not None:
                    timeProcessing = timeNow - timeProcessStarted
                    timeProcessingMax = timedelta(seconds=self._getProcessingMax(request))
                    if timeProcessing > timeProcessingMax:
                       self._email_cleanup_message(request,timeProcessingMax)
                       bChanged = True

            if bChanged:
                # commit the queue change(s)
                transaction.commit()
        finally:
            mutex.release()

    
    security.declarePrivate('_get_ProcessingMax')
    def _getProcessingMax(self,request):
        # Get process max timeout, checking for exact queue key, keyclass, and default, in that order.
        key = request['key']
        key_class = key.split('_')[0]
        windows = self.secProcessWindows
        return windows.get(key, windows.get(key_class, self.secDefaultProcessWindow))
    
    security.declarePrivate('_email_cleanup_message')
    def _email_cleanup_message(self,request,timeout):
        # email techsupport to notify that request has been forcibly been removed from the queue
        mailhost = self.MailHost
        portal = self.portal_url.getPortalObject()
        mfrom = portal.getProperty('email_from_address')
        mto   = portal.getProperty('techsupport_email_address') or mfrom
        if mto:
            subject = "Request '%s' was forcibly removed from processing queue." % request['key']
            messageText = "The request did not successfully complete within the required time window of %s.  The request may have crashed, may be in an infinite loop, or may just be taking a really long time.\n\nThe server and pid referenced in the request need to be checked to see if the process is still active.  If still active, the process may need to be killed.  The QueueTool will no longer consider this an active request.\n\nThe complete request was:\n\n%s\n\n" % (timeout,str(request))
            self._mailhost_send(messageText, mto, mfrom, subject)

        self.removeRequest(request['key'])
        self.processingRequests._p_changed = 1
    
    security.declarePrivate('_mailhost_send')
    def _mailhost_send(self, messageText, mto, mfrom, subject):
        """attempt to send mail: log if fail"""
        portal = self.portal_url.getPortalObject()
        try:
            mailhost.send(messageText, mto, mfrom, subject)
        except:
            portal.plone_log("Can't send email:\n\nSubject:%s\n\n%s" % (subject,messageText))

    security.declarePublic('ping')
    def ping(self):
        """Test method for ClockServer. Logs when called."""
        self.plone_log("... QueueTool.ping() has been called ...")

    security.declarePublic('clockTick')
    def clockTick(self):
        """
        Called on a clock event every N seconds.  If work is available, a child zopectl process
        is launched.
        """
        # clockTick() acquires mutex lock and is responsible for commiting the transaction.
        mutex.acquire()
        portal = self.portal_url.getPortalObject()
        try:
            if self.gotRequest():
                dictRequest = self.getRequest()
                if dictRequest:
                    transaction.commit() # make list change visible to child instance - other way was race condition
                    self.launchRequest(dictRequest)
                    portal.plone_log("... QueueTool.clockTick() has spawned a child process for key '%s' ..." % dictRequest['key'])

        finally:
            mutex.release()

        # removed "expired" request in processing
        self.cleanupProcessing()

    security.declarePublic('checkDepends')
    def checkDepends(self, request):
        """Check the request dependecies, in declared order. Return first unmet
        one. On success of all (or no declared dependencies) return 'None'"""
        for dep in request.get('depends',[]):
            try:
                checker = getattr(self,"%s_dep" % dep[1])
                if checker(request,dep):
                    return dep
            except AttributeError:
                self.portal_url.getPortalObject().plone_log('Skipping badly declared dependency: %s' % (dep,))
                pass

        return None
                
    security.declarePrivate('_email_depend_fail')
    def _email_depend_fail(self,request,depend):
        # email techsupport to notify that request has been forcibly been removed from the queue
        mailhost = self.MailHost
        portal = self.portal_url.getPortalObject()
        mfrom = portal.getProperty('email_from_address')
        mto   = portal.getProperty('techsupport_email_address') or mfrom
        if mto:
            subject = "Request '%s' was removed from pending queue. (dependency)" % request['key']
            messageText = "The request declared a dependency that was not available at the time of processing.  The QueueTool will no longer consider this an active request.\n\nThe failed dependency was:\n\n%s\n\nThe complete request was:\n\n%s\n\n" % (str(depend),str(request))
            self._mailhost_send(messageText, mto, mfrom, subject)
        self.removeRequest(request['key'], self.pendingRequests)
        self.pendingRequests._p_changed = 1
    
    security.declarePrivate('launchRequest')
    def launchRequest(self, dictRequest):
        """ Launch the input request.
        """
        # caller must acquire mutex lock.  caller is responsible for commiting the transaction.
        portal = self.portal_url.getPortalObject()
        if dictRequest is not None:
            key = dictRequest['key'] or None
            iListIndex = key is not None and self.find(key, self.processingRequests)
            if iListIndex is not None:
                # request is in the processing queue
                if 'timeRequestProcessed' not in dictRequest:
                    # request has not already been started
                    dictRequest['timeRequestProcessed'] = datetime.now()
                    # dictRequest['ip'] = getipaddr()
                    dictRequest['hostName'] = getfqdn()
                    # dictRequest['pid'] = -1

                    key = dictRequest['key']
                    serverURL = dictRequest.get('serverURL',self.REQUEST['SERVER_URL'])
                    callbackrequesthandler = dictRequest['requestHandler']
                    cmd = os.path.join(INSTANCE_HOME, 'bin', 'zopectl')
                    callback = os.path.join(INSTANCE_HOME, 'Products', callbackrequesthandler)

                    #pid = os.spawnl(os.P_NOWAIT, cmd, 'zopectl', 'run', callback, key)
                    # call newer, better python API which allows for the stdout and stderr to be captured
                    import subprocess
                    f1 = open('/tmp/%s.stdout' % key, 'w')
                    f2 = open('/tmp/%s.stderr' % key, 'w')
                    pid = subprocess.Popen([cmd, "run", callback, key, serverURL], stdout=f1, stderr=f2).pid

                    # child will call start() and stop()

                    dictRequest['pid'] = pid
                    self.processingRequests._p_changed = 1

    security.declareProtected(ManagePermission, 'manage_tick')
    def manage_tick(self):
        """ZMI-useful variant of 'clockTick'. Goes back to overview page of tool.
        From anywhere else, use 'clockTick' instead.
        """
        self.clockTick()
        self.REQUEST.RESPONSE.redirect('manage_overview')
        return

    security.declareProtected(ManagePermission, 'manage_cut_in_line')
    def manage_cut_in_line(self, key=""):
        """Move request to the head of the pendingRequests queue.
        """
        # manage_cut_in_line() acquires mutex lock and is responsible for commiting the transaction.
        mutex.acquire()
        try:
            portal = self.portal_url.getPortalObject()
            iListIndex = self.find(key, self.pendingRequests)
            if iListIndex is not None and iListIndex > 0:
                dictRequest = self.pendingRequests[iListIndex] or None
                if dictRequest is not None:
                    del self.pendingRequests[iListIndex]
                    self.pendingRequests.insert(0, dictRequest)
                    transaction.commit()
                    portal.plone_log("... QueueTool.manage_cut_in_line: '%s' is cutting in line ..." % key)
                    self.REQUEST.RESPONSE.redirect('manage_overview')
        finally:
            mutex.release()
        return

    security.declareProtected(ManagePermission, 'manage_remove')
    def manage_remove(self, key="", queue="pending"):
        """Remove request from a queue list
        """
        # manage_cut_in_line() acquires mutex lock and is responsible for commiting the transaction.
        mutex.acquire()
        if queue == "pending":
            fromList = self.pendingRequests
        elif queue == "processing":
            fromList = self.processingRequests
        else:
            return
        try:
            self.removeRequest(key,fromList)
            portal = self.portal_url.getPortalObject()
            portal.plone_log("... QueueTool.manage_remove: '%s' has been removed ..." % key)
            self.REQUEST.RESPONSE.redirect('manage_overview')
        finally:
            mutex.release()
        return

    security.declareProtected(ManagePermission, 'getProcessingRequests')
    def getProcessingRequests(self):
        """Get process requests."""
        return self.processingRequests

    security.declareProtected(ManagePermission, 'getPendingRequests')
    def getPendingRequests(self):
        """Get pending requests."""
        return self.pendingRequests

    security.declarePublic('pending_count')
    def pending_count(self):
        """Return number of pending entries in the queue. Useful for monitoring."""
        return len(self.getPendingRequests())

    security.declarePublic('processing_count')
    def processing_count(self):
        """Return number of pending entries in the queue. Useful for monitoring."""
        return len(self.getProcessingRequests())
示例#17
0
class ProcessManager(Products.AlphaFlow.rolecache.RoleCache, UniqueObject,
                     ActionProviderBase, OFS.Folder.Folder):
    """A process management object."""

    zope.interface.implements(Products.AlphaFlow.interfaces.IProcessManager)

    id = 'workflow_manager'
    meta_type = "AlphaFlow Process Manager"
    plone_tool = 1

    manage_options = (
        dict(label='Overview', action='manage_overview'),
        dict(label='Processes', action='manage_definitions'),
        dict(label='Instances', action='manage_instances'),
        dict(label='Tools', action='manage_tools')
        ) + OFS.Folder.Folder.manage_options + \
            ActionProviderBase.manage_options

    manage_options = [
        opt for opt in manage_options
        if opt['label'] not in ['Contents', 'View', 'Properties']
    ]

    security = AccessControl.ClassSecurityInfo()

    # Rolecache security settings
    security.declarePrivate('updateWorkItemCache')
    security.declarePrivate('updateCacheByContent')
    security.declarePrivate('updateCacheByInstance')
    security.declarePrivate('getDynamicRolesForWorkItem')
    security.declarePrivate('getDynamicRolesForInstance')
    security.declarePrivate('getDynamicRolesForContent')
    security.declarePrivate('listRelevantUsersForWorkItem')
    security.declarePrivate('listRelevantUsersForInstance')
    security.declarePrivate('listRelevantUsersForContent')
    security.declarePrivate('_get_role_cache')
    security.declarePrivate('_get_role_cache_entry')
    security.declarePrivate('_build_workitem_cache')
    security.declarePrivate('_aggregate_role_cache')

    processes = None
    instances = None

    def __init__(self, *args, **kwargs):
        ProcessManager.inheritedAttribute("__init__")(self, *args, **kwargs)
        self.portal_process_refs = BTrees.OOBTree.OOTreeSet()

    #########################
    # IProcessManager

    security.declarePublic("initProcess")

    def initProcess(self, definition, obj):
        """Create a new process instance for a content object."""
        # We need to handle the security for this methods ourselves. The user
        # has to have INIT_PROCESS on the content object.
        user = AccessControl.getSecurityManager().getUser()
        if not user.has_permission(Products.AlphaFlow.config.INIT_PROCESS,
                                   obj):
            raise zExceptions.Unauthorized(
                "initProcess", obj, Products.AlphaFlow.config.INIT_PROCESS)

        id = Products.AlphaFlow.utils.generateUniqueId(definition.getId())
        instance = zope.component.getMultiAdapter(
            (definition, obj, id),
            Products.AlphaFlow.interfaces.ILifeCycleObject)
        self.instances._setObject(id, instance)
        return self.instances[id]

    security.declareProtected(Products.AlphaFlow.config.MANAGE_WORKFLOW,
                              'listProcessDefinitions')

    def listProcessDefinitions(self):
        """Returns all processes defined in the portal."""
        rc = getToolByName(self, "reference_catalog")
        for uid in self.portal_process_refs:
            obj = rc.lookupObject(uid)
            if obj is None:
                continue
            yield obj

    security.declareProtected(Products.AlphaFlow.config.MANAGE_WORKFLOW,
                              'getStatistics')

    def getStatistics(self):
        """Return a dictionary with various statistical information"""
        cat = getToolByName(self, "workflow_catalog")
        query = dict(meta_type='Instance')

        all_count = len(cat(**query))

        query['state'] = 'active'
        active_count = len(cat(**query))

        query['state'] = 'failed'
        failed_count = len(cat(**query))

        result = {}
        result['all_count'] = all_count
        result['active_count'] = active_count
        result['failed_count'] = failed_count
        return result

    security.declareProtected(Products.AlphaFlow.config.MANAGE_WORKFLOW,
                              'listInstances')

    def listInstances(self, **search):
        """Return a list of instance objects found by the specified search."""
        query = dict(meta_type='Instance',
                     sort_on='modified',
                     sort_order='descending',
                     **search)
        wc = getToolByName(self, 'workflow_catalog')
        instances = wc(query)
        instances = [x.getObject() for x in instances]
        instances = [x for x in instances if x is not None]
        return instances

    security.declareProtected(Products.AlphaFlow.config.MANAGE_WORKFLOW,
                              "replaceInstances")

    def replaceInstances(self, old_version, new_process=None):
        """Terminate instances of old process version and restart with new
        process.

        XXX This method is untested.

        """
        if new_process is None:
            new_process = old_version.aq_inner.aq_parent.current()
        old_instances = self.listInstances(process_uid=old_version.UID())
        new_instances = []
        for instance in old_instances:
            # First terminate the old process ...
            obj = instance.getContentObject()
            Products.AlphaFlow.interfaces.ILifeCycleController(
                instance).terminate(
                    "Replaced with an instance of process %s." %
                    new_process.getId())
            # ... then create a new process.
            obj.assignProcess(new_process)
            new_instance = obj.getInstance()
            Products.AlphaFlow.interfaces.ILifeCycleController(
                new_instance).start(
                    "Replaced an instance of process version %s." %
                    old_version.UID())
            new_instances.append(new_instance)
        return new_instances

    security.declarePrivate('cleanUpInstances')

    def cleanUpInstances(self):
        """Removes garbage process instances."""
        orphans = []
        for id, instance in self.instances.objectItems():
            if (instance.state == "terminated"
                    or instance.getContentObject() is None):
                orphans.append(id)
                continue
            if instance.getProcess() is None:
                orphans.append(id)

        self.instances.manage_delObjects(orphans)
        wc = getToolByName(self, 'workflow_catalog')
        wc.refreshCatalog(clear=1)

    security.declareProtected(Products.AlphaFlow.config.WORK_WITH_PROCESS,
                              "queryWorkItems")

    def queryWorkItems(self, user):
        """Return list of work items for the given user."""
        result = []
        wc = getToolByName(self, "workflow_catalog")
        rc = getToolByName(self, "reference_catalog")
        user = AccessControl.getSecurityManager().getUser().getUserName()  #XXX

        for wi in wc(listRelevantUsers=user,
                     state="active",
                     showInWorkList=True):
            content = rc.lookupObject(wi.getContentObjectUID)
            if content is None:
                continue
            result.append(
                dict(wi=wi,
                     contentTitle=content.Title(),
                     getViewUrl=wi.getViewUrl,
                     getActivityTitleOrId=wi.getActivityTitleOrId,
                     getShortInfo=wi.getShortInfo))
        return result

    security.declarePublic("queryWorkItemsForCurrentUser")

    def queryWorkItemsForCurrentUser(self):
        """Return list of work items for the current user."""
        user = AccessControl.getSecurityManager().getUser()
        return self.queryWorkItems(user)

    security.declareProtected(Products.AlphaFlow.config.MANAGE_WORKFLOW,
                              'pingCronItems')

    def pingCronItems(self):
        """Send a trigger to all time-dependent objects."""
        # - We *lock* to avoid long running pings to run in
        #   parallel doing the same work and ending up with conflicts.
        # - We lock *non-blocking* and return a message when we could not
        #   acquire the lock to avoid subsequent pings to pile up.
        locked = ping_lock.acquire(False)
        if not locked:
            return ("AlphaFlow is already processing another ping. "
                    "This ping was ignored.")
        try:
            zope.event.notify(Products.AlphaFlow.interfaces.CronPing(self))
        finally:
            ping_lock.release()

    security.declarePrivate('restartHelper')

    def restartHelper(self, process, activity):
        """Restarts all work items of the given process and activity
        that are currently fallen out.

        """
        wc = getToolByName(self, "workflow_catalog")
        restarted = 0
        candidates = wc(process_uid=process,
                        activity_id=activity,
                        state="failed")
        for candidate in candidates:
            workitem = candidate.getObject()
            if workitem is None:
                continue
            # Restart it
            controller = \
                Products.AlphaFlow.interfaces.ILifeCycleController(workitem)
            controller.reset("Reset by restart helper.")
            controller.start("Start by restart helper.")
            restarted += 1
            if Products.AlphaFlow.config.ENABLE_ZODB_COMMITS:
                transaction.commit()
        return restarted

    security.declarePrivate('bulkDropin')

    def bulkDropin(self):
        """Recovers all instances that are currently fallen out."""
        candidates = list(self.listInstances(state='failed'))
        dropped_in = 0
        ignored = 0
        for candidate in candidates:
            controller = \
                Products.AlphaFlow.interfaces.ILifeCycleController(candidate)
            if controller.state != 'failed':
                # XXX For some reason I sometimes received instances multiple
                # times from the catalog.
                continue
            try:
                controller.recover("Bulk dropin via ZMI.")
            except:
                # XXX This happens when a work item is still failed.
                # This should use a dedicated exception.
                ignored += 1
            else:
                dropped_in += 1
        return dropped_in, ignored

    security.declarePrivate('doSanityCheck')

    def doSanityCheck(self):
        """Perform a sanity check and cleanup."""
        issues = []

        def _clean_object(o, path):
            local_roles = o.__ac_local_roles__
            try:
                delattr(o.aq_base, '__ac_local_roles__')
            except:
                # don't care
                pass
            # IAlphaflowed have a class attribute __ac_local_roles__, which is
            # computed, we update this now:
            for user, roles in local_roles.items():
                if 'Assignee' in roles:
                    del roles[roles.index('Assignee')]
                local_roles[user] = roles
            o.__ac_local_roles__.update(local_roles)

        def _check_double_refs(o, path):
            # 1. check instance to content object mapping.
            # geee, this is going to be expensive
            #   a. get a content object (c1)
            #   b. get its instance
            #   c. get instance's content object (c2)
            #   d. see if c1 is c2
            c1 = o
            instance = c1.getInstance()
            if instance is None:
                return
            c2 = instance.getContentObject()

            if c1.aq_base is not c2.aq_base:
                issues.append("Not sane: %r (%r, %r)" % (instance, c1, c2))
                c1.alf_clearInstances()

        def _check(o, path):
            if Products.AlphaFlow.interfaces.IAlphaFlowed.providedBy(o):
                _check_double_refs(o, path)
                _clean_object(o, path)

        portal = getToolByName(self, 'portal_url').getPortalObject()
        cat = getToolByName(self, 'portal_catalog')
        cat.ZopeFindAndApply(portal, search_sub=True, apply_func=_check)

        return issues
示例#18
0
class LinkMapTool(UniqueObject, BTreeFolder2):

    __implements__ = (ILinkMapTool)

    id = 'portal_linkmap'
    meta_type = 'LinkMap Tool'
    security = AccessControl.ClassSecurityInfo()

    manage_options=(( {'label':'Overview', 'action':'manage_overview'},
                      { 'label' : 'Catalog', 'action' : 'manage_catalog'},
                      ) + BTreeFolder2.manage_options
                    )

    ##   ZMI methods
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = PageTemplateFile('zpt/explainLinkMapTool', globals() )

    security.declareProtected(ManagePortal, 'manage_catalog')
    def manage_catalog(self, REQUEST=None):
        """Access to the ZCatalog"""
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.catalog.absolute_url()+'/manage_catalogView')


    def __init__(self, *args, **kw):
        BTreeFolder2.__init__(self, *args, **kw)
        self._create_catalog()
        self._linkrange = (1,3)  # currently unused; just a marker

    security.declarePrivate("_create_catalog")
    def _create_catalog(self):
        """Creates the ZCatalog instance for searching links"""
#        self.catalog = ZCatalog('catalog').__of__(self)
        self.catalog = ZCatalog('catalog')
        
        self.catalog.addIndex('source', 'FieldIndex')
        self.catalog.addIndex('strength', 'FieldIndex')

        self.catalog.addColumn('target')
        self.catalog.addColumn('category')
        self.catalog.addColumn('strength')
        self.catalog.addColumn('title')

        self._p_changed=1


    security.declareProtected('LinkMap: Add Link', 'addLink')
    def addLink(self, source, target, title, category, strength, context=None):
        """Create a link"""
        
        id = self.generateId()
        self._setObject(id, ExtendedLink(id))
        ob = getattr(self, id)
        ob.edit(source, target, title, category, strength)

        self.catalog.catalog_object(ob)

    security.declarePublic('searchLinks')
    def searchLinks(self, source=None, context=None):
        """Return all links for a particular source and context"""
        # FIXME: do we have to worry about 'latest' translation?
        results = self.catalog(source=source, sort_on='strength', sort_order='descending')
        
        return results

    def deleteLinks(self,objectId,version=None):
        """Delete all links for which the objectId is either source or target"""
	# This code assumes a ZRepository instance at /content
        myhost = urlparse.urlparse(self.REQUEST.SERVER_URL)[1]
	mypath = '/'.join(filter(None,['/content',objectId,version]))
	mylinks = []
	
	# FIXME: once a better storage and search interface exists, we can use that
	for link in self.objectValues('Extended Link'):
	    #Check source
	    tokens = urlparse.urlparse(link.source)
	    if (tokens[1] or myhost) == myhost and tokens[2].startswith(mypath): 
	        mylinks.append(link.id)
	    else:
	    #Check target
	        tokens = urlparse.urlparse(link.target)
	        if (tokens[1] or myhost) == myhost and tokens[2].startswith(mypath): 
	            mylinks.append(link.id)

	# Blow'em away!
        self.manage_delObjects(mylinks)

	
    security.setPermissionDefault('LinkMap: Add Link', ('Manager', 'Owner',))
示例#19
0
class HTMLTextarea(WidgetBase, OFS.SimpleItem.Item, Persistent,
                   Acquisition.Implicit, AccessControl.Role.RoleManager):
    """HTMLWidget for Textareas"""

    __implements__ = IWidget

    security = AccessControl.ClassSecurityInfo()

    security.declareProtected('View management screens', 'manage_properties')
    security = AccessControl.ClassSecurityInfo()

    meta_type = 'HTML Textarea'
    css_class = 'FormTextarea'
    user_css = ''
    aliases = []

    colspan = 0
    wrapping = 'virtual'

    manage_properties = HTMLFile('dtml/HTMLTextareaEdit', globals())

    manage_options = (
        {
            'label': 'Properties',
            'action': 'manage_properties'
        },
        {
            'label': 'Security',
            'action': 'manage_access'
        },
    )

    ################## Some zopish management stuff

    def __init__(self, id, REQUEST=None):
        self.id = id
        self.__version__ = __version__
        if REQUEST is not None:
            self.changeProperties(REQUEST)

    def __setstate__(self, state):
        HTMLTextarea.inheritedAttribute('__setstate__')(self, state)
        self._update_cache()

    security.declareProtected('Change Formulon instances', 'manage_edit')

    def manage_edit(self, REQUEST=None):
        """Sets the new properties."""
        if REQUEST is not None:
            self.changeProperties(REQUEST)
            return MessageDialog(title='Edited',
                                 message="Properties for %s changed." %
                                 self.id,
                                 action='./manage_properties')

    security.declareProtected('Change Formulon instances', 'changeProperties')

    def changeProperties(self, REQUEST, encoding='iso-8859-1'):
        if REQUEST is None:
            return

        # Update the base properties
        self.changeBaseProperties(REQUEST, encoding)

        self.title = unicode(REQUEST.title, encoding)
        self.cols = REQUEST.cols
        self.rows = REQUEST.rows
        self.colspan = REQUEST.colspan
        self.aliases = REQUEST.aliases
        self.user_css = unicode(REQUEST.user_css, encoding)

        self.column = REQUEST.column
        if self.column < 1:
            self.column = 1

        self.row = REQUEST.row
        if self.row < 1:
            self.row = 1

        if REQUEST.has_key("wrapping"):
            self.wrapping = REQUEST["wrapping"]

        # Update TALES Methods
        tales = ['default', 'regexp', 'hint']
        for x in tales:
            expression = REQUEST.get(x)
            if expression is None:
                continue
            setattr(self, x, TALESMethod(unicode(expression, encoding)))

        # cache the compiled regular expression:
        self._update_cache()

    def _update_cache(self):
        """Compiles a volatile attribute for caching the regex."""
        self._v_regex = self._compile_regex()

    def _compile_regex(self, force=False):
        """Compiles the regex attribute.

        If force is False regex is only compiled if it starts with 'string:'.
        (This is for the cache.)
        """
        if force or self.regexp.getExpression().startswith('string:'):
            try:
                return re.compile(self.callMethod(self.regexp), re.DOTALL)
            except AttributeError:
                # happens creating new widget because callMethod needs aq_parent
                return None

    #################### The widget specific code

    security.declareProtected('View', 'render')

    def render(self, parent, tabindex=None):
        doc = parent.ownerDocument
        node = parent.appendChild(doc.createElement("textarea"))
        if tabindex is not None:
            node.setAttribute("tabindex", str(tabindex))
        if self.user_css:
            node.setAttribute("class", self.user_css)
        else:
            node.setAttribute("class", self.css_class)

        node.setAttribute("name", self.id + "_value")
        node.setAttribute("cols", str(self.cols))
        node.setAttribute("rows", str(self.rows))
        node.setAttribute("wrapping", self.wrapping)
        node.setAttribute("onChange", "reportChange(this)")
        node.appendChild(doc.createTextNode(self.value()))
        return node

    security.declareProtected('View', 'validate')

    def validate(self, against=None):
        __traceback_info__ = self.getId(), against
        against = against["value"]
        if isinstance(against, str):
            against = unicode(against, self.site_encoding)
        regex = getattr(self, '_v_regex', None)
        if regex is None:
            regex = self._compile_regex()
            if regex is not None:  # starts with 'string:' so save it
                self._v_regex = regex
            else:
                regex = self._compile_regex(force=True)
        if not len(regex.findall(against)):
            raise ValidationError(self.callMethod(self.hint))
        return against

    security.declareProtected('View', 'height')

    def height(self):
        height = self.rows - 2
        if height < 1:
            height = 1
        return height

    security.declareProtected('View', 'value')

    def value(self):
        try:
            a = self.REQUEST['__source_data__'][self.id]
            if a is None:
                a = ''
            return a
        except KeyError:
            pass
        try:
            return self.REQUEST[self.id]
        except KeyError:
            pass
        return self.callMethod(self.default)

    security.declareProtected('View', 'getValue')
    getValue = value
class ControllerValidator(BaseClass, ControllerBase):
    """Web-callable scripts written in a safe subset of Python.

    The function may include standard python code, so long as it does
    not attempt to use the "exec" statement or certain restricted builtins.
    """

    meta_type = 'Controller Validator'

    implements(IControllerValidator)

    manage_options = (
        {'label':'Edit',
         'action':'ZPythonScriptHTML_editForm',
         'help': ('PythonScripts', 'PythonScript_edit.stx')},
        ) + BindingsUI.manage_options + (
        {'label':'Test',
         'action':'ZScriptHTML_tryForm',
         'help': ('PythonScripts', 'PythonScript_test.stx')},
            #        {'label':'Actions','action':'manage_formActionsForm'},
        {'label':'Proxy',
         'action':'manage_proxyForm',
         'help': ('OFSP','DTML-DocumentOrMethod_Proxy.stx')},
        ) + Historical.manage_options + SimpleItem.manage_options + \
        Cacheable.manage_options

    is_validator = 1

    security = AccessControl.ClassSecurityInfo()
    security.declareObjectProtected('View')

    security.declareProtected('View', '__call__')

    security.declareProtected('View management screens',
                              'ZPythonScriptHTML_editForm', 'manage_main',
                              'read', 'ZScriptHTML_tryForm',
                              'PrincipiaSearchSource', 'document_src',
                              'params', 'body')

    security.declareProtected('Change Python Scripts',
                              'ZPythonScriptHTML_editAction',
                              'ZPythonScript_setTitle', 'ZPythonScript_edit',
                              'ZPythonScriptHTML_upload',
                              'ZPythonScriptHTML_changePrefs')

    def __init__(self, *args, **kwargs):
        #        self.actions = FormActionContainer()
        return ControllerValidator.inheritedAttribute('__init__')(self, *args,
                                                                  **kwargs)

    def __call__(self, *args, **kwargs):
        result = ControllerValidator.inheritedAttribute('__call__')(self,
                                                                    *args,
                                                                    **kwargs)
        #        if getattr(result, '__class__', None) == ControllerState and not result._isValidating():
        #            return self.getNext(result, self.REQUEST)
        return result

    def _getState(self):
        return getToolByName(self,
                             'portal_form_controller').getState(self,
                                                                is_validator=1)

    def _notifyOfCopyTo(self, container, op=0):
        # BaseClass.inheritedAttribute('notifyOfCopyTo')(self, container, op)
        self._base_notifyOfCopyTo(container, op)

    def manage_afterAdd(self, object, container):
        BaseClass.inheritedAttribute('manage_afterAdd')(self, object,
                                                        container)
        self._base_manage_afterAdd(object, container)

    def manage_afterClone(self, object):
        BaseClass.inheritedAttribute('manage_afterClone')(self, object)
        self._base_manage_afterClone(object)
示例#21
0
class HTMLLabel(WidgetBase, OFS.SimpleItem.Item, Persistent,
                Acquisition.Implicit, AccessControl.Role.RoleManager):
    """HTMLWidget for Labels"""

    __implements__ = IWidget

    security = AccessControl.ClassSecurityInfo()

    meta_type = 'HTML Label'
    css_class = 'FormLabel'
    user_css = ""
    colspan = -1
    aliases = []

    security.declareProtected('View management screens', 'manage_properties')
    manage_properties = HTMLFile('dtml/HTMLLabelEdit', globals())

    manage_options = (
        {
            'label': 'Properties',
            'action': 'manage_properties'
        },
        {
            'label': 'Security',
            'action': 'manage_access'
        },
    )

    ########### Zopish management stuff

    def __init__(self, id, REQUEST=None):
        self.id = id
        self.__version__ = __version__
        if REQUEST is not None:
            self.changeProperties(REQUEST)

    security.declareProtected('Change Formulon instances', 'manage_edit')

    def manage_edit(self, REQUEST=None):
        """ set the new properties """
        if REQUEST is not None:
            self.changeProperties(REQUEST)
            return MessageDialog(title='Edited',
                                 message="Properties for %s changed." %
                                 (self.id, ),
                                 action='./manage_properties')

    security.declareProtected('Change Formulon instances', 'changeProperties')

    def changeProperties(self, REQUEST=None, encoding='iso-8859-1'):
        if REQUEST is not None:
            # Update the base properties
            self.changeBaseProperties(REQUEST, encoding)

            self.value = unicode(REQUEST.value, encoding)
            self.title = self.value
            self.column = REQUEST.column
            self.user_css = REQUEST.user_css
            if self.column < 1:
                self.column = 1
            self.row = REQUEST.row
            if self.row < 1:
                self.row = 1
            self.colspan = REQUEST.colspan

    def title_or_id(self):
        assert isinstance(self.value, unicode)
        return self.value

    security.declareProtected('View', 'validate')

    def validate(self, against):
        return against

    security.declareProtected('View', 'height')

    def height(self):
        return 1

    security.declareProtected('View', 'render')

    def render(self, parent, tabindex=None):
        node = parent.appendChild(parent.ownerDocument.createElement("span"))

        if self.user_css:
            node.setAttribute("class", self.user_css)
        else:
            node.setAttribute("class", self.css_class)

        node.appendChild(parent.ownerDocument.createTextNode(self.value))
        return node

    security.declareProtected('View', 'getValue')

    def getValue(self):
        return None
示例#22
0
class PDFLatexTool(UniqueObject, SimpleItem):

    __implements__ = (IPDFLatexTool)

    id = 'portal_pdflatex'
    meta_type = 'PDFLatex Tool'
    security = AccessControl.ClassSecurityInfo()

    manage_options=(( {'label':'Overview', 'action':'manage_overview'},
                      )
                    + SimpleItem.manage_options
                    )

    ##   ZMI methods
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = PageTemplateFile('zpt/explainPDFLatexTool', globals() )

    def convertObjectToPDF(self, object, style, **params):
        """
        Convert the given object to a PDF file, possible using its subjects as necessary
        """
        fs_tool = getToolByName(self, 'portal_fsimport')

        tempdir = tempfile.mkdtemp()
        fs_tool.exportToFS(tempdir, object)

        export = object.module_export_cnxml()
        if type(export) is unicode:
            export = export.encode('utf-8')
        export_file = open(os.path.join(tempdir, object.getId(), 'export.cnxml'), 'wb')
        export_file.write(export)
        export_file.close()
        
        zLOG.LOG('RhaptosPDFLatexTool',0, "module printing convertObjectToPDF")
        pdf = self.convertFSDirToPDF(os.path.join(tempdir, object.getId()), 'export.cnxml', style, **params)
        shutil.rmtree(tempdir)
        return pdf


    def convertFSDirToPDF(self, path, filename, style = '', **params):
        """
        Produce a PDF from a directory on the filesystem
        """
        # canonical name for module_export_template file
        os.rename(os.path.join(path, filename), os.path.join(path, 'module.mxt'))

        ## this might want to be refactored with Collection.triggerPrint at some point

        # look up makefile params on print tool
        # we don't visit a remote server, so this is not quite correct: we assume a local AsyncPrint for config
        # if not available, use Makefile-encoded values
        printtool = getToolByName(self,'rhaptos_print')
        portal = self.portal_url.getPortalObject()
        
        host = printtool.getHost()
        if host.startswith('http://'):
            host = host[7:]

        project_name = portal.Title()
        project_short_name = project_name
        if project_name == 'OpenStax CNX':
              project_name = 'The OpenStax CNX Project'

        env = os.environ
        env['HOST'] = host
        env['PROJECT_NAME'] = project_name
        env['PROJECT_SHORT_NAME'] = project_short_name
        env['EPUB_DIR'] = printtool.getEpubDir()
        env['PRINT_STYLE'] = style

        script_location = 'SCRIPTSDIR' in env and env['SCRIPTSDIR'] or '.'

        # Should be setup inside zope.conf on Zope client
        if not env.has_key('PRINT_DIR'):
            # BBB
            makefile = printtool.getMakefile()
            makefiledir = os.path.dirname(makefile)
            env['PRINT_DIR'] = makefiledir
        else:
            makefiledir = env['PRINT_DIR']
        
        makefile = "%s/module_print.mak" % makefiledir

        # copy makefile into tempdir
        shutil.copy(makefile, path)

        # call makefile
        f = open(os.path.join(path, 'make.log'), 'w', 0)
        now = datetime.now()
        zLOG.LOG('RhaptosPDFLatexTool',0, "module printing starts in tempdir %s at %s" % (path, now) )
        f.write(now.isoformat()+"\n")
        subprocess.call(['sh', '%s/module2pdf.sh' % script_location, 'module.pdf'], cwd=path, env=env,
                        stdout=f, stderr=subprocess.STDOUT)
        f.write(now.isoformat()+"\n")
        f.close()

        # evaluate result
        foundPDF = True
        try:
            # read the PDF (if it doesn't exist, fail)
            f = open(os.path.join(path, 'module.pdf'))
            pdf = f.read()
            f.close()
        except IOError:
            foundPDF = False
            #log("-----> could not print %s; pdf probably missing" % self.collectionId)
            raise PDFLatexError, "No output file"

        # check for good PDF here. if not, do we set some property on callback object?
        # all our bad PDFs seem to have the problem of not ending on %%EOF so we check that
        # it's a nice light-weight check
        if not (foundPDF and ( pdf.endswith("%%EOF\n") or pdf.endswith("%%EOF\n\n") ) ):
            raise PDFLatexError, "Bad output file"

        zLOG.LOG('RhaptosPDFLatexTool',0, "module printing finished successfully in tempdir %s at %s in %s" % (path, now, datetime.now()-now) )
        return pdf
class ControllerPythonScript(BaseClass, ControllerBase):
    """Web-callable scripts written in a safe subset of Python.

    The function may include standard python code, so long as it does
    not attempt to use the "exec" statement or certain restricted builtins.
    """

    meta_type = 'Controller Python Script'

    manage_options = (
        {'label':'Edit',
         'action':'ZPythonScriptHTML_editForm',
         'help': ('PythonScripts', 'PythonScript_edit.stx')},
        ) + BindingsUI.manage_options + (
        {'label':'Test',
         'action':'ZScriptHTML_tryForm',
         'help': ('PythonScripts', 'PythonScript_test.stx')},
        {'label':'Validation',
         'action':'manage_formValidatorsForm'},
        {'label':'Actions',
         'action':'manage_formActionsForm'},
        {'label':'Proxy',
         'action':'manage_proxyForm',
         'help': ('OFSP','DTML-DocumentOrMethod_Proxy.stx')},
        ) + Historical.manage_options + SimpleItem.manage_options + \
        Cacheable.manage_options

    is_validator = 0

    security = AccessControl.ClassSecurityInfo()
    security.declareObjectProtected('View')

    security.declareProtected('View', '__call__')

    security.declareProtected('View management screens',
                              'ZPythonScriptHTML_editForm', 'manage_main',
                              'read', 'ZScriptHTML_tryForm',
                              'PrincipiaSearchSource', 'document_src',
                              'params', 'body')

    security.declareProtected('Change Python Scripts',
                              'ZPythonScriptHTML_editAction',
                              'ZPythonScript_setTitle', 'ZPythonScript_edit',
                              'ZPythonScriptHTML_upload',
                              'ZPythonScriptHTML_changePrefs')

    def __init__(self, *args, **kwargs):
        self.validators = FormValidatorContainer()
        self.actions = FormActionContainer()
        return ControllerPythonScript.inheritedAttribute('__init__')(self,
                                                                     *args,
                                                                     **kwargs)

    def __call__(self, *args, **kwargs):
        REQUEST = self.REQUEST
        controller = getToolByName(self, 'portal_form_controller')
        controller_state = controller.getState(self, is_validator=0)
        controller_state = self.getButton(controller_state, REQUEST)
        validators = self.getValidators(controller_state,
                                        REQUEST).getValidators()

        # put all arguments into a dict
        c = self.func_code
        param_names = c.co_varnames[:c.co_argcount]
        argdict = {}
        index = 0
        # grab the names for positional arguments out of the function code
        for a in args:
            argdict[param_names[index]] = a
            index += 1
        argdict.update(kwargs)

        controller_state = controller.validate(controller_state, REQUEST,
                                               validators, argdict)

        if controller_state.getStatus() == 'success':
            result = ControllerPythonScript.inheritedAttribute('__call__')(
                self, *args, **kwargs)
            if getattr(result, '__class__',
                       None) == ControllerState and not result._isValidating():
                return self.getNext(result, self.REQUEST)
            return result
        else:
            return self.getNext(controller_state, self.REQUEST)

    def _getState(self):
        return getToolByName(self,
                             'portal_form_controller').getState(self,
                                                                is_validator=0)

    def _notifyOfCopyTo(self, container, op=0):
        # BaseClass.inheritedAttribute('notifyOfCopyTo')(self, container, op)
        self._base_notifyOfCopyTo(container, op)

    def manage_afterAdd(self, object, container):
        BaseClass.inheritedAttribute('manage_afterAdd')(self, object,
                                                        container)
        self._base_manage_afterAdd(object, container)

    def manage_afterClone(self, object):
        BaseClass.inheritedAttribute('manage_afterClone')(self, object)
        self._base_manage_afterClone(object)
示例#24
0
class FavoritesLens(ContentSelectionLens):
    """A restricted sort of lens, for holding personal bookmarks, etc."""
    archetype_name = "Favorites Lens"
    content_icon = 'star.png'

    security = AccessControl.ClassSecurityInfo()

    schema = schema

    actions = (
        {
            'id': 'view',
            'title': 'View',
            'action': Expression('string:${object_url}/lens_view'),
            'permissions': (View, ),
            'visible': 0
        },
        {
            'id': 'contents',
            'title': 'Edit lens contents',
            'action': Expression('string:${object_url}/favorite_content_view'),
            'permissions': (ModifyPortalContent, )
        },
        {
            'id': 'edit',
            'title': 'Edit lens properties',
            'action': Expression('string:${object_url}/lens_edit'),
            'permissions': (ModifyPortalContent, )
        },
        {
            'id': 'preview',
            'title': 'Preview lens',
            'action': Expression('string:${object_url}/lens_preview'),
            'permissions': (ModifyPortalContent, )
        },
        {
            'id': 'metadata',  # turn off 'properties' tab
            'visible': 0
        },
    )

    aliases = {
        '(Default)': '',
        'edit': 'lens_edit',
        'gethtml': '',
        'index.html': '',
        'properties': '',
        'sharing': '',
        'view': 'lens_view',
        'contents': 'favorite_content_view',
    }

    security.declarePublic('workflowStateEditable')

    def workflowStateEditable(self):
        """Return a boolean whether or not the user may edit the workflow state of this lens."""
        return False  # not very smart at the moment, but accurate.

    security.declarePublic('suggestId')

    def suggestId(self):
        """Return the suggested id of this lens; used particularly for portal_factory create."""
        return FAVORITE_ID

    security.declareProtected(View, 'notifyRead')

    def notifyRead(self, collectionId, moduleId):
        """The user read 'moduleId' in 'collectionId'. If we have that collection, mark that module
        as the "last read". No return value.
        """
        if collectionId:
            entry = getattr(self, collectionId, None)
            if not entry is None:
                if getattr(
                        entry, 'lastRead',
                        None) != moduleId:  # avoid touching object if possible
                    entry.lastRead = moduleId

    security.declareProtected(View, 'getLastRead')

    def getLastRead(self, collectionId):
        """Return the 'moduleId' in 'collectionId' (if we have that collection) "last read"
        by the owner. If no collection by that id, return None.
        """
        if collectionId:
            entry = getattr(self, collectionId, None)
            if not entry is None:
                moduleId = getattr(entry, 'lastRead', None)
                return moduleId
示例#25
0
class BugTrackingTool(UniqueObject, SimpleItem, PropertyManager):

    implements(IBugTrackingTool)

    id = 'portal_bugtracking'
    meta_type = 'BugTracking Tool'
    security = AccessControl.ClassSecurityInfo()

    # ZMI methods
    manage_options = ((
        {
            'label': 'Overview',
            'action': 'manage_overview'
        },
        {
            'label': 'Properties',
            'action': 'manage_propertiesForm'
        },
    ) + SimpleItem.manage_options)

    #def __init__(self):
    #    self.trac_dir = '/opt/trac/tracdev'
    ##   ZMI methods
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = PageTemplateFile('zpt/explainBugTrackingTool', globals())

    # IBugTrackingTool Interface fulfillment

    def submitBug(self,
                  summary,
                  email,
                  name=None,
                  contentUrl=None,
                  bugType='Unknown',
                  severity='minor',
                  description=None):
        """
        Submit a bug to the trac system
        """
        if not hasattr(self, 'trac_xmlrpc'):
            raise ValueError, "Trac_xmlrpc is not configured"
        trac_xmlrpc = getattr(self, 'trac_xmlrpc')

        # Set some default values for properties that can be over_written
        if hasattr(self, 'trac_type'):
            trac_type = getattr(self, 'trac_type')
        else:
            trac_type = 'defect'

        if hasattr(self, 'trac_state'):
            trac_state = getattr(self, 'trac_state')
        else:
            trac_state = 'new'

        server = xmlrpclib.ServerProxy(trac_xmlrpc)
        attributes = {}
        attributes['version'] = 'Live'
        attributes['type'] = trac_type
        attributes['status'] = trac_state
        attributes['summary'] = summary
        attributes['keywords'] = 'ReportABug'
        if name and email:
            attributes['reporter'] = '%s <%s>' % (name, email)
        elif name:
            attributes['reporter'] = name
        elif email:
            attributes['reporter'] = email
        if description:
            attributes['description'] = description
        attributes['severity'] = severity
        if contentUrl:
            attributes['contentobj'] = contentUrl
        attributes['area'] = bugType

        # Create ticket through XML-RPC. The "True" is for the "notify" parameter.
        newid = server.ticket.create(summary, description, attributes, True)
        return (newid)
class ModuleDBTool(UniqueObject, SimpleItem):
    """Provide access to data stored in SQL for objects stored with RhaptosModuleStorage"""


    __implements__ = (IModuleDBTool)

    id = 'portal_moduledb'
    meta_type = 'Module DB Tool'
    security = AccessControl.ClassSecurityInfo()

    sqlModuleExists = ZSQLFile('sql/moduleExists', globals(), __name__='sqlModuleExists')
    sqlCountModules = ZSQLFile('sql/countModules', globals(), __name__='sqlCountModules')
    sqlGetModulesByAuthors = ZSQLFile('sql/getModulesByAuthors', globals(), __name__='sqlGetModulesByAuthors')
    sqlGetModulesByRole = ZSQLFile('sql/getModulesByRole', globals(), __name__='sqlGetModulesByRole')
    sqlGetModulesByKeyword = ZSQLFile('sql/getModulesByKeyword', globals(), __name__='sqlGetModulesByKeyword')
    sqlGetModulesByTitle = ZSQLFile('sql/getModulesByTitle', globals(), __name__='sqlGetModulesByTitle')
    sqlGetModulesByLanguage = ZSQLFile('sql/getModulesByLanguage', globals(), __name__='sqlGetModulesByLanguage')
    sqlInsertFTIWords = ZSQLFile('sql/insertFTIWords', globals(), __name__='sqlInsertFTIWords')
    sqlGetPeoplebyName = ZSQLFile('sql/getPeoplebyName', globals(), __name__='sqlGetPeoplebyName')
    sqlDeleteModule = ZSQLFile('sql/deleteModule', globals(), __name__='sqlDeleteModule')
    sqlGetModule = ZSQLFile('sql/getModule', globals(), __name__='sqlGetModule')
    sqlGetModules = ZSQLFile('sql/getModules', globals(), __name__='sqlGetModules')
    sqlGetLatestModule = ZSQLFile('sql/getLatestModule', globals(), __name__='sqlGetLatestModule')
    sqlGetNewestModules = ZSQLFile('sql/getNewestModules', globals(), __name__='sqlGetNewestModule')
    sqlGetKeywords = ZSQLFile('sql/getKeywords', globals(), __name__='sqlGetKeywords')
    sqlGetHistory = ZSQLFile('sql/getHistory', globals(), __name__='sqlGetHistory')
    sqlGetNextModuleId = ZSQLFile('sql/getNextModuleId', globals(), __name__='sqlGetNextModuleId')
    sqlGetNextCollectionId = ZSQLFile('sql/getNextCollectionId', globals(), __name__='sqlGetNextCollectionId')
    sqlInsertNewVersion = ZSQLFile('sql/insertNewVersion', globals(), __name__='sqlInsertNewVersion')
    sqlGetAbstractID = ZSQLFile('sql/getAbstractID', globals(), __name__=='sqlGetAbstractID')
    sqlInsertAbstract = ZSQLFile('sql/insertAbstract', globals(), __name__='sqlInsertAbstract')
    sqlGetKeywordID = ZSQLFile('sql/getKeywordID', globals(), __name__=='sqlGetKeywordID')
    sqlInsertKeyword = ZSQLFile('sql/insertKeyword', globals(), __name__='sqlInsertKeyword')
    sqlInsertModuleKeyword = ZSQLFile('sql/insertKeywords', globals(), __name__='sqlInsertModuleKeyword')
    sqlInsertModuleOptionalRole = ZSQLFile('sql/insertOptionalRoles', globals(), __name__='sqlInsertModuleOptionalRole')
    sqlGetLicense = ZSQLFile('sql/getLicense', globals(), __name__='sqlGetLicense')
    sqlGetLicenses = ZSQLFile('sql/getLicenses', globals(), __name__='sqlGetLicenses')
    sqlGetTags = ZSQLFile('sql/getTags', globals(), __name__='sqlGetTags')
    sqlSearchModules = ZSQLFile('sql/searchModules', globals(), __name__='sqlSearchModules')
    sqlSearchModulesByDate = ZSQLFile('sql/searchModulesByDate', globals(), __name__='sqlSearchModulesByDate')
    sqlGetKeywordByFirstChar = ZSQLFile('sql/getKeywordByFirstChar', globals(), __name__='sqlGetKeywordByFirstChar')
    sqlGetLanguageCounts = ZSQLFile('sql/getLanguages', globals(), __name__='sqlGetLanguageCounts')
    sqlWrapAbstract = ZSQLFile('sql/wrapAbstract', globals(), __name__='sqlWrapAbstract')
    sqlWrapText = ZSQLFile('sql/wrapText', globals(), __name__='sqlWrapText')
    sqlInsertModuleTag = ZSQLFile('sql/insertTag', globals(), __name__='sqlInsertModuleTag')
    sqlRegisterRating = ZSQLFile('sql/registerRating', globals(), __name__='sqlRegisterRating')
    sqlDeregisterRating = ZSQLFile('sql/deregisterRating', globals(), __name__='sqlDeregisterRating')
    sqlGetRating = ZSQLFile('sql/getRating', globals(), __name__='sqlGetRating')
    sqlGetModuleFile = ZSQLFile('sql/getModuleFile', globals(), __name__='sqlGetModuleFile')
    sqlGetModuleFileSize = ZSQLFile('sql/getModuleFileSize', globals(), __name__='sqlGetModuleFileSize')
    sqlGetModuleFilenames = ZSQLFile('sql/getModuleFilenames', globals(), __name__='sqlGetModuleFilenames')
    sqlGetFileByMd5 = ZSQLFile('sql/getFileByMd5', globals(), __name__='sqlGetFileByMd5')
    sqlInsertFile = ZSQLFile('sql/insertFile', globals(), __name__='sqlInsertFile')
    sqlInsertModuleFile = ZSQLFile('sql/insertModuleFile', globals(), __name__='sqlInsertModuleFile')
    sqlGetCollectionTree = ZSQLFile('sql/getCollectionTree', globals(), __name__='sqlGetCollectionTree')
    sqlGetPrintStyles = ZSQLFile('sql/getPrintStyles', globals(), __name__='sqlGetPrintStyles')


    # Member-data functions
    sqlGetAuthorByFirstChar = ZSQLFile('sql/getAuthorByFirstChar', globals(), __name__='sqlGetAuthorByFirstChar')
    sqlGetAuthorById = ZSQLFile('sql/getAuthorById', globals(), __name__='sqlGetAuthorById')
    sqlInsertMember = ZSQLFile('sql/insertMemberData', globals(), __name__='sqlInsertMember')
    sqlUpdateMember = ZSQLFile('sql/updateMemberData', globals(), __name__='sqlUpdateMember')
    sqlUpdateMemberPassword = ZSQLFile('sql/updateMemberPassword', globals(), __name__='sqlUpdateMemberPassword')
    sqlUpdateMemberGroups = ZSQLFile('sql/updateMemberGroups', globals(), __name__='sqlUpdateMemberGroups')

    ##   ZMI methods
    manage_options=(( {'label':'Overview', 'action':'manage_overview'},
                      {'label':'Configure', 'action':'manage_moduledb_properties'},
                      ) + SimpleItem.manage_options
                    )

    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = PageTemplateFile('zpt/explainModuleDBTool', globals() )

    security.declareProtected(ManagePortal, 'manage_moduledb_properties')
    manage_moduledb_properties = PageTemplateFile('zpt/editModuleDBTool', globals() )

    def __init__(self, db=None):
        self.db = db

    security.declareProtected(ManagePortal, 'setDB')
    def setDB(self, db):
        self.db = db

    security.declareProtected(ManagePortal, 'manage_editModuleDBTool')
    def manage_editModuleDBTool(self, connection, REQUEST=None):
        """Edit the ModuleDBTool parameters"""

        self.db = connection

        if REQUEST:
            return self.manage_moduledb_properties(manage_tabs_message="ModuleDBTool updated")

    security.declarePublic('getLicenseData')
    def getLicenseData(self, url):
        """
        Get all the information about a given license from the database table.
        Returns a dictionary with keys: code, label, name, url, and version.
        Examples of code, label, and version: 'by', 'CC-BY 3.0', '3.0'.
        """
        # FIXME: not the best place for this; should be somewhere in RhaptosContent probably
        licensedata = self.sqlGetLicense(url=url)[0]
        licensedict = {}
        for f in ['code', 'label', 'name', 'url', 'version']:
            licensedict[f] = licensedata[f]
        return licensedict

    def insertModuleVersion(self, object):
        """Insert new module version into the database"""

        # Get ID of (possibly new) abstract
        abstractId = self._getAbstractID(object.abstract)

        # Get ID of license
        try:
            licenseId = self.sqlGetLicense(url=object.license)[0].licenseid
        except IndexError:
            raise CommitError, "Unknown license: %s" % object.license

        # Set state to 'submitted'
        stateId = 1

        # Put new version of module into the DB
        parentObj = object.getParent()
        parent = parentObj and self.sqlGetModule(id=parentObj.objectId,version=parentObj.version)[0].ident or None
        print_style = object.portal_type == 'Collection' and object.parameters.getProperty('printstyle') or None
        self.sqlInsertNewVersion(moduleid=object.objectId,
                                 portal_type=object.portal_type,
                                 version=object.version,
                                 name=object.title,
                                 created=object.created.HTML4(),
                                 revised=object.revised.HTML4(),
                                 authors=object.authors,
                                 maintainers=object.maintainers,
                                 licensors=object.licensors,
                                 parentauthors=object.parentAuthors,
                                 abstractid=abstractId, stateid=stateId, licenseid=licenseId,
                                 doctype=getattr(object, 'doctype', ''),  # 0.6 loses doctype attr
                                 submitter=object.submitter,
                                 submitlog=object.submitlog,
                                 parent=parent, language=object.language,
                                 google_analytics=object.getGoogleAnalyticsTrackingCode(),
                                 print_style=print_style)

        # Put the file objects in the files table
        files = [f for f in object.objectValues() if hasattr(f,'data')] #It's a file object
        defaultFile = getattr(object,'getDefaultFile',lambda : None)()
        # Move index.cnxml (or other default file) to end of list, so all files referenced by it are in db first
        if defaultFile:
            files.append(files.pop(files.index(defaultFile)))

        for fob in files:
            fid = self._getFileID(fob)
            self.sqlInsertModuleFile(moduleid=object.objectId, version=object.version, fileid=fid, filename=fob.id)

        # Put non-blank keywords into module-keyword table
        for word in [' '.join(w.strip().split()) for w in object.keywords if w.strip()]:
            keywordId = self._getKeywordID(word)
            self.sqlInsertModuleKeyword(moduleid=object.objectId, version=object.version, keywordid=keywordId)

        # Insert optional roles, if any
        for role in object.optional_roles.keys():
            value = getattr(object,role.lower()+'s',None)
            if value:
                self.sqlInsertModuleOptionalRole(moduleid=object.objectId, version=object.version,rolename=role,persons=value)

        # Stores subjects as tags
        if type(_utf8(object.subject)) == type(''):
            self.sqlInsertModuleTag(moduleid=object.objectId, version=object.version,tag=_utf8(object.subject))
        else:
            for subj in object.subject:
                self.sqlInsertModuleTag(moduleid=object.objectId, version=object.version,tag=_utf8(subj))

        # Put fulltext index words in place
        if object.SearchableText():
            self.sqlInsertFTIWords(moduleid=object.objectId, version=object.version, modulecontents=object.SearchableText())

    def _getAbstractID(self, abstract):
        """Return a unique (possibly new) ID for abstract text"""

        abstract = _utf8(abstract)
        result = self.sqlGetAbstractID(abstract=abstract)
        if not len(result): # If the abstract doesn't already exist, insert it
            self.sqlInsertAbstract(abstract=abstract)
            result = self.sqlGetAbstractID(abstract=abstract)
        return result[0].id

    def _getKeywordID(self, word):
        """Return a unique (possibly new) ID for keyword"""
        word = _utf8(word)
        result = self.sqlGetKeywordID(word=word)
        if not len(result): # If the keyword doesn't already exist, insert it
            self.sqlInsertKeyword(word=word)
            result = self.sqlGetKeywordID(word=word)
        return result[0].id

    def _getFileID(self,fileob):
        """Return the fileid for a file, stored in the DB"""
        # let's make sure we've got a utf-8 string
        fdata = _utf8(fileob.data)
        m = md5.new(fdata).hexdigest()
        sha = sha1(fdata).hexdigest()
        res = self.sqlGetFileByMd5(md5=m)
        for r in res:
            if sha1(r.file).hexdigest() == sha:
                return r.fileid
        # Fell through, must be new bytes
        res = self.sqlInsertFile(file = Binary(fdata), media_type=fileob.content_type)
        return res[0].fileid
示例#27
0
class ZopeRepository(Repository.Repository, RoleManager, OFS.SimpleItem.Item):
    """The ZopeRepository class builds on the core Repository implementation
       to provide the Zope management interface and other product trappings."""

    security = AccessControl.ClassSecurityInfo()

    meta_type = 'Repository'

    manage_options = ((
        {
            'label': 'Contents',
            'action': 'manage_main',
            'help': ('ZopeVersionControl', 'Repository-Manage.stx')
        },
        {
            'label': 'Properties',
            'action': 'manage_properties_form',
            'help': ('ZopeVersionControl', 'Repository-Properties.stx')
        },
    ) + RoleManager.manage_options + OFS.SimpleItem.Item.manage_options)

    security.declareProtected('View management screens', 'manage_main')
    manage_main = DTMLFile('dtml/RepositoryManageMain', globals())
    manage_main._setName('manage_main')
    manage = manage_main

    def __init__(self, id=None, title=''):
        Repository.Repository.__init__(self)
        if id is not None:
            self._setId(id)
        self.title = title

    security.declareProtected('View management screens',
                              'manage_properties_form')
    manage_properties_form = DTMLFile('dtml/RepositoryProperties', globals())

    security.declareProtected('Manage repositories', 'manage_edit')

    def manage_edit(self, title='', REQUEST=None):
        """Change object properties."""
        self.title = title
        if REQUEST is not None:
            message = "Saved changes."
            return self.manage_properties_form(self,
                                               REQUEST,
                                               manage_tabs_message=message)

    def __getitem__(self, name):
        history = self._histories.get(name)
        if history is not None:
            return history.__of__(self)
        raise KeyError(name)

    security.declarePrivate('objectIds')

    def objectIds(self, spec=None):
        return SequenceWrapper(self, self._histories.keys())

    security.declarePrivate('objectValues')

    def objectValues(self, spec=None):
        return SequenceWrapper(self, self._histories.values())

    security.declarePrivate('objectItems')

    def objectItems(self, spec=None):
        return SequenceWrapper(self, self._histories.items(), 1)
示例#28
0
class HTMLButton(WidgetBase, OFS.SimpleItem.Item, Persistent, Acquisition.Implicit, \
            AccessControl.Role.RoleManager):
    """HTMLWidget for Buttons"""

    __implements__ = IWidget

    security = AccessControl.ClassSecurityInfo()

    meta_type = 'HTML Button'
    css_class = 'FormButton'
    user_css = ''

    colspan = 0
    hint = ''

    title = u""

    aliases = []

    security.declareProtected('View management screens', 'manage_properties')
    manage_properties = HTMLFile('dtml/HTMLButtonEdit', globals())

    manage_options = (
        {
            'label': 'Properties',
            'action': 'manage_properties'
        },
        {
            'label': 'Security',
            'action': 'manage_access'
        },
    )

    ########### Zopish management stuff
    def __init__(self, id, REQUEST=None):
        self.id = id
        self.__version__ = __version__
        if REQUEST is not None:
            self.changeProperties(REQUEST)

    def title_or_id(self):
        return self.value

    security.declareProtected('Change Formulon instances', 'manage_edit')

    def manage_edit(self, REQUEST=None):
        """Save edited properties."""
        self.changeProperties(REQUEST)
        return MessageDialog(title='Edited',
                             message="Properties for %s changed." %
                             (self.id, ),
                             action='./manage_properties')

    security.declareProtected('Change Formulon instances', 'changeProperties')

    def changeProperties(self, REQUEST=None, encoding='iso-8859-1'):
        # iso-8859 is ZMI's encoding.
        if REQUEST is not None:
            # Update the base properties
            self.changeBaseProperties(REQUEST, encoding)

            self.type = REQUEST.type
            self.value = unicode(REQUEST.value, encoding)
            self.user_css = unicode(REQUEST.user_css, encoding)

            self.column = REQUEST.column
            if self.column < 1:
                self.column = 1

            self.row = REQUEST.row
            if self.row < 1:
                self.row = 1

            self.colspan = REQUEST.colspan
            if REQUEST.has_key('isimg'):
                self.isimg = 1
            else:
                self.isimg = 0

            self.imgsrc = REQUEST.imgsrc
            self.aliases = REQUEST.aliases

    ############ Here comes the widget specific code
    security.declareProtected('View', 'render')

    def render(self, parent, tabindex=None):
        node = parent.appendChild(parent.ownerDocument.createElement("input"))

        if self.type == 'submit' and self.isimg:
            node.setAttribute("type", "image")
            node.setAttribute("src", self.imgsrc)
        else:
            node.setAttribute("type", self.type)
        if not tabindex is None:
            node.setAttribute("tabindex", str(tabindex))

        if not self.type == 'submit':
            node.setAttribute("onClick", "resetForm()")

        if self.user_css:
            node.setAttribute("class", self.user_css)
        else:
            node.setAttribute("class", self.css_class)

        node.setAttribute("name", self.id + "_value")
        node.setAttribute("value", self.value)
        return node

    security.declareProtected('View', 'validate')

    def validate(self, against=None):
        return against

    security.declareProtected('View', 'height')

    def height(self):
        return 1

    security.declareProtected('View', 'getValue')

    def getValue(self):
        return self.value
示例#29
0
class HTMLFileupload(WidgetBase, OFS.SimpleItem.Item, Persistent,
                     Acquisition.Implicit, AccessControl.Role.RoleManager):
    """HTMLWidget for Fileuploads"""

    __implements__ = IWidget

    security = AccessControl.ClassSecurityInfo()

    meta_type = 'HTML Fileupload'
    css_class = 'FormFileupload'

    colspan = 1
    required = 0
    aliases = []

    security.declareProtected('View management screens', 'manage_properties')
    manage_properties = HTMLFile('dtml/HTMLFileuploadEdit', globals())

    manage_options = (
        {
            'label': 'Properties',
            'action': 'manage_properties'
        },
        {
            'label': 'Security',
            'action': 'manage_access'
        },
    )

    ################ Here comes Zope specific code

    def __init__(self, id, REQUEST):
        self.id = id
        self.__version__ = __version__
        if REQUEST is not None:
            self.changeProperties(REQUEST)

    security.declareProtected('Change Formulon instances', 'manage_edit')

    def manage_edit(self, REQUEST=None):
        """ set the new properties """
        if REQUEST is not None:
            self.changeProperties(REQUEST)
            return MessageDialog(title='Edited',
                                 message="Properties for %s changed." %
                                 self.id,
                                 action='./manage_properties')

    security.declareProtected('Change Formulon instances', 'changeProperties')

    def changeProperties(self, REQUEST=None, encoding='iso-8859-1'):
        if REQUEST is None:
            return
        # Update the base properties
        self.changeBaseProperties(REQUEST, encoding)

        self.title = unicode(REQUEST.title, encoding)
        self.column = REQUEST.column
        if self.column < 1:
            self.column = 1
        self.row = REQUEST.row
        if self.row < 1:
            self.row = 1
        self.colspan = REQUEST.colspan

        self.required = 0
        if REQUEST.has_key('required'):
            self.required = 1

        self.aliases = REQUEST.aliases

        # Update TALES Methods
        for x in ['hint']:
            expression = REQUEST.get(x)
            if expression is None:
                continue
            setattr(self, x, TALESMethod(unicode(expression, encoding)))

    ################# Here comes the widget specific code
    security.declareProtected('View', 'render')

    def render(self, parent, tabindex=None):
        doc = parent.ownerDocument
        widget = parent.appendChild(doc.createElement('input'))
        set = widget.setAttribute
        if tabindex is not None:
            set('tabindex', str(tabindex))
        set('name', self.id + "_value")
        set('type', 'file')
        set('class', self.css_class)
        set("onChange", "reportChange(this)")
        return widget

    security.declareProtected('View', 'validate')

    def validate(self, against):
        filename = ''
        against = against.get('value')

        if isinstance(against, ZPublisher.HTTPRequest.FileUpload):
            filename = against.filename

        if self.required and not filename:
            raise ValidationError(self.callMethod(self.hint))
        return against

    security.declareProtected('View', 'height')

    def height(self):
        return 1

    security.declareProtected('View', 'getValue')

    def getValue(self):
        if self.REQUEST.has_key(self.id):
            return self.REQUEST[self.id]
        else:
            return None
class ModuleView(SimpleItem):
    """Dyamically created Zope object for displaying Modules"""

    security = AccessControl.ClassSecurityInfo()

    implements(IMDML)

    meta_type = 'Rhaptos Module View'
    icon = 'module_icon.gif'
    isPrincipiaFolderish = 1
    state = 'public'
    actor = None

    def getPendingCollaborations(self):
        return {}

    def __init__(self, id, objectId, data=None, **kwargs):
        # Since this is a versioned object, the id is the version
        self.id = id
        self.objectId = objectId
        self._data = data

    def _log(self, message, severity):
        zLOG.LOG("ModuleView", severity,
                 "%s (%s)" % (message, self.REQUEST['PATH_INFO']))

    def _setLastModHeader(self):
        #print self.revised
        self.REQUEST.RESPONSE.setHeader('Last-Modified',
                                        rfc1123_date(self.revised))

    def __getitem__(self, name):
        """basic get item"""
        if name == self.getDefaultFilename():
            f = self.getDefaultFile()
        else:
            if name in self.objectIds():
                f = self.getFile(name)
            else:
                raise KeyError, name
        return f

    security.declarePublic('index_html')

    def index_html(self):
        """Default display method"""
        # If we got here without specifying the trailing slash, redirect
        if not self.REQUEST.PATH_INFO[-1].endswith('/'):
            path = self.REQUEST.URL1 + '/'
            if self.REQUEST.QUERY_STRING:
                path = path + '?' + self.REQUEST.QUERY_STRING
            self.REQUEST.RESPONSE.redirect(path, status=301)

        # Otherwise, do the default
        else:
            return self.default()

    security.declarePublic('isPublic')

    def isPublic(self):
        """Boolean answer true iff collection is in versioned repository.
        Based currently on value of 'state' attribute.
        """
        return True

    security.declarePublic('url')

    def url(self):
        """Return the canonical URL used to access the object"""
        return self.absolute_url() + '/'

    security.declarePrivate('_getDBProperty')

    def _getDBProperty(self, property):
        """Get module property from DB storage"""
        # Store local copy of DB data so we don't have to keep looking it up
        if not self._data:
            #self._log("Querying DB for %s" % self.objectId, zLOG.INFO)
            if not self.id or self.id == 'latest':
                data = self.portal_moduledb.sqlGetLatestModule(
                    id=self.objectId)
            else:
                data = self.portal_moduledb.sqlGetModule(id=self.objectId,
                                                         version=self.id)
            try:
                self._data = data[0]
            except IndexError:
                raise IndexError, "Couldn't find version %s of object %s" % (
                    self.id, self.objectId)
        return getattr(self._data, property)

    security.declarePublic('getMetadata')

    def getMetadata(self):
        """Returns a dictionary of metadata, so that children can implement IMDML"""
        metadata = {}

        repos = getToolByName(self, 'content')
        metadata['repository'] = repos.absolute_url()

        metadata['url'] = self.absolute_url()
        metadata['objectId'] = self.objectId
        metadata['title'] = self.Title()
        metadata['keywords'] = self.keywords
        metadata['subject'] = self.subject
        metadata['abstract'] = self.abstract
        metadata['language'] = self.language
        metadata['license'] = self.license

        metadata['version'] = self.version
        metadata['created'] = self.created
        metadata['revised'] = self.revised

        metadata['authors'] = self.authors
        metadata['maintainers'] = self.maintainers
        metadata['licensors'] = self.licensors
        # Optional roles, currently Translator, Editor
        for role, actors in self.roles.items():
            metadata[role] = actors

        # Collection specific metadata below
        metadata['homepage'] = None
        metadata['institution'] = None
        metadata['coursecode'] = None
        metadata['instructor'] = None

        parent = {}
        pobj = self.getParent()
        if pobj:
            parent = pobj.getMetadata()
        metadata['parent'] = parent

        return metadata

    security.declarePublic('getKeywords')

    def getKeywords(self):
        """Return list of keywords"""
        return tuple([
            row.word
            for row in self.portal_moduledb.sqlGetKeywords(ident=self.ident)
        ])

    security.declarePublic('Title')

    def Title(self):
        """Return title; some performance optimizations over plain 'title' attribute."""
        if not hasattr(self, '_cataloging') and self.isLatestVersion(
                self):  # ...acquired from Repository
            # catalog retrieval only for 'latest'; we don't store values for old modules
            # _cataloging is a semaphore for when cataloging is going on right after publish
            try:
                cat = getToolByName(self, 'content').catalog
                mod = cat(objectId=self.objectId)
                if mod:
                    return mod[0].Title
            except AttributeError:  # not be in content context?
                pass

        return self.title

    security.declarePublic('wrapAbstract')

    def wrapAbstract(self, terms, open_wrap_tag='<b>', close_wrap_tag='</b>'):
        """Wrap matches to list of terms with tags. Returns tuple of (excerpt,abstract)"""
        q = '&'.join(terms)
        res = self.portal_moduledb.sqlWrapAbstract(
            moduleid=self.objectId,
            version=self.version,
            query=q,
            open_wrap_tag='OPEN_CNX_WRAP_TAG',
            close_wrap_tag='CLOSE_CNX_WRAP_TAG')
        headline, abstract = res.tuples()[0]
        cutoff = 20

        headline_start = abstract.find(headline)

        if headline_start != -1:
            headline_end = headline_start + len(headline)
            head = abstract[:headline_start]
            tail = abstract[headline_end:]

            if len(' '.join(head.split()[2:])) > cutoff:
                headline = ' '.join(head.split()[:2]) + " ... " + headline
            else:
                headline = head + headline

            if len(' '.join(tail.split()[:-2])) > cutoff:
                headline = headline + " ... " + ' '.join(tail.split()[-2:])
            else:
                headline = headline + tail
        return (headline.replace('OPEN_CNX_WRAP_TAG', open_wrap_tag).replace(
            'CLOSE_CNX_WRAP_TAG', close_wrap_tag),
                abstract.replace('OPEN_CNX_WRAP_TAG', open_wrap_tag).replace(
                    'CLOSE_CNX_WRAP_TAG', close_wrap_tag))

    security.declarePublic('wrapAbstract')

    def wrapBodyText(self, terms, open_wrap_tag='<b>', close_wrap_tag='</b>'):
        """Wrap matches to list of terms with tags. Returns tuple of (excerpt,fulltext)"""
        q = '&'.join(terms)
        res = self.portal_moduledb.sqlWrapText(text=self.bareText(),
                                               query=q,
                                               open_wrap_tag=open_wrap_tag,
                                               close_wrap_tag=close_wrap_tag)
        return (res[0].headline, res[0].fulltext)

    security.declarePublic('portal_type')
    portal_type = ComputedAttribute(
        lambda self: self._getDBProperty('portal_type'), 1)
    # FIXME: COMMENTED until such time as we want to have a different portal_type for ModuleView
    # see also Install.py
    #portal_type = ComputedAttribute(lambda self: "Published%s" % self._getDBProperty('portal_type'), 1)

    security.declarePublic('name')
    name = ComputedAttribute(lambda self: self._getDBProperty('name'), 1)
    title = name

    security.declarePublic('version')
    version = ComputedAttribute(lambda self: self._getDBProperty('version'), 1)

    security.declarePublic('doctype')
    doctype = ComputedAttribute(lambda self: self._getDBProperty('doctype'), 1)

    security.declarePublic('abstract')
    abstract = ComputedAttribute(lambda self: self._getDBProperty('abstract'),
                                 1)

    security.declarePublic('license')
    license = ComputedAttribute(lambda self: self._getDBProperty('license'), 1)

    security.declarePublic('created')
    created = ComputedAttribute(lambda self: self._getDBProperty('created'), 1)

    security.declarePublic('revised')
    revised = ComputedAttribute(lambda self: self._getDBProperty('revised'), 1)

    security.declarePrivate('ident')
    ident = ComputedAttribute(lambda self: self._getDBProperty('ident'), 1)

    security.declarePublic('keywords')
    keywords = ComputedAttribute(getKeywords, 1)

    security.declarePublic('authors')
    authors = ComputedAttribute(lambda self: self._getDBProperty('authors'), 1)

    security.declarePublic('parentAuthors')
    parentAuthors = ComputedAttribute(
        lambda self: self._getDBProperty('parentAuthors'), 1)

    security.declarePublic('maintainers')
    maintainers = ComputedAttribute(
        lambda self: self._getDBProperty('maintainers'), 1)

    security.declarePublic('licensors')
    licensors = ComputedAttribute(
        lambda self: self._getDBProperty('licensors'), 1)

    security.declarePublic('submitter')
    submitter = ComputedAttribute(
        lambda self: self._getDBProperty('submitter'), 1)

    security.declarePublic('message')
    message = ComputedAttribute(lambda self: self._getDBProperty('submitlog'),
                                1)

    security.declarePublic('_links')
    _links = ComputedAttribute(lambda self: self.getContextLinks(), 1)

    security.declarePublic('language')
    language = ComputedAttribute(lambda self: self._getDBProperty('language'),
                                 1)

    security.declarePublic('subject')
    subject = ComputedAttribute(lambda self: self._getDBProperty('subject'), 1)

    security.declarePublic('roles')
    roles = ComputedAttribute(lambda self: self._getDBProperty('roles'), 1)

    # collection attributes, which don't matter here, but set to make sure the catalog doesn't pick something else up
    # FIXME: a more general way to do this (probably defining all index/metadata names on the Repository object) when
    # types become more flexible.
    code = None
    collectionType = None
    institution = None
    instructor = None

    security.declarePrivate('getFile')

    def getFile(self, name):
        """Retrieve a module file from DB"""
        db_str = getattr(self, self.portal_moduledb.db).connection_string

        mf = ModuleFile(modid=self.objectId,
                        version=self.version,
                        name=name,
                        db_connect=db_str,
                        parent=self)
        try:
            # for when file is fetched on its own, we want to say some things about it;
            # we certainly don't want to over-ride the callers' response settings.
            response = self.REQUEST.RESPONSE
            if not response.headers.has_key('content-length'):
                response.setHeader('Content-Length', len(mf))
            content_type = mf.content_type()
            if content_type and not response.headers.has_key('content-type'):
                response.setHeader('Content-Type', content_type)
            if not response.headers.has_key('last-modified'):
                self._setLastModHeader()
            self.REQUEST.RESPONSE.setHeader(
                'Cache-Control',
                'max-age=0, s-maxage=31536000, public, must-revalidate')
        except AttributeError:
            pass
        return mf

    security.declarePrivate('getFileSize')

    def getFileSize(self, name):
        """Retrieve a module file size from DB"""
        res = self.portal_moduledb.sqlGetModuleFileSize(id=self.objectId,
                                                        version=self.version,
                                                        filename=name)

        if len(res) == 1:
            filesize = res[0][0]
            return filesize
        else:
            raise KeyError, "No such file %s/%s/%s" % (self.objectId,
                                                       self.version, name)

    security.declarePrivate('getDoctype')

    def getDoctype(self):
        """Get module's document type"""
        return self._getDBProperty('doctype')

    security.declarePublic('getContextLinks')

    def getContextLinks(self, context=None):
        """Return a list of links for this module"""
        if context:
            # Assume context is the URL of a collection
            # FIXME: this isn't very robust...
            (scheme, netloc, path, params, query, fragment) = urlparse(context)
            portal_url = getToolByName(self, 'portal_url')
            portal_path = portal_url.getPortalPath()
            # if path includes portal path strip it out
            if path.startswith(portal_path):
                path = path[len(portal_path):]
            # Strip off the leading slash so the traversal will be done
            # from portal root, not Zope root
            if path.startswith('/'):
                path = path[1:]
            course = portal_url.getPortalObject().restrictedTraverse(path)
            lm_links = course.getContainedObject(self.objectId).getLinks()
        else:
            lm_links = self.getLinks()

        links = [{
            'url': l.target,
            'title': l.title,
            'type': l.category,
            'strength': l.strength
        } for l in lm_links]
        return links

    security.declarePublic('getLinks')

    def getLinks(self):
        """Return a list of links for this module"""
        # Get versioned URL relative to root
        urltool = getToolByName(self, 'portal_url')
        source = '/'.join(('', ) + urltool.getRelativeContentPath(self)[:-1] +
                          (self.version, ''))  # '/content/m9000/2.34/'
        return self.portal_linkmap.searchLinks(source)

    security.declarePublic('getParent')

    def getParent(self):
        id = self._getDBProperty('parent_id')
        version = self._getDBProperty('parent_version')
        if id:
            return self.getRhaptosObject(id, version)

    security.declarePrivate('getVersion')

    def getVersion(self):
        """Return version string or None if version is 'latest'"""
        if self.id == 'latest':
            return None
        else:
            return self.id

    security.declarePublic('source')

    def source(self):
        """Get module source"""
        self.REQUEST.RESPONSE.setHeader('Content-Type', "application/xml")
        self._setLastModHeader()
        if self.REQUEST.REQUEST_METHOD == 'HEAD': return
        return self.normalize()

    security.declarePrivate('default')

    def default(self):
        """Make render() the default method for viewing ZRhaptosModules"""
        REQUEST = self.REQUEST
        request_format = REQUEST.get('format', '')
        if request_format == 'pdf' or request_format == 'epub':
            self._setLastModHeader()
            if REQUEST.REQUEST_METHOD == 'HEAD':
                REQUEST.RESPONSE.setHeader(
                    'Cache-Control',
                    'max-age=0, s-maxage=31536000, public, must-revalidate')
                return  # HEAD short-circuiting

            if self.id == 'latest':  # Redirect to specific version: 302 since it'll change w/ each publish
                path = self.REQUEST.URL2 + '/' + self.version
                path = path + '/?' + self.REQUEST.QUERY_STRING
                self.REQUEST.RESPONSE.redirect(path, status=302)
                return

            if self.testIfModSince():
                #True is 'not modified'; status code 304 is also 'not modified'
                REQUEST.RESPONSE.setHeader(
                    'Cache-Control',
                    'max-age=0, s-maxage=31536000, public, must-revalidate')
                REQUEST.RESPONSE.setStatus(304)
                return

            if request_format == 'pdf':
                file = self.downloadPDF()
            else:
                file = self.downloadEPUB()
            if file:
                REQUEST.RESPONSE.setHeader(
                    'Cache-Control',
                    'max-age=0, s-maxage=31536000, public, must-revalidate')
            return file
        elif request_format == 'offline':
            return self.downloadOfflineZip()
        else:
            resp = self.module_render()

            if REQUEST.RESPONSE.getStatus() in (301, 302):
                return resp

            pcs = self.portal_cache_settings
            view = 'module_render'
            member = pcs.getMember()
            rule, header_set = pcs.getRuleAndHeaderSet(REQUEST, self, view,
                                                       member)
            if header_set is not None:
                expr_context = rule._getExpressionContext(REQUEST,
                                                          self,
                                                          view,
                                                          member,
                                                          keywords={})
            else:
                expr_context = None
            _setCacheHeaders(self, {}, rule, header_set, expr_context)
            return resp

    security.declarePrivate('testIfModSince')

    def testIfModSince(self):
        """Test if the current REQUEST has an 'If-Modified-Since' header, and if so, test it. 
        Returns True for 'not modified'. False means either 'was modified' or 'no header'"""

        ifmodsince = self.REQUEST.get_header('If-Modified-Since', None)
        try:
            if ifmodsince:
                ifmodsince = DateTime(ifmodsince)
        except DTSyntaxError:
            ifmodsince = None  # bad date, just ignore, per spec. See also Zope's OFS/Image.py

        # short circuit for if-modified-since: spec recommends clients send value from
        # previous Modified header, so the = case could be significant
        return bool(ifmodsince and self.revised <= ifmodsince)

    def HEAD(self, REQUEST, RESPONSE):
        """Retrieve resource information without a response body."""
        SimpleItem.HEAD(self, REQUEST, RESPONSE)
        if self.REQUEST.get('format', '') == 'pdf':
            self._setLastModHeader()

    security.declarePublic('normalize')

    def normalize(self):
        """Get modules's normalized source"""
        return self.getDefaultFile().normalize()

    security.declarePrivate('downloadOfflineZip')

    def downloadOfflineZip(self):
        """Returns a PDF version of the module
        return the ePub file from the PrintTool storage, if possible.
        if not possible, returns nothing.

        Returns:
            EPUB file or nothing
        """
        request = self.REQUEST
        module_id = self.objectId
        module_version = self.version
        ptool = getToolByName(self, 'rhaptos_print')

        file = ptool.getFile(module_id, module_version, 'offline.zip')
        bIsOfflineZipFileCached = (file is not None and file.get_size() > 0)
        if not bIsOfflineZipFileCached:
            file = None

        if file is not None:
            offlinezipfilename = '%s_%s_offline.zip' % (module_id,
                                                        module_version)
            request.RESPONSE.setHeader('Content-Type', 'application/zip')
            request.RESPONSE.setHeader(
                'Content-Disposition',
                'attachment; filename=%s' % offlinezipfilename)

        return file

    security.declarePrivate('downloadEPUB')

    def downloadEPUB(self):
        """Returns a PDF version of the module
        return the ePub file from the PrintTool storage, if possible.
        if not possible, returns nothing.

        Returns:
            EPUB file or nothing
        """
        request = self.REQUEST
        module_id = self.objectId
        module_version = self.version
        ptool = getToolByName(self, 'rhaptos_print')

        file = ptool.getFile(module_id, module_version, 'epub')
        bIsEpubFileCached = (file is not None and file.get_size() > 0)
        if not bIsEpubFileCached:
            file = None

        if file is not None:
            epubfilename = '%s_%s.epub' % (module_id, module_version)
            request.RESPONSE.setHeader('Content-Type', 'application/epub+zip')
            request.RESPONSE.setHeader(
                'Content-Disposition',
                'attachment; filename=%s' % epubfilename)

        return file

    security.declarePrivate('downloadPDF')

    def downloadPDF(self):
        """Returns a PDF version of the module
        Checks for status in RhaptosPrintTool.  If it is 'failed', method returns
        if it is succeeded, tries to get PDF from RhaptosPrintTool.
        If is not there, the PDF is generated on-demand and added to RhaptosPrintTool

        Returns:
            PDF file or nothing
        """
        data = None
        fs_tool = getToolByName(self, 'portal_fsimport')
        printTool = getToolByName(self, 'rhaptos_print')
        printStatus = printTool.getStatus(self.objectId, self.version, 'pdf')
        if printStatus == 'failed':
            return
        else:
            if printStatus != None and printStatus != '':
                data = printTool.getFile(self.objectId, self.version, 'pdf')
            if data == None and printStatus != 'locked':
                wd = tempfile.mkdtemp()
                export = self.module_export_cnxml()
                export_file = open(os.path.join(wd, 'export.cnxml'), 'wb')
                if type(export) is unicode:
                    export_file.write(export.encode('utf-8'))
                else:
                    export_file.write(export)
                export_file.close()

                files = self.objectIds()
                for fname in files:
                    #FIXME should be someway to offload the file-from-db-to-filesystem
                    export = self.getFile(fname)
                    export_file = open(os.path.join(wd, fname), 'wb')
                    export_file.write(str(export))
                    export_file.close()

                pdf_tool = getToolByName(self, 'portal_pdflatex')

                params = {}

                data = pdf_tool.convertFSDirToPDF(wd, 'export.cnxml', **params)
                printTool.setFile(self.objectId, self.version, 'pdf', data)
                printTool.setStatus(self.objectId, self.version, 'pdf',
                                    'succeeded')

                shutil.rmtree(wd)

        self.REQUEST.RESPONSE.setHeader('Content-Type', 'application/pdf')
        self.REQUEST.RESPONSE.setHeader(
            'Content-Disposition',
            'attachment; filename=%s.pdf' % self.objectId)

        return data

    security.declarePublic('checkout')

    def checkout(self, container, skipfiles=None):
        """Checkout a copy of the module"""

        if skipfiles is None:
            skipfiles = ['index.cnxml.html', 'index.html.cnxml']

        files = self.objectIds()
        for fname in files:
            if fname not in skipfiles:
                self._createFile(fname, self.getFile(fname).read(), container)

    def _createFile(self, name, body, container):
        """Create a file from a name and bits into the specified ZODB container"""

        typ, encoding = mimetypes.guess_type(name)

        #registry = getToolByName(self, 'content_type_registry')
        #if not registry:
        #    portal_type = 'File'
        #portal_type = registry.findTypeName(name, typ, body) or 'File'
        if name == self.getDefaultFilename():
            portal_type = "CNXML Document"
        else:
            portal_type = 'UnifiedFile'

        content = getattr(container.aq_base, name, None)
        if not content:
            getToolByName(self, 'portal_types').constructContent(portal_type,
                                                                 container,
                                                                 name,
                                                                 file=body)
        else:
            # we may have existing index.cnxmls, for example
            content.update_data(body)

    security.declarePublic('getDefaultFilename')

    def getDefaultFilename(self):
        """Return filename used in the default 'view' of this module"""
        return 'index.cnxml'

    security.declarePublic('getDefaultFile')

    def getDefaultFile(self):
        """Return the file object used in the default 'view' of this module"""
        name = self.getDefaultFilename()
        content = self.getFile(name).read()
        portal_type = CNXMLFile
        f = portal_type(name, '', content, None, 'text/xml').__of__(self)
        return f

    security.declarePublic('SearchableText')

    def SearchableText(self):
        """Return the text of the module for searching"""
        content = self.getDefaultFile().getSource()
        bare = XMLService.transform(content, baretext)
        return bare

    security.declarePublic('objectIds')

    def objectIds(self):
        """Return the list of files in this module"""
        res = self.portal_moduledb.sqlGetModuleFilenames(id=self.objectId,
                                                         version=self.version)
        if res:
            files = [r['filename'] for r in res]
        else:
            files = []
        return files

    security.declarePublic('getAboutActions')

    def getAboutActions(self):
        return [{
            'id': 'module_render',
            'url': '.',
            'name': 'View'
        }, {
            'id': 'about',
            'url': 'about',
            'name': 'About'
        }, {
            'id': 'history',
            'url': 'history',
            'name': 'History'
        }, {
            'id': 'print',
            'url': '?format=pdf',
            'name': 'Print'
        }]

    security.declarePublic('rating')

    def rating(self):
        # xxx: it seems impossibly difficult to just delegate to the wrapped
        # object.
        res = self.portal_moduledb.sqlGetRating(moduleid=self.objectId,
                                                version=self.version)
        if not res:
            return 0.0
        totalrating = res[0].totalrating
        votes = res[0].votes
        if votes == 0:
            return 0.0
        return round(totalrating * 1.0 / votes, 1)

    security.declarePublic('numberOfRatings')

    def numberOfRatings(self):
        # xxx: it seems impossibly difficult to just delegate to the wrapped
        # object.
        res = self.portal_moduledb.sqlGetRating(moduleid=self.objectId,
                                                version=self.version)
        if not res:
            return 0
        return res[0].votes

    # ---- Local role manipulations to allow group memners access

    security.declarePrivate('_getLocalRoles')

    def _getLocalRoles(self):
        """Query the database for the local roles in this workgroup"""
        # Give all maintainers the 'Maintainer' role
        dict = {}
        for m in self.maintainers:
            dict[m] = ['Maintainer']
        return dict

    __ac_local_roles__ = ComputedAttribute(_getLocalRoles, 1)

    def manage_addLocalRoles(self, userid, roles, REQUEST=None):
        """Set local roles for a user."""
        pass

    def manage_setLocalRoles(self, userid, roles, REQUEST=None):
        """Set local roles for a user."""
        pass

    def manage_delLocalRoles(self, userids, REQUEST=None):
        """Remove all local roles for a user."""
        pass

    # Compatibility with CMF
    security.declarePublic('getIcon')

    def getIcon(self, *args):
        """CMF Combatibility method"""
        return self.icon

    security.declarePublic('enqueue')

    def enqueue(self):
        """Add module to queue tool to recreate module export zip, 
           epub and offline HTML
           
           returns string message
        """
        qtool = getToolByName(self, 'queue_tool')
        key = "modexport_%s" % self.objectId
        dictRequest = {
            "id": self.objectId,
            "version": self.version,
            "serverURL": self.REQUEST['SERVER_URL']
        }
        script_location = 'SCRIPTSDIR' in os.environ and os.environ[
            'SCRIPTSDIR'] or '.'
        qtool.add(
            key, dictRequest,
            "%s/create_and_store_pub_module_export.zctl" % script_location)
        return "modexport enqueued"

    # Set default roles for these permissions
    security.setPermissionDefault('Edit Rhaptos Object',
                                  ('Manager', 'Owner', 'Maintainer'))