diff --git a/product/ERP5/interfaces/convertable.py b/product/ERP5/interfaces/convertable.py index fd7470d2bab4f0ccab0265f7518a8b0677ae0e20..0441e576ba53586cf36206e316466d0f23879500 100644 --- a/product/ERP5/interfaces/convertable.py +++ b/product/ERP5/interfaces/convertable.py @@ -109,3 +109,10 @@ class IConvertable(Interface): where format is an extension (ex. 'png') which can be passed to IConvertable.convert or to IDownloadable.index_html """ + def getPermittedTargetFormatItemList(): + """ + Returns the list of authorized formats for conversion + in the form of tuples. For each format, checks + if the current user can convert the current + document into that format. + """ \ No newline at end of file diff --git a/product/ERP5/mixin/base_convertable.py b/product/ERP5/mixin/base_convertable.py new file mode 100644 index 0000000000000000000000000000000000000000..f1f751f40ad9359dd72b942c33d1540ae6ed0da5 --- /dev/null +++ b/product/ERP5/mixin/base_convertable.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import md5 +import string +import xmlrpclib, base64, re, zipfile, cStringIO +from xmlrpclib import Fault +from xmlrpclib import Transport +from xmlrpclib import SafeTransport +from Acquisition import aq_base +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions +from Products.CMFCore.utils import getToolByName +from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE +from Products.ERP5Type.Base import WorkflowMethod +from zLOG import LOG +from Products.ERP5Type.Cache import CachingMethod + +# Mixin import +from Products.ERP5.mixin.convertable import ConvertableMixin + + + + +class BaseConvertableMixin: + """ + This class provides a generic implementation of IBaseConvertable. + + """ + + # Declarative security + security = ClassSecurityInfo() + + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + + + security.declareProtected(Permissions.AccessContentsInformation, 'hasBaseData') + def hasBaseData(self): + """ + A TextDocument store its data in the "text_content" property. Since + there is no such thing as base_data in TextDocument, having base_data + is equivalent to having some text_content. + """ + return self.hasTextContent() + + def convertFile(self, **kw): # XXX - It it really useful to explicitly define ? + """ + Workflow transition invoked when conversion occurs. + """ + convertFile = WorkflowMethod(convertFile) + + security.declareProtected(Permissions.ModifyPortalContent, 'convertToBaseFormat') + def convertToBaseFormat(self, **kw): + """ + Converts the content of the document to a base format + which is later used for all conversions. This method + is common to all kinds of documents and handles + exceptions in a unified way. + + Implementation is delegated to _convertToBaseFormat which + must be overloaded by subclasses of Document which + need a base format. + + convertToBaseFormat is called upon file upload, document + ingestion by the processing_status_workflow. + + NOTE: the data of the base format conversion should be stored + using the base_data property. Refer to Document.py propertysheet. + Use accessors (getBaseData, setBaseData, hasBaseData, etc.) + """ + if getattr(self, 'hasData', None) is not None and not self.hasData(): + # Empty document cannot be converted + return + try: + message = self._convertToBaseFormat() # Call implemetation method + self.clearConversionCache() # Conversion cache is now invalid + if message is None: + # XXX Need to translate. + message = 'Converted to %s.' % self.getBaseContentType() + self.convertFile(comment=message) # Invoke workflow method + except NotImplementedError: + message = '' + return message + + def _convertToBaseFormat(self): + """ + """ + raise NotImplementedError + + security.declareProtected(Permissions.AccessContentsInformation, + 'getMetadataMappingDict') + def getMetadataMappingDict(self): + """ + Return a dict of metadata mapping used to update base metadata of the + document + """ + try: + method = self._getTypeBasedMethod('getMetadataMappingDict') + except KeyError, AttributeError: + method = None + if method is not None: + return method() + else: + return {} + + security.declareProtected(Permissions.ModifyPortalContent, 'updateBaseMetadata') + def updateBaseMetadata(self, **kw): + """ + Update the base format data with the latest properties entered + by the user. For example, if title is changed in ERP5 interface, + the base format file should be updated accordingly. + + Default implementation does nothing. Refer to OOoDocument class + for an example of implementation. + """ + pass \ No newline at end of file diff --git a/product/ERP5/mixin/convertable.py b/product/ERP5/mixin/convertable.py new file mode 100644 index 0000000000000000000000000000000000000000..e4da90d0b6864c717822be86f451f371046656ff --- /dev/null +++ b/product/ERP5/mixin/convertable.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import md5 +import string +import xmlrpclib, base64, re, zipfile, cStringIO +from xmlrpclib import Fault +from xmlrpclib import Transport +from xmlrpclib import SafeTransport +from Acquisition import aq_base +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions +from Products.CMFCore.utils import getToolByName +from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE +from Products.ERP5Type.Base import WorkflowMethod +from zLOG import LOG +from Products.ERP5Type.Cache import CachingMethod + + + +def makeSortedTuple(kw): + items = kw.items() + items.sort() + return tuple(items) + + +class ConvertableMixin: + """ + This class provides a generic implementation of IConvertable. + + This class provides a generic API to store in the ZODB + various converted versions of a file or of a string. + + Versions are stored in dictionaries; the class stores also + generation time of every format and its mime-type string. + Format can be a string or a tuple (e.g. format, resolution). + """ + + # Declarative security + security = ClassSecurityInfo() + + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + # Conversion methods + security.declareProtected(Permissions.AccessContentsInformation, 'convert') + def convert(self, format, **kw): + """ + Main content conversion function, returns result which should + be returned and stored in cache. + format - the format specied in the form of an extension + string (ex. jpeg, html, text, txt, etc.) + **kw can be various things - e.g. resolution + + Default implementation returns an empty string (html, text) + or raises an error. + + TODO: + - implement guards API so that conversion to certain + formats require certain permission + """ + # Raise an error if the format is not permitted + if not self.isTargetFormatPermitted(format): + raise Unauthorized("User does not have enough permission to access document" + " in %s format" % (format or 'original')) + if format == 'html': + return 'text/html', '' # XXX - Why ? + if format in ('text', 'txt'): + return 'text/plain', '' # XXX - Why ? + raise NotImplementedError + + security.declareProtected(Permissions.View,'isTargetFormatAllowed') + def isTargetFormatAllowed(self,format): + """ + Checks if the current document can be converted + to the specified target format. + + format -- the target conversion format specified either as an + extension (ex. 'png') or as a mime type + string (ex. 'text/plain') + """ + return format in self.getTargetFormatList() + + + security.declareProtected(Permissions.View,'isTargetFormatPermitted') + def isTargetFormatPermitted(self, format): + """ + Checks if the current user can convert the current document + into the specified target format. + """ + method = self._getTypeBasedMethod('isTargetFormatPermitted', + fallback_script_id='Document_isTargetFormatPermitted') + return method(format) + + security.declareProtected(Permissions.View,'getTargetFormatItemList') + def getTargetFormatItemList(self): + """ + Returns the list of acceptable formats for conversion + in the form of tuples which can be used for example for + listfield in ERP5Form. Each tuple in the list has the form + (title, format) where format is an extension (ex. 'png') + which can be passed to IConvertable.convert or to + IDownloadable.index_html and title is a string which + can be translated and displayed to the user. + + Example of result: + [('ODF Drawing', 'odg'), ('ODF Drawing Template', 'otg'), + ('OpenOffice.org 1.0 Drawing', 'sxd')] + """ + return self.getAllowedTargetItemList() + + security.declareProtected(Permissions.View,'getTargetFormatTitleList') + def getTargetFormatTitleList(self): + """ + Returns the list of titles of acceptable formats for conversion + as a list of strings which can be translated and displayed + to the user. + """ + return map(lambda x: x[0], self.getTargetFormatItemList()) + + security.declareProtected(Permissions.View,'getTargetFormatList') + def getTargetFormatList(self): + """ + Returns the list of acceptable formats for conversion + where format is an extension (ex. 'png') which can be + passed to IConvertable.convert or to IDownloadable.index_html + """ + return map(lambda x: x[1], self.getTargetFormatItemList()) + + security.declareProtected(Permissions.View,'getPermittedTargetFormatItemList') + def getPermittedTargetFormatItemList(self): + """ + Returns the list of authorized formats for conversion + in the form of tuples. For each format, checks + if the current user can convert the current + document into that format. + + """ + authorized_format_list = [] + format_list = self.getTargetFormatItemList() + for format in format_list: + if self.isTargetFormatPermitted(format[1]): + authorized_format_list.append(format) + return authorized_format_list \ No newline at end of file diff --git a/product/ERP5/mixin/document.py b/product/ERP5/mixin/document.py new file mode 100644 index 0000000000000000000000000000000000000000..e8a7b5e1dd89b676bb921a242881d02cc5196e76 --- /dev/null +++ b/product/ERP5/mixin/document.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import md5 +import string +import xmlrpclib, base64, re, zipfile, cStringIO +from xmlrpclib import Fault +from xmlrpclib import Transport +from xmlrpclib import SafeTransport +from Acquisition import aq_base +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions +from Products.CMFCore.utils import getToolByName +from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE +from Products.ERP5Type.Base import WorkflowMethod +from zLOG import LOG +from Products.ERP5Type.Cache import CachingMethod + +#Mixin import +from Products.ERP5.mixin.convertable import ConvertableMixin +from Products.ERP5.mixin.cached_convertable import CachedConvertableMixin + +class DocumentMixin(ConvertableMixin, CachedConvertableMixin): + """ + This class provides a generic implementation of IDocument. + """ + + # Declarative security + security = ClassSecurityInfo() + + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + + security.declareProtected(Permissions.AccessContentsInformation, + 'isSupportBaseDataConversion') + def isSupportBaseDataConversion(self): + """ + """ + return False + + \ No newline at end of file diff --git a/product/ERP5/mixin/html_convertable.py b/product/ERP5/mixin/html_convertable.py new file mode 100644 index 0000000000000000000000000000000000000000..3024c940a352fd62572ab9fc32450a1fe3a56555 --- /dev/null +++ b/product/ERP5/mixin/html_convertable.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import md5 +import string +import xmlrpclib, base64, re, zipfile, cStringIO +from xmlrpclib import Fault +from xmlrpclib import Transport +from xmlrpclib import SafeTransport +from Acquisition import aq_base +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions +from Products.CMFCore.utils import getToolByName +from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE +from Products.ERP5Type.Base import WorkflowMethod +from zLOG import LOG +from Products.ERP5Type.Cache import CachingMethod + +# Mixin import +from Products.ERP5.mixin.convertable import ConvertableMixin +from Products.ERP5.mixin.cached_convertable import CachedConvertableMixin + + + +class HTMLConvertableMixin(CachedConvertableMixin): + """ + This class provides a generic implementation of IHTMLConvertable. + + """ + + # Declarative security + security = ClassSecurityInfo() + + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + security.declarePrivate('_asHTML') + def _asHTML(self, **kw): + """ + A private method which converts to HTML. This method + is the one to override in subclasses. + """ + if not self.hasBaseData(): + raise ConversionError('This document has not been processed yet.') + try: + # FIXME: no substitution may occur in this case. + mime, data = self.getConversion(format='base-html') + return data + except KeyError: + kw['format'] = 'html' + mime, html = self.convert(**kw) + return html + + security.declareProtected(Permissions.View, 'asEntireHTML') + def asEntireHTML(self, **kw): + """ + Returns a complete HTML representation of the document + (with body tags, etc.). Adds if necessary a base + tag so that the document can be displayed in an iframe + or standalone. + + Actual conversion is delegated to _asHTML + """ + html = self._asHTML(**kw) + if self.getUrlString(): + # If a URL is defined, add the base tag + # if base is defined yet. + html = str(html) + if not html.find('<base') >= 0: + base = '<base href="%s">' % self.getContentBaseURL() + html = html.replace('<head>', '<head>%s' % base) + self.setConversion(html, mime='text/html', format='base-html') + return html + + security.declareProtected(Permissions.View, 'asStrippedHTML') + def asStrippedHTML(self, **kw): + """ + Returns a stripped HTML representation of the document + (without html and body tags, etc.) which can be used to inline + a preview of the document. + """ + if not self.hasBaseData(): + return '' + try: + # FIXME: no substitution may occur in this case. + mime, data = self.getConversion(format='stripped-html') + return data + except KeyError: + kw['format'] = 'html' + mime, html = self.convert(**kw) + return self._stripHTML(str(html)) + + def _guessEncoding(self, string): + """ + Try to guess the encoding for this string. + Returns None if no encoding can be guessed. + """ + try: + import chardet + except ImportError: + return None + return chardet.detect(string).get('encoding', None) + + def _stripHTML(self, html, charset=None): + """ + A private method which can be reused by subclasses + to strip HTML content + """ + body_list = re.findall(self.body_parser, str(html)) + if len(body_list): + stripped_html = body_list[0] + else: + stripped_html = html + # find charset and convert to utf-8 + charset_list = self.charset_parser.findall(str(html)) # XXX - Not efficient if this + # is datastream instance but hard to do better + if charset and not charset_list: + # Use optional parameter is we can not find encoding in HTML + charset_list = [charset] + if charset_list and charset_list[0] not in ('utf-8', 'UTF-8'): + try: + stripped_html = unicode(str(stripped_html), + charset_list[0]).encode('utf-8') + except (UnicodeDecodeError, LookupError): + return str(stripped_html) + return stripped_html \ No newline at end of file diff --git a/product/ERP5/mixin/metadata_discoverable.py b/product/ERP5/mixin/metadata_discoverable.py new file mode 100644 index 0000000000000000000000000000000000000000..d8047816b0aa29b070e947ede03739af5c6c0fd1 --- /dev/null +++ b/product/ERP5/mixin/metadata_discoverable.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import md5 +import string +import xmlrpclib, base64, re, zipfile, cStringIO +from xmlrpclib import Fault +from xmlrpclib import Transport +from xmlrpclib import SafeTransport +from Acquisition import aq_base +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions +from Products.CMFCore.utils import getToolByName +from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE +from Products.ERP5Type.Base import WorkflowMethod +from zLOG import LOG +from Products.ERP5Type.Cache import CachingMethod + +# Mixin import +from Products.ERP5.mixin.convertable import ConvertableMixin +from Products.ERP5.mixin.html_convertable import HTMLConvertableMixin + + + + +class MetadataDiscoverableMixin(HTMLConvertableMixin): + """ + This class provides a generic implementation of IMetadataDiscoverable. + + """ + + # Declarative security + security = ClassSecurityInfo() + + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + + + security.declareProtected(Permissions.AccessContentsInformation, 'getContentInformation') + def getContentInformation(self): + """ + Returns the content information from the HTML conversion. + The default implementation tries to build a dictionnary + from the HTML conversion of the document and extract + the document title. + """ + result = {} + html = self.asEntireHTML() + if not html: return result + title_list = re.findall(self.title_parser, str(html)) + if title_list: + result['title'] = title_list[0] + return result + + \ No newline at end of file diff --git a/product/ERP5/mixin/text_convertable.py b/product/ERP5/mixin/text_convertable.py new file mode 100644 index 0000000000000000000000000000000000000000..923c91e7f126e51f5cf1de5500e1cdc0330fcee3 --- /dev/null +++ b/product/ERP5/mixin/text_convertable.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import md5 +import string +import xmlrpclib, base64, re, zipfile, cStringIO +from xmlrpclib import Fault +from xmlrpclib import Transport +from xmlrpclib import SafeTransport +from Acquisition import aq_base +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions +from Products.CMFCore.utils import getToolByName +from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE +from Products.ERP5Type.Base import WorkflowMethod +from zLOG import LOG +from Products.ERP5Type.Cache import CachingMethod + +# Mixin import +from Products.ERP5.mixin.convertable import ConvertableMixin + + + + +class TextConvertableMixin: + """ + This class provides a generic implementation of ITextConvertable. + + This class provides a generic API to store in the ZODB + various converted versions of a file or of a string. + + Versions are stored in dictionaries; the class stores also + generation time of every format and its mime-type string. + Format can be a string or a tuple (e.g. format, resolution). + """ + + # Declarative security + security = ClassSecurityInfo() + + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + + security.declareProtected(Permissions.View, 'asText') + def asText(self, **kw): + """ + Text Convertable interface specification + Documents which implement the ITextConvertable interface + can be converted to plain text. + Converts the content of the document to a textual representation. + """ + kw['format'] = 'txt' + mime, data = self.convert(**kw) + return str(data) + + + security.declareProtected(Permissions.View, 'asSubjectText') + def asSubjectText(self, **kw): + """ + Converts the subject of the document to a textual representation. + """ + subject = self.getSubject() + if not subject: + # XXX not sure if this fallback is a good idea. + subject = self.getTitle() + if subject is None: + subject = '' + return str(subject) \ No newline at end of file