TransformEngine.py 26 KB
Newer Older
Nicolas Delaby's avatar
Nicolas Delaby committed
1
# -*- coding: utf-8 -*-
2
from logging import DEBUG
3 4

from persistent.list import PersistentList
5 6
from zope.interface import implements

Yusei Tahara's avatar
Yusei Tahara committed
7 8
from AccessControl import ClassSecurityInfo
from Acquisition import aq_base
9
from App.class_init import default__class_init__ as InitializeClass
Yusei Tahara's avatar
Yusei Tahara committed
10
from OFS.Folder import Folder
11
from Persistence import PersistentMapping
Yusei Tahara's avatar
Yusei Tahara committed
12 13
from Products.CMFCore.ActionProviderBase import ActionProviderBase
from Products.CMFCore.permissions import ManagePortal, View
14 15 16 17
try:
    from Products.CMFCore.utils import registerToolInterface
except ImportError: # BACK: Zope 2.8
    registerToolInterface = lambda tool_id, tool_interface: None
Yusei Tahara's avatar
Yusei Tahara committed
18 19
from Products.CMFCore.utils import UniqueObject
from Products.CMFCore.utils import getToolByName
20
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
Yusei Tahara's avatar
Yusei Tahara committed
21 22 23 24 25

from Products.PortalTransforms.data import datastream
from Products.PortalTransforms.chain import TransformsChain
from Products.PortalTransforms.chain import chain
from Products.PortalTransforms.cache import Cache
26 27 28 29 30
from Products.PortalTransforms.interfaces import IDataStream
from Products.PortalTransforms.interfaces import ITransform
from Products.PortalTransforms.interfaces import IEngine
from Products.PortalTransforms.interfaces import IPortalTransformsTool
from Products.PortalTransforms.libtransforms.utils import MissingBinary
Yusei Tahara's avatar
Yusei Tahara committed
31
from Products.PortalTransforms.Transform import Transform
32
from Products.PortalTransforms.transforms import initialize
Yusei Tahara's avatar
Yusei Tahara committed
33 34 35 36 37
from Products.PortalTransforms.utils import log
from Products.PortalTransforms.utils import TransformException
from Products.PortalTransforms.utils import _www


38
from zLOG import WARNING
Yusei Tahara's avatar
Yusei Tahara committed
39 40 41

class TransformTool(UniqueObject, ActionProviderBase, Folder):

42
    id = 'portal_transforms'
Yusei Tahara's avatar
Yusei Tahara committed
43 44 45
    meta_type = id.title().replace('_', ' ')
    isPrincipiaFolderish = 1 # Show up in the ZMI

46
    implements(IPortalTransformsTool, IEngine)
Yusei Tahara's avatar
Yusei Tahara committed
47 48

    meta_types = all_meta_types = (
49 50
        {'name': 'Transform', 'action': 'manage_addTransformForm'},
        {'name': 'TransformsChain', 'action': 'manage_addTransformsChainForm'},
Yusei Tahara's avatar
Yusei Tahara committed
51 52 53
        )

    manage_addTransformForm = PageTemplateFile('addTransform', _www)
54 55
    manage_addTransformsChainForm = PageTemplateFile(
        'addTransformsChain', _www)
Yusei Tahara's avatar
Yusei Tahara committed
56
    manage_cacheForm = PageTemplateFile('setCacheTime', _www)
57 58
    manage_editTransformationPolicyForm = PageTemplateFile(
        'editTransformationPolicy', _www)
Yusei Tahara's avatar
Yusei Tahara committed
59 60
    manage_reloadAllTransforms = PageTemplateFile('reloadAllTransforms', _www)

61 62 63 64 65 66 67
    manage_options = (
        (Folder.manage_options[0], ) + Folder.manage_options[2:] +
        ({'label': 'Caches', 'action': 'manage_cacheForm'},
         {'label': 'Policy', 'action': 'manage_editTransformationPolicyForm'},
         {'label': 'Reload transforms',
          'action': 'manage_reloadAllTransforms'},
        ))
Yusei Tahara's avatar
Yusei Tahara committed
68 69 70 71 72 73 74 75 76

    security = ClassSecurityInfo()

    def __init__(self, policies=None, max_sec_in_cache=3600):
        self._mtmap = PersistentMapping()
        self._policies = policies or PersistentMapping()
        self.max_sec_in_cache = max_sec_in_cache
        self._new_style_pt = 1

77
    # mimetype oriented conversions (iengine interface)
Yusei Tahara's avatar
Yusei Tahara committed
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93

    def unregisterTransform(self, name):
        """ unregister a transform
        name is the name of a registered transform
        """
        self._unmapTransform(getattr(self, name))
        if name in self.objectIds():
            self._delObject(name)


    def convertTo(self, target_mimetype, orig, data=None, object=None,
                  usedby=None, context=None, **kwargs):
        """Convert orig to a given mimetype

        * orig is an encoded string

94
        * data an optional IDataStream object. If None a new datastream will be
Yusei Tahara's avatar
Yusei Tahara committed
95 96 97 98 99 100 101 102
        created and returned

        * optional object argument is the object on which is bound the data.
        If present that object will be used by the engine to bound cached data.

        * additional arguments (kwargs) will be passed to the transformations.
        Some usual arguments are : filename, mimetype, encoding

103
        return an object implementing IDataStream or None if no path has been
Yusei Tahara's avatar
Yusei Tahara committed
104 105 106 107 108
        found.
        """
        target_mimetype = str(target_mimetype)

        if object is not None:
109
            cache = Cache(object, context=context)
Yusei Tahara's avatar
Yusei Tahara committed
110 111 112 113 114 115 116 117 118 119 120 121
            data = cache.getCache(target_mimetype)
            if data is not None:
                time, data = data
                if self.max_sec_in_cache == 0 or time < self.max_sec_in_cache:
                    return data

        if data is None:
            data = self._wrap(target_mimetype)

        registry = getToolByName(self, 'mimetypes_registry')

        if not getattr(aq_base(registry), 'classify', None):
122 123
            # avoid problems when importing a site with an old mimetype
            # registry
Yusei Tahara's avatar
Yusei Tahara committed
124 125 126 127 128 129 130
            return None

        orig_mt = registry.classify(orig,
                                    mimetype=kwargs.get('mimetype'),
                                    filename=kwargs.get('filename'))
        orig_mt = str(orig_mt)
        if not orig_mt:
131 132 133
            log('Unable to guess input mime type (filename=%s, mimetype=%s)' %
                (kwargs.get('mimetype'), kwargs.get('filename')),
                severity=WARNING)
Yusei Tahara's avatar
Yusei Tahara committed
134 135 136 137 138 139 140
            return None

        target_mt = registry.lookup(target_mimetype)
        if target_mt:
            target_mt = target_mt[0]
        else:
            log('Unable to match target mime type %s'% str(target_mimetype),
141
                severity=WARNING)
Yusei Tahara's avatar
Yusei Tahara committed
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
            return None

        ## fastpath
        # If orig_mt and target_mt are the same, we only allow
        # a one-hop transform, a.k.a. filter.
        # XXX disabled filtering for now
        if orig_mt == str(target_mt):
            data.setData(orig)
            md = data.getMetadata()
            md['mimetype'] = str(orig_mt)
            if object is not None:
                cache.setCache(str(target_mimetype), data)
            return data

        ## get a path to output mime type
157 158
        requirements = self.getRequirementListByMimetype(str(orig_mt),
                                                         str(target_mt))
Yusei Tahara's avatar
Yusei Tahara committed
159 160 161
        path = self._findPath(orig_mt, target_mt, list(requirements))
        if not path and requirements:
            log('Unable to satisfy requirements %s' % ', '.join(requirements),
162
                severity=WARNING)
Yusei Tahara's avatar
Yusei Tahara committed
163 164 165
            path = self._findPath(orig_mt, target_mt)

        if not path:
166 167 168
            log('NO PATH FROM %s TO %s : %s' %
                (orig_mt, target_mimetype, path), severity=WARNING)
            return None
Yusei Tahara's avatar
Yusei Tahara committed
169 170 171 172 173 174 175 176 177

        if len(path) > 1:
            ## create a chain on the fly (sly)
            transform = chain()
            for t in path:
                transform.registerTransform(t)
        else:
            transform = path[0]

178 179
        result = transform.convert(orig, data, context=context,
                                   usedby=usedby, **kwargs)
Yusei Tahara's avatar
Yusei Tahara committed
180 181 182 183 184 185
        self._setMetaData(result, transform)

        # set cache if possible
        if object is not None and result.isCacheable():
            cache.setCache(str(target_mimetype), result)

186
        # return IDataStream object
Yusei Tahara's avatar
Yusei Tahara committed
187 188
        return result

189 190
    def getRequirementListByMimetype(self, origin_mimetype, target_mimetype):
      """Return requirements only if origin_mimetype
Nicolas Delaby's avatar
Nicolas Delaby committed
191
      and target_mimetype are matching transform policy
192 193

      As an example pdf => text conversion force a transformation
Nicolas Delaby's avatar
Nicolas Delaby committed
194 195
      to intermediate HTML format, because w3m_dump is a requirement
      to output plain/text.
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
      But we want using pdf_to_text directly.

      So requirements are returned only if
      origin_mimetype and target_mimetype sastify
      the requirement: ie html_to_text is returned
      only if origin_mimetype  == 'text/html' and
      target_mimetype == 'text/plain'
      """
      result_list = []
      candidate_requirement_list = self._policies.get(target_mimetype, [])
      for candidate_requirement in candidate_requirement_list:
        transform = getattr(self, candidate_requirement)
        if origin_mimetype in transform.inputs:
          result_list.append(candidate_requirement)
      return result_list

Yusei Tahara's avatar
Yusei Tahara committed
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
    security.declarePublic('convertToData')
    def convertToData(self, target_mimetype, orig, data=None, object=None,
                      usedby=None, context=None, **kwargs):
        """Convert to a given mimetype and return the raw data
        ignoring subobjects. see convertTo for more information
        """
        data =self.convertTo(target_mimetype, orig, data, object, usedby,
                       context, **kwargs)
        if data:
            return data.getData()
        return None

    security.declarePublic('convert')
    def convert(self, name, orig, data=None, context=None, **kwargs):
        """run a tranform of a given name on data

        * name is the name of a registered transform

        see convertTo docstring for more info
        """
        if not data:
            data = self._wrap(name)
        try:
            transform = getattr(self, name)
        except AttributeError:
            raise Exception('No such transform "%s"' % name)
        data = transform.convert(orig, data, context=context, **kwargs)
        self._setMetaData(data, transform)
        return data


    def __call__(self, name, orig, data=None, context=None, **kwargs):
        """run a transform by its name, returning the raw data product

        * name is the name of a registered transform.

        return an encoded string.
        see convert docstring for more info on additional arguments.
        """
        data = self.convert(name, orig, data, context, **kwargs)
        return data.getData()


    # utilities ###############################################################

    def _setMetaData(self, datastream, transform):
        """set metadata on datastream according to the given transform
        (mime type and optionaly encoding)
        """
        md = datastream.getMetadata()
        if hasattr(transform, 'output_encoding'):
            md['encoding'] = transform.output_encoding
        md['mimetype'] = transform.output

    def _wrap(self, name):
        """wrap a data object in an icache"""
        return datastream(name)

    def _unwrap(self, data):
        """unwrap data from an icache"""
272
        if IDataStream.providedBy(data):
Yusei Tahara's avatar
Yusei Tahara committed
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
            data = data.getData()
        return data

    def _mapTransform(self, transform):
        """map transform to internal structures"""
        registry = getToolByName(self, 'mimetypes_registry')
        inputs = getattr(transform, 'inputs', None)
        if not inputs:
            raise TransformException('Bad transform %s : no input MIME type' %
                                     (transform))
        for i in inputs:
            mts = registry.lookup(i)
            if not mts:
                msg = 'Input MIME type %r for transform %s is not registered '\
                      'in the MIME types registry' % (i, transform.name())
                raise TransformException(msg)
            for mti in mts:
                for mt in mti.mimetypes:
                    mt_in = self._mtmap.setdefault(mt, PersistentMapping())
                    output = getattr(transform, 'output', None)
                    if not output:
                        msg = 'Bad transform %s : no output MIME type'
                        raise TransformException(msg % transform.name())
                    mto = registry.lookup(output)
                    if not mto:
                        msg = 'Output MIME type %r for transform %s is not '\
                              'registered in the MIME types registry' % \
                              (output, transform.name())
                        raise TransformException(msg)
                    if len(mto) > 1:
303 304
                        msg = ("Wildcarding not allowed in transform's output "
                               "MIME type")
Yusei Tahara's avatar
Yusei Tahara committed
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
                        raise TransformException(msg)

                    for mt2 in mto[0].mimetypes:
                        try:
                            if not transform in mt_in[mt2]:
                                mt_in[mt2].append(transform)
                        except KeyError:
                            mt_in[mt2] = PersistentList([transform])

    def _unmapTransform(self, transform):
        """unmap transform from internal structures"""
        registry = getToolByName(self, 'mimetypes_registry')
        for i in transform.inputs:
            for mti in registry.lookup(i):
                for mt in mti.mimetypes:
                    mt_in = self._mtmap.get(mt, {})
                    output = transform.output
                    mto = registry.lookup(output)
                    for mt2 in mto[0].mimetypes:
                        l = mt_in[mt2]
                        for i in range(len(l)):
                            if transform.name() == l[i].name():
                                l.pop(i)
                                break
                        else:
                            log('Can\'t find transform %s from %s to %s' % (
                                transform.name(), mti, mt),
332
                                severity=WARNING)
Yusei Tahara's avatar
Yusei Tahara committed
333 334 335 336 337 338 339 340

    def _findPath(self, orig, target, required_transforms=()):
        """return the shortest path for transformation from orig mimetype to
        target mimetype
        """
        if not self._mtmap:
            return None

341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
        orig = str(orig)
        target = str(target)
        # First, let's deal with required transforms.
        if required_transforms:
            # Let's decompose paths, then.
            required_transform = required_transforms.pop(0)
            # The first path must lead to one of the inputs supported
            # by this first required transform.
            # Which input types are supported by this transform ?
            supportedInputs = {}
            for input, outputs in self._mtmap.items():
                for output, transforms in outputs.items():
                    for transform in transforms:
                        if transform.name() == required_transform:
                            supportedInputs[input] = 'ok'
                            # BTW, let's remember the output type
                            transformOutput = output
                            # and remember the transform, it is
                            # useful later
                            requiredTransform = transform
            # Which of these inputs will be reachable with the
            # shortest path ?
            shortest = 9999 # big enough, I guess
            shortestFirstPath = None
            for supportedInput in supportedInputs.keys():
                # We start from orig
                firstOrig = orig
                # And want to reach supportedInput
                firstTarget = supportedInput
                # What's the shortest path ?
                firstPath = self._findPath(firstOrig, firstTarget)
                if firstPath is not None:
                    if len(firstPath) < shortest:
                        # Here is a path which is shorter than others
                        # which also reach the required transform.
                        shortest = len(firstPath)
                        shortestFirstPath = firstPath
            if shortestFirstPath == None:
                return None # there is no path leading to this transform
            # Then we have to take this transform.
            secondPath = [requiredTransform]
            # From the output of this transform, we then have to
            # reach our target, possible through other required
            # transforms.
            thirdOrig = transformOutput
            thirdTarget = target
            thirdPath = self._findPath(thirdOrig, thirdTarget,
                                       required_transforms)
            if thirdPath is None:
                return None # no path
            # Final result is the concatenation of these 3 parts
            return shortestFirstPath + secondPath + thirdPath

        if orig == target:
            return []

        # Now let's efficiently find the shortest path from orig
        # to target (without required transforms).
        # The overall idea is that we build all possible paths
        # starting from orig and of given length. And we increment
        # this length until one of these paths reaches our target or
        # until all reachable types have been reached.
        currentPathLength = 0
        pathToType = {orig: []} # all paths we know, by end of path.
        def typesWithPathOfLength(length):
            '''Returns the lists of known paths of a given length'''
            result = []
            for type_, path in pathToType.items():
                if len(path) == length:
                    result.append(type_)
Yusei Tahara's avatar
Yusei Tahara committed
411
            return result
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
        # We will start exploring paths which start from types
        # reachable in zero steps. That is paths which start from
        # orig.
        typesToStartFrom = typesWithPathOfLength(currentPathLength)
        # Explore paths while there are new paths to be explored
        while len(typesToStartFrom) > 0:
            for startingType in typesToStartFrom:
                # Where can we go in one step starting from here ?
                outputs = self._mtmap.get(startingType)
                if outputs:
                    for reachedType, transforms in outputs.items():
                        # Does this lead to a type we never reached before ?
                        if reachedType not in pathToType.keys() and transforms:
                            # Yes, we did not know any path reaching this type
                            # Let's remember the path to here
                            pathToType[reachedType] = (
                                pathToType[startingType] + [transforms[0]])
                            if reachedType == target:
                                # This is the first time we reach our target.
                                # We have our shortest path to target.
                                return pathToType[target]
            # We explored all possible paths of length currentPathLength
            # Let's increment that length.
            currentPathLength += 1
            # What are the next types to start from ?
            typesToStartFrom = typesWithPathOfLength(currentPathLength)
        # We are done exploring paths starting from orig
        # and this exploration did not reach our target.
        # Hence there is no path from orig to target.
        return None

    def _getPaths(self, orig, target, requirements, path=None, result=None):
        """return some of the paths for transformation from orig mimetype to
        target mimetype with the guarantee that the shortest path is included.
        If target is the same as orig, then returns an empty path.
        """

        shortest = 9999
        if result:
            for okPath in result:
                shortest = min(shortest, len(okPath))
Yusei Tahara's avatar
Yusei Tahara committed
453

454 455
        if orig == target:
            return [[]]
Yusei Tahara's avatar
Yusei Tahara committed
456 457 458 459 460 461 462
        if path is None:
            result = []
            path = []
            requirements = list(requirements)
        outputs = self._mtmap.get(orig)
        if outputs is None:
            return result
463

464 465 466
        registry = getToolByName(self, 'mimetypes_registry')
        mto = registry.lookup(target)
        # target mimetype aliases
467 468
        target_aliases = mto[0].mimetypes

Yusei Tahara's avatar
Yusei Tahara committed
469 470 471 472 473 474 475 476 477 478 479 480
        path.append(None)
        for o_mt, transforms in outputs.items():
            for transform in transforms:
                required = 0
                name = transform.name()
                if name in requirements:
                    requirements.remove(name)
                    required = 1
                if transform in path:
                    # avoid infinite loop...
                    continue
                path[-1] = transform
481
                if o_mt in target_aliases:
Yusei Tahara's avatar
Yusei Tahara committed
482 483
                    if not requirements:
                        result.append(path[:])
484 485 486
                        if len(path[:]) < shortest:
                            # here is a shorter one !
                            shortest = len(path)
Yusei Tahara's avatar
Yusei Tahara committed
487
                else:
488 489 490 491
                    if len(path) < shortest:
                        # keep exploring this path, it is still short enough
                        self._getPaths(o_mt, target, requirements,
                                       path, result)
Yusei Tahara's avatar
Yusei Tahara committed
492 493 494 495 496 497 498 499 500 501 502 503
                if required:
                    requirements.append(name)
        path.pop()

        return result

    security.declarePrivate('manage_afterAdd')
    def manage_afterAdd(self, item, container):
        """ overload manage_afterAdd to finish initialization when the
        transform tool is added
        """
        Folder.manage_afterAdd(self, item, container)
504 505 506 507 508
        try:
            initialize(self)
        except TransformException:
            # may fail on copy or zexp import
            pass
Yusei Tahara's avatar
Yusei Tahara committed
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551

    security.declareProtected(ManagePortal, 'manage_addTransform')
    def manage_addTransform(self, id, module, REQUEST=None):
        """ add a new transform to the tool """
        transform = Transform(id, module)
        self._setObject(id, transform)
        self._mapTransform(transform)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')

    security.declareProtected(ManagePortal, 'manage_addTransform')
    def manage_addTransformsChain(self, id, description, REQUEST=None):
        """ add a new transform to the tool """
        transform = TransformsChain(id, description)
        self._setObject(id, transform)
        self._mapTransform(transform)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')

    security.declareProtected(ManagePortal, 'manage_addTransform')
    def manage_setCacheValidityTime(self, seconds, REQUEST=None):
        """set  the lifetime of cached data in seconds"""
        self.max_sec_in_cache = int(seconds)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')

    security.declareProtected(ManagePortal, 'reloadTransforms')
    def reloadTransforms(self, ids=()):
        """ reload transforms with the given ids
        if no ids, reload all registered transforms

        return a list of (transform_id, transform_module) describing reloaded
        transforms
        """
        if not ids:
            ids = self.objectIds()
        reloaded = []
        for id in ids:
            o = getattr(self, id)
            o.reload()
            reloaded.append((id, o.module))
        return reloaded

552
    # Policy handling methods
Yusei Tahara's avatar
Yusei Tahara committed
553

554 555
    def manage_addPolicy(self, output_mimetype, required_transforms,
                         REQUEST=None):
Yusei Tahara's avatar
Yusei Tahara committed
556 557 558 559
        """ add a policy for a given output mime types"""
        registry = getToolByName(self, 'mimetypes_registry')
        if not registry.lookup(output_mimetype):
            raise TransformException('Unknown MIME type')
560
        if output_mimetype in self._policies:
Yusei Tahara's avatar
Yusei Tahara committed
561 562 563 564 565 566
            msg = 'A policy for output %s is yet defined' % output_mimetype
            raise TransformException(msg)

        required_transforms = tuple(required_transforms)
        self._policies[output_mimetype] = required_transforms
        if REQUEST is not None:
567 568
            REQUEST['RESPONSE'].redirect(self.absolute_url() +
                '/manage_editTransformationPolicyForm')
Yusei Tahara's avatar
Yusei Tahara committed
569 570 571 572 573 574

    def manage_delPolicies(self, outputs, REQUEST=None):
        """ remove policies for given output mime types"""
        for mimetype in outputs:
            del self._policies[mimetype]
        if REQUEST is not None:
575 576
            REQUEST['RESPONSE'].redirect(self.absolute_url() +
                '/manage_editTransformationPolicyForm')
Yusei Tahara's avatar
Yusei Tahara committed
577 578 579 580 581 582 583 584 585 586 587

    def listPolicies(self):
        """ return the list of defined policies

        a policy is a 2-uple (output_mime_type, [list of required transforms])
        """
        # XXXFIXME: backward compat, should be removed latter
        if not hasattr(self, '_policies'):
            self._policies = PersistentMapping()
        return self._policies.items()

588
    # mimetype oriented conversions (iengine interface)
Yusei Tahara's avatar
Yusei Tahara committed
589 590 591 592

    def registerTransform(self, transform):
        """register a new transform

593 594
        transform isn't a Zope Transform (the wrapper) but the wrapped
        transform the persistence wrapper will be created here
Yusei Tahara's avatar
Yusei Tahara committed
595 596 597 598 599
        """
        # needed when call from transform.transforms.initialize which
        # register non zope transform
        module = str(transform.__module__)
        transform = Transform(transform.name(), module, transform)
600
        if not ITransform.providedBy(transform):
601 602
            raise TransformException('%s does not implement ITransform' %
                                     transform)
Yusei Tahara's avatar
Yusei Tahara committed
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
        name = transform.name()
        __traceback_info__ = (name, transform)
        if name not in self.objectIds():
            self._setObject(name, transform)
            self._mapTransform(transform)

    security.declareProtected(ManagePortal, 'ZopeFind')
    def ZopeFind(self, *args, **kwargs):
        """Don't break ZopeFind feature when a transform can't be loaded
        """
        try:
            return Folder.ZopeFind(self, *args, **kwargs)
        except MissingBinary:
            log('ZopeFind: catched MissingBinary exception')

    security.declareProtected(View, 'objectItems')
    def objectItems(self, *args, **kwargs):
        """Don't break ZopeFind feature when a transform can't be loaded
        """
        try:
            return Folder.objectItems(self, *args, **kwargs)
        except MissingBinary:
            log('objectItems: catched MissingBinary exception')
            return []

628 629
    # available mimetypes ####################################################
    def listAvailableTextInputs(self):
630 631 632
        """Returns a list of mimetypes that can be used as input for textfields
        by building a list of the inputs beginning with "text/" of all
        transforms.
633 634 635 636 637 638 639 640 641
        """
        available_types = []
        candidate_transforms = [object[1] for object in self.objectItems()]
        for candidate in candidate_transforms:
            for input in candidate.inputs:
                if input.startswith("text/") and input not in available_types:
                    available_types.append(input)
        return available_types

Yusei Tahara's avatar
Yusei Tahara committed
642
InitializeClass(TransformTool)
643
registerToolInterface('portal_transforms', IPortalTransformsTool)