# -*- 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.
+
+
-$Id: AttachmentTool.py 1261 2008-01-07 01:34:23Z pin $
-$URL: http://svn.cri.ensmp.fr/svn/Plinn/branches/CMF-2.1/AttachmentTool.py $
"""
from AccessControl import ClassSecurityInfo
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_height', 128),
- 'thumb_width' : portal.getProperty('thumb_width', 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