X-Git-Url: https://svn.cri.ensmp.fr/git/Plinn.git/blobdiff_plain/99afe0ce8700bf835ff7bbedf77bb46e58f996d8..e932032bdbb367869cd8c7346a98f6b7660c41dd:/AttachmentTool.py diff --git a/AttachmentTool.py b/AttachmentTool.py index aea969b..87535c7 100644 --- a/AttachmentTool.py +++ b/AttachmentTool.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- ####################################################################################### -# Plinn - http://plinn.org # -# Copyright (C) 2007 Benoît PIN # -# # -# 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 # +# # +# 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_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 '' % (ob.getId(), escape(ob.title_or_id())) + elif ob.meta_type == 'Photo' : + width, height = ob.getResizedImageSize(size=(310, 310)) + return '' % \ + {'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 = '
%s
' % 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