1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Plinn - http://plinn.org #
4 # © 2007-2014 Benoît Pin <pin@cri.ensmp.fr> #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 """ Portal attachment management tool.
26 from AccessControl
import ClassSecurityInfo
27 from Acquisition
import aq_base
28 from Globals
import InitializeClass
29 from OFS
.SimpleItem
import SimpleItem
30 from OFS
.Folder
import Folder
31 from OFS
.Image
import cookId
32 from Products
.Photo
.blobbases
import File
33 from zExceptions
import Unauthorized
34 from zExceptions
import BadRequest
35 from Products
.Photo
import Photo
36 from Products
.CMFCore
.utils
import UniqueObject
, getToolByName
, getUtilityByInterfaceName
37 from Products
.CMFCore
.permissions
import ModifyPortalContent
38 from Products
.CMFCore
.exceptions
import AccessControl_Unauthorized
39 from Products
.Plinn
.utils
import makeValidId
41 from urllib
import unquote
42 from cgi
import escape
43 from ZServer
import LARGE_FILE_THRESHOLD
44 from webdav
.interfaces
import IWriteLock
45 from webdav
.common
import Locked
46 from webdav
.common
import PreconditionFailed
47 from zope
.contenttype
import guess_content_type
49 from libxml2
import HTML_PARSE_RECOVER
, HTML_PARSE_NOERROR
, HTML_PARSE_NOWARNING
50 from libxml2
import htmlReadDoc
51 PARSE_OPTIONS
= HTML_PARSE_RECOVER
+ HTML_PARSE_NOERROR
+ HTML_PARSE_NOWARNING
53 class AttachmentTool( UniqueObject
, SimpleItem
):
54 """ Links attachment objects to contents.
57 id = 'portal_attachment'
58 meta_type
= 'Attachment Tool'
59 manage_options
= SimpleItem
.manage_options
61 security
= ClassSecurityInfo()
63 security
.declarePublic('getAttachmentsFor')
64 def getAttachmentsFor(self
, content
):
65 """getAttachmentsFor returns attachments container of content
67 if getattr( aq_base(content
), 'attachments', None ) is None :
68 self
._createAttachmentContainerFor
(content
)
70 return content
.attachments
72 security
.declarePrivate('_createAttachmentContainerFor')
73 def _createAttachmentContainerFor(self
, content
):
74 """_createAttachmentContainerFor documentation
77 content
.attachments
= AttachmentContainer()
79 security
.declarePublic('uploadAttachmentFor')
80 def uploadAttachmentFor(self
, content
, file, title
='', typeName
='File') :
81 "upload attachment inside content's attachment folder."
83 mtool
= getToolByName(self
, 'portal_membership')
84 if not mtool
.checkPermission(ModifyPortalContent
, content
) :
85 raise AccessControl_Unauthorized
87 utool
= getToolByName(self
, 'portal_url')
88 portal
= utool
.getPortalObject()
90 attachments
= self
.getAttachmentsFor(content
)
91 dummy
, title
= cookId('', title
, file)
92 id = makeValidId(attachments
, title
)
94 if typeName
== 'Photo':
95 thumbSize
= {'thumb_height' : portal
.getProperty('thumb_size', 192),
96 'thumb_width' : portal
.getProperty('thumb_size', 192)}
97 fileOb
= Photo(id, title
, file, **thumbSize
)
98 elif typeName
== 'File' :
99 fileOb
= File(id, title
, '')
100 fileOb
.manage_upload(file)
102 raise AccessControl_Unauthorized
104 content
.attachments
._setObject
(id, fileOb
)
105 fileOb
= getattr(content
.attachments
, id)
110 InitializeClass( AttachmentTool
)
113 class AttachmentContainer (Folder
):
115 meta_type
= 'Attachment container'
116 security
= ClassSecurityInfo()
119 self
.id = 'attachments'
121 security
.declarePrivate('checkIdAvailable')
122 def checkIdAvailable(self
, id):
131 security
.declareProtected(ModifyPortalContent
, 'put_upload')
132 def put_upload(self
, REQUEST
, RESPONSE
):
133 """ Upload a content thru webdav put method.
134 The default behavior (NullRessource.PUT + PortalFolder.PUT_factory)
135 disallow files names with '_' at the begining.
138 self
.dav__init(REQUEST
, RESPONSE
)
139 fileName
= unquote(REQUEST
.getHeader('X-File-Name', ''))
140 validId
= makeValidId(self
, fileName
, allow_dup
=True)
142 ifhdr
= REQUEST
.get_header('If', '')
143 if self
.wl_isLocked():
145 self
.dav__simpleifhandler(REQUEST
, RESPONSE
, col
=1)
149 raise PreconditionFailed
151 if int(REQUEST
.get('CONTENT_LENGTH') or 0) > LARGE_FILE_THRESHOLD
:
152 file = REQUEST
['BODYFILE']
153 body
= file.read(LARGE_FILE_THRESHOLD
)
156 body
= REQUEST
.get('BODY', '')
158 typ
=REQUEST
.get_header('content-type', None)
160 typ
, enc
=guess_content_type(validId
, body
)
162 if self
.checkIdAvailable(validId
) :
163 if typ
.startswith('image/') :
164 utool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool')
165 portal
= utool
.getPortalObject()
166 thumbSize
= {'thumb_height' : portal
.getProperty('thumb_size', 192),
167 'thumb_width' : portal
.getProperty('thumb_size', 192)}
168 ob
= Photo(validId
, fileName
, '', **thumbSize
)
170 ob
= File(validId
, fileName
, '')
172 self
._setObject
(validId
, ob
)
176 ob
= self
._getOb
(validId
)
178 # We call _verifyObjectPaste with verify_src=0, to see if the
179 # user can create this type of object (and we don't need to
180 # check the clipboard.
182 self
._verifyObjectPaste
(ob
.__of
__(self
), 0)
184 sMsg
= 'Unable to create object of class %s in %s: %s' % \
185 (ob
.__class
__, repr(self
), sys
.exc_info()[1],)
186 raise Unauthorized
, sMsg
188 ob
.PUT(REQUEST
, RESPONSE
)
189 RESPONSE
.setStatus(httpRespCode
)
190 RESPONSE
.setHeader('Content-Type', 'text/xml;;charset=utf-8')
191 if ob
.meta_type
== 'Blob File' :
192 return '<element id="%s" title="%s"/>' % (ob
.getId(), escape(ob
.title_or_id()))
193 elif ob
.meta_type
== 'Photo' :
194 width
, height
= ob
.getResizedImageSize(size
=(310, 310))
195 return '<element src="%(src)s" title="%(title)s" width="%(width)d" height="%(height)d"/>' % \
196 {'src' : 'attachments/%s/getResizedImage?size=%d_%d' % (ob
.getId(), width
, height
),
197 'title' : escape(ob
.title_or_id()),
202 security
.declareProtected(ModifyPortalContent
, 'removeUnusedAttachments')
203 def removeUnusedAttachments(self
, html
) :
204 html
= '<div>%s</div>' % html
205 doc
= htmlReadDoc(html
, '', None, PARSE_OPTIONS
)
208 hrefs
= doc
.xpathEval('//a/@href')
209 for href
in [a
.get_content() for a
in hrefs
] :
210 if href
.startswith('attachments/') :
211 used
[href
[len('attachments/'):]] = True
213 srcs
= doc
.xpathEval('//img/@src')
214 for src
in [a
.get_content() for a
in srcs
] :
215 if src
.startswith('attachments/') :
216 parts
= src
.split('/')
218 used
[parts
[1]] = True
220 unused
= [id for id in self
.objectIds() if not used
.has_key(id)]
222 self
.manage_delObjects(unused
)
224 InitializeClass(AttachmentContainer
)