File:  [Repository] / basket / basket.py
Revision 1.15: download - view: text, annotated - select for diffs - revision graph
Tue Apr 21 07:57:00 2009 UTC (15 years ago) by dwinter
Branches: MAIN
CVS tags: HEAD
error in basket and others

###objects to store and manage objects in baskets

import os
import os.path
import urllib
import cgi


from OFS.OrderedFolder import OrderedFolder
from OFS.SimpleItem import SimpleItem
from OFS.Image import File

from AccessControl import ClassSecurityInfo
from AccessControl.User import UserFolder
from Globals import InitializeClass
from Globals import DTMLFile
import Globals
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PageTemplates.PageTemplate import PageTemplate
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
from Globals import Persistent, package_home
from Acquisition import Implicit
from Products.ZCatalog.CatalogPathAwareness import CatalogAware

from groups import manage_addGroupFolder

from Products.ECHO_content.ECHO_helpers import unicodify

refBasis="http://vlp.mpiwg-berlin.mpg.de/references?id=%s" 

basketMetatypes=['BasketXRef','BasketInternalLink','BasketExternalLink','BasketText','BasketFile']

#basis url for references

publicationStatusList=['private','hidden','open_intern','open','group'] 
# private only access for the owner, restricted only for users in accessGroup, open open for everyone, published occurs in basket list

class BasketBasis(OrderedFolder):
    """Basis class for BasketFolder and Baskets"""
    
    security=ClassSecurityInfo()
    
    def content_html(self,type):
        """generische ausgabe des objectes als html"""
        if hasattr(self,type+"_template"):
            obj=getattr(self,type+"_template")
            return obj()
        else:
            pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','%s_template_standard.zpt'%type)).__of__(self)
            pt.content_type="text/html"
            return pt()
    
    def checkPermission(self,modus):
        """check permission"""
        if modus=='open':
            return True
        elif modus=='private':
            return self.groupFolder.isMemberOf('admin')
        elif modus=='admin':
            return self.groupFolder.isMemberOf('admin')
        elif modus=='edit':
            return (self.groupFolder.isMemberOf('editor') or self.groupFolder.isMemberOf('admin'))
        elif modus=='publish':
            return self.groupFolder.isMemberOf('publish')
        elif modus=='authorized':
            if self.getActualUserName().lower() == "anonymous user":
                return False
            else:
                return True
            
     
    def getNewId(self):
        """createIds"""
        last=getattr(self,'last',0)
        last +=1
        while len(self.ZopeFind(self,obj_ids=[str(last)]))>0:
            last+=1
    
        return last
    
    def addBasketFile(self,REQUEST=None,fileUpload=None,comment=None,title=""):
        """add a file to the basket"""
        if fileUpload==None:
            pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','addBasketFile.zpt')).__of__(self)
            pt.content_type="text/html"
            return pt()
        else:
            id=self.getNewId()
            if title=="":
                title=fileUpload.filename
                
            manage_addBasketFile(self,id,title,comment,fileUpload)
            if REQUEST:
                REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket")
    
    def addBasketText(self,REQUEST=None):
        """add a text"""
        id=self.getNewId()
        manage_addBasketText(self,str(id),'','',RESPONSE=None)
        if REQUEST:
            REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket#"+str(id))
    
    def addBasketExternalLink(self,REQUEST=None):
        """add an external link"""
        id=self.getNewId()
        
        manage_addBasketExternalLink(self,str(id),"","","","",RESPONSE=None)
        if REQUEST:
            REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket#"+str(id))
        
    def deleteObject(self,id,REQUEST=None):
        """delete object"""
        self.manage_delObjects([id])
        if REQUEST:
            REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket")
    
    def moveUp(self,id,REQUEST=None):
        """move id one up"""
        self.moveObjectsUp([id], 1)
        if REQUEST:
            REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket#"+id)
    
    def moveDown(self,id,REQUEST=None):
        """move id one up"""
        self.moveObjectsDown([id], 1)
        if REQUEST:
            REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket#"+id)
            
    def moveTop(self,id,REQUEST=None):
        """move to top"""
        self.moveObjectsToTop([id])
        if REQUEST:
            REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket#"+id)
  
    def moveBottom(self,id,REQUEST=None):
        """move to top"""
        self.moveObjectsToBottom([id])
        if REQUEST:
            REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket#"+id)
    
    security.declareProtected('View','getOwner')
          
    def getOwner(self):
        """get Owner as string"""
        return unicodify(self.owner)
    
    def getTitle(self):
        """get title"""
        return unicodify(self.title)
    
    def getActualUserName(self):
        """get username of actual user"""
        return self.REQUEST['AUTHENTICATED_USER'].getUserName()
        
    def getObjects(self):
        """"get objects"""
        return self.objectValues()
        #return [getattr(self,x['id']) for x in self._objects]
   
    def numberOfObjects(self):
        """anzahl der elemente im basket"""
        return len(self._objects)

    
class BasketFolder(BasketBasis):
    """Folder for Baskets"""
    
    meta_type="Basket Folder"
    
    #security=ClassSecurityInfo()
    
    def getSets(self,mode='open'):
        """get sets, depending on mode selected"""
        ret=[]
        
        if mode=='open':
            for object in self.getObjects():
           
                if object.publicationStatus=='open':
                    ret.append(object)
            return ret
        elif mode=='open_intern':
            print "open_intern"
            if self.checkPermission('authorized'):
                for object in self.getObjects():
                    if object.publicationStatus=='open_intern' or object.publicationStatus=='open':
                        ret.append(object)
                return ret
            else:
                return ret
        elif mode=='private':
            for object in self.getObjects():
                if object.groupFolder.isMemberOf('admin') or object.groupFolder.isMemberOf('editor'):
                    ret.append(object)
            return ret
        elif mode=='groups':
            for object in self.getObjects():
                if object.groupFolder.isMemberOf('reader') or object.groupFolder.isMemberOf('editor') or object.groupFolder.isMemberOf('admin'):
                    ret.append(object)
                return ret

    def getCurrentBasket(self):
        """select this basket for further work"""
     
        id=self.REQUEST.cookies.get('vlp_basket',None)
	if id:	
		return getattr(self,id,None)
  	else:
		return None
        
         
    def index_html(self,mode='open'):
        """generische ansicht"""

        #check if user has right for other modi
        
        if hasattr(self,"BasketFolder_template"):
            obj=getattr(self,"BasketFolder_template")
            return obj(mode=mode)
        else:
            pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','BasketFolder_template_standard.zpt')).__of__(self)
            pt.content_type="text/html"
            return pt(mode=mode)
  
    def __init__(self,id,title):
        """init basket folder"""
        
        self.id=id
        self.title=title
        
    def manageBasketFolder(self):
        """manage the basket"""
        
        if hasattr(self,"BasketFolder_manage_template"):
            obj=getattr(self,"BasketFolder_manage_template")
            return obj()
        else:
            pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','BasketFolder_manage_template_standard.zpt')).__of__(self)
            pt.content_type="text/html"
            return pt()
    
    def addBasket(self,title,RESPONSE=None):
        """add a new basket"""
        id=self.getNewId()
        username=self.getActualUserName()
        publicationStatus="private"
        manage_addBasket(self,str(id),title,"",username,publicationStatus,RESPONSE=None)
        newObj=getattr(self,str(id))
        #create a groupfolder
        
        manage_addGroupFolder(newObj,'groupFolder')
        
        #add current user to admin group of the new set
        
        newObj.groupFolder.addUserToGroups(str(self.REQUEST['AUTHENTICATED_USER']),['admin'])
        if RESPONSE:
            RESPONSE.redirect(newObj.absolute_url()+'/manageBasket')
        

def manage_addBasketFolderForm(self):
    """form for adding a basket"""
    pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','addBasketFolder.zpt')).__of__(self)
    return pt()

def manage_addBasketFolder(self,id,title,RESPONSE=None):
    """add a basket folder object"""

  
    newObj=BasketFolder(id,title)
    
    self._setObject(id,newObj)
    
    if RESPONSE is not None:
        RESPONSE.redirect('manage_main')
     
class Basket(BasketBasis):
    """Basket class"""
    
    meta_type="basket"
    
    security=ClassSecurityInfo()
    
    publicationStatusList=publicationStatusList
    
    manage_options=OrderedFolder.manage_options+(
        {'label':'manage main','action':'changeBasketForm'},
        )
    
 
    def saveButton(self,actualId):
        """return the save button"""
        ret="""
        <a class="editLink" style="cursor:pointer"
                    onClick="forms.changeSetContents.actualId.value='%s';forms.changeSetContents.submit();">save</a> -
    
        """%actualId
        return ret
    
    def manageUserRights_html(self):
        """manage user rights"""
        if hasattr(self,"Basket_manageUserRights_template"):
            obj=getattr(self,"Basket_manageUserRights_template")
            return obj()
        else:
            pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','BasketMain_Basket_manageUserRights_standard.zpt')).__of__(self)
            pt.content_type="text/html"
            return pt()
    
 
    def getBasketObjects(self):
        """get all basket objects"""
#         objs=self.getObjects()
#         ret=[]
#         for x in objs:
#             if x.meta_type in basketMetatypes:
#                ret.append(x)
#         return ret
        return self.objectValues(basketMetatypes)

    
    def checkRef(self,xref):
        """check if XRef is already in Basket"""
        
        founds=self.ZopeFind(self,obj_metatypes=["BasketXRef"])
        for found in founds:
            if self.xref==xref:
                return True
        return False
            
    def __init__(self,id,title,comment,owner,publicationStatus,shortDescription):
        '''
        init the basket
        @param id: id
        @param title: title of the basket
        @param comment: description of the basket
        @param shortDescription: short description of the basket for the overviewList
        @param owner: user object describing the owner
        @param publication_status: one of publicationStatus if restricted, optional parameter accessGroups has to be set
        '''
        
        self.id=id
        self.title=title
        self.comment=comment
        self.owner=owner
        self.publicationStatus=publicationStatus
        self.shortDescription=shortDescription
        
      
    def getComment(self):
        """get the comment"""
        return unicodify(self.comment)
        

    def getShortDescription(self):
        """get the short description"""
        return unicodify(self.shortDescription)

        
    security.declareProtected('Manage','changeBasketForm')
    def changeBasketForm(self):
        """form for changing the basket"""
        pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','changeBasket.zpt')).__of__(self)
        return pt()


    def changeBasket(self,title,comment,shortDescription,publicationStatus=None,username=None,accessGroups=None,RESPONSE=None,target=None):
        '''
        init the basket
        @param title: title of the basket
        @param comment: description of the basket
        @param owner: user object describing the owner
        @param publication_status: one of publicationStatus if restricted, optional parameter accessGroups has to be set
        @param aaccessGroups: default value is none, contains list of groups which are allowed to access this basket
        '''
         #TODO:what happens in username does not exists
        #find username
        owner=username
    
        self.title=title
        self.comment=comment
        if owner:
            self.owner=owner
        if publicationStatus:
            self.publicationStatus=publicationStatus
        if accessGroups:
            self.accessGroups=accessGroups
        self.shortDescription=shortDescription
    
        
        if RESPONSE is not None:
            if target:
                RESPONSE.redirect(target)
            else:
                RESPONSE.redirect('manage_main')
                
    def index_html(self):
        """generische ansicht"""

        if hasattr(self,"BasketMain_template"):
            obj=getattr(self,"BasketMain_template")
            return obj()
        else:
            pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','BasketMain_template_standard.zpt')).__of__(self)
            pt.content_type="text/html"
            return pt()
    
    def manageBasket(self):
        """manage the basket"""
        
        if hasattr(self,"BasketMain_manage_template"):
            obj=getattr(self,"BasketMain_manage_template")
            return obj()
        else:
            pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','BasketMain_manage_template_standard.zpt')).__of__(self)
            pt.content_type="text/html"
            return pt()
    
 
    def changeBasketComments(self,REQUEST):
        """Change comment of basket elements"""
        form=REQUEST.form

        for key in form.keys():
            splitted=key.split("_")
            objects=self.ZopeFind(self,obj_ids=[splitted[0]])
            if len(objects)>0:
                setattr(objects[0][1],splitted[1],form[key])

   
        if REQUEST:
            REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket#"+form.get('actualId',''))  
              

    def selectThisBasketAsCurrent(self,REQUEST=None):
        """select this basket for further work"""
        REQUEST.RESPONSE.setCookie('vlp_basket',self.getId(),path="/")
        
        if REQUEST:
            REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket")
        
   
    def generateNewObject(self,type,link,title='',comment='',REQUEST=None):
        """generate a new object in the basket"""
        
        if type=="xref":

            splitted=urllib.unquote(link).split("?")
            if len(splitted)>1:
                #assumes references id is contained in the url as parameter id=
                params=cgi.parse_qs(splitted[1])
                xref=params.get('id',None)
                return False
            else:
                #addumes link contains only the reference id
                xref=link

            id=self.getNewId()
            title=""

            if xref is None:
                #no parameter id
                return False
            manage_addBasketXRef(self,str(id),title,comment,xref)
        
        if REQUEST:
            #import random
            #rd=random.random()
            urlSplit=REQUEST['HTTP_REFERER'].split("?")
            if len(urlSplit)>1:
                parsed=cgi.parse_qs(urlSplit[1])
                parsed['-link']=link
                qs=urllib.urlencode(parsed,doseq=True)
                
            else:
                qs=""
                
            REQUEST.RESPONSE.redirect(urlSplit[0]+"?"+qs)
        else:
            return True

def manage_addBasketForm(self):
    """form for adding a basket"""
    pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','addBasket.zpt')).__of__(self)
    return pt()

def manage_addBasket(self,id,title,comment,username,publicationStatus,shortDescription="",accessGroups=None,RESPONSE=None):
    """add a basket object"""
    #TODO:what happens in username does not exists
    #find username
    owner=username
  
    newObj=Basket(id,title,comment,owner,publicationStatus,shortDescription)

    self._setObject(id,newObj)
    
    if RESPONSE is not None:
        RESPONSE.redirect('manage_main')
     
class BasketObject(SimpleItem):
    """basic class for baskets"""
    
    #security=ClassSecurityInfo()
    
    manage_options=SimpleItem.manage_options+(
        {'label':'manage main','action':'changeBasketBasisForm'},
        )
    
    def __init__(self,id,title,comment):
        '''
        init basket basis
        @param id: id
        @param title: title
        @param comment commentar:
        '''
        self.id=id
        self.title=title
        self.comment=comment

    def getTitle(self):
        """get the title"""
        return unicodify(self.title)

    def getComment(self):
        """get the comment"""
        return unicodify(self.comment)

        
    def changeBasketBasisForm(self):
        """form for changing the basket"""
        pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','changeBasketBasis.zpt')).__of__(self)
        return pt()
    
    def changeBasketBasis(self,title,comment,RESPONSE=None):
        '''
        init the basket
        @param title: title of the basket
        @param comment: description of the basket
        '''
        
        self.title=title
        self.comment=comment
        
        if RESPONSE is not None:
            RESPONSE.redirect('manage')
    
    def linkToObject(self):
        """generate url to open the resource, has to be implemented in the different objects, generic return None"""
        return None
    
    def content_html(self,type):
        """generische ausgabe des objectes als html"""
        if hasattr(self,type+"_template"):
            obj=getattr(self,type+"_template")
            return obj()
        else:
            pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','%s_template_standard.zpt'%type)).__of__(self)
            pt.content_type="text/html"
            return pt()

class BasketFile(BasketObject,File):
    """class for fileupload"""
    meta_type="BasketFile"
    
    def __init__(self,id,title,comment,content_type='',precondition=''):
        """init"""
        self.id=id
        self.title=title
        self.comment=comment
        self.content_type=content_type
        self.precondition=precondition
        
    
    def download(self):
        """download the file"""

        self.REQUEST.RESPONSE.setHeader("Content-Disposition","""attachement; filename=%s"""%self.title)
        self.REQUEST.RESPONSE.setHeader("Content-Type","application/octet-stream")
   
        self.content_type="application/octet-stream"
	try:
        	self.REQUEST.RESPONSE.write(self.data)
        except:
		try:
			self.REQUEST.RESPONSE.write(str(self.data))
		except:
			self.REQUEST.RESPONSE.write(repr(self.data))

    def upDateFile(self,fileUpload=None,comment=None,title="",REQUEST=None):
        """update file"""
        
        if fileUpload==None:
            pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','upDateBasketFile.zpt')).__of__(self)
            pt.content_type="text/html"
            return pt()
        else:
            
            if title=="":
                title=fileUpload.filename
            
            self.title=title    
            if fileUpload:
                self.manage_upload(fileUpload)
    
            if comment:
                self.comment=comment
                
            if REQUEST:
                REQUEST.RESPONSE.redirect(self.absolute_url()+"/manageBasket")
    
    def content_html(self):
        """format object as html fragment"""
        
        
        return BasketObject.content_html(self,"BasketFile")
    
def manage_addBasketFile(self,id,title,comment,fileUpload,content_type='',precondition='',REQUEST=None):
    """add a basket file"""
    
    id=str(id)
    title=str(title)
    content_type=str(content_type)
    precondition=str(precondition)

    #id, title = cookId(id, title, file)

    self=self.this()

    # First, we create the file without data:
    self._setObject(id, BasketFile(id,title,comment,content_type, precondition))

    # Now we "upload" the data.  By doing this in two steps, we
    # can use a database trick to make the upload more efficient.
    if fileUpload:
        self._getOb(id).manage_upload(fileUpload)
    if content_type:
        self._getOb(id).content_type=content_type

    if REQUEST is not None:
        REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
        
class BasketXRef(BasketObject):
    """class for internal references based on xrefs"""
    
    #security=ClassSecurityInfo()
    meta_type="BasketXRef"

    manage_options=BasketBasis.manage_options+(
        {'label':'manage xref','action':'changeBasketXRefForm'},
        )
    
    def changeBasketXRefForm(self):
        """form for changing the basket"""
        pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','changeBasketXRef.zpt')).__of__(self)
        return pt()
    
    def changeBasketXRef(self,xref,RESPONSE=None):
        '''
        change the basket
        @param xref: reference
        '''
        
        self.xref=xref
        
        if RESPONSE is not None:
            RESPONSE.redirect('manage')
      
    def linkToObject(self):
        """generate url to open the resource"""
        return refBasis%self.xref
    
    def content_html(self):
        """format object as html fragment"""
        
        type=self.xref[0:3]
        return BasketObject.content_html(self,"BasketXRef_%s"%type)
    
def manage_addBasketXRefForm(self):
    """form for adding a basket"""
    pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','addBasketXRef.zpt')).__of__(self)
    return pt()

def manage_addBasketXRef(self,id,title,comment,xref,RESPONSE=None):
    """add a basketXRef object"""
    
    
    newObj=BasketXRef(id,title,comment)
    newObj.xref=xref
    
    self._setObject(id,newObj)
    
    if RESPONSE is not None:
        RESPONSE.redirect('manage_main')
       
class BasketInternalLink(BasketObject):
    """class for internal referencens based on links"""
    
    #security=ClassSecurityInfo()
    meta_type="BasketInternalLink"
    
    manage_options=BasketObject.manage_options+(
        {'label':'manage internal link','action':'changeBasketInternalLinkForm'},
        )
 
    def changeBasketInternalLinkForm(self):
        """form for changing the basket"""
        pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','changeBasketInternalLink.zpt')).__of__(self)
        return pt()
    
    def changeBasketInternalLink(self,link,linkText,RESPONSE=None):
        '''
        change the Internallink
        @param link: reference
        @param linkText: reference
        '''
        
        self.link=link
        self.linkText=linkText
        
        if RESPONSE is not None:
            RESPONSE.redirect('manage')
    
    def content_html(self):
        """format object as html fragment"""
        
        return BasketObject.content_html(self,"BasketInternalLink")
    
    def linkToObject(self):
        """link to the object (internalLink)"""
        return self.link
    
def manage_addBasketInternalLinkForm(self):
    """form for adding a basket"""
    pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','addBasketInternalLink.zpt')).__of__(self)
    return pt()

def manage_addBasketInternalLink(self,id,title,comment,link,linkText,RESPONSE=None):
    """add a basketXRef object"""
    
    
    newObj=BasketInternalLink(id,title,comment)
    newObj.link=link
    newObj.linkText=linkText
    
    self._setObject(id,newObj)
    
    if RESPONSE is not None:
        RESPONSE.redirect('manage_main')
       
    
class BasketExternalLink(BasketObject):
    """class for external links"""
    
    #security=ClassSecurityInfo()
    meta_type="BasketExternalLink"
    
    manage_options=BasketObject.manage_options+(
        {'label':'manage internal link','action':'changeBasketExternalLinkForm'},
        )

    def getLinkText(self):
        """get the link text"""
        return unicodify(self.linkText)
 
    def changeBasketExternalLinkForm(self):
        """form for changing the basket"""
        pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','changeBasketExternalLink.zpt')).__of__(self)
        return pt()
    
    def changeBasketExternalLink(self,link,linkText,RESPONSE=None):
        '''
        change the Externallink
        @param link: reference
        @param linkText: reference
        '''
        
        self.link=link
        self.linkText=linkText
        
        if RESPONSE is not None:
            RESPONSE.redirect('manage')
    
    def content_html(self):
        """format object as html fragment"""
        
        return BasketObject.content_html(self,"BasketExternalLink")
    
    def linkToObject(self):
        """link to the object (externalLink)"""
        return self.link

def manage_addBasketExternalLinkForm(self):
    """form for adding a basket"""
    pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','addBasketExternalLink.zpt')).__of__(self)
    return pt()

def manage_addBasketExternalLink(self,id,title,comment,link,linkText,RESPONSE=None):
    """add a basket external link object"""
    
    newObj=BasketExternalLink(id,title,comment)
    newObj.link=link
    newObj.linkText=linkText
    
    self._setObject(id,newObj)
    
    if RESPONSE is not None:
        RESPONSE.redirect('manage_main')
       
    
class BasketText(BasketObject):
    """class for text elements in baskets"""
    
    #security=ClassSecurityInfo()
    meta_type="BasketText"

    def content_html(self):
        """format object as html fragment"""
        return "" # text has no content
        #return BasketBasis.content_html(self,"BasketText")
    
def manage_addBasketTextForm(self):
    """form for adding a basket"""
    pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt','addBasketText.zpt')).__of__(self)
    return pt()

def manage_addBasketText(self,id,title,comment,RESPONSE=None):
    """add a basketXRef object"""
    
    newObj=BasketText(id,title,comment)
    
    self._setObject(id,newObj)
    
    if RESPONSE is not None:
        RESPONSE.redirect('manage_main')

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>