+# -*- coding: utf-8 -*-
+##############################################################################
+# This module is based on OFS.Image originaly copyrighted as:
+#
+# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""Image object
+
+$Id: blobbases.py 949 2009-04-30 14:42:24Z pin $
+$URL: http://svn.luxia.fr/svn/labo/projects/zope/Photo/trunk/blobbases.py $
+"""
+
+import struct
+from warnings import warn
+from zope.contenttype import guess_content_type
+from Globals import DTMLFile
+from Globals import InitializeClass
+from OFS.PropertyManager import PropertyManager
+from AccessControl import ClassSecurityInfo
+from AccessControl.Role import RoleManager
+from AccessControl.Permissions import change_images_and_files
+from AccessControl.Permissions import view_management_screens
+from AccessControl.Permissions import view as View
+from AccessControl.Permissions import ftp_access
+from AccessControl.Permissions import delete_objects
+from webdav.common import rfc1123_date
+from webdav.Lockable import ResourceLockedError
+from webdav.WriteLockInterface import WriteLockInterface
+from OFS.SimpleItem import Item_w__name__
+from cStringIO import StringIO
+from Globals import Persistent
+from Acquisition import Implicit
+from DateTime import DateTime
+from OFS.Cache import Cacheable
+from mimetools import choose_boundary
+from ZPublisher import HTTPRangeSupport
+from ZPublisher.HTTPRequest import FileUpload
+from ZPublisher.Iterators import filestream_iterator
+from zExceptions import Redirect
+from cgi import escape
+import transaction
+from ZODB.blob import Blob
+
+CHUNK_SIZE = 1 << 16
+
+manage_addFileForm=DTMLFile('dtml/imageAdd', globals(),Kind='File',kind='file')
+def manage_addFile(self,id,file='',title='',precondition='', content_type='',
+                                  REQUEST=None):
+       """Add a new File object.
+
+       Creates a new File object 'id' with the contents of 'file'"""
+
+       id=str(id)
+       title=str(title)
+       content_type=str(content_type)
+       precondition=str(precondition)
+
+       id, title = cookId(id, title, file)
+
+       self=self.this()
+       self._setObject(id, File(id,title,file,content_type, precondition))
+
+       if REQUEST is not None:
+               REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
+
+
+class File(Persistent, Implicit, PropertyManager,
+                  RoleManager, Item_w__name__, Cacheable):
+       """A File object is a content object for arbitrary files."""
+
+       __implements__ = (WriteLockInterface, HTTPRangeSupport.HTTPRangeInterface)
+       meta_type='Blob File'
+
+       security = ClassSecurityInfo()
+       security.declareObjectProtected(View)
+
+       precondition=''
+       size=None
+
+       manage_editForm  =DTMLFile('dtml/fileEdit',globals(),
+                                                          Kind='File',kind='file')
+       manage_editForm._setName('manage_editForm')
+
+       security.declareProtected(view_management_screens, 'manage')
+       security.declareProtected(view_management_screens, 'manage_main')
+       manage=manage_main=manage_editForm
+       manage_uploadForm=manage_editForm
+
+       manage_options=(
+               (
+               {'label':'Edit', 'action':'manage_main',
+                'help':('OFSP','File_Edit.stx')},
+               {'label':'View', 'action':'',
+                'help':('OFSP','File_View.stx')},
+               )
+               + PropertyManager.manage_options
+               + RoleManager.manage_options
+               + Item_w__name__.manage_options
+               + Cacheable.manage_options
+               )
+
+       _properties=({'id':'title', 'type': 'string'},
+                                {'id':'content_type', 'type':'string'},
+                                )
+
+       def __init__(self, id, title, file, content_type='', precondition=''):
+               self.__name__=id
+               self.title=title
+               self.precondition=precondition
+               self.uploaded_filename = cookId('', '', file)[0]
+               self.bdata = Blob()
+
+               content_type=self._get_content_type(file, id, content_type)
+               self.update_data(file, content_type)
+       
+       security.declarePrivate('save')
+       def save(self, file):
+               bf = self.bdata.open('w')
+               bf.write(file.read())
+               self.size = bf.tell()
+               bf.close()
+       
+       security.declarePrivate('open')
+       def open(self, mode='r'):
+               bf = self.bdata.open(mode)
+               return bf
+       
+       security.declarePrivate('updateSize')
+       def updateSize(self, size=None):
+               if size is None :
+                       bf = self.open('r')
+                       bf.seek(0,2)
+                       self.size = bf.tell()
+                       bf.close()
+               else :
+                       self.size = size
+
+       def _getLegacyData(self) :
+               warn("Accessing 'data' attribute may be inefficient with "
+                        "this blob based file. You should refactor your product "
+                        "by accessing data like: "
+                        "f = self.open('r') "
+                        "data = f.read()",
+                       DeprecationWarning, stacklevel=2)
+               f = self.open()
+               data = f.read()
+               f.close()
+               return data
+       
+       def _setLegacyData(self, data) :
+               warn("Accessing 'data' attribute may be inefficient with "
+                        "this blob based file. You should refactor your product "
+                        "by accessing data like: "
+                        "f = self.save(data)",
+                       DeprecationWarning, stacklevel=2)
+               if isinstance(data, str) :
+                       sio = StringIO()
+                       sio.write(data)
+                       sio.seek(0)
+                       data = sio
+               self.save(data)
+               
+       data = property(_getLegacyData, _setLegacyData,
+                                       "Data Legacy attribute to ensure compatibility "
+                                       "with derived classes that access data by this way.")
+
+       def id(self):
+               return self.__name__
+
+       def _if_modified_since_request_handler(self, REQUEST, RESPONSE):
+               # HTTP If-Modified-Since header handling: return True if
+               # we can handle this request by returning a 304 response
+               header=REQUEST.get_header('If-Modified-Since', None)
+               if header is not None:
+                       header=header.split( ';')[0]
+                       # Some proxies seem to send invalid date strings for this
+                       # header. If the date string is not valid, we ignore it
+                       # rather than raise an error to be generally consistent
+                       # with common servers such as Apache (which can usually
+                       # understand the screwy date string as a lucky side effect
+                       # of the way they parse it).
+                       # This happens to be what RFC2616 tells us to do in the face of an
+                       # invalid date.
+                       try:    mod_since=long(DateTime(header).timeTime())
+                       except: mod_since=None
+                       if mod_since is not None:
+                               if self._p_mtime:
+                                       last_mod = long(self._p_mtime)
+                               else:
+                                       last_mod = long(0)
+                               if last_mod > 0 and last_mod <= mod_since:
+                                       RESPONSE.setHeader('Last-Modified',
+                                                                          rfc1123_date(self._p_mtime))
+                                       RESPONSE.setHeader('Content-Type', self.content_type)
+                                       RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                                       RESPONSE.setStatus(304)
+                                       return True
+
+       def _range_request_handler(self, REQUEST, RESPONSE):
+               # HTTP Range header handling: return True if we've served a range
+               # chunk out of our data.
+               range = REQUEST.get_header('Range', None)
+               request_range = REQUEST.get_header('Request-Range', None)
+               if request_range is not None:
+                       # Netscape 2 through 4 and MSIE 3 implement a draft version
+                       # Later on, we need to serve a different mime-type as well.
+                       range = request_range
+               if_range = REQUEST.get_header('If-Range', None)
+               if range is not None:
+                       ranges = HTTPRangeSupport.parseRange(range)
+
+                       if if_range is not None:
+                               # Only send ranges if the data isn't modified, otherwise send
+                               # the whole object. Support both ETags and Last-Modified dates!
+                               if len(if_range) > 1 and if_range[:2] == 'ts':
+                                       # ETag:
+                                       if if_range != self.http__etag():
+                                               # Modified, so send a normal response. We delete
+                                               # the ranges, which causes us to skip to the 200
+                                               # response.
+                                               ranges = None
+                               else:
+                                       # Date
+                                       date = if_range.split( ';')[0]
+                                       try: mod_since=long(DateTime(date).timeTime())
+                                       except: mod_since=None
+                                       if mod_since is not None:
+                                               if self._p_mtime:
+                                                       last_mod = long(self._p_mtime)
+                                               else:
+                                                       last_mod = long(0)
+                                               if last_mod > mod_since:
+                                                       # Modified, so send a normal response. We delete
+                                                       # the ranges, which causes us to skip to the 200
+                                                       # response.
+                                                       ranges = None
+
+                       if ranges:
+                               # Search for satisfiable ranges.
+                               satisfiable = 0
+                               for start, end in ranges:
+                                       if start < self.size:
+                                               satisfiable = 1
+                                               break
+
+                               if not satisfiable:
+                                       RESPONSE.setHeader('Content-Range',
+                                               'bytes */%d' % self.size)
+                                       RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                                       RESPONSE.setHeader('Last-Modified',
+                                               rfc1123_date(self._p_mtime))
+                                       RESPONSE.setHeader('Content-Type', self.content_type)
+                                       RESPONSE.setHeader('Content-Length', self.size)
+                                       RESPONSE.setStatus(416)
+                                       return True
+
+                               ranges = HTTPRangeSupport.expandRanges(ranges, self.size)
+
+                               if len(ranges) == 1:
+                                       # Easy case, set extra header and return partial set.
+                                       start, end = ranges[0]
+                                       size = end - start
+
+                                       RESPONSE.setHeader('Last-Modified',
+                                               rfc1123_date(self._p_mtime))
+                                       RESPONSE.setHeader('Content-Type', self.content_type)
+                                       RESPONSE.setHeader('Content-Length', size)
+                                       RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                                       RESPONSE.setHeader('Content-Range',
+                                               'bytes %d-%d/%d' % (start, end - 1, self.size))
+                                       RESPONSE.setStatus(206) # Partial content
+
+                                       bf = self.open('r')
+                                       bf.seek(start)
+                                       RESPONSE.write(bf.read(size))
+                                       bf.close()
+                                       return True
+
+                               else:
+                                       boundary = choose_boundary()
+
+                                       # Calculate the content length
+                                       size = (8 + len(boundary) + # End marker length
+                                               len(ranges) * (                 # Constant lenght per set
+                                                       49 + len(boundary) + len(self.content_type) +
+                                                       len('%d' % self.size)))
+                                       for start, end in ranges:
+                                               # Variable length per set
+                                               size = (size + len('%d%d' % (start, end - 1)) +
+                                                       end - start)
+
+
+                                       # Some clients implement an earlier draft of the spec, they
+                                       # will only accept x-byteranges.
+                                       draftprefix = (request_range is not None) and 'x-' or ''
+
+                                       RESPONSE.setHeader('Content-Length', size)
+                                       RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                                       RESPONSE.setHeader('Last-Modified',
+                                               rfc1123_date(self._p_mtime))
+                                       RESPONSE.setHeader('Content-Type',
+                                               'multipart/%sbyteranges; boundary=%s' % (
+                                                       draftprefix, boundary))
+                                       RESPONSE.setStatus(206) # Partial content
+
+                                       bf = self.open('r')
+
+                                       for start, end in ranges:
+                                               RESPONSE.write('\r\n--%s\r\n' % boundary)
+                                               RESPONSE.write('Content-Type: %s\r\n' %
+                                                       self.content_type)
+                                               RESPONSE.write(
+                                                       'Content-Range: bytes %d-%d/%d\r\n\r\n' % (
+                                                               start, end - 1, self.size))
+
+                                               
+                                               size = end - start
+                                               bf.seek(start)
+                                               RESPONSE.write(bf.read(size))
+                                       
+                                       bf.close()
+
+                                       RESPONSE.write('\r\n--%s--\r\n' % boundary)
+                                       return True
+
+       security.declareProtected(View, 'index_html')
+       def index_html(self, REQUEST, RESPONSE):
+               """
+               The default view of the contents of a File or Image.
+
+               Returns the contents of the file or image.      Also, sets the
+               Content-Type HTTP header to the objects content type.
+               """
+
+               if self._if_modified_since_request_handler(REQUEST, RESPONSE):
+                       # we were able to handle this by returning a 304
+                       # unfortunately, because the HTTP cache manager uses the cache
+                       # API, and because 304 responses are required to carry the Expires
+                       # header for HTTP/1.1, we need to call ZCacheable_set here.
+                       # This is nonsensical for caches other than the HTTP cache manager
+                       # unfortunately.
+                       self.ZCacheable_set(None)
+                       return ''
+
+               if self.precondition and hasattr(self, str(self.precondition)):
+                       # Grab whatever precondition was defined and then
+                       # execute it.  The precondition will raise an exception
+                       # if something violates its terms.
+                       c=getattr(self, str(self.precondition))
+                       if hasattr(c,'isDocTemp') and c.isDocTemp:
+                               c(REQUEST['PARENTS'][1],REQUEST)
+                       else:
+                               c()
+
+               if self._range_request_handler(REQUEST, RESPONSE):
+                       # we served a chunk of content in response to a range request.
+                       return ''
+
+               RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
+               RESPONSE.setHeader('Content-Type', self.content_type)
+               RESPONSE.setHeader('Content-Length', self.size)
+               RESPONSE.setHeader('Accept-Ranges', 'bytes')
+
+               if self.ZCacheable_isCachingEnabled():
+                       result = self.ZCacheable_get(default=None)
+                       if result is not None:
+                               # We will always get None from RAMCacheManager and HTTP
+                               # Accelerated Cache Manager but we will get
+                               # something implementing the IStreamIterator interface
+                               # from a "FileCacheManager"
+                               return result
+
+               self.ZCacheable_set(None)
+
+               bf = self.open('r')
+               chunk = bf.read(CHUNK_SIZE)
+               while chunk :
+                       RESPONSE.write(chunk)
+                       chunk = bf.read(CHUNK_SIZE)
+               bf.close()
+               return ''               
+
+       security.declareProtected(View, 'view_image_or_file')
+       def view_image_or_file(self, URL1):
+               """
+               The default view of the contents of the File or Image.
+               """
+               raise Redirect, URL1
+
+       security.declareProtected(View, 'PrincipiaSearchSource')
+       def PrincipiaSearchSource(self):
+               """ Allow file objects to be searched.
+               """
+               if self.content_type.startswith('text/'):
+                       bf = self.open('r')
+                       data = bf.read()
+                       bf.close()
+                       return data
+               return ''
+
+       security.declarePrivate('update_data')
+       def update_data(self, file, content_type=None):
+               if isinstance(file, unicode):
+                       raise TypeError('Data can only be str or file-like.      '
+                                                       'Unicode objects are expressly forbidden.')
+               elif isinstance(file, str) :
+                       sio = StringIO()
+                       sio.write(file)
+                       sio.seek(0)
+                       file = sio
+
+               if content_type is not None: self.content_type=content_type
+               self.save(file)
+               self.ZCacheable_invalidate()
+               self.ZCacheable_set(None)
+               self.http__refreshEtag()
+
+       security.declareProtected(change_images_and_files, 'manage_edit')
+       def manage_edit(self, title, content_type, precondition='',
+                                       filedata=None, REQUEST=None):
+               """
+               Changes the title and content type attributes of the File or Image.
+               """
+               if self.wl_isLocked():
+                       raise ResourceLockedError, "File is locked via WebDAV"
+
+               self.title=str(title)
+               self.content_type=str(content_type)
+               if precondition: self.precondition=str(precondition)
+               elif self.precondition: del self.precondition
+               if filedata is not None:
+                       self.update_data(filedata, content_type)
+               else:
+                       self.ZCacheable_invalidate()
+               if REQUEST:
+                       message="Saved changes."
+                       return self.manage_main(self,REQUEST,manage_tabs_message=message)
+
+       security.declareProtected(change_images_and_files, 'manage_upload')
+       def manage_upload(self,file='',REQUEST=None):
+               """
+               Replaces the current contents of the File or Image object with file.
+
+               The file or images contents are replaced with the contents of 'file'.
+               """
+               if self.wl_isLocked():
+                       raise ResourceLockedError, "File is locked via WebDAV"
+
+               content_type=self._get_content_type(file, self.__name__,
+                                                                                       'application/octet-stream')
+               self.update_data(file, content_type)
+
+               if REQUEST:
+                       message="Saved changes."
+                       return self.manage_main(self,REQUEST,manage_tabs_message=message)
+
+       def _get_content_type(self, file, id, content_type=None):
+               headers=getattr(file, 'headers', None)
+               if headers and headers.has_key('content-type'):
+                       content_type=headers['content-type']
+               else:
+                       name = getattr(file, 'filename', self.uploaded_filename) or id
+                       content_type, enc=guess_content_type(name, '', content_type)
+               return content_type
+
+       security.declareProtected(delete_objects, 'DELETE')
+
+       security.declareProtected(change_images_and_files, 'PUT')
+       def PUT(self, REQUEST, RESPONSE):
+               """Handle HTTP PUT requests"""
+               self.dav__init(REQUEST, RESPONSE)
+               self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
+               type=REQUEST.get_header('content-type', None)
+
+               file=REQUEST['BODYFILE']
+
+               content_type = self._get_content_type(file, self.__name__,
+                                                                                         type or self.content_type)
+               self.update_data(file, content_type)
+
+               RESPONSE.setStatus(204)
+               return RESPONSE
+
+       security.declareProtected(View, 'get_size')
+       def get_size(self):
+               """Get the size of a file or image.
+
+               Returns the size of the file or image.
+               """
+               size=self.size
+               if size is None :
+                       bf = self.open('r')
+                       bf.seek(0,2)
+                       self.size = size = bf.tell()
+                       bf.close()
+               return size
+
+       # deprecated; use get_size!
+       getSize=get_size
+
+       security.declareProtected(View, 'getContentType')
+       def getContentType(self):
+               """Get the content type of a file or image.
+
+               Returns the content type (MIME type) of a file or image.
+               """
+               return self.content_type
+
+
+       def __str__(self): return str(self.data)
+       def __len__(self): return 1
+
+       security.declareProtected(ftp_access, 'manage_FTPstat')
+       security.declareProtected(ftp_access, 'manage_FTPlist')
+
+       security.declareProtected(ftp_access, 'manage_FTPget')
+       def manage_FTPget(self):
+               """Return body for ftp."""
+               RESPONSE = self.REQUEST.RESPONSE
+
+               if self.ZCacheable_isCachingEnabled():
+                       result = self.ZCacheable_get(default=None)
+                       if result is not None:
+                               # We will always get None from RAMCacheManager but we will get
+                               # something implementing the IStreamIterator interface
+                               # from FileCacheManager.
+                               # the content-length is required here by HTTPResponse, even
+                               # though FTP doesn't use it.
+                               RESPONSE.setHeader('Content-Length', self.size)
+                               return result
+
+               bf = self.open('r')
+               data = bf.read()
+               bf.close()
+               RESPONSE.setBase(None)
+               return data
+
+manage_addImageForm=DTMLFile('dtml/imageAdd',globals(),
+                                                        Kind='Image',kind='image')
+def manage_addImage(self, id, file, title='', precondition='', content_type='',
+                                       REQUEST=None):
+       """
+       Add a new Image object.
+
+       Creates a new Image object 'id' with the contents of 'file'.
+       """
+
+       id=str(id)
+       title=str(title)
+       content_type=str(content_type)
+       precondition=str(precondition)
+
+       id, title = cookId(id, title, file)
+
+       self=self.this()
+       self._setObject(id, Image(id,title,file,content_type, precondition))
+
+       if REQUEST is not None:
+               try:    url=self.DestinationURL()
+               except: url=REQUEST['URL1']
+               REQUEST.RESPONSE.redirect('%s/manage_main' % url)
+       return id
+
+
+def getImageInfo(file):
+       height = -1
+       width = -1
+       content_type = ''
+
+       # handle GIFs
+       data = file.read(24)
+       size = len(data)
+       if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
+               # Check to see if content_type is correct
+               content_type = 'image/gif'
+               w, h = struct.unpack("<HH", data[6:10])
+               width = int(w)
+               height = int(h)
+
+       # See PNG v1.2 spec (http://www.cdrom.com/pub/png/spec/)
+       # Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
+       # and finally the 4-byte width, height
+       elif ((size >= 24) and (data[:8] == '\211PNG\r\n\032\n')
+                 and (data[12:16] == 'IHDR')):
+               content_type = 'image/png'
+               w, h = struct.unpack(">LL", data[16:24])
+               width = int(w)
+               height = int(h)
+
+       # Maybe this is for an older PNG version.
+       elif (size >= 16) and (data[:8] == '\211PNG\r\n\032\n'):
+               # Check to see if we have the right content type
+               content_type = 'image/png'
+               w, h = struct.unpack(">LL", data[8:16])
+               width = int(w)
+               height = int(h)
+
+       # handle JPEGs
+       elif (size >= 2) and (data[:2] == '\377\330'):
+               content_type = 'image/jpeg'
+               jpeg = file
+               jpeg.seek(0)
+               jpeg.read(2)
+               b = jpeg.read(1)
+               try:
+                       while (b and ord(b) != 0xDA):
+                               while (ord(b) != 0xFF): b = jpeg.read(1)
+                               while (ord(b) == 0xFF): b = jpeg.read(1)
+                               if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
+                                       jpeg.read(3)
+                                       h, w = struct.unpack(">HH", jpeg.read(4))
+                                       break
+                               else:
+                                       jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
+                               b = jpeg.read(1)
+                       width = int(w)
+                       height = int(h)
+               except: pass
+
+       return content_type, width, height
+
+
+class Image(File):
+       """Image objects can be GIF, PNG or JPEG and have the same methods
+       as File objects.  Images also have a string representation that
+       renders an HTML 'IMG' tag.
+       """
+       __implements__ = (WriteLockInterface,)
+       meta_type='Blob Image'
+
+       security = ClassSecurityInfo()
+       security.declareObjectProtected(View)
+
+       alt=''
+       height=''
+       width=''
+
+       # FIXME: Redundant, already in base class
+       security.declareProtected(change_images_and_files, 'manage_edit')
+       security.declareProtected(change_images_and_files, 'manage_upload')
+       security.declareProtected(change_images_and_files, 'PUT')
+       security.declareProtected(View, 'index_html')
+       security.declareProtected(View, 'get_size')
+       security.declareProtected(View, 'getContentType')
+       security.declareProtected(ftp_access, 'manage_FTPstat')
+       security.declareProtected(ftp_access, 'manage_FTPlist')
+       security.declareProtected(ftp_access, 'manage_FTPget')
+       security.declareProtected(delete_objects, 'DELETE')
+
+       _properties=({'id':'title', 'type': 'string'},
+                                {'id':'alt', 'type':'string'},
+                                {'id':'content_type', 'type':'string','mode':'w'},
+                                {'id':'height', 'type':'string'},
+                                {'id':'width', 'type':'string'},
+                                )
+
+       manage_options=(
+               ({'label':'Edit', 'action':'manage_main',
+                'help':('OFSP','Image_Edit.stx')},
+                {'label':'View', 'action':'view_image_or_file',
+                'help':('OFSP','Image_View.stx')},)
+               + PropertyManager.manage_options
+               + RoleManager.manage_options
+               + Item_w__name__.manage_options
+               + Cacheable.manage_options
+               )
+
+       manage_editForm  =DTMLFile('dtml/imageEdit',globals(),
+                                                          Kind='Image',kind='image')
+       manage_editForm._setName('manage_editForm')
+
+       security.declareProtected(View, 'view_image_or_file')
+       view_image_or_file =DTMLFile('dtml/imageView',globals())
+
+       security.declareProtected(view_management_screens, 'manage')
+       security.declareProtected(view_management_screens, 'manage_main')
+       manage=manage_main=manage_editForm
+       manage_uploadForm=manage_editForm
+       
+       security.declarePrivate('update_data')
+       def update_data(self, file, content_type=None):
+               super(Image, self).update_data(file, content_type)
+               self.updateFormat(size=self.size, content_type=content_type)
+               
+       security.declarePrivate('updateFormat')
+       def updateFormat(self, size=None, dimensions=None, content_type=None):
+               self.updateSize(size=size)
+
+               if dimensions is None or content_type is None :
+                       bf = self.open('r')
+                       ct, width, height = getImageInfo(bf)
+                       bf.close()
+                       if ct:
+                               content_type = ct
+                       if width >= 0 and height >= 0:
+                               self.width = width
+                               self.height = height
+
+                       # Now we should have the correct content type, or still None
+                       if content_type is not None: self.content_type = content_type
+               else :
+                       self.width, self.height = dimensions
+                       self.content_type = content_type
+
+       def __str__(self):
+               return self.tag()
+
+       security.declareProtected(View, 'tag')
+       def tag(self, height=None, width=None, alt=None,
+                       scale=0, xscale=0, yscale=0, css_class=None, title=None, **args):
+               """
+               Generate an HTML IMG tag for this image, with customization.
+               Arguments to self.tag() can be any valid attributes of an IMG tag.
+               'src' will always be an absolute pathname, to prevent redundant
+               downloading of images. Defaults are applied intelligently for
+               'height', 'width', and 'alt'. If specified, the 'scale', 'xscale',
+               and 'yscale' keyword arguments will be used to automatically adjust
+               the output height and width values of the image tag.
+
+               Since 'class' is a Python reserved word, it cannot be passed in
+               directly in keyword arguments which is a problem if you are
+               trying to use 'tag()' to include a CSS class. The tag() method
+               will accept a 'css_class' argument that will be converted to
+               'class' in the output tag to work around this.
+               """
+               if height is None: height=self.height
+               if width is None:  width=self.width
+
+               # Auto-scaling support
+               xdelta = xscale or scale
+               ydelta = yscale or scale
+
+               if xdelta and width:
+                       width =  str(int(round(int(width) * xdelta)))
+               if ydelta and height:
+                       height = str(int(round(int(height) * ydelta)))
+
+               result='<img src="%s"' % (self.absolute_url())
+
+               if alt is None:
+                       alt=getattr(self, 'alt', '')
+               result = '%s alt="%s"' % (result, escape(alt, 1))
+
+               if title is None:
+                       title=getattr(self, 'title', '')
+               result = '%s title="%s"' % (result, escape(title, 1))
+
+               if height:
+                       result = '%s height="%s"' % (result, height)
+
+               if width:
+                       result = '%s width="%s"' % (result, width)
+
+               # Omitting 'border' attribute (Collector #1557)
+#               if not 'border' in [ x.lower() for x in  args.keys()]:
+#                       result = '%s border="0"' % result
+
+               if css_class is not None:
+                       result = '%s class="%s"' % (result, css_class)
+
+               for key in args.keys():
+                       value = args.get(key)
+                       if value:
+                               result = '%s %s="%s"' % (result, key, value)
+
+               return '%s />' % result
+
+
+def cookId(id, title, file):
+       if not id and hasattr(file,'filename'):
+               filename=file.filename
+               title=title or filename
+               id=filename[max(filename.rfind('/'),
+                                               filename.rfind('\\'),
+                                               filename.rfind(':'),
+                                               )+1:]
+       return id, title
+
+#class Pdata(Persistent, Implicit):
+#      # Wrapper for possibly large data
+#
+#      next=None
+#
+#      def __init__(self, data):
+#              self.data=data
+#
+#      def __getslice__(self, i, j):
+#              return self.data[i:j]
+#
+#      def __len__(self):
+#              data = str(self)
+#              return len(data)
+#
+#      def __str__(self):
+#              next=self.next
+#              if next is None: return self.data
+#
+#              r=[self.data]
+#              while next is not None:
+#                      self=next
+#                      r.append(self.data)
+#                      next=self.next
+#
+#              return ''.join(r)