clarification
[Plinn.git] / AttachmentTool.py
index a61b4e4..87535c7 100644 (file)
@@ -1,23 +1,23 @@
 # -*- coding: utf-8 -*-
 #######################################################################################
-#      Plinn - http://plinn.org                                                                                                                  #
-#      Copyright (C) 2007      Benoît PIN <benoit.pin@ensmp.fr>                                                         #
-#                                                                                                                                                                        #
-#      This program is free software; you can redistribute it and/or                                     #
-#      modify it under the terms of the GNU General Public License                                               #
-#      as published by the Free Software Foundation; either version 2                                    #
-#      of the License, or (at your option) any later version.                                                    #
-#                                                                                                                                                                        #
-#      This program is distributed in the hope that it will be useful,                                   #
-#      but WITHOUT ANY WARRANTY; without even the implied warranty of                                    #
-#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                     #
-#      GNU General Public License for more details.                                                                      #
-#                                                                                                                                                                        #
-#      You should have received a copy of the GNU General Public License                                 #
-#      along with this program; if not, write to the Free Software                                               #
-#      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.   #
+#   Plinn - http://plinn.org                                                          #
+#   © 2007-2014  Benoît Pin <pin@cri.ensmp.fr>                                        #
+#                                                                                     #
+#   This program is free software; you can redistribute it and/or                     #
+#   modify it under the terms of the GNU General Public License                       #
+#   as published by the Free Software Foundation; either version 2                    #
+#   of the License, or (at your option) any later version.                            #
+#                                                                                     #
+#   This program is distributed in the hope that it will be useful,                   #
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of                    #
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     #
+#   GNU General Public License for more details.                                      #
+#                                                                                     #
+#   You should have received a copy of the GNU General Public License                 #
+#   along with this program; if not, write to the Free Software                       #
+#   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.   #
 #######################################################################################
-""" Basic portal attachment management tool.
+""" Portal attachment management tool.
 
 
 
@@ -28,80 +28,197 @@ from Acquisition import aq_base
 from Globals import InitializeClass
 from OFS.SimpleItem import SimpleItem
 from OFS.Folder import Folder
-from OFS.Image import File, cookId
+from OFS.Image import cookId
+from Products.Photo.blobbases import File
+from zExceptions import Unauthorized
+from zExceptions import BadRequest
 from Products.Photo import Photo
-from Products.CMFCore.utils import UniqueObject, getToolByName
+from Products.CMFCore.utils import UniqueObject, getToolByName, getUtilityByInterfaceName
 from Products.CMFCore.permissions import ModifyPortalContent
 from Products.CMFCore.exceptions import AccessControl_Unauthorized
 from Products.Plinn.utils import makeValidId
 
+from urllib import unquote
+from cgi import escape
+from ZServer import LARGE_FILE_THRESHOLD
+from webdav.interfaces import IWriteLock
+from webdav.common import Locked
+from webdav.common import PreconditionFailed
+from zope.contenttype import guess_content_type
+
+from libxml2 import HTML_PARSE_RECOVER, HTML_PARSE_NOERROR, HTML_PARSE_NOWARNING
+from libxml2 import htmlReadDoc
+PARSE_OPTIONS = HTML_PARSE_RECOVER + HTML_PARSE_NOERROR + HTML_PARSE_NOWARNING
 
 class AttachmentTool( UniqueObject, SimpleItem):
-       """ Links attachment objects to contents.
-       """
-
-       id = 'portal_attachment'
-       meta_type = 'Attachment Tool'
-       manage_options = SimpleItem.manage_options
-
-       security = ClassSecurityInfo()
-
-       security.declarePublic('getAttachmentsFor')
-       def getAttachmentsFor(self, content):
-               """getAttachmentsFor returns attachments container of content
-               """
-               if getattr( aq_base(content), 'attachments', None ) is None :
-                       self._createAttachmentContainerFor(content)
-               
-               return content.attachments
-       
-       security.declarePrivate('_createAttachmentContainerFor')
-       def _createAttachmentContainerFor(self, content):
-               """_createAttachmentContainerFor documentation
-               """
-               
-               content.attachments = AttachmentContainer()
-       
-       security.declarePublic('uploadAttachmentFor')
-       def uploadAttachmentFor(self, content, file, title='', typeName='File') :
-               "upload attachment inside content's attachment folder."
-               
-               mtool = getToolByName(self, 'portal_membership')
-               if not mtool.checkPermission(ModifyPortalContent, content) :
-                       raise AccessControl_Unauthorized
-               
-               utool = getToolByName(self, 'portal_url')
-               portal = utool.getPortalObject()
-               
-               attachments = self.getAttachmentsFor(content)
-               dummy, title = cookId('', title, file)
-               id = makeValidId(attachments, title)
-               
-               if typeName == 'Photo':
-                       thumbSize = {'thumb_height'     : portal.getProperty('thumb_size', 128),
-                                                'thumb_width'  : portal.getProperty('thumb_size', 128)}
-                       fileOb = Photo(id, title, file, **thumbSize)
-               elif typeName == 'File' :
-                       fileOb = File(id, title, '')
-                       fileOb.manage_upload(file)
-               else :
-                       raise AccessControl_Unauthorized
-
-               content.attachments._setObject(id, fileOb)
-               fileOb = getattr(content.attachments, id)
-               return fileOb
-               
-               
+    """ Links attachment objects to contents.
+    """
+
+    id = 'portal_attachment'
+    meta_type = 'Attachment Tool'
+    manage_options = SimpleItem.manage_options
+
+    security = ClassSecurityInfo()
+
+    security.declarePublic('getAttachmentsFor')
+    def getAttachmentsFor(self, content):
+        """getAttachmentsFor returns attachments container of content
+        """
+        if getattr( aq_base(content), 'attachments', None ) is None :
+            self._createAttachmentContainerFor(content)
+        
+        return content.attachments
+    
+    security.declarePrivate('_createAttachmentContainerFor')
+    def _createAttachmentContainerFor(self, content):
+        """_createAttachmentContainerFor documentation
+        """
+        
+        content.attachments = AttachmentContainer()
+    
+    security.declarePublic('uploadAttachmentFor')
+    def uploadAttachmentFor(self, content, file, title='', typeName='File') :
+        "upload attachment inside content's attachment folder."
+        
+        mtool = getToolByName(self, 'portal_membership')
+        if not mtool.checkPermission(ModifyPortalContent, content) :
+            raise AccessControl_Unauthorized
+        
+        utool = getToolByName(self, 'portal_url')
+        portal = utool.getPortalObject()
+        
+        attachments = self.getAttachmentsFor(content)
+        dummy, title = cookId('', title, file)
+        id = makeValidId(attachments, title)
+        
+        if typeName == 'Photo':
+            thumbSize = {'thumb_height' : portal.getProperty('thumb_size', 192),
+                         'thumb_width'  : portal.getProperty('thumb_size', 192)}
+            fileOb = Photo(id, title, file, **thumbSize)
+        elif typeName == 'File' :
+            fileOb = File(id, title, '')
+            fileOb.manage_upload(file)
+        else :
+            raise AccessControl_Unauthorized
+
+        content.attachments._setObject(id, fileOb)
+        fileOb = getattr(content.attachments, id)
+        return fileOb
+        
+        
 
 InitializeClass( AttachmentTool )
 
 
 class AttachmentContainer (Folder):
-       
-       meta_type = 'Attachment container'
-       security = ClassSecurityInfo()
-       
-       def __init__(self):
-               self.id = 'attachments'
+    
+    meta_type = 'Attachment container'
+    security = ClassSecurityInfo()
+    
+    def __init__(self):
+        self.id = 'attachments'
+
+    security.declarePrivate('checkIdAvailable')
+    def checkIdAvailable(self, id):
+        try:
+            self._checkId(id)
+        except BadRequest:
+            return False
+        else:
+            return True
+
+
+    security.declareProtected(ModifyPortalContent, 'put_upload')
+    def put_upload(self, REQUEST, RESPONSE):
+        """ Upload a content thru webdav put method.
+            The default behavior (NullRessource.PUT + PortalFolder.PUT_factory)
+            disallow files names with '_' at the begining.
+        """
+
+        self.dav__init(REQUEST, RESPONSE)
+        fileName = unquote(REQUEST.getHeader('X-File-Name', ''))
+        validId = makeValidId(self, fileName, allow_dup=True)
+
+        ifhdr = REQUEST.get_header('If', '')
+        if self.wl_isLocked():
+            if ifhdr:
+                self.dav__simpleifhandler(REQUEST, RESPONSE, col=1)
+            else:
+                raise Locked
+        elif ifhdr:
+            raise PreconditionFailed
+
+        if int(REQUEST.get('CONTENT_LENGTH') or 0) > LARGE_FILE_THRESHOLD:
+            file = REQUEST['BODYFILE']
+            body = file.read(LARGE_FILE_THRESHOLD)
+            file.seek(0)
+        else:
+            body = REQUEST.get('BODY', '')
+
+        typ=REQUEST.get_header('content-type', None)
+        if typ is None:
+            typ, enc=guess_content_type(validId, body)
+
+        if self.checkIdAvailable(validId) :
+            if typ.startswith('image/') :
+                utool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool')
+                portal = utool.getPortalObject()
+                thumbSize = {'thumb_height' : portal.getProperty('thumb_size', 192),
+                             'thumb_width'  : portal.getProperty('thumb_size', 192)}
+                ob = Photo(validId, fileName, '', **thumbSize)
+            else :
+                ob = File(validId, fileName, '')
+            
+            self._setObject(validId, ob)
+            httpRespCode = 201
+        else :
+            httpRespCode = 200
+            ob = self._getOb(validId)
+
+        # We call _verifyObjectPaste with verify_src=0, to see if the
+        # user can create this type of object (and we don't need to
+        # check the clipboard.
+        try:
+            self._verifyObjectPaste(ob.__of__(self), 0)
+        except CopyError:
+             sMsg = 'Unable to create object of class %s in %s: %s' % \
+                    (ob.__class__, repr(self), sys.exc_info()[1],)
+             raise Unauthorized, sMsg
+
+        ob.PUT(REQUEST, RESPONSE)
+        RESPONSE.setStatus(httpRespCode)
+        RESPONSE.setHeader('Content-Type', 'text/xml;;charset=utf-8')
+        if ob.meta_type == 'Blob File' :
+            return '<element id="%s" title="%s"/>' % (ob.getId(), escape(ob.title_or_id()))
+        elif ob.meta_type == 'Photo' :
+            width, height = ob.getResizedImageSize(size=(310, 310))
+            return '<element src="%(src)s" title="%(title)s" width="%(width)d" height="%(height)d"/>' % \
+                {'src' : 'attachments/%s/getResizedImage?size=%d_%d' % (ob.getId(), width, height),
+                 'title' : escape(ob.title_or_id()),
+                 'width' : width,
+                 'height' : height
+                 }
+    
+    security.declareProtected(ModifyPortalContent, 'removeUnusedAttachments')
+    def removeUnusedAttachments(self, html) :
+        html = '<div>%s</div>' % html
+        doc = htmlReadDoc(html, '', None, PARSE_OPTIONS)
+        used = {}
+
+        hrefs = doc.xpathEval('//a/@href')
+        for href in [a.get_content() for a in hrefs] :
+            if href.startswith('attachments/') :
+                used[href[len('attachments/'):]] = True
+
+        srcs = doc.xpathEval('//img/@src')
+        for src in [a.get_content() for a in srcs] :
+            if src.startswith('attachments/') :
+                parts = src.split('/')
+                if len(parts) >=2 :
+                    used[parts[1]] = True
+        
+        unused = [id for id in self.objectIds() if not used.has_key(id)]
+        if unused :
+            self.manage_delObjects(unused)
 
 InitializeClass(AttachmentContainer)
\ No newline at end of file