BusinessTemplate.py 168 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
4
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#
# 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.
#
##############################################################################

29
from Globals import Persistent, PersistentMapping
30
from Acquisition import Implicit, aq_base
31
from AccessControl.Permission import Permission
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32 33
from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
34
from Products.CMFCore.WorkflowCore import WorkflowMethod
Jean-Paul Smets's avatar
Jean-Paul Smets committed
35
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
36 37 38 39
from Products.ERP5Type.Utils import readLocalDocument, \
                                    writeLocalDocument, \
                                    importLocalDocument, \
                                    removeLocalDocument
40 41 42 43
from Products.ERP5Type.Utils import readLocalPropertySheet, \
                                    writeLocalPropertySheet, \
                                    importLocalPropertySheet, \
                                    removeLocalPropertySheet
44 45 46 47 48 49
from Products.ERP5Type.Utils import readLocalConstraint, \
                                    writeLocalPropertySheet, \
                                    importLocalConstraint, \
                                    removeLocalConstraint
from Products.ERP5Type.Utils import readLocalExtension, \
                                    writeLocalExtension, \
50
                                    removeLocalExtension
51 52
from Products.ERP5Type.Utils import readLocalTest, \
                                    writeLocalTest, \
53
                                    removeLocalTest
Jean-Paul Smets's avatar
Jean-Paul Smets committed
54
from Products.ERP5Type.XMLObject import XMLObject
55
from Products.ERP5Type.RoleInformation import RoleInformation
56
import fnmatch
Aurel's avatar
Aurel committed
57
import re, os, sys, string, tarfile
Yoshinori Okuji's avatar
Yoshinori Okuji committed
58
from Products.ERP5Type.Cache import clearCache
59
from DateTime import DateTime
Aurel's avatar
Aurel committed
60
from OFS.Traversable import NotFound
61 62
from OFS import XMLExportImport
from cStringIO import StringIO
Aurel's avatar
Aurel committed
63 64 65 66 67 68
from copy import deepcopy
from App.config import getConfiguration
import OFS.XMLExportImport
customImporters={
    XMLExportImport.magic: XMLExportImport.importXML,
    }
Jean-Paul Smets's avatar
Jean-Paul Smets committed
69 70

from zLOG import LOG
Aurel's avatar
Aurel committed
71 72
from OFS.ObjectManager import customImporters
from gzip import GzipFile
73
from xml.dom.minidom import parse
74
from Products.CMFCore.Expression import Expression
Aurel's avatar
Aurel committed
75
import tarfile
76
from urllib import pathname2url, url2pathname
77
from difflib import unified_diff
Aurel's avatar
Aurel committed
78 79


Aurel's avatar
Aurel committed
80 81
catalog_method_list = ('_is_catalog_list_method_archive',
                       '_is_uncatalog_method_archive',
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
                       '_is_clear_method_archive', '_is_filtered_archive')

catalog_method_filter_list = ('_filter_expression_archive', '_filter_expression_instance_archive',
                              '_filter_type_archive')


def removeAll(entry):
  '''
    Remove all files and directories under 'entry'.
    XXX: This is defined here, because os.removedirs() is buggy.
  '''
  try:
    if os.path.isdir(entry) and not os.path.islink(entry):
      pwd = os.getcwd()
      os.chmod(entry, 0755)
      os.chdir(entry)
      for e in os.listdir(os.curdir):
        removeAll(e)
      os.chdir(pwd)
      os.rmdir(entry)
    else:
      if not os.path.islink(entry):
        os.chmod(entry, 0644)
      os.remove(entry)
  except OSError:
    pass

Aurel's avatar
Aurel committed
109

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
def getChainByType(context):
  """
  This is used in order to construct the full list
  of mapping between type and list of workflow associated
  This is only useful in order to use
  portal_workflow.manage_changeWorkflows
  """
  pw = context.portal_workflow
  cbt = pw._chains_by_type
  ti = pw._listTypeInfo()
  types_info = []
  for t in ti:
    id = t.getId()
    title = t.Title()
    if title == id:
      title = None
    if cbt is not None and cbt.has_key(id):
      chain = ', '.join(cbt[id])
    else:
      chain = '(Default)'
    types_info.append({'id': id,
                      'title': title,
                      'chain': chain})
  new_dict = {}
  for item in types_info:
    new_dict['chain_%s' % item['id']] = item['chain']
  default_chain=', '.join(pw._default_chain)
  return (default_chain, new_dict)

Aurel's avatar
Aurel committed
139 140 141
class BusinessTemplateArchive:
  """
    This is the base class for all Business Template archives
142
  """
Aurel's avatar
Aurel committed
143 144 145 146 147 148 149 150 151 152 153 154

  def __init__(self, creation=0, importing=0, file=None, path=None, **kw):
    if creation:
      self._initCreation(path=path, **kw)
    elif importing:
      self._initImport(file=file, path=path, **kw)

  def addFolder(self, **kw):
    pass

  def addObject(self, *kw):
    pass
155

Aurel's avatar
Aurel committed
156 157 158 159 160 161
  def finishCreation(self, **kw):
    pass

class BusinessTemplateFolder(BusinessTemplateArchive):
  """
    Class archiving businnes template into a folder tree
162
  """
Aurel's avatar
Aurel committed
163 164 165 166 167 168
  def _initCreation(self, path):
    self.path = path
    try:
      os.makedirs(self.path)
    except OSError:
      # folder already exists, remove it
169
      removeAll(self.path)
Aurel's avatar
Aurel committed
170 171 172
      os.makedirs(self.path)

  def addFolder(self, name=''):
173
     if name !='':
Aurel's avatar
Aurel committed
174
      path = os.path.join(self.path, name)
175
      if not os.path.exists(path):
Aurel's avatar
Aurel committed
176 177 178
        os.makedirs(path)
      return path

179
  def addObject(self, obj, name, path=None, ext='.xml'):
180
    name = pathname2url(name)
Aurel's avatar
Aurel committed
181 182 183
    if path is None:
      object_path = os.path.join(self.path, name)
    else:
184 185
      if '%' not in path:
        path = pathname2url(path)
Aurel's avatar
Aurel committed
186 187
      object_path = os.path.join(path, name)
    f = open(object_path+ext, 'wt')
188
    f.write(str(obj))
Aurel's avatar
Aurel committed
189 190 191 192
    f.close()

  def _initImport(self, file=None, path=None, **kw):
    self.file_list = file
193
    # to make id consistent, must remove a part of path while importing
194
    self.root_path_len = len(string.split(path, os.sep)) + 1
Aurel's avatar
Aurel committed
195

196
  def importFiles(self, klass, **kw):
Aurel's avatar
Aurel committed
197 198 199 200
    """
      Import file from a local folder
    """
    class_name = klass.__class__.__name__
201
    for file_path in self.file_list:
202
      if class_name in file_path.split(os.sep):
203 204
        if os.path.isfile(file_path):
          file = open(file_path, 'r')
Aurel's avatar
Aurel committed
205
          # get object id
206 207
          folders = file_path.split(os.sep)
          file_name = string.join(folders[self.root_path_len:], os.sep)
208 209
          if '%' in file_name:
            file_name = url2pathname(file_name)
210
          klass._importFile(file_name, file)
Aurel's avatar
Aurel committed
211
          # close file
212
          file.close()
213

Aurel's avatar
Aurel committed
214 215 216 217 218 219
class BusinessTemplateTarball(BusinessTemplateArchive):
  """
    Class archiving businnes template into a tarball file
  """

  def _initCreation(self, path):
220
    # make tmp dir, must use stringIO instead
Aurel's avatar
Aurel committed
221 222 223 224 225
    self.path = path
    try:
      os.makedirs(self.path)
    except OSError:
      # folder already exists, remove it
226
      removeAll(self.path)
Aurel's avatar
Aurel committed
227 228 229 230 231 232
      os.makedirs(self.path)
    # init tarfile obj
    self.fobj = StringIO()
    self.tar = tarfile.open('', 'w:gz', self.fobj)

  def addFolder(self, name=''):
Aurel's avatar
Aurel committed
233
    if not os.path.exists(name):
Aurel's avatar
Aurel committed
234 235
      os.makedirs(name)

236
  def addObject(self, obj, name, path=None, ext='.xml'):
237
    name = pathname2url(name)
Aurel's avatar
Aurel committed
238 239 240
    if path is None:
      object_path = os.path.join(self.path, name)
    else:
241 242
      if '%' not in path:
        path = pathname2url(path)
Aurel's avatar
Aurel committed
243 244
      object_path = os.path.join(path, name)
    f = open(object_path+ext, 'wt')
245
    f.write(str(obj))
Aurel's avatar
Aurel committed
246 247 248 249 250
    f.close()

  def finishCreation(self):
    self.tar.add(self.path)
    self.tar.close()
251
    removeAll(self.path)
Aurel's avatar
Aurel committed
252 253 254 255 256
    return self.fobj

  def _initImport(self, file=None, **kw):
    self.f = file

257
  def importFiles(self, klass, **kw):
Aurel's avatar
Aurel committed
258 259
    """
      Import all file from the archive to the site
260
    """
Aurel's avatar
Aurel committed
261 262 263 264 265 266
    class_name = klass.__class__.__name__
    self.f.seek(0)
    data = GzipFile(fileobj=self.f).read()
    io = StringIO(data)
    tar = tarfile.TarFile(fileobj=io)
    for info in tar.getmembers():
Yoshinori Okuji's avatar
Yoshinori Okuji committed
267 268
      if 'CVS' in info.name.split('/'):
        continue
269
      if class_name in info.name.split('/'):
Aurel's avatar
Aurel committed
270 271
        if info.isreg():
          file = tar.extractfile(info)
272 273 274
          tar_file_name = info.name.startswith('./') and info.name[2:] or \
              info.name
          folders = string.split(tar_file_name, os.sep)
275 276 277 278
          file_name = (os.sep).join(folders[2:])
          if '%' in file_name:
            file_name = url2pathname(file_name)
          klass._importFile(file_name, file)
Aurel's avatar
Aurel committed
279 280 281
          file.close()
    tar.close()
    io.close()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
282

283 284
class TemplateConditionError(Exception): pass

285 286
class TemplateConflictError(Exception): pass

287
class BaseTemplateItem(Implicit, Persistent):
288
  """
289
    This class is the base class for all template items.
290
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
291

292
  def __init__(self, id_list, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
293
    self.__dict__.update(kw)
294
    self._archive = PersistentMapping()
Aurel's avatar
Aurel committed
295
    self._objects = PersistentMapping()
296 297 298 299 300 301 302
    for id in id_list:
      if not id: continue
      self._archive[id] = None

  def build(self, context, **kw):
    pass

303
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
304 305
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
306
      new_keys = self._objects.keys()
307
      for path in new_keys:          
308 309
        if installed_bt._objects.has_key(path):
          # compare object to see it there is changes
310 311
          new_obj_xml = self.generateXml(path=path)
          old_obj_xml = installed_bt.generateXml(path=path)
312 313 314 315 316 317 318 319 320 321 322 323
          if new_obj_xml != old_obj_xml:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
324
    pass
325 326 327

  def uninstall(self, context, **kw):
    pass
328

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
  def remove(self, context, **kw):
    remove_dict = kw.get('remove_object_dict', {})
    keys = self._objects.keys()
    keys.sort()
    # if you choose remove, the object and all its subobjects will be removed
    # even if you choose backup or keep for subobjects
    # it is same behaviour for backup_and_remove, all we be save
    for path in keys:
      if remove_dict.has_key(path):
        action = remove_dict[path]
        if action == 'save_and_remove':
          # like trash
          self.uninstall(context, trash=1, object_path=path, **kw)
        elif action == 'remove':
          self.uninstall(context, trash=0, object_path=path, **kw)
        

346 347 348 349
  def trash(self, context, new_item, **kw):
    # trash is quite similar to uninstall.
    return self.uninstall(context, new_item=new_item, trash=1, **kw)

Aurel's avatar
Aurel committed
350
  def export(self, context, bta, **kw):
351
    pass
Aurel's avatar
Aurel committed
352 353

  def importFile(self, bta, **kw):
354
    bta.importFiles(klass=self)
355

356 357 358 359 360 361 362 363 364 365 366
  def removeProperties(self, obj):
    """
    Remove unneeded properties for export
    """  
    if hasattr(obj, '__ac_local_roles__'):
      # remove local roles
      obj.__ac_local_roles__ = None
    if hasattr(obj, '_owner'):
      obj._owner = None
    if hasattr(aq_base(obj), 'uid'):
      obj.uid = None
367 368 369 370 371
    if getattr(obj, 'meta_type', None) == 'Script (Python)':
      if hasattr(aq_base(obj), '_code'):
        obj._code = None
    return obj

372 373 374
class ObjectTemplateItem(BaseTemplateItem):
  """
    This class is used for generic objects and as a subclass.
375
  """
376

377 378 379 380
  def __init__(self, id_list, tool_id=None, **kw):
    BaseTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
    if tool_id is not None:
      id_list = self._archive.keys()
381
      self._archive.clear()
382 383 384
      for id in id_list:
        self._archive["%s/%s" % (tool_id, id)] = None

385 386 387 388 389
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    for key in self._objects.keys():
390
      obj = self._objects[key]
391 392
      # create folder and subfolders
      folders, id = os.path.split(key)
Aurel's avatar
Aurel committed
393 394 395 396 397 398 399
      encode_folders = []
      for folder in folders.split('/'):
        if '%' not in folder:
          encode_folders.append(pathname2url(folder))
        else:
          encode_folders.append(folder)
      path = os.path.join(root_path, (os.sep).join(encode_folders))
400 401 402
      bta.addFolder(name=path)
      # export object in xml
      f=StringIO()
403 404
      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
      bta.addObject(obj=f.getvalue(), name=id, path=path)
405

Aurel's avatar
Aurel committed
406 407 408 409
  def build_sub_objects(self, context, id_list, url, **kw):
    p = context.getPortalObject()
    sub_list = {}
    for id in id_list:
410
      relative_url = '/'.join([url,id])
411 412
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
413
      obj = self.removeProperties(obj)
414
      id_list = obj.objectIds()
415
      if hasattr(aq_base(obj), 'groups'):
Aurel's avatar
Aurel committed
416
        # we must keep groups because it's ereased when we delete subobjects
417
        groups = deepcopy(obj.groups)
Aurel's avatar
Aurel committed
418 419
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
420
        obj.manage_delObjects(list(id_list))
421
      if hasattr(aq_base(obj), 'groups'):
422 423 424
        obj.groups = groups
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
Aurel's avatar
Aurel committed
425 426
    return sub_list

427 428 429 430
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
431 432
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
433
      obj = self.removeProperties(obj)
434
      id_list = obj.objectIds()
435
      if hasattr(aq_base(obj), 'groups'):
Aurel's avatar
Aurel committed
436
        # we must keep groups because it's ereased when we delete subobjects
437
        groups = deepcopy(obj.groups)
Aurel's avatar
Aurel committed
438 439
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
440
        obj.manage_delObjects(list(id_list))
441
      if hasattr(aq_base(obj), 'groups'):
442 443 444
        obj.groups = groups
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
445

446 447 448 449 450 451 452 453 454 455
  def _importFile(self, file_name, file):
    # import xml file
    obj = self
    connection = None
    while connection is None:
      obj=obj.aq_parent
      connection=obj._p_jar
    obj = connection.importFile(file, customImporters=customImporters)
    self._objects[file_name[:-4]] = obj

456
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
457 458
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
459 460 461 462 463 464 465
      portal = context.getPortalObject()
      new_keys = self._objects.keys()
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see it there is changes
          new_object = self._objects[path]
          old_object = installed_bt._objects[path]
466 467
          new_object = self.removeProperties(new_object)
          old_object = self.removeProperties(old_object)
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
          new_io = StringIO()
          old_io = StringIO()
          OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, new_io)
          OFS.XMLExportImport.exportXML(old_object._p_jar, old_object._p_oid, old_io)
          new_obj_xml = new_io.getvalue()
          old_obj_xml = old_io.getvalue()
          new_io.close()
          old_io.close()
          if new_obj_xml != old_obj_xml:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def _backupObject(self, action, trashbin, container_path, object_id):
    """
      Backup the object in portal trash if necessery and return its subobjects
    """
491
    subobjects_dict = {}
492
    if trashbin is None: # must return subobjects
493 494 495 496 497 498 499 500
      object_path = container_path + [object_id]
      obj = self.unrestrictedTraverse(object_path)
      for subobject_id in list(obj.objectIds()):
        subobject_path = object_path + [subobject_id]
        subobject = self.unrestrictedTraverse(subobject_path)
        subobject_copy = subobject._p_jar.exportFile(subobject._p_oid)
        subobjects_dict[subobject_id] = subobject_copy      
      return subobjects_dict
501 502
    # XXX btsave is for backward compatibility
    if action == 'backup' or action == 'btsave':
503 504 505 506 507 508 509 510
      subobjects_dict = self.portal_trash.backupObject(trashbin, container_path, object_id, save=1)
    elif action == 'install':
      subobjects_dict = self.portal_trash.backupObject(trashbin, container_path, object_id, save=0)
    return subobjects_dict
    
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
511
    if context.getTemplateFormatVersion() == 1:
512 513 514 515 516 517
      groups = {}
      portal = context.getPortalObject()
      # sort to add objects before their subobjects
      keys = self._objects.keys()
      keys.sort()
      for path in keys:
518 519 520 521 522 523 524
        if update_dict.has_key(path) or force:
          # get action for the oject
          if not force:
            action = update_dict[path]
            if action == 'nothing':
              continue
          else:
525
            action = 'backup'
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
          # get subobjects in path
          container_path = path.split('/')[:-1]
          object_id = path.split('/')[-1]
          try:
            container = portal.unrestrictedTraverse(container_path)
          except KeyError:
            # parent object can be set to nothing, in this case just go on
            container_url = '/'.join(container_path)            
            if update_dict.has_key(container_url):
              if update_dict[container_url] == 'nothing':
                continue
            raise
          container_ids = container.objectIds()
          subobjects_dict = {}
          # Object already exists
          if object_id in container_ids:
            subobjects_dict = self._backupObject(action, trashbin, container_path, object_id)
            container.manage_delObjects([object_id])
          # install object
545
          obj = self._objects[path]          
546 547 548
          if getattr(obj, 'meta_type', None) == 'Script (Python)':
            if getattr(obj, '_code') is None:
              obj._compile()
549
          if hasattr(aq_base(obj), 'groups'):
550
            # we must keep original order groups because they change when we add subobjects
551
            groups[path] = deepcopy(obj.groups)
552
          # copy the object
553 554 555 556 557
          obj = obj._getCopy(container)
          container._setObject(object_id, obj)
          obj = container._getOb(object_id)
          obj.manage_afterClone(obj)
          obj.wl_clearLocks()
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
          # if portal types upgrade, set backup properties
          if getattr(obj, 'meta_type', None) == 'ERP5 Type Information' and len(subobjects_dict) > 0:
            setattr(obj, 'allowed_content_types', subobjects_dict['allowed_content_type_list'] or [])
            setattr(obj, 'hidden_content_type_list', subobjects_dict['hidden_content_type_list'] or [])
            setattr(obj, 'property_sheet_list', subobjects_dict['property_sheet_list'] or [])
            setattr(obj, 'base_category_list', subobjects_dict['base_category_list'] or [])
            setattr(obj, '_roles', subobjects_dict['roles_list'] or [])
            # set actions
            action_list = subobjects_dict['action_list']
            for action in action_list:
              obj.addAction(id = action.id
                            , name = action.title
                            , action = action.action.text
                            , condition = action.getCondition()
                            , permission = action.permissions
                            , category = action.category
                            , visible = action.visible
                            , icon = getattr(action, 'icon', None) and action.icon.text or ''
                            , priority = action.priority
                            )
            # set workflow chain
            wf_chain = subobjects_dict['workflow_chain']
            chain_dict = getChainByType(context)[1]
            default_chain = ''
            chain_dict['chain_%s' %(object_id)] = wf_chain
583
            context.portal_workflow.manage_changeWorkflows(default_chain, props=chain_dict) 
584
          # import sub objects if there is
585
          elif len(subobjects_dict) > 0:
586
            # get a jar
587 588
            connection = obj._p_jar
            o = obj
589
            while connection is None:
590 591
              o = o.aq_parent
              connection = o._p_jar
592 593 594 595 596
            # import subobjects
            for subobject_id in subobjects_dict.keys():
              subobject_data = subobjects_dict[subobject_id]
              subobject_data.seek(0)
              subobject = connection.importFile(subobject_data)
597
              if subobject_id not in obj.objectIds():
598
                obj._setObject(subobject_id, subobject)          
599
          if obj.meta_type in ('Z SQL Method',):
600 601 602
            # It is necessary to make sure that the sql connection
            # in this method is valid.
            sql_connection_list = portal.objectIds(spec=('Z MySQL Database Connection',))
603 604
            if obj.connection_id not in sql_connection_list:
              obj.connection_id = sql_connection_list[0]
605 606
      # now put original order group
      for path in groups.keys():
607 608
        obj = portal.unrestrictedTraverse(path)
        obj.groups = groups[path]
Aurel's avatar
Aurel committed
609
    else:
610 611
      # for old business template format
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
612
      portal = context.getPortalObject()
613
      for relative_url in self._archive.keys():
614
        obj = self._archive[relative_url]
Aurel's avatar
Aurel committed
615 616 617 618
        container_path = relative_url.split('/')[0:-1]
        object_id = relative_url.split('/')[-1]
        container = portal.unrestrictedTraverse(container_path)
        container_ids = container.objectIds()
619
        if object_id in container_ids:    # Object already exists          
620
          self._backupObject('backup', trashbin, container_path, object_id)
621
          container.manage_delObjects([object_id])
Aurel's avatar
Aurel committed
622
        # Set a hard link
623 624 625 626 627 628
        obj = obj._getCopy(container)
        container._setObject(object_id, obj)
        obj = container._getOb(object_id)
        obj.manage_afterClone(obj)
        obj.wl_clearLocks()
        if obj.meta_type in ('Z SQL Method',):
629
          # It is necessary to make sure that the sql connection
Aurel's avatar
Aurel committed
630 631 632
          # in this method is valid.
          sql_connection_list = portal.objectIds(
                                   spec=('Z MySQL Database Connection',))
633 634
          if obj.connection_id not in sql_connection_list:
            obj.connection_id = sql_connection_list[0]
635 636 637

  def uninstall(self, context, **kw):
    portal = context.getPortalObject()
638
    trash = kw.get('trash', 0)
639 640 641 642 643
    trashbin = kw.get('trashbin', None)
    object_path = kw.get('object_path', None)
    if object_path is not None:
      object_keys = [object_path]
    else:
Aurel's avatar
Aurel committed
644
      object_keys = self._archive.keys()
645
    for relative_url in object_keys:
646 647
      container_path = relative_url.split('/')[0:-1]
      object_id = relative_url.split('/')[-1]
648
      try:        
649
        container = portal.unrestrictedTraverse(container_path)
650
        if trash and trashbin is not None:
651 652 653 654
          self.portal_trash.backupObject(trashbin, container_path, object_id, save=1, keep_subobjects=1)
        container.manage_delObjects([object_id])
      except (NotFound, KeyError):
        # object is already backup and/or removed
655
        pass
656 657
    BaseTemplateItem.uninstall(self, context, **kw)

658 659 660 661 662 663 664 665 666 667 668 669
class PathTemplateItem(ObjectTemplateItem):
  """
    This class is used to store objects with wildcards supported.
  """
  def __init__(self, id_list, tool_id=None, **kw):
    BaseTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
    id_list = self._archive.keys()
    self._archive.clear()
    self._path_archive = PersistentMapping()
    for id in id_list:
      self._path_archive[id] = None

670 671 672 673 674 675 676 677 678
  def uninstall(self, context, **kw):
    portal = context.getPortalObject()
    trash = kw.get('trash', 0)
    trashbin = kw.get('trashbin', None)
    object_path = kw.get('object_path', None)
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._path_archive.keys()
679 680
    object_keys.sort()
    object_keys.reverse()
681
    p = context.getPortalObject()
682 683
    object_keys.sort()
    object_keys.reverse()
684 685 686
    for path in object_keys:
      for relative_url in self._resolvePath(p, [], path.split('/')):
        try:        
Aurel's avatar
Aurel committed
687 688
          container_path = relative_url.split('/')[0:-1]
          object_id = relative_url.split('/')[-1]
689 690 691 692 693 694 695 696 697
          container = portal.unrestrictedTraverse(container_path)
          if trash and trashbin is not None:
            self.portal_trash.backupObject(trashbin, container_path, object_id, save=1, keep_subobjects=1)
          container.manage_delObjects([object_id])
        except (NotFound, KeyError):
          # object is already backup and/or removed
          pass
    BaseTemplateItem.uninstall(self, context, **kw)

698 699 700
  def _resolvePath(self, folder, relative_url_list, id_list):
    """
      This method calls itself recursively.
701

702 703 704 705 706 707 708 709 710
      The folder is the current object which contains sub-objects.
      The list of ids are path components. If the list is empty,
      the current folder is valid.
    """
    if len(id_list) == 0:
      return ['/'.join(relative_url_list)]
    id = id_list[0]
    if re.search('[\*\?\[\]]', id) is None:
      # If the id has no meta character, do not have to check all objects.
711 712
      obj = folder._getOb(id)
      return self._resolvePath(obj, relative_url_list + [id], id_list[1:])
713 714
    path_list = []
    for object_id in fnmatch.filter(folder.objectIds(), id):
715 716
      if object_id != "":
        path_list.extend(self._resolvePath(folder._getOb(object_id), relative_url_list + [object_id], id_list[1:]))
717
    return path_list
Aurel's avatar
Aurel committed
718

719 720 721
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
Aurel's avatar
Aurel committed
722 723 724
    keys = self._path_archive.keys()
    keys.sort()    
    for path in keys:
725 726 727
      include_subobjects = 0
      if '**' in path:
        include_subobjects = 1
728
      for relative_url in self._resolvePath(p, [], path.split('/')):
729 730 731
        obj = p.unrestrictedTraverse(relative_url)
        obj = obj._getCopy(context)
        id_list = obj.objectIds()
732
        obj = self.removeProperties(obj)
733
        if hasattr(aq_base(obj), 'groups'):
734
          # we must keep groups because it's ereased when we delete subobjects
735
          groups = deepcopy(obj.groups)
736
        if len(id_list) > 0:
737 738
          if include_subobjects:
            self.build_sub_objects(context, id_list, relative_url)
739
          obj.manage_delObjects(list(id_list))
740
        if hasattr(aq_base(obj), 'groups'):
741 742 743
          obj.groups = groups
        self._objects[relative_url] = obj
        obj.wl_clearLocks()
744
      
745 746
class CategoryTemplateItem(ObjectTemplateItem):

747 748
  def __init__(self, id_list, tool_id='portal_categories', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
749

750 751 752 753
  def build_sub_objects(self, context, id_list, url, **kw):
    p = context.getPortalObject()
    for id in id_list:
      relative_url = '/'.join([url,id])
754 755
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
756
      obj = self.removeProperties(obj)
757
      id_list = obj.objectIds()
758 759
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
760 761 762
        obj.manage_delObjects(list(id_list))
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
763 764 765 766 767

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
768 769
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
770
      obj = self.removeProperties(obj)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
771
      include_sub_categories = obj.__of__(context).getProperty('business_template_include_sub_categories', 0)
772
      id_list = obj.objectIds()
773 774
      if len(id_list) > 0 and include_sub_categories:
        self.build_sub_objects(context, id_list, relative_url)
775
        obj.manage_delObjects(list(id_list))
776
      else:
777 778 779
        obj.manage_delObjects(list(id_list))
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
780 781 782 783
      
  def install(self, context, trashbin, light_install = 0, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
784
    if context.getTemplateFormatVersion() == 1:
785 786
      if light_install == 0:
        ObjectTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
787 788 789 790 791 792 793
      else:
        portal = context.getPortalObject()
        category_tool = portal.portal_categories
        tool_id = self.tool_id
        keys = self._objects.keys()
        keys.sort()
        for path in keys:
794 795 796 797 798 799
          if update_dict.has_key(path) or force:
            if not force:
              action = update_dict[path]
              if action == 'nothing':
                continue
            else:
800
              action = 'backup'
801
            # Wrap the object by an aquisition wrapper for _aq_dynamic.
802 803
            obj = self._objects[path]
            obj = obj.__of__(category_tool)
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
            container_path = path.split('/')[:-1]
            category_id = path.split('/')[-1]
            try:
              container = category_tool.unrestrictedTraverse(container_path)
            except KeyError:
              # parent object can be set to nothing, in this case just go on
              container_url = '/'.join(container_path)            
              if update_dict.has_key(container_url):
                if update_dict[container_url] == 'nothing':
                  continue
              raise
            container_ids = container.objectIds() 
            # Object already exists
            if category_id in container_ids:
              subobjects_dict = self._backupObject(action, trashbin, container_path, category_id)
              container.manage_delObjects([category_id])
820 821
            category = container.newContent(portal_type=obj.getPortalType(), id=category_id)
            for property in obj.propertyIds():
822
              if property not in ('id', 'uid'):
823
                category.setProperty(property, obj.getProperty(property, evaluate=0))
824 825 826
            # import sub objects if there is
            if len(subobjects_dict) > 0:
              # get a jar
827 828
              connection = obj._p_jar
              o = category
829
              while connection is None:
830 831
                o = o.aq_parent
                connection = o._p_jar
832 833 834 835 836 837 838
              # import subobjects
              for subobject_id in subobjects_dict.keys():
                subobject_data = subobjects_dict[subobject_id]
                subobject_data.seek(0)
                subobject = connection.importFile(subobject_data)
                if subobject_id not in category.objectIds():
                  category._setObject(subobject_id, subobject)
839
    else:
840
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
841 842 843 844
      portal = context.getPortalObject()
      category_tool = portal.portal_categories
      tool_id = self.tool_id
      if light_install==0:
845
        ObjectTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
846
      else:
847
        for relative_url in self._archive.keys():
848
          obj = self._archive[relative_url]
Aurel's avatar
Aurel committed
849
          # Wrap the object by an aquisition wrapper for _aq_dynamic.
850
          obj = obj.__of__(category_tool)
Aurel's avatar
Aurel committed
851 852 853 854 855
          container_path = relative_url.split('/')[0:-1]
          category_id = relative_url.split('/')[-1]
          container = category_tool.unrestrictedTraverse(container_path)
          container_ids = container.objectIds()
          if category_id in container_ids:    # Object already exists
856 857
            # XXX call backup here
            subobjects_dict = self._backupObject('backup', trashbin, container_path, category_id)
858
            container.manage_delObjects([category_id])
859 860
          category = container.newContent(portal_type=obj.getPortalType(), id=category_id)
          for property in obj.propertyIds():
Aurel's avatar
Aurel committed
861
            if property not in ('id', 'uid'):
862
              category.setProperty(property, obj.getProperty(property, evaluate=0))
863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
          # import sub objects if there is
          if len(subobjects_dict) > 0:
            # get a jar
            connection = obj._p_jar
            o = category
            while connection is None:
              o = o.aq_parent
              connection = o._p_jar
            # import subobjects
            for subobject_id in subobjects_dict.keys():
              subobject_data = subobjects_dict[subobject_id]
              subobject_data.seek(0)
              subobject = connection.importFile(subobject_data)
              if subobject_id not in category.objectIds():
                category._setObject(subobject_id, subobject)

879

880 881
class SkinTemplateItem(ObjectTemplateItem):

882 883
  def __init__(self, id_list, tool_id='portal_skins', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
884

885
  def install(self, context, trashbin, **kw):
886
    ObjectTemplateItem.install(self, context, trashbin, **kw)
887 888
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
889 890 891 892 893
    p = context.getPortalObject()
    # It is necessary to make sure that the sql connections in Z SQL Methods are valid.
    sql_connection_list = p.objectIds(spec=('Z MySQL Database Connection',))
    for relative_url in self._archive.keys():
      folder = p.unrestrictedTraverse(relative_url)
894 895 896
      for obj in folder.objectValues(spec=('Z SQL Method',)):
        if obj.connection_id not in sql_connection_list:
          obj.connection_id = sql_connection_list[0]
897 898 899 900 901 902
    # Add new folders into skin paths.
    ps = p.portal_skins
    for skin_name, selection in ps.getSkinPaths():
      new_selection = []
      selection = selection.split(',')
      for relative_url in self._archive.keys():
Yoshinori Okuji's avatar
Yoshinori Okuji committed
903
        if context.getTemplateFormatVersion() == 1:
904 905
          if update_dict.has_key(relative_url) or force:
            if not force:
906
              if update_dict[relative_url] == 'nothing':
907
                continue
908
          obj = self._objects[relative_url]
909
        else:
910
          obj = self._archive[relative_url]
911
        skin_id = relative_url.split('/')[-1]
912
        selection_list = obj.getProperty('business_template_registered_skin_selections', None)
913 914 915 916 917 918 919 920 921 922 923 924 925
        if selection_list is None or skin_name in selection_list:
          if skin_id not in selection:
            new_selection.append(skin_id)
      new_selection.extend(selection)
      # sort the layer according to skin priorities
      new_selection.sort(lambda a, b : cmp(
        b in ps.objectIds() and ps[b].getProperty(
            'business_template_skin_layer_priority', 0) or 0,
        a in ps.objectIds() and ps[a].getProperty(
            'business_template_skin_layer_priority', 0) or 0))
      ps.manage_skinLayers(skinpath = tuple(new_selection), skinname = skin_name, add_skin = 1)
    # Make sure that skin data is up-to-date (see CMFCore/Skinnable.py).
    p.changeSkin(None)
926 927 928

  def uninstall(self, context, **kw):
    # Remove folders from skin paths.
929 930 931 932 933
    object_path = kw.get('object_path', None)
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()    
934
    ps = context.portal_skins
935
    skin_id_list = [relative_url.split('/')[-1] for relative_url in object_keys]
936 937 938 939 940 941 942
    for skin_name, selection in ps.getSkinPaths():
      new_selection = []
      selection = selection.split(',')
      for skin_id in selection:
        if skin_id not in skin_id_list:
          new_selection.append(skin_id)
      ps.manage_skinLayers(skinpath = tuple(new_selection), skinname = skin_name, add_skin = 1)
943
    # Make sure that skin data is up-to-date (see CMFCore/Skinnable.py).
944
    context.getPortalObject().changeSkin(None)
945 946 947
    ObjectTemplateItem.uninstall(self, context, **kw)


948
class WorkflowTemplateItem(ObjectTemplateItem):
949

950 951
  def __init__(self, id_list, tool_id='portal_workflow', **kw):
    return ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
952

953
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
954 955
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
956 957 958
      portal = context.getPortalObject()
      new_keys = self._objects.keys()
      for path in new_keys:
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975
        if installed_bt._objects.has_key(path):          
          # compare object to see it there is changes
          new_object = self._objects[path]
          old_object = installed_bt._objects[path]
          new_io = StringIO()
          old_io = StringIO()
          OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, new_io)
          OFS.XMLExportImport.exportXML(old_object._p_jar, old_object._p_oid, old_io)
          new_obj_xml = new_io.getvalue()
          old_obj_xml = old_io.getvalue()
          new_io.close()
          old_io.close()
          if new_obj_xml != old_obj_xml:
            wf_id = path.split('/')[:2]
            modified_object_list.update({'/'.join(wf_id) : ['Modified', 'Workflow']})
        else: # new object
          modified_object_list.update({path : ['New', 'Workflow']})
976 977 978 979 980 981 982 983
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
984
    if context.getTemplateFormatVersion() == 1:
985 986 987 988 989 990 991 992 993 994 995 996 997 998
      portal = context.getPortalObject()
      # sort to add objects before their subobjects
      keys = self._objects.keys()
      keys.sort()
      update_dict = kw.get('object_to_update')
      force = kw.get('force')
      for path in keys:
        wf_path = '/'.join(path.split('/')[:2])
        if wf_path in update_dict or force:
          if not force:
            action = update_dict[wf_path]
            if action == 'nothing':
              continue
          else:
999
            action = 'backup'
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
          container_path = path.split('/')[:-1]
          object_id = path.split('/')[-1]
          try:
            container = portal.unrestrictedTraverse(container_path)
          except KeyError:
            # parent object can be set to nothing, in this case just go on
            container_url = '/'.join(container_path)            
            if update_dict.has_key(container_url):
              if update_dict[container_url] == 'nothing':
                continue
            raise
          container_ids = container.objectIds()
          if object_id in container_ids:    # Object already exists
            self._backupObject(action, trashbin, container_path, object_id)
            container.manage_delObjects([object_id])
1015
          obj = self._objects[path]
1016 1017 1018
          if getattr(obj, 'meta_type', None) == 'Script (Python)':
            if getattr(obj, '_code') is None:
              obj._compile()
1019 1020 1021 1022 1023
          obj = obj._getCopy(container)
          container._setObject(object_id, obj)
          obj = container._getOb(object_id)
          obj.manage_afterClone(obj)
          obj.wl_clearLocks()
1024 1025 1026 1027
    else:
      ObjectTemplateItem.install(self, context, trashbin, **kw)


1028 1029
class PortalTypeTemplateItem(ObjectTemplateItem):

1030 1031
  def __init__(self, id_list, tool_id='portal_types', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1032 1033
    # XXX : this statement can be removed once all bt5 have separated
    # workflow-chain information
1034 1035 1036
    self._workflow_chain_archive = PersistentMapping()

  def build(self, context, **kw):
1037 1038
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
1039 1040
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
1041 1042 1043
      # remove actions and properties
      action_len = len(obj.listActions())
      obj.deleteActions(selections=range(action_len))
1044
      obj = self.removeProperties(obj)
1045 1046 1047 1048 1049 1050 1051 1052 1053
      # remove some properties
      if hasattr(obj, 'allowed_content_types'):
        setattr(obj, 'allowed_content_types', ())
      if hasattr(obj, 'hidden_content_type_list'):
        setattr(obj, 'hidden_content_type_list', ())
      if hasattr(obj, 'property_sheet_list'):
        setattr(obj, 'property_sheet_list', ())
      if hasattr(obj, 'base_category_list'):
        setattr(obj, 'base_category_list', ())
Alexandre Boeglin's avatar
Alexandre Boeglin committed
1054 1055
      if hasattr(obj, '_roles'):
        setattr(obj, '_roles', [])
1056 1057
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
1058

1059 1060
  # XXX : this method is kept temporarily, but can be removed once all bt5 are
  # re-exported with separated workflow-chain information
1061 1062 1063 1064
  def install(self, context, trashbin, **kw):
    ObjectTemplateItem.install(self, context, trashbin, **kw)
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1065 1066
    # We now need to setup the list of workflows corresponding to
    # each portal type
1067
    (default_chain, chain_dict) = getChainByType(context)
1068 1069 1070
    # Set the default chain to the empty string is probably the
    # best solution, by default it is 'default_workflow', wich is
    # not very usefull
1071
    default_chain = ''
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1072
    if context.getTemplateFormatVersion() == 1:
1073
      object_list = self._objects
1074
    else:
1075 1076
      object_list = self._archive
    for path in object_list.keys():
1077 1078 1079 1080 1081
      if update_dict.has_key(path) or force:
        if not force:
          action = update_dict[path]
          if action == 'nothing':
            continue          
1082 1083
        obj = object_list[path]
        portal_type = obj.id
1084 1085 1086
        if self._workflow_chain_archive.has_key(portal_type):
          chain_dict['chain_%s' % portal_type] = \
              self._workflow_chain_archive[portal_type]
1087 1088
    context.portal_workflow.manage_changeWorkflows(default_chain,
                                                   props=chain_dict)
1089

1090 1091
  # XXX : this method is kept temporarily, but can be removed once all bt5 are
  # re-exported with separated workflow-chain information
1092 1093 1094 1095 1096 1097 1098
  def _importFile(self, file_name, file):
    if 'workflow_chain_type.xml' in file_name:
      # import workflow chain for portal_type
      dict = {}
      xml = parse(file)
      chain_list = xml.getElementsByTagName('chain')
      for chain in chain_list:
1099
        ptype = chain.getElementsByTagName('type')[0].childNodes[0].data
1100 1101 1102 1103 1104
        workflow_list = chain.getElementsByTagName('workflow')[0].childNodes
        if len(workflow_list) == 0:
          workflow = ''
        else:
          workflow = workflow_list[0].data
1105
        dict[str(ptype)] = str(workflow)
1106 1107 1108 1109
      self._workflow_chain_archive = dict
    else:
      ObjectTemplateItem._importFile(self, file_name, file)

1110
class PortalTypeWorkflowChainTemplateItem(BaseTemplateItem):
1111 1112 1113

  def build(self, context, **kw):
    p = context.getPortalObject()
1114
    (default_chain, chain_dict) = getChainByType(context)
Aurel's avatar
Aurel committed
1115
    for key in self._archive.keys():
1116 1117 1118
      wflist = key.split(' | ')
      if len(wflist) == 2:
        portal_type = wflist[0]
Aurel's avatar
Aurel committed
1119
        workflow = wflist[1]
1120
      else:
Aurel's avatar
Aurel committed
1121
        portal_type = wflist[0][:-2]
1122 1123 1124
        workflow = ''
      if chain_dict.has_key('chain_%s' % portal_type):
        if workflow not in chain_dict['chain_%s' % portal_type]:
Alexandre Boeglin's avatar
Alexandre Boeglin committed
1125 1126
          raise NotFound, 'workflow %s not found in chain for portal_type %s'\
        % (workflow, portal_type)
1127 1128 1129 1130 1131
        if self._objects.has_key(portal_type):
          workflow_list = self._objects[portal_type]
          workflow_list.append(workflow)
          self._objects[portal_type] = workflow_list
        else:
1132
          self._objects[portal_type] = [workflow,]
1133
      else:
Aurel's avatar
Aurel committed
1134
        LOG('BusinessTemplate build', 100, 'portal type %s not found in workflow chain' %(portal_type))
1135 1136 1137 1138 1139 1140

  def generateXml(self, path=None):
    xml_data = '<workflow_chain>'
    keys = self._objects.keys()
    keys.sort()
    for key in keys:
1141
      workflow_list = self._objects[key]
1142
      LOG('generate xml', 0, workflow_list)
1143 1144
      xml_data += os.linesep+' <chain>'
      xml_data += os.linesep+'  <type>%s</type>' %(key,)
1145
      xml_data += os.linesep+'  <workflow>%s</workflow>' %(', '.join(workflow_list))
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163
      xml_data += os.linesep+' </chain>'
    xml_data += os.linesep+'</workflow_chain>'
    return xml_data

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
    # export workflow chain
    xml_data = self.generateXml()
    bta.addObject(obj=xml_data, name='workflow_chain_type',  path=root_path)

  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    # We now need to setup the list of workflows corresponding to
    # each portal type
1164
    (default_chain, chain_dict) = getChainByType(context)
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175
    # Set the default chain to the empty string is probably the
    # best solution, by default it is 'default_workflow', wich is
    # not very usefull
    default_chain = ''
    for path in self._objects.keys():
      if update_dict.has_key(path) or force:
        if not force:
          action = update_dict[path]
          if action == 'nothing':
            continue          
        portal_type = path.split('/', 1)[1]
1176
        if chain_dict.has_key('chain_%s' % portal_type):
Aurel's avatar
Aurel committed
1177 1178 1179
          old_chain_dict = chain_dict['chain_%s' % portal_type]
          # XXX we don't use the chain (Default) in erp5 so don't keep it
          if old_chain_dict != '(Default)' and old_chain_dict != '':
1180 1181 1182 1183 1184 1185 1186 1187
            # unique workflow chains
            old_chain_workflow_id_set = {}
            for wf_id in old_chain_dict.split(', '):
              old_chain_workflow_id_set[wf_id] = 1
            for wf_id in self._objects[path].split(', '):
              old_chain_workflow_id_set[wf_id] = 1
            chain_dict['chain_%s' % portal_type] = ', '.join(
                                              old_chain_workflow_id_set.keys())
Aurel's avatar
Aurel committed
1188
          else:
1189
            chain_dict['chain_%s' % portal_type] = self._objects[path]
Aurel's avatar
Aurel committed
1190 1191
        else:
          chain_dict['chain_%s' % portal_type] = self._objects[path]
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206
    context.portal_workflow.manage_changeWorkflows(default_chain,
                                                   props=chain_dict)

  def _importFile(self, file_name, file):
    # import workflow chain for portal_type
    dict = {}
    xml = parse(file)
    chain_list = xml.getElementsByTagName('chain')
    for chain in chain_list:
      ptype = chain.getElementsByTagName('type')[0].childNodes[0].data
      workflow_list = chain.getElementsByTagName('workflow')[0].childNodes
      if len(workflow_list) == 0:
        workflow = ''
      else:
        workflow = workflow_list[0].data
1207 1208
      if 'portal_type_workflow_chain/' not in str(ptype):
        ptype = 'portal_type_workflow_chain/' + str(ptype)
1209 1210 1211
      dict[str(ptype)] = str(workflow)
    self._objects = dict

1212 1213
# just for backward compatibility
PortalTypeTemplateWorkflowChainItem = PortalTypeWorkflowChainTemplateItem
1214

1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230
class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):

  xml_tag = 'allowed_content_type_list'
  class_property = 'allowed_content_types'

  def build(self, context, **kw):
    for key in self._archive.keys():
      portal_type, allowed_type = key.split(' | ')
      if self._objects.has_key(portal_type):
        allowed_list = self._objects[portal_type]
        allowed_list.append(allowed_type)
        self._objects[portal_type] = allowed_list
      else:
        self._objects[portal_type] = [allowed_type]

  def generateXml(self, path=None):
1231
    dictio = self._objects
1232
    xml_data = '<%s>' %(self.xml_tag,)
1233
    keys = dictio.keys()
1234 1235
    keys.sort()
    for key in keys:
1236
      allowed_list = dictio[key]
1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252
      xml_data += os.linesep+' <portal_type id="%s">' %(key,)
      for allowed_item in allowed_list:
        xml_data += os.linesep+'  <item>%s</item>' %(allowed_item,)
      xml_data += os.linesep+' </portal_type>'
    xml_data += os.linesep+'</%s>' %(self.xml_tag,)
    return xml_data

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    path = self.__class__.__name__+os.sep+self.class_property
    xml_data = self.generateXml(path=None)
    bta.addObject(obj=xml_data, name=path, path=None)

1253 1254 1255 1256 1257 1258
  def preinstall(self, context, installed_bt, **kw):
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
      portal = context.getPortalObject()
      new_keys = self._objects.keys()
      if installed_bt.id == 'installed_bt_for_diff':
1259
        #must rename keys in dict if reinstall
1260 1261 1262 1263 1264 1265 1266 1267 1268
        new_dict = PersistentMapping()
        for key in installed_bt._objects.keys():
          new_key = self.class_property+'/'+key
          new_dict[new_key] = installed_bt._objects[key]
        installed_bt._objects = new_dict
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see it there is changes
          new_object = self._objects[path]
1269
          old_object = installed_bt._objects[path]
1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280
          if new_object != old_object:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316
  def _importFile(self, file_name, file):
    path, name = os.path.split(file_name)
    id = string.split(name, '.')[0]
    xml = parse(file)
    portal_type_list = xml.getElementsByTagName('portal_type')
    for portal_type in portal_type_list:
      id = portal_type.getAttribute('id')
      item_type_list = []
      item_list = portal_type.getElementsByTagName('item')
      for item in item_list:
        item_type_list.append(str(item.childNodes[0].data))
      self._objects[self.class_property+'/'+id] = item_type_list

  def install(self, context, trashbin, **kw):
    p = context.getPortalObject()
    pt = p.unrestrictedTraverse('portal_types')
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    for key in self._objects.keys():
      if update_dict.has_key(key) or force:
        if not force:
          action = update_dict[key]
          if action == 'nothing':
            continue
        try:
          portal_id = key.split('/')[-1]
          portal_type = pt._getOb(portal_id)
        except KeyError:
          LOG("portal types not found : ", 100, portal_id)
          continue
        property_list = self._objects[key]
        object_property_list = getattr(portal_type, self.class_property, ())
        if len(object_property_list) > 0:
          # merge differences between portal types properties
          # only add new, do not remove
          for id in object_property_list:
1317 1318
            if id not in property_list:
              property_list.append(id)
1319 1320
        setattr(portal_type, self.class_property, list(property_list))

Aurel's avatar
Aurel committed
1321
  def uninstall(self, context, **kw):
1322
    object_path = kw.get('object_path', None)
1323
    p = context.getPortalObject()
1324
    pt = p.unrestrictedTraverse('portal_types')
1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._objects.keys()
    for key in object_keys:
      try:
        portal_id = key.split('/')[-1]
        portal_type = pt._getOb(portal_id)
      except KeyError:
        LOG("portal types not found : ", 100, portal_id)
        continue
      property_list = self._objects[key]
1337 1338
      original_property_list = list(getattr(portal_type,
                                    self.class_property, ()))
Aurel's avatar
Aurel committed
1339 1340
      for id in property_list:
        if id in original_property_list:
1341
          original_property_list.remove(id)
1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358
      setattr(portal_type, self.class_property, list(original_property_list))
    
class PortalTypeHiddenContentTypeTemplateItem(PortalTypeAllowedContentTypeTemplateItem):

  xml_tag = 'hidden_content_type_list'
  class_property = 'hidden_content_type_list'

class PortalTypePropertySheetTemplateItem(PortalTypeAllowedContentTypeTemplateItem):

  xml_tag = 'property_sheet_list'
  class_property = 'property_sheet_list'

class PortalTypeBaseCategoryTemplateItem(PortalTypeAllowedContentTypeTemplateItem):

  xml_tag = 'base_category_list'
  class_property = 'base_category_list'

1359 1360
class CatalogMethodTemplateItem(ObjectTemplateItem):

1361 1362
  def __init__(self, id_list, tool_id='portal_catalog', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1363
    self._is_catalog_list_method_archive = PersistentMapping()
1364 1365 1366 1367 1368 1369 1370 1371 1372
    self._is_uncatalog_method_archive = PersistentMapping()
    self._is_clear_method_archive = PersistentMapping()
    self._is_filtered_archive = PersistentMapping()
    self._filter_expression_archive = PersistentMapping()
    self._filter_expression_instance_archive = PersistentMapping()
    self._filter_type_archive = PersistentMapping()

  def build(self, context, **kw):
    ObjectTemplateItem.build(self, context, **kw)
1373 1374
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1375 1376
    except KeyError:
      catalog = None
1377
    if catalog is None:
1378
      LOG('BusinessTemplate build', 0, 'catalog not found')
1379
      return
1380 1381
    for obj in self._objects.values():
      method_id = obj.id
1382 1383 1384
      self._is_catalog_list_method_archive[method_id] = method_id in catalog.sql_catalog_object_list
      self._is_uncatalog_method_archive[method_id] = method_id in catalog.sql_uncatalog_object
      self._is_clear_method_archive[method_id] = method_id in catalog.sql_clear_catalog
1385
      self._is_filtered_archive[method_id] = 0
1386 1387 1388 1389 1390
      if catalog.filter_dict.has_key(method_id):
        self._is_filtered_archive[method_id] = catalog.filter_dict[method_id]['filtered']
        self._filter_expression_archive[method_id] = catalog.filter_dict[method_id]['expression']
        self._filter_expression_instance_archive[method_id] = catalog.filter_dict[method_id]['expression_instance']
        self._filter_type_archive[method_id] = catalog.filter_dict[method_id]['type']
1391

Aurel's avatar
Aurel committed
1392 1393 1394 1395 1396
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    for key in self._objects.keys():
1397
      obj = self._objects[key]
Aurel's avatar
Aurel committed
1398 1399 1400 1401 1402 1403
      # create folder and subfolders
      folders, id = os.path.split(key)
      path = os.path.join(root_path, folders)
      bta.addFolder(name=path)
      # export object in xml
      f=StringIO()
1404 1405
      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
      bta.addObject(obj=f.getvalue(), name=id, path=path)
Aurel's avatar
Aurel committed
1406 1407
      # add all datas specific to catalog inside one file
      catalog = context.portal_catalog.getSQLCatalog()
1408
      method_id = obj.id
1409 1410
      object_path = os.path.join(path, method_id+'.catalog_keys.xml')

Aurel's avatar
Aurel committed
1411
      f = open(object_path, 'wt')
1412 1413 1414
      xml_data = '<catalog_method>'
      for method in catalog_method_list:
        value = getattr(self, method, 0)[method_id]
1415
        xml_data += os.linesep+' <item key="%s" type="int">' %(method,)
1416
        xml_data += os.linesep+'  <value>%s</value>' %(str(int(value)))
1417
        xml_data += os.linesep+' </item>'
Aurel's avatar
Aurel committed
1418
      if catalog.filter_dict.has_key(method_id):
1419 1420 1421
        for method in catalog_method_filter_list:
          value = getattr(self, method, '')[method_id]
          if method == '_filter_expression_instance_archive':
1422
            pass
1423
          else:
1424
            if type(value) in (type(''), type(u'')):
1425
              xml_data += os.linesep+' <item key="%s" type="str">' %(method,)
1426
              xml_data += os.linesep+'  <value>%s</value>' %(str(value))
1427
              xml_data += os.linesep+' </item>'
1428
            elif type(value) in (type(()), type([])):
1429
              xml_data += os.linesep+' <item key="%s" type="tuple">'%(method)
1430 1431
              for item in value:
                xml_data += os.linesep+'  <value>%s</value>' %(str(item))
1432
              xml_data += os.linesep+' </item>'
1433 1434
      xml_data += os.linesep+'</catalog_method>'
      f.write(str(xml_data))
Aurel's avatar
Aurel committed
1435
      f.close()
1436

1437 1438
  def install(self, context, trashbin, **kw):
    ObjectTemplateItem.install(self, context, trashbin, **kw)
1439 1440
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1441
    except KeyError:
1442 1443 1444 1445 1446 1447 1448 1449
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    # Make copies of attributes of the default catalog of portal_catalog.
    sql_catalog_object_list = list(catalog.sql_catalog_object_list)
    sql_uncatalog_object = list(catalog.sql_uncatalog_object)
    sql_clear_catalog = list(catalog.sql_clear_catalog)
1450

1451 1452 1453
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    values = []
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1454
    new_bt_format = context.getTemplateFormatVersion()
1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475

    if force: # get all objects
      if new_bt_format:
        values = self._objects.values()
      else:
        values = self._archive.values()
    else: # get only selected object
      if new_bt_format == 1:
        keys = self._objects.keys()
      else:
        keys = self._archive.keys()
      for key in keys:
        if update_dict.has_key(key) or force:
          if not force:
            action = update_dict[key]
            if action == 'nothing':
              continue          
          if new_bt_format:
            values.append(self._objects[key])
          else:
            values.append(self._archive[key])
1476

1477 1478
    for obj in values:
      method_id = obj.id
1479

Aurel's avatar
Aurel committed
1480 1481 1482 1483
      is_catalog_list_method = int(self._is_catalog_list_method_archive[method_id])
      is_uncatalog_method = int(self._is_uncatalog_method_archive[method_id])
      is_clear_method = int(self._is_clear_method_archive[method_id])
      is_filtered = int(self._is_filtered_archive[method_id])
1484

1485 1486 1487 1488 1489
      if is_catalog_list_method and method_id not in sql_catalog_object_list:
        sql_catalog_object_list.append(method_id)
      elif not is_catalog_list_method and method_id in sql_catalog_object_list:
        sql_catalog_object_list.remove(method_id)

1490
      if is_uncatalog_method and method_id not in sql_uncatalog_object:
1491
        sql_uncatalog_object.append(method_id)
1492
      elif not is_uncatalog_method and method_id in sql_uncatalog_object:
1493 1494 1495 1496 1497 1498 1499 1500
        sql_uncatalog_object.remove(method_id)

      if is_clear_method and method_id not in sql_clear_catalog:
        sql_clear_catalog.append(method_id)
      elif not is_clear_method and method_id in sql_clear_catalog:
        sql_clear_catalog.remove(method_id)

      if is_filtered:
1501
        expression = self._filter_expression_archive[method_id]
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1502
        if context.getTemplateFormatVersion() == 1:
1503 1504 1505
          expr_instance = Expression(expression)
        else:
          expr_instance = self._filter_expression_instance_archive[method_id]
1506
        filter_type = self._filter_type_archive[method_id]
1507 1508 1509
        catalog.filter_dict[method_id] = PersistentMapping()
        catalog.filter_dict[method_id]['filtered'] = 1
        catalog.filter_dict[method_id]['expression'] = expression
1510
        catalog.filter_dict[method_id]['expression_instance'] = expr_instance
1511
        catalog.filter_dict[method_id]['type'] = filter_type
1512
      elif method_id in catalog.filter_dict.keys():
1513
        catalog.filter_dict[method_id]['filtered'] = 0
1514

1515 1516
    sql_catalog_object_list.sort()
    catalog.sql_catalog_object_list = tuple(sql_catalog_object_list)
1517
    sql_uncatalog_object.sort()
1518
    catalog.sql_uncatalog_object = tuple(sql_uncatalog_object)
1519
    sql_clear_catalog.sort()
1520
    catalog.sql_clear_catalog = tuple(sql_clear_catalog)
1521 1522

  def uninstall(self, context, **kw):
1523 1524
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1525
    except KeyError:
1526 1527 1528 1529
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
1530
    
1531 1532 1533 1534
    values = []
    object_path = kw.get('object_path', None)
    # get required values
    if object_path is None:
1535 1536 1537 1538
      if context.getTemplateFormatVersion() == 1:
        values = self._objects.values()
      else:
        values = self._archive.values()
1539 1540 1541 1542
    else:
      value = self._archive[object_path]
      if value is not None:
        values.append(value)
1543 1544 1545 1546
    # Make copies of attributes of the default catalog of portal_catalog.
    sql_catalog_object_list = list(catalog.sql_catalog_object_list)
    sql_uncatalog_object = list(catalog.sql_uncatalog_object)
    sql_clear_catalog = list(catalog.sql_clear_catalog)
1547

1548
    for obj in values:      
1549
      method_id = obj.id
1550 1551
      if method_id in sql_catalog_object_list:
        sql_catalog_object_list.remove(method_id)
1552 1553 1554 1555
      if method_id in sql_uncatalog_object:
        sql_uncatalog_object.remove(method_id)
      if method_id in sql_clear_catalog:
        sql_clear_catalog.remove(method_id)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1556
      if catalog.filter_dict.has_key(method_id):
1557
        del catalog.filter_dict[method_id]
1558
        
1559 1560 1561
    catalog.sql_catalog_object_list = tuple(sql_catalog_object_list)
    catalog.sql_uncatalog_object = tuple(sql_uncatalog_object)
    catalog.sql_clear_catalog = tuple(sql_clear_catalog)
1562
    # uninstall objects
1563
    ObjectTemplateItem.uninstall(self, context, **kw)
1564

1565
  def _importFile(self, file_name, file):
1566
    if not '.catalog_keys' in file_name:
1567 1568 1569 1570 1571 1572 1573 1574
      # just import xml object
      obj = self
      connection = None
      while connection is None:
        obj=obj.aq_parent
        connection=obj._p_jar
      obj = connection.importFile(file, customImporters=customImporters)
      self._objects[file_name[:-4]] = obj
1575
    elif '.catalog_keys' in file_name:
1576 1577 1578 1579
      # recreate data mapping specific to catalog method
      path, name = os.path.split(file_name)
      id = string.split(name, '.')[0]
      xml = parse(file)
1580
      method_list = xml.getElementsByTagName('item')
1581
      for method in method_list:
1582
        key = method.getAttribute('key')
1583 1584
        key_type = str(method.getAttribute('type'))
        if key_type == "str":
1585 1586
          value = str(method.getElementsByTagName('value')[0].childNodes[0].data)
          key = str(key)
1587
        elif key_type == "int":
1588
          value = int(method.getElementsByTagName('value')[0].childNodes[0].data)
1589
          key = str(key)
1590
        elif key_type == "tuple":
1591 1592 1593 1594
          value = []
          value_list = method.getElementsByTagName('value')
          for item in value_list:
            value.append(item.childNodes[0].data)
1595
        else:
1596
          LOG('BusinessTemplate import CatalogMethod, type unknown', 0, key_type)
1597
          continue
1598 1599 1600
        if key in catalog_method_list or key in catalog_method_filter_list:
          dict = getattr(self, key)
          dict[id] = value
1601 1602

class ActionTemplateItem(ObjectTemplateItem):
1603 1604

  def __init__(self, id_list, **kw):
1605
    # XXX It's look like ObjectTemplateItem __init__
1606 1607 1608 1609 1610 1611
    BaseTemplateItem.__init__(self, id_list, **kw)
    id_list = self._archive.keys()
    self._archive.clear()
    for id in id_list:
      self._archive["%s/%s" % ('portal_types', id)] = None

1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632
  def _splitPath(self, path):
    """
      Split path tries to split a complexe path such as:

      "foo/bar[id=zoo]"

      into

      "foo/bar", "id", "zoo"

      This is used mostly for generic objects
    """
    # Add error checking here
    if path.find('[') >= 0 and path.find(']') > path.find('=') and path.find('=') > path.find('['):
      relative_url = path[0:path.find('[')]
      id_block = path[path.find('[')+1:path.find(']')]
      key = id_block.split('=')[0]
      value = id_block.split('=')[1]
      return relative_url, key, value
    return path, None, None

1633 1634 1635 1636
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
1637
      relative_url, value = id.split(' | ')
1638 1639
      obj = p.unrestrictedTraverse(relative_url)
      for ai in obj.listActions():
1640
        if getattr(ai, 'id') == value:
1641
          url = os.path.split(relative_url)
Aurel's avatar
Aurel committed
1642
          key = os.path.join(url[-2], url[-1], value)
1643
          action = ai._getCopy(context)
1644
          action = self.removeProperties(action)
1645
          self._objects[key] = action
Aurel's avatar
Aurel committed
1646
          self._objects[key].wl_clearLocks()
1647 1648
          break
      else:
1649
        raise NotFound, 'Action %r not found' %(id,)
Aurel's avatar
Aurel committed
1650

1651 1652 1653
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1654
    if context.getTemplateFormatVersion() == 1:
Aurel's avatar
Aurel committed
1655 1656
      p = context.getPortalObject()
      for id in self._objects.keys():
1657 1658 1659 1660 1661 1662
        if update_dict.has_key(id) or force:
          if not force:
            action = update_dict[id]
            if action == 'nothing':
              continue
          path = id.split(os.sep)
1663 1664
          obj = p.unrestrictedTraverse(path[:-1])
          action_list = obj.listActions()
1665 1666 1667
          for index in range(len(action_list)):
            if getattr(action_list[index], 'id') == path[-1]:          
              # remove previous action
1668
              obj.deleteActions(selections=(index,))
1669
          action = self._objects[id]
1670
          obj.addAction(
1671 1672 1673 1674 1675 1676 1677 1678 1679
                        id = action.id
                      , name = action.title
                      , action = action.action.text
                      , condition = action.getCondition()
                      , permission = action.permissions
                      , category = action.category
                      , visible = action.visible
                      , icon = getattr(action, 'icon', None) and action.icon.text or ''
                      , optional = getattr(action, 'optional', 0)
1680
                      , priority = action.priority
Aurel's avatar
Aurel committed
1681
                    )
1682
          # sort action based on the priority define on it
1683 1684 1685 1686 1687 1688 1689 1690 1691
          # XXX suppose that priority are properly on actions
          new_priority = action.priority
          action_list = obj.listActions()
          move_down_list = []
          for index in range(len(action_list)):
            action = action_list[index]
            if action.priority > new_priority:
              move_down_list.append(str(index))
          obj.moveDownActions(selections=tuple(move_down_list))
Aurel's avatar
Aurel committed
1692
    else:
1693
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
1694
      p = context.getPortalObject()
1695 1696
      for id in self._archive.keys():
        action = self._archive[id]
Aurel's avatar
Aurel committed
1697
        relative_url, key, value = self._splitPath(id)
1698 1699
        obj = p.unrestrictedTraverse(relative_url)
        for ai in obj.listActions():
Aurel's avatar
Aurel committed
1700
          if getattr(ai, key) == value:
1701 1702
            raise TemplateConflictError, 'the portal type %s already has the action %s' % (obj.id, value)
        obj.addAction(
Aurel's avatar
Aurel committed
1703 1704 1705 1706 1707 1708 1709 1710 1711 1712
                      id = action.id
                    , name = action.title
                    , action = action.action.text
                    , condition = action.getCondition()
                    , permission = action.permissions
                    , category = action.category
                    , visible = action.visible
                    , icon = getattr(action, 'icon', None) and action.icon.text or ''
                    , optional = getattr(action, 'optional', 0)
                    )
Aurel's avatar
Aurel committed
1713 1714 1715 1716 1717 1718 1719
        new_priority = action.priority
        action_list = obj.listActions()
        move_down_list = []
        for index in range(len(action_list)):
          action = action_list[index]
          if action.priority > new_priority:
            move_down_list.append(str(index))
1720 1721
          obj.moveDownActions(selections=tuple(move_down_list))

1722 1723
  def uninstall(self, context, **kw):
    p = context.getPortalObject()
1724 1725 1726 1727
    object_path = kw.get("object_path", None)
    if object_path is not None:
      keys = [object_path]
    else:
1728
      keys = self._archive.keys()
1729
    for id in keys:
1730 1731 1732 1733
      if  '|' in id:
        relative_url, value = id.split(' | ')
        key = 'id'
      else:
1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744
        relative_url, key, value = self._splitPath(id)
      obj = p.unrestrictedTraverse(relative_url, None)
      if obj is not None:
        action_list = obj.listActions()
        for index in range(len(action_list)):
          if getattr(action_list[index], key) == value:
            obj.deleteActions(selections=(index,))
            break
      else :
        LOG('BusinessTemplate', 100,
            'unable to uninstall action at %s, ignoring' % relative_url )
1745 1746
    BaseTemplateItem.uninstall(self, context, **kw)

1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762
class PortalTypeRolesTemplateItem(BaseTemplateItem):

  def __init__(self, id_list, **kw):
    id_list = ['portal_type_roles/%s' % id for id in id_list]
    BaseTemplateItem.__init__(self, id_list, **kw)

  def build(self, context, **kw):
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
      obj = p.unrestrictedTraverse("portal_types/%s" %
          relative_url.split('/', 1)[1])
      type_roles_obj = getattr(obj, '_roles', ())
      type_role_list = []
      for role in type_roles_obj:
        type_role_dict = {}
        # uniq
1763
        for property in ('id', 'title', 'description',
1764 1765 1766 1767
            'priority', 'base_category_script'):
          prop_value = getattr(role, property)
          if prop_value:
            type_role_dict[property] = prop_value
1768 1769 1770 1771
        # condition
        prop_value = getattr(role, 'condition')
        if prop_value:
          type_role_dict['condition'] = prop_value.text
1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823
        # multi
        for property in ('category', 'base_category'):
          prop_value_list = []
          for prop_value in getattr(role, property):
            prop_value_list.append(prop_value)
          type_role_dict[property] = prop_value_list
        type_role_list.append(type_role_dict)
      self._objects[relative_url] = type_role_list

  def generateXml(self, path=None):
    type_role_list = self._objects[path]
    xml_data = '<type_roles>'
    for role in type_role_list:
      xml_data += os.linesep+"  <role id='%s'>" % role['id']
      # uniq
      for property in ('title', 'description', 'condition', 'priority',
          'base_category_script'):
        prop_value = role.get(property)
        if prop_value:
          xml_data += os.linesep+"   <property id='%s'>%s</property>" % \
              (property, prop_value)
      # multi
      for property in ('category', 'base_category'):
        for prop_value in role.get(property):
          xml_data += os.linesep+"   <multi_property "\
          "id='%s'>%s</multi_property>" % (property, prop_value)
      xml_data += os.linesep+"  </role>"
    xml_data += os.linesep+'</type_roles>'
    return xml_data
      
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
    for key in self._objects.keys():
      xml_data = self.generateXml(key)
      name = key.split('/', 1)[1]
      bta.addObject(obj=xml_data, name=name, path=root_path)

  def _importFile(self, file_name, file):
    type_roles_list = []
    xml = parse(file)
    xml_type_roles_list = xml.getElementsByTagName('role')    
    for role in xml_type_roles_list:
      id = role.getAttribute('id')
      type_role_property_dict = {'id':id}
      # uniq
      property_list = role.getElementsByTagName('property')
      for property in property_list:
        property_id = property.getAttribute('id').encode()
        if property.hasChildNodes():
1824
          property_value = property.childNodes[0].data.encode('utf_8', 'backslashreplace')
1825 1826 1827 1828 1829 1830 1831 1832 1833 1834
          if property_id == 'priority':
            property_value = float(property_value)
          type_role_property_dict[property_id] = property_value
      # multi
      multi_property_list = role.getElementsByTagName('multi_property')
      for property in multi_property_list:
        property_id = property.getAttribute('id').encode()
        if not type_role_property_dict.has_key(property_id):
          type_role_property_dict[property_id] = []
        if property.hasChildNodes():
1835
          property_value = property.childNodes[0].data.encode('utf_8', 'backslashreplace')
1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852
          type_role_property_dict[property_id].append(property_value)
      type_roles_list.append(type_role_property_dict)
    self._objects['portal_type_roles/'+file_name[:-4]] = type_roles_list

  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    p = context.getPortalObject()
    for roles_path in self._objects.keys():
      if update_dict.has_key(roles_path) or force:
        if not force:
          action = update_dict[roles_path]
          if action == 'nothing':
            continue
      path = 'portal_types/%s' % roles_path.split('/', 1)[1]
      obj = p.unrestrictedTraverse(path)
      setattr(obj, '_roles', []) # reset roles before applying
Alexandre Boeglin's avatar
Alexandre Boeglin committed
1853
      type_roles_list = self._objects[roles_path] or []
1854 1855 1856 1857 1858 1859 1860 1861 1862 1863
      for type_role_property_dict in type_roles_list:
        obj._roles.append(RoleInformation(**type_role_property_dict))

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
    for roles_path in self._objects.keys():
      path = 'portal_types/%s' % roles_path.split('/', 1)[1]
      obj = p.unrestrictedTraverse(path)
      setattr(obj, '_roles', [])

1864 1865 1866 1867 1868 1869
class SitePropertyTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
1870 1871
      for property in p.propertyMap():
        if property['id'] == id:
1872
          obj = p.getProperty(id)
1873
          prop_type = property['type']
1874 1875
          break
      else:
1876 1877
        obj = None
      if obj is None:
1878
        raise NotFound, 'the property %s is not found' % id
1879
      self._objects[id] = (prop_type, obj)
Aurel's avatar
Aurel committed
1880

1881 1882 1883 1884 1885 1886
  def _importFile(self, file_name, file):
    # recreate list of site property from xml file
    xml = parse(file)
    property_list = xml.getElementsByTagName('property')
    for prop in property_list:
      id = prop.getElementsByTagName('id')[0].childNodes[0].data
1887 1888
      prop_type = prop.getElementsByTagName('type')[0].childNodes[0].data
      if prop_type in ('lines', 'tokens'):
1889 1890 1891 1892 1893 1894 1895
        value = []
        values = prop.getElementsByTagName('value')[0]
        items = values.getElementsByTagName('item')
        for item in items:
          i = item.childNodes[0].data
          value.append(str(i))
      else:
1896
        value = str(prop.getElementsByTagName('value')[0].childNodes[0].data)
1897
      self._objects[str(id)] = (str(prop_type), value)
1898

1899 1900 1901
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1902
    if context.getTemplateFormatVersion() == 1:
Aurel's avatar
Aurel committed
1903 1904
      p = context.getPortalObject()
      for path in self._objects.keys():
1905 1906 1907 1908 1909 1910 1911 1912
        if update_dict.has_key(path) or force:
          if not force:
            action = update_dict[path]
            if action == 'nothing':
              continue          
          dir, id = os.path.split(path)
          if p.hasProperty(id):
            continue
1913 1914
          prop_type, property = self._objects[path]
          p._setProperty(id, property, type=prop_type)
Aurel's avatar
Aurel committed
1915
    else:
1916
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
1917
      p = context.getPortalObject()
1918 1919
      for id,property in self._archive.keys():
        property = self._archive[id]
Aurel's avatar
Aurel committed
1920 1921 1922 1923 1924
        if p.hasProperty(id):
          continue
          # Too much???
          #raise TemplateConflictError, 'the property %s already exists' % id
        p._setProperty(id, property['value'], type=property['type'])
1925 1926 1927

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
1928 1929 1930 1931 1932 1933
    object_path = kw.get('object_path', None)
    if object_path is not None:
      keys = [object_path]
    else:
      keys = self._archive.keys()
    for id in keys:
1934 1935 1936 1937
      if p.hasProperty(id):
        p._delProperty(id)
    BaseTemplateItem.uninstall(self, context, **kw)

1938
  def generateXml(self, path=None):
1939
    xml_data = ''
1940
    prop_type, obj = self._objects[path]
1941 1942
    xml_data += os.linesep+' <property>'
    xml_data += os.linesep+'  <id>%s</id>' %(path,)
1943 1944
    xml_data += os.linesep+'  <type>%s</type>' %(prop_type,)
    if prop_type in ('lines', 'tokens'):
1945
      xml_data += os.linesep+'  <value>'
1946
      for item in obj:
1947 1948 1949 1950
        if item != '':
          xml_data += os.linesep+'   <item>%s</item>' %(item,)
      xml_data += os.linesep+'  </value>'
    else:
1951
      xml_data += os.linesep+'  <value>%r</value>' %((os.linesep).join(obj),)
1952
    xml_data += os.linesep+' </property>'
1953 1954
    return xml_data

Aurel's avatar
Aurel committed
1955 1956 1957 1958 1959
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
1960 1961 1962 1963
    xml_data = '<site_property>'
    keys = self._objects.keys()
    keys.sort()
    for path in keys:
1964
      xml_data += self.generateXml(path)
1965
    xml_data += os.linesep+'</site_property>'
1966
    bta.addObject(obj=xml_data, name='properties', path=root_path)
1967

1968 1969 1970 1971 1972 1973 1974
class ModuleTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
      module = p.unrestrictedTraverse(id)
1975 1976 1977 1978 1979 1980 1981 1982
      dict = {}
      dict['id'] = module.getId()
      dict['title'] = module.getTitle()
      dict['portal_type'] = module.getPortalType()
      permission_list = []
      # use show permission
      dict['permission_list'] = module.showPermissions()
      self._objects[id] = dict
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1983

1984
  def generateXml(self, path=None):
1985 1986
    dict = self._objects[path]
    xml_data = '<module>'
1987 1988 1989 1990
    # sort key
    keys = dict.keys()
    keys.sort()
    for key in keys:
1991 1992 1993 1994 1995 1996 1997
      if key =='permission_list':
        # separe permission dict into xml
        xml_data += os.linesep+' <%s>' %(key,)
        permission_list = dict[key]
        for perm in permission_list:
          xml_data += os.linesep+'  <permission>'
          xml_data += os.linesep+'   <name>%s</name>' %(perm[0])
Aurel's avatar
Aurel committed
1998
          role_list = list(perm[1])
1999
          role_list.sort()
2000 2001 2002 2003 2004 2005 2006 2007 2008
          for role in role_list:
            xml_data += os.linesep+'   <role>%s</role>' %(role)
          xml_data += os.linesep+'  </permission>'
        xml_data += os.linesep+' </%s>' %(key,)
      else:
        xml_data += os.linesep+' <%s>%s</%s>' %(key, dict[key], key)
    xml_data += os.linesep+'</module>'
    return xml_data

2009 2010 2011 2012 2013
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(path)
2014 2015 2016
    keys = self._objects.keys()
    keys.sort()
    for id in keys:
2017
      # expor module one by one
2018
      xml_data = self.generateXml(path=id)
2019
      bta.addObject(obj=xml_data, name=id, path=path)
2020

2021
  def install(self, context, trashbin, **kw):
2022
    portal = context.getPortalObject()
2023 2024
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2025
    if context.getTemplateFormatVersion() == 1:
2026
      items = self._objects
2027
    else:
2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050
      items = self._archive

    for id in items.keys():
      if update_dict.has_key(id) or force:
        if not force:
          action = update_dict[id]
          if action == 'nothing':
            continue
        mapping = items[id]
        path, id = os.path.split(id)
        if id in portal.objectIds():
          module = portal._getOb(id)
          module.portal_type = str(mapping['portal_type']) 
        else:
          module = portal.newContent(id=id, portal_type=str(mapping['portal_type']))
        module.setTitle(str(mapping['title']))
        for name,role_list in list(mapping['permission_list']):
          acquire = (type(role_list) == type([]))
          try:
            module.manage_permission(name, roles=role_list, acquire=acquire)
          except ValueError:
            # Ignore a permission not present in this system.
            pass
2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081

  def _importFile(self, file_name, file):
    dict = {}
    xml = parse(file)
    for id in ('portal_type', 'id', 'title', 'permission_list'):
      elt = xml.getElementsByTagName(id)[0]
      if id == 'permission_list':
        plist = []
        perm_list = elt.getElementsByTagName('permission')
        for perm in perm_list:
          name_elt = perm.getElementsByTagName('name')[0]
          name_node = name_elt.childNodes[0]
          name = name_node.data
          role_list = perm.getElementsByTagName('role')
          rlist = []
          for role in role_list:
            role_node = role.childNodes[0]
            role = role_node.data
            rlist.append(str(role))
          perm_tuple = (str(name), rlist)
          plist.append(perm_tuple)
        dict[id] = plist
      else:
        node_list = elt.childNodes
        if len(node_list) == 0:
          value=''
        else:
          value = node_list[0].data
        dict[id] = str(value)
    self._objects[file_name[:-4]] = dict

2082
  def uninstall(self, context, **kw):
Aurel's avatar
Aurel committed
2083 2084 2085
    trash = kw.get('trash', 0)
    if trash:
      return
2086 2087 2088 2089 2090 2091
    object_path = kw.get('object_path', None)
    trashbin = kw.get('trashbin', None)
    if object_path is None:
      keys = self._archive.keys()
    else:
      keys = [object_path]
2092 2093
    p = context.getPortalObject()
    id_list = p.objectIds()
2094
    for id in keys:
2095
      if id in id_list:
2096
        try:
2097
          if trash and trashbin is not None:
2098 2099
            container_path = id.split('/')
            self.portal_trash.backupObject(trashbin, container_path, id, save=1, keep_subobjects=1)
2100
          p.manage_delObjects([id])
2101
        except NotFound:
2102
          pass
2103 2104
    BaseTemplateItem.uninstall(self, context, **kw)

2105 2106 2107
  def trash(self, context, new_item, **kw):
    # Do not remove any module for safety.
    pass
2108 2109

class DocumentTemplateItem(BaseTemplateItem):
2110 2111 2112 2113
  local_file_reader_name = 'readLocalDocument'
  local_file_writer_name = 'writeLocalDocument'
  local_file_importer_name = 'importLocalDocument'
  local_file_remover_name = 'removeLocalDocument'
2114 2115 2116 2117

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    for id in self._archive.keys():
2118
      self._objects[self.__class__.__name__+os.sep+id] = globals()[self.local_file_reader_name](id)
Aurel's avatar
Aurel committed
2119

2120
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2121 2122
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142
      new_keys = self._objects.keys()
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see if there is changes
          new_obj_code = self._objects[path]
          old_obj_code = installed_bt._objects[path]
          if new_obj_code != old_obj_code:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
          # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2143
    if context.getTemplateFormatVersion() == 1:
Aurel's avatar
Aurel committed
2144
      for id in self._objects.keys():
2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158
        if update_dict.has_key(id) or force:
          if not force:
            action = update_dict[id]
            if action == 'nothing':
              continue
          text = self._objects[id]
          path, name = os.path.split(id)
          # This raises an exception if the file already exists.
          try:
            globals()[self.local_file_writer_name](name, text, create=1)
          except IOError:
            continue
          if self.local_file_importer_name is not None:
            globals()[self.local_file_importer_name](name)
Aurel's avatar
Aurel committed
2159
    else:
2160 2161 2162
      BaseTemplateItem.install(self, context, trashbin, **kw)
      for id in self._archive.keys():
        text = self._archive[id]
Aurel's avatar
Aurel committed
2163
        # This raises an exception if the file exists.
2164
        globals()[self.local_file_writer_name](id, text, create=1)
Aurel's avatar
Aurel committed
2165 2166
        if self.local_file_importer_name is not None:
          globals()[self.local_file_importer_name](id)
2167 2168

  def uninstall(self, context, **kw):
2169 2170 2171 2172 2173 2174
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for id in object_keys:
2175
      globals()[self.local_file_remover_name](id)
2176 2177
    BaseTemplateItem.uninstall(self, context, **kw)

Aurel's avatar
Aurel committed
2178 2179 2180 2181 2182 2183
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    for path in self._objects.keys():
2184 2185
      obj = self._objects[path]
      bta.addObject(obj=obj, name=path, path=None, ext='.py')
Aurel's avatar
Aurel committed
2186

2187 2188
  def _importFile(self, file_name, file):
    text = file.read()
2189
    self._objects[file_name[:-3]]=text
2190

2191 2192 2193 2194 2195 2196
class PropertySheetTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalPropertySheet'
  local_file_writer_name = 'writeLocalPropertySheet'
  local_file_importer_name = 'importLocalPropertySheet'
  local_file_remover_name = 'removeLocalPropertySheet'

2197 2198 2199 2200 2201
class ConstraintTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalConstraint'
  local_file_writer_name = 'writeLocalConstraint'
  local_file_importer_name = 'importLocalConstraint'
  local_file_remover_name = 'removeLocalConstraint'
2202

2203 2204 2205
class ExtensionTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalExtension'
  local_file_writer_name = 'writeLocalExtension'
2206 2207
  # Extension needs no import
  local_file_importer_name = None
2208 2209 2210 2211 2212
  local_file_remover_name = 'removeLocalExtension'

class TestTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalTest'
  local_file_writer_name = 'writeLocalTest'
2213
  # Test needs no import
2214 2215 2216
  local_file_importer_name = None
  local_file_remover_name = 'removeLocalTest'

Aurel's avatar
Aurel committed
2217

2218 2219 2220
class ProductTemplateItem(BaseTemplateItem):
  # XXX Not implemented yet
  pass
2221 2222 2223

class RoleTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2224 2225 2226 2227
  def build(self, context, **kw):
    role_list = []
    for key in self._archive.keys():
      role_list.append(key)
2228 2229
    if len(role_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'role_list'] = role_list
Aurel's avatar
Aurel committed
2230

2231
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2232 2233
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
2234
      new_roles = self._objects.keys()
2235 2236 2237
      if installed_bt.id == 'installed_bt_for_diff':
        #must rename keys in dict if reinstall
        new_dict = PersistentMapping()
Aurel's avatar
Aurel committed
2238 2239 2240
        old_keys = ()
        if len(installed_bt._objects.values()) > 0:
          old_keys = installed_bt._objects.values()[0]
2241 2242 2243
        for key in old_keys:
          new_dict[key] = ''
        installed_bt._objects = new_dict      
2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256
      for role in new_roles:
        if installed_bt._objects.has_key(role):
          continue
        else: # only show new roles
          modified_object_list.update({role : ['New', 'Role']})
      # get removed roles
      old_roles = installed_bt._objects.keys()
      for role in old_roles:
        if role not in new_roles:
          modified_object_list.update({role : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
2257
    p = context.getPortalObject()
2258
    # get roles
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2259
    if context.getTemplateFormatVersion() == 1:
2260
      role_list = self._objects.keys()
2261
    else:
2262
      role_list = self._archive.keys()
2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274
    # set roles in PAS
    if p.acl_users.meta_type == 'Pluggable Auth Service':
      role_manager_list = p.acl_users.objectValues('ZODB Role Manager')
      for role_manager in role_manager_list:
        existing_role_list = role_manager.listRoleIds()
        for role in role_list:
          if role not in existing_role_list:
            role_manager.addRole(role)
    # set roles on portal
    roles = {}
    for role in p.__ac_roles__:
      roles[role] = 1
2275
    for role in role_list:
2276 2277 2278 2279 2280 2281 2282 2283 2284 2285
      roles[role] = 1
    p.__ac_roles__ = tuple(roles.keys())

  def _importFile(self, file_name, file):
    xml = parse(file)
    role_list = xml.getElementsByTagName('role')
    for role in role_list:
      node = role.childNodes[0]
      value = node.data
      self._objects[str(value)] = 1
2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
    roles = {}
    for role in p.__ac_roles__:
      roles[role] = 1
    for role in self._archive.keys():
      if role in roles:
        del roles[role]
    p.__ac_roles__ = tuple(roles.keys())
    BaseTemplateItem.uninstall(self, context, **kw)

2298 2299 2300 2301 2302 2303 2304 2305 2306
  def trash(self, context, new_item, **kw):
    p = context.getPortalObject()
    new_roles = {}
    for role in new_item._archive.keys():
      new_roles[role] = 1
    roles = {}
    for role in p.__ac_roles__:
      roles[role] = 1
    for role in self._archive.keys():
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2307
      if role in roles and role not in new_roles:
2308 2309 2310
        del roles[role]
    p.__ac_roles__ = tuple(roles.keys())

2311
  def generateXml(self, path):
2312
    obj = self._objects[path]
2313
    xml_data = '<role_list>'
2314 2315
    obj.sort()
    for role in obj:
2316 2317 2318 2319
      xml_data += os.linesep+' <role>%s</role>' %(role)
    xml_data += os.linesep+'</role_list>'
    return xml_data

Aurel's avatar
Aurel committed
2320 2321 2322 2323 2324 2325
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    for path in self._objects.keys():
2326
      xml_data = self.generateXml(path=path)
2327
      bta.addObject(obj=xml_data, name=path, path=None,)
2328

2329 2330
class CatalogResultKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2331
  def build(self, context, **kw):
2332 2333
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2334
    except KeyError:
2335 2336 2337 2338
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2339
    sql_search_result_keys = list(catalog.sql_search_result_keys)
2340
    key_list = []
2341
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
2342
      if key in sql_search_result_keys:
2343
        key_list.append(key)
Aurel's avatar
Aurel committed
2344 2345
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
2346
    if len(key_list) > 0:
Aurel's avatar
Aurel committed
2347
      self._objects[self.__class__.__name__+os.sep+'result_key_list'] = key_list
2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358

  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list

2359
  def install(self, context, trashbin, **kw):
2360 2361 2362 2363 2364 2365 2366
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2367

2368
    sql_search_result_keys = list(catalog.sql_search_result_keys)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2369
    if context.getTemplateFormatVersion() == 1:
2370 2371
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
2372 2373 2374
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
2375
    else:
2376
      keys = self._archive.keys()
2377 2378
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2379
    # XXX same as related key
Aurel's avatar
Aurel committed
2380
    if update_dict.has_key('result_key_list') or force:
2381
      if not force:
Aurel's avatar
Aurel committed
2382
        action = update_dict['result_key_list']
2383
        if action == 'nothing':
Aurel's avatar
Aurel committed
2384
          return
2385
      for key in keys:
2386 2387
        if key not in sql_search_result_keys:
          sql_search_result_keys.append(key)
2388
      catalog.sql_search_result_keys = sql_search_result_keys
2389

2390
  def uninstall(self, context, **kw):
2391 2392
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2393
    except KeyError:
2394 2395 2396 2397 2398
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_search_result_keys = list(catalog.sql_search_result_keys)
2399
    object_path = kw.get('object_path', None)
2400 2401 2402 2403 2404
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
2405 2406
      if key in sql_search_result_keys:
        sql_search_result_keys.remove(key)
2407
    catalog.sql_search_result_keys = sql_search_result_keys
2408 2409
    BaseTemplateItem.uninstall(self, context, **kw)

2410
  def generateXml(self, path=None):
2411
    obj = self._objects[path]
2412
    xml_data = '<key_list>'
2413 2414
    obj.sort()
    for key in obj:
2415 2416 2417 2418
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

Aurel's avatar
Aurel committed
2419 2420 2421 2422 2423
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
2424
    for path in self._objects.keys():
2425
      xml_data = self.generateXml(path=path)
2426
      bta.addObject(obj=xml_data, name=path, path=None)
2427

2428 2429
class CatalogRelatedKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2430
  def build(self, context, **kw):
2431 2432
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2433
    except KeyError:
2434 2435 2436 2437
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
2438
    sql_search_related_keys = list(catalog.sql_catalog_related_keys)
2439
    key_list = []
2440
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
2441
      if key in sql_search_related_keys:
2442
        key_list.append(key)
Aurel's avatar
Aurel committed
2443 2444
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
2445
    if len(key_list) > 0:
Aurel's avatar
Aurel committed
2446
      self._objects[self.__class__.__name__+os.sep+'related_key_list'] = key_list
2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457

  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list

2458
  def install(self, context, trashbin, **kw):
2459 2460 2461 2462 2463 2464 2465
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2466

2467
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2468
    if context.getTemplateFormatVersion() == 1:
2469
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
2470
        return
2471 2472 2473
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
2474
    else:
2475
      keys = self._archive.keys()
2476 2477
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Aurel's avatar
Aurel committed
2478
    # XXX must a find a better way to manage related key
Aurel's avatar
Aurel committed
2479
    if update_dict.has_key('related_key_list') or update_dict.has_key('key_list') or force:
Aurel's avatar
Aurel committed
2480
      if not force:
Aurel's avatar
Aurel committed
2481 2482 2483 2484
        if update_dict.has_key('related_key_list'):
          action = update_dict['related_key_list']
        else: # XXX for backward compatibility
          action = update_dict['key_list']
Aurel's avatar
Aurel committed
2485 2486 2487
        if action == 'nothing':
          return
      for key in keys:
2488 2489
        if key not in sql_catalog_related_keys:
          sql_catalog_related_keys.append(key)
Aurel's avatar
Aurel committed
2490
      catalog.sql_catalog_related_keys = tuple(sql_catalog_related_keys)
2491

2492 2493 2494
  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2495
    except KeyError:
2496 2497 2498 2499
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2500
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
2501 2502 2503 2504 2505 2506
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
2507 2508 2509 2510 2511
      if key in sql_catalog_related_keys:
        sql_catalog_related_keys.remove(key)
    catalog.sql_catalog_related_keys = sql_catalog_related_keys
    BaseTemplateItem.uninstall(self, context, **kw)

2512
  def generateXml(self, path=None):
2513
    obj = self._objects[path]
2514
    xml_data = '<key_list>'
2515 2516
    obj.sort()
    for key in obj:
2517 2518 2519 2520
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

Aurel's avatar
Aurel committed
2521 2522 2523 2524 2525 2526
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    for path in self._objects.keys():
2527
      xml_data = self.generateXml(path=path)
2528
      bta.addObject(obj=xml_data, name=path, path=None)
2529

2530 2531
class CatalogResultTableTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2532
  def build(self, context, **kw):
2533 2534
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2535
    except KeyError:
2536 2537 2538 2539
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
2540
    sql_search_result_tables = list(catalog.sql_search_tables)
2541
    key_list = []
2542
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
2543
      if key in sql_search_result_tables:
2544
        key_list.append(key)
Aurel's avatar
Aurel committed
2545 2546
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
2547
    if len(key_list) > 0:
Aurel's avatar
Aurel committed
2548
      self._objects[self.__class__.__name__+os.sep+'resutl_table_list'] = key_list
2549 2550 2551 2552 2553 2554 2555 2556 2557 2558

  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list
Aurel's avatar
Aurel committed
2559

2560
  def install(self, context, trashbin, **kw):
2561 2562 2563 2564 2565 2566 2567
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2568

2569
    sql_search_tables = list(catalog.sql_search_tables)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2570
    if context.getTemplateFormatVersion() == 1:
2571
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
2572
        return
2573 2574 2575
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
2576
    else:
2577
      keys = self._archive.keys()
2578 2579
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2580
    # XXX same as related keys
Aurel's avatar
Aurel committed
2581
    if update_dict.has_key('result_table_list') or force:
2582
      if not force:
Aurel's avatar
Aurel committed
2583
        action = update_dict['result_table_list']
2584
        if action == 'nothing':
Aurel's avatar
Aurel committed
2585
          return
2586
      for key in keys:
2587 2588
        if key not in sql_search_tables:
          sql_search_tables.append(key)
2589
      catalog.sql_search_tables = sql_search_tables
2590 2591

  def uninstall(self, context, **kw):
2592 2593
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2594
    except KeyError:
2595 2596 2597 2598 2599
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_search_tables = list(catalog.sql_search_tables)
2600 2601 2602 2603 2604 2605
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
2606 2607
      if key in sql_search_tables:
        sql_search_tables.remove(key)
2608
    catalog.sql_search_tables = sql_search_tables
2609 2610
    BaseTemplateItem.uninstall(self, context, **kw)

2611
  def generateXml(self, path=None):
2612
    obj = self._objects[path]
2613
    xml_data = '<key_list>'
2614 2615
    obj.sort()
    for key in obj:
2616 2617 2618 2619
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'    
    return xml_data

Aurel's avatar
Aurel committed
2620 2621 2622 2623 2624 2625
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    for path in self._objects.keys():
2626
      xml_data = self.generateXml(path=path)
2627
      bta.addObject(obj=xml_data, name=path, path=None)
2628 2629 2630
      
# keyword
class CatalogKeywordKeyTemplateItem(BaseTemplateItem):
2631 2632

  def build(self, context, **kw):
2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_keyword_keys = list(catalog.sql_catalog_keyword_search_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_keyword_keys:
        key_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'keyword_key_list'] = key_list
Aurel's avatar
Aurel committed
2649

2650 2651 2652 2653 2654 2655 2656 2657 2658
  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list
2659 2660

  def install(self, context, trashbin, **kw):
2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

    sql_keyword_keys = list(catalog.sql_catalog_keyword_search_keys)
    if context.getTemplateFormatVersion() == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
    else:
      keys = self._archive.keys()
2678 2679
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702
    # XXX same as related key
    if update_dict.has_key('keyword_key_list') or force:
      if not force:
        action = update_dict['keyword_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_keyword_keys:
          sql_keyword_keys.append(key)
      catalog.sql_catalog_keyword_search_keys = sql_keyword_keys

  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_keyword_keys = list(catalog.sql_catalog_keyword_search_keys)
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
Aurel's avatar
Aurel committed
2703
    else:
2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718
      object_keys = self._archive.keys()
    for key in object_keys:
      if key in sql_keyword_keys:
        sql_keyword_keys.remove(key)
    catalog.sql_catalog_keyword_search_keys = sql_keyword_keys
    BaseTemplateItem.uninstall(self, context, **kw)

  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data
Aurel's avatar
Aurel committed
2719 2720 2721 2722

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
2723 2724 2725 2726 2727
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    for path in self._objects.keys():
      xml_data = self.generateXml(path=path)
      bta.addObject(obj=xml_data, name=path, path=None)
2728

2729 2730
# full text
class CatalogFullTextKeyTemplateItem(BaseTemplateItem):
2731

2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748
  def build(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_full_text_keys = list(catalog.sql_catalog_full_text_search_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_full_text_keys:
        key_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'ful_text_key_list'] = key_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2749

2750 2751 2752 2753 2754 2755 2756 2757 2758
  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2759

2760 2761 2762 2763 2764 2765 2766 2767
  def install(self, context, trashbin, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2768

2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789
    sql_full_text_keys = list(catalog.sql_catalog_full_text_search_keys)
    if context.getTemplateFormatVersion() == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
    else:
      keys = self._archive.keys()
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    # XXX same as related key
    if update_dict.has_key('full_text_key_list') or force:
      if not force:
        action = update_dict['full_text_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_full_text_keys:
          sql_full_text_keys.append(key)
      catalog.sql_catalog_full_text_search_keys = sql_full_text_keys
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2790

2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200
  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_full_text_keys = list(catalog.sql_catalog_full_text_search_keys)
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
      if key in sql_full_text_keys:
        sql_full_text_keys.remove(key)
    catalog.sql_catalog_full_text_search_keys = sql_full_text_keys
    BaseTemplateItem.uninstall(self, context, **kw)

  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    for path in self._objects.keys():
      xml_data = self.generateXml(path=path)
      bta.addObject(obj=xml_data, name=path, path=None)


# request
class CatalogRequestKeyTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_request_keys = list(catalog.sql_catalog_request_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_request_keys:
        key_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'request_key_list'] = key_list

  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list

  def install(self, context, trashbin, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

    sql_catalog_request_keys = list(catalog.sql_catalog_request_keys)
    if context.getTemplateFormatVersion() == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
    else:
      keys = self._archive.keys()
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    # XXX must a find a better way to manage related key
    if update_dict.has_key('request_key_list') or force:
      if not force:
        action = update_dict['request_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_catalog_request_keys:
          sql_catalog_request_keys.append(key)
      catalog.sql_catalog_request_keys = tuple(sql_catalog_request_keys)

  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_request_keys = list(catalog.sql_catalog_request_keys)
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
      if key in sql_catalog_request_keys:
        sql_catalog_request_keys.remove(key)
    catalog.sql_catalog_request_keys = sql_catalog_request_keys
    BaseTemplateItem.uninstall(self, context, **kw)

  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    for path in self._objects.keys():
      xml_data = self.generateXml(path=path)
      bta.addObject(obj=xml_data, name=path, path=None)

# multivalue
class CatalogMultivalueKeyTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_multivalue_keys = list(catalog.sql_catalog_multivalue_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_multivalue_keys:
        key_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'multivalue_key_list'] = key_list

  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list

  def install(self, context, trashbin, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

    sql_catalog_multivalue_keys = list(catalog.sql_catalog_multivalue_keys)
    if context.getTemplateFormatVersion() == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
    else:
      keys = self._archive.keys()
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    if update_dict.has_key('multivalue_key_list') or force:
      if not force:
        action = update_dict['multivalue_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_catalog_multivalue_keys:
          sql_catalog_multivalue_keys.append(key)
      catalog.sql_catalog_multivalue_keys = tuple(sql_catalog_multivalue_keys)

  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_multivalue_keys = list(catalog.sql_catalog_multivalue_keys)
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
      if key in sql_catalog_multivalue_keys:
        sql_catalog_multivalue_keys.remove(key)
    catalog.sql_catalog_multivalue_keys = sql_catalog_multivalue_keys
    BaseTemplateItem.uninstall(self, context, **kw)

  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    for path in self._objects.keys():
      xml_data = self.generateXml(path=path)
      bta.addObject(obj=xml_data, name=path, path=None)

# topic
class CatalogTopicKeyTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_topic_search_keys = list(catalog.sql_catalog_topic_search_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_catalog_topic_search_keys:
        key_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'topic_key_list'] = key_list

  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list

  def install(self, context, trashbin, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

    sql_catalog_topic_search_keys = list(catalog.sql_catalog_topic_search_keys)
    if context.getTemplateFormatVersion() == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
    else:
      keys = self._archive.keys()
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    # XXX same as related key
    if update_dict.has_key('topic_key_list') or force:
      if not force:
        action = update_dict['topic_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_catalog_topic_search_keys:
          sql_catalog_topic_search_keys.append(key)
      catalog.sql_catalog_topic_search_keys = sql_catalog_topic_search_keys

  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_topic_search_keys = list(catalog.sql_catalog_topic_search_keys)
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
      if key in sql_catalog_topic_search_keys:
        sql_catalog_topic_search_keys.remove(key)
    catalog.sql_catalog_topic_search_keys = sql_catalog_topic_search_keys
    BaseTemplateItem.uninstall(self, context, **kw)

  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
    for path in self._objects.keys():
      xml_data = self.generateXml(path=path)
      bta.addObject(obj=xml_data, name=path, path=None)

class MessageTranslationTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    localizer = context.getPortalObject().Localizer
    for lang in self._archive.keys():
      # Export only erp5_ui at the moment.
      # This is safer against information leak.
      for catalog in ('erp5_ui', ):
        path = os.path.join(lang, catalog)
        mc = localizer._getOb(catalog)
        self._objects[path] = mc.manage_export(lang)

  def preinstall(self, context, installed_bt, **kw):
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
      new_keys = self._objects.keys()
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see if there is changes
          new_obj_code = self._objects[path]
          old_obj_code = installed_bt._objects[path]
          if new_obj_code != old_obj_code:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
    localizer = context.getPortalObject().Localizer
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    if context.getTemplateFormatVersion() == 1:
      for path, po in self._objects.items():
        if update_dict.has_key(path) or force:
          if not force:
            action = update_dict[path]
            if action == 'nothing':
              continue          
          path = string.split(path, '/')
          lang = path[-3]
          catalog = path[-2]
          if lang not in localizer.get_languages():
            localizer.manage_addLanguage(lang)
          mc = localizer._getOb(catalog)
          if lang not in mc.get_languages():
            mc.manage_addLanguage(lang)
          mc.manage_import(lang, po)
    else:
      BaseTemplateItem.install(self, context, trashbin, **kw)
      for lang, catalogs in self._archive.items():
        if lang not in localizer.get_languages():
          localizer.manage_addLanguage(lang)
        for catalog, po in catalogs.items():
          mc = localizer._getOb(catalog)
          if lang not in mc.get_languages():
            mc.manage_addLanguage(lang)
          mc.manage_import(lang, po)

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
    for key in self._objects.keys():
      obj = self._objects[key]
      path = os.path.join(root_path, key)
      bta.addFolder(name=path)
Aurel's avatar
Aurel committed
3201
      f = open(path+os.sep+'translation.po', 'wt')
3202 3203 3204 3205 3206 3207 3208
      f.write(str(obj))
      f.close()

  def _importFile(self, file_name, file):
    text = file.read()
    self._objects[file_name[:-3]]=text

Aurel's avatar
Aurel committed
3209 3210
class LocalRolesTemplateItem(BaseTemplateItem):

3211 3212 3213 3214
  def __init__(self, id_list, **kw):
    id_list = ['local_roles/%s' % id for id in id_list]
    BaseTemplateItem.__init__(self, id_list, **kw)

Aurel's avatar
Aurel committed
3215 3216 3217
  def build(self, context, **kw):
    p = context.getPortalObject()
    for path in self._archive.keys():
3218
      obj = p.unrestrictedTraverse(path.split('/', 1)[1])
3219 3220 3221 3222
      local_roles_dict = getattr(obj, '__ac_local_roles__',
                                        {}) or {}
      group_local_roles_dict = getattr(obj, '__ac_local_group_roles__',
                                        {}) or {}
Aurel's avatar
Aurel committed
3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262
      self._objects[path] = (local_roles_dict, group_local_roles_dict)

  def generateXml(self, path=None):
    local_roles_dict, group_local_roles_dict = self._objects[path]
    local_roles_keys = local_roles_dict.keys()
    group_local_roles_keys = group_local_roles_dict.keys()
    local_roles_keys.sort()
    group_local_roles_keys.sort()
    # local roles
    xml_data = '<local_roles_item>'
    xml_data += os.linesep+' <local_roles>'
    for key in local_roles_keys:
      xml_data += os.linesep+"  <role id='%s'>" %(key,)
      tuple = local_roles_dict[key]
      for item in tuple:
        xml_data += os.linesep+"   <item>%s</item>" %(item,)
      xml_data += os.linesep+"  </role>"
    xml_data += os.linesep+' </local_roles>'
    # group local roles
    xml_data += os.linesep+' <group_local_roles>'
    for key in group_local_roles_keys:
      xml_data += os.linesep+"  <role id='%s'>" %(key,)
      tuple = group_local_roles_dict[key]
      for item in tuple:
        xml_data += os.linesep+"   <item>%s</item>" %(item,)
      xml_data += os.linesep+"  </role>"
    xml_data += os.linesep+' </group_local_roles>'
    xml_data += os.linesep+'</local_roles_item>'
    return xml_data
      
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
    for key in self._objects.keys():
      xml_data = self.generateXml(key)

      folders, id = os.path.split(key)
      encode_folders = []
3263
      for folder in folders.split('/')[1:]:
Aurel's avatar
Aurel committed
3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321
        if '%' not in folder:
          encode_folders.append(pathname2url(folder))
        else:
          encode_folders.append(folder)
      path = os.path.join(root_path, (os.sep).join(encode_folders))    
      bta.addFolder(name=path)
      bta.addObject(obj=xml_data, name=id, path=path)

  def _importFile(self, file_name, file):
    xml = parse(file)
    # local roles
    local_roles = xml.getElementsByTagName('local_roles')[0]
    local_roles_list = local_roles.getElementsByTagName('role')    
    local_roles_dict = {}
    for role in local_roles_list:
      id = role.getAttribute('id')
      item_type_list = []
      item_list = role.getElementsByTagName('item')
      for item in item_list:
        item_type_list.append(str(item.childNodes[0].data))
      local_roles_dict[id] = item_type_list
    # group local roles
    group_local_roles = xml.getElementsByTagName('group_local_roles')[0]
    local_roles_list = group_local_roles.getElementsByTagName('role')    
    group_local_roles_dict = {}
    for role in local_roles_list:
      id = role.getAttribute('id')
      item_type_list = []
      item_list = role.getElementsByTagName('item')
      for item in item_list:
        item_type_list.append(str(item.childNodes[0].data))
      group_local_roles_dict[id] = item_type_list
    self._objects['local_roles/'+file_name[:-4]] = (local_roles_dict, group_local_roles_dict)

  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    p = context.getPortalObject()
    for roles_path in self._objects.keys():
      if update_dict.has_key(roles_path) or force:
        if not force:
          action = update_dict[roles_path]
          if action == 'nothing':
            continue
      path = roles_path.split('/')[1:]
      obj = p.unrestrictedTraverse(path)
      local_roles_dict, group_local_roles_dict = self._objects[roles_path]
      setattr(obj, '__ac_local_roles__', local_roles_dict)
      setattr(obj, '__ac_local_group_roles__', group_local_roles_dict)

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
    for roles_path in self._objects.keys():
      path = roles_path.split('/')[1:]
      obj = p.unrestrictedTraverse(path)
      setattr(obj, '__ac_local_roles__', {})
      setattr(obj, '__ac_local_group_roles__', {})
    
3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347
class BusinessTemplate(XMLObject):
    """
    A business template allows to construct ERP5 modules
    in part or completely. Each object are separated from its
    subobjects and exported in xml format.
    It may include:

    - catalog definition
      - SQL method objects
      - SQL methods including:
        - purpose (catalog, uncatalog, etc.)
        - filter definition

    - portal_types definition
      - object without optinal actions
      - list of relation between portal type and worklfow

    - module definition
      - id
      - title
      - portal type
      - roles/security

    - site property definition
      - id
      - type
3348
      - value
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3349

3350 3351
    - document/propertysheet/extension/test definition
      - copy of the local file
3352

3353
    - message transalation definition
3354
      - .po file
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3355

3356 3357
    The Business Template properties are exported to the bt folder with
    one property per file
3358

Jean-Paul Smets's avatar
Jean-Paul Smets committed
3359 3360
    Technology:

3361 3362
    - download a gzip file or folder tree (from the web, from a CVS repository,
      from local file system) (import/donwload)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3363

3364
    - install files to the right location (install)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3365 3366 3367 3368

    Use case:

    - install core ERP5 (the minimum)
3369

3370
    - go to "BT" menu. Import BT. Select imported BT. Click install.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3371

3372 3373
    - go to "BT" menu. Create new BT.
      Define BT elements (workflow, methods, attributes, etc.).
3374
      Build BT and export or save it
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3375 3376 3377 3378 3379
      Done.
    """

    meta_type = 'ERP5 Business Template'
    portal_type = 'Business Template'
3380
    add_permission = Permissions.AddPortalContent
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3381 3382 3383 3384 3385
    isPortalContent = 1
    isRADContent = 1

    # Declarative security
    security = ClassSecurityInfo()
3386
    security.declareObjectProtected(Permissions.AccessContentsInformation)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3387 3388 3389 3390 3391 3392 3393

    # Declarative interfaces
    __implements__ = ( Interface.Variated, )

    # Declarative properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
3394
                      , PropertySheet.SimpleItem
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3395
                      , PropertySheet.CategoryCore
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3396
                      , PropertySheet.Version
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3397 3398 3399
                      , PropertySheet.BusinessTemplate
                      )

3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416
    # Factory Type Information
    factory_type_information = \
      {    'id'             : portal_type
         , 'meta_type'      : meta_type
         , 'description'    : """\
Business Template is a set of definitions, such as skins, portal types and categories. This is used to set up a new ERP5 site very efficiently."""
         , 'icon'           : 'order_line_icon.gif'
         , 'product'        : 'ERP5Type'
         , 'factory'        : 'addBusinessTemplate'
         , 'immediate_view' : 'BusinessTemplate_view'
         , 'allow_discussion'     : 1
         , 'allowed_content_types': (
                                      )
         , 'filter_content_types' : 1
         , 'global_allow'   : 1
      }

3417 3418 3419 3420 3421
    # This is a global variable
    # Order is important for installation
    _item_name_list = [
      '_product_item',
      '_property_sheet_item',
3422
      '_constraint_item',
3423 3424 3425 3426 3427 3428 3429 3430 3431
      '_document_item',
      '_extension_item',
      '_test_item',
      '_role_item',
      '_message_translation_item',
      '_workflow_item',
      '_catalog_method_item',
      '_site_property_item',
      '_portal_type_item',
3432
      '_portal_type_workflow_chain_item',
3433 3434 3435 3436
      '_portal_type_allowed_content_type_item',
      '_portal_type_hidden_content_type_item',
      '_portal_type_property_sheet_item',
      '_portal_type_base_category_item',
3437 3438 3439
      '_category_item',
      '_module_item',
      '_skin_item',
3440
      '_path_item',
3441
      '_action_item',
3442
      '_portal_type_roles_item',
3443
      '_local_roles_item',
3444 3445 3446
      '_catalog_result_key_item',
      '_catalog_related_key_item',
      '_catalog_result_table_item',
3447 3448 3449 3450 3451
      '_catalog_keyword_key_item',
      '_catalog_full_text_key_item',
      '_catalog_request_key_item',
      '_catalog_multivalue_key_item',
      '_catalog_topic_key_item',
3452 3453 3454 3455
    ]

    def __init__(self, *args, **kw):
      XMLObject.__init__(self, *args, **kw)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3456 3457 3458 3459 3460 3461
      self._clean()

    def getTemplateFormatVersion(self, **kw):
      """This is a workaround, because template_format_version was not set even for the new format.
      """
      if self.hasProperty('template_format_version'):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3462
        self._baseGetTemplateFormatVersion()
3463

Yoshinori Okuji's avatar
Yoshinori Okuji committed
3464 3465 3466
      # the attribute _objects in BaseTemplateItem was added in the new format.
      if hasattr(self._path_item, '_objects'):
        return 1
3467
      
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3468 3469
      return 0
        
3470
    security.declareProtected(Permissions.ManagePortal, 'manage_afterAdd')
3471 3472 3473 3474 3475 3476 3477
    def manage_afterAdd(self, item, container):
      """
        This is called when a new business template is added or imported.
      """
      portal_workflow = getToolByName(self, 'portal_workflow')
      if portal_workflow is not None:
        # Make sure that the installation state is "not installed".
3478 3479
        if portal_workflow.getStatusOf(
                'business_template_installation_workflow', self) is not None:
3480
          # XXX Not good to access the attribute directly,
3481 3482 3483
          # but there is no API for clearing the history.
          self.workflow_history[
                            'business_template_installation_workflow'] = None
3484

3485
    security.declareProtected(Permissions.ManagePortal, 'build')
3486
    def build(self, no_action=0):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3487 3488 3489
      """
        Copy existing portal objects to self
      """
3490 3491
      if no_action: return # this is use at import of Business Template to get the status built
      
3492 3493
      # Make sure that everything is sane.
      self.clean()
3494

Yoshinori Okuji's avatar
Yoshinori Okuji committed
3495 3496 3497
      # Set the format version.
      self._setTemplateFormatVersion(1)

3498 3499 3500
      # Store all datas
      self._portal_type_item = \
          PortalTypeTemplateItem(self.getTemplatePortalTypeIdList())
3501
      self._portal_type_workflow_chain_item = \
3502
          PortalTypeWorkflowChainTemplateItem(self.getTemplatePortalTypeWorkflowChainList())
3503 3504 3505 3506 3507 3508 3509 3510 3511 3512
      self._workflow_item = \
          WorkflowTemplateItem(self.getTemplateWorkflowIdList())
      self._skin_item = \
          SkinTemplateItem(self.getTemplateSkinIdList())
      self._category_item = \
          CategoryTemplateItem(self.getTemplateBaseCategoryList())
      self._catalog_method_item = \
          CatalogMethodTemplateItem(self.getTemplateCatalogMethodIdList())
      self._action_item = \
          ActionTemplateItem(self.getTemplateActionPathList())
3513 3514
      self._portal_type_roles_item = \
          PortalTypeRolesTemplateItem(self.getTemplatePortalTypeRolesList())
3515 3516 3517 3518 3519 3520 3521 3522
      self._site_property_item = \
          SitePropertyTemplateItem(self.getTemplateSitePropertyIdList())
      self._module_item = \
          ModuleTemplateItem(self.getTemplateModuleIdList())
      self._document_item = \
          DocumentTemplateItem(self.getTemplateDocumentIdList())
      self._property_sheet_item = \
          PropertySheetTemplateItem(self.getTemplatePropertySheetIdList())
3523 3524
      self._constraint_item = \
          ConstraintTemplateItem(self.getTemplateConstraintIdList())
3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544
      self._extension_item = \
          ExtensionTemplateItem(self.getTemplateExtensionIdList())
      self._test_item = \
          TestTemplateItem(self.getTemplateTestIdList())
      self._product_item = \
          ProductTemplateItem(self.getTemplateProductIdList())
      self._role_item = \
          RoleTemplateItem(self.getTemplateRoleList())
      self._catalog_result_key_item = \
          CatalogResultKeyTemplateItem(
               self.getTemplateCatalogResultKeyList())
      self._catalog_related_key_item = \
          CatalogRelatedKeyTemplateItem(
               self.getTemplateCatalogRelatedKeyList())
      self._catalog_result_table_item = \
          CatalogResultTableTemplateItem(
               self.getTemplateCatalogResultTableList())
      self._message_translation_item = \
          MessageTranslationTemplateItem(
               self.getTemplateMessageTranslationList())
3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556
      self._portal_type_allowed_content_type_item = \
           PortalTypeAllowedContentTypeTemplateItem(
               self.getTemplatePortalTypeAllowedContentTypeList())
      self._portal_type_hidden_content_type_item = \
           PortalTypeHiddenContentTypeTemplateItem(
               self.getTemplatePortalTypeHiddenContentTypeList())
      self._portal_type_property_sheet_item = \
           PortalTypePropertySheetTemplateItem(
               self.getTemplatePortalTypePropertySheetList())
      self._portal_type_base_category_item = \
           PortalTypeBaseCategoryTemplateItem(
               self.getTemplatePortalTypeBaseCategoryList())
3557 3558
      self._path_item = \
               PathTemplateItem(self.getTemplatePathList())
3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573
      self._catalog_keyword_key_item = \
          CatalogKeywordKeyTemplateItem(
               self.getTemplateCatalogKeywordKeyList())      
      self._catalog_full_text_key_item = \
          CatalogFullTextKeyTemplateItem(
               self.getTemplateCatalogFullTextKeyList())      
      self._catalog_request_key_item = \
          CatalogRequestKeyTemplateItem(
               self.getTemplateCatalogRequestKeyList())    
      self._catalog_multivalue_key_item = \
          CatalogMultivalueKeyTemplateItem(
               self.getTemplateCatalogMultivalueKeyList())      
      self._catalog_topic_key_item = \
          CatalogTopicKeyTemplateItem(
               self.getTemplateCatalogTopicKeyList())
Aurel's avatar
Aurel committed
3574 3575 3576 3577
      self._local_roles_item = \
          LocalRolesTemplateItem(
               self.getTemplateLocalRolesList())
            
3578 3579 3580
      # Build each part
      for item_name in self._item_name_list:
        getattr(self, item_name).build(self)
3581

3582
    build = WorkflowMethod(build)
3583 3584

    def publish(self, url, username=None, password=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3585
      """
3586
        Publish in a format or another
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3587
      """
3588
      return self.portal_templates.publish(self, url, username=username,
3589
                                           password=password)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3590

3591
    def update(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3592
      """
3593
        Update template: download new template definition
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3594
      """
3595
      return self.portal_templates.update(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3596

3597 3598 3599 3600
    def preinstall(self, **kw):
      """
        Return the list of modified/new/removed object between a Business Template
        and the one installed if exists
Aurel's avatar
Aurel committed
3601 3602
      """
                
3603 3604
      modified_object_list = {}
      bt_title = self.getTitle()
3605 3606 3607 3608 3609 3610 3611

      #  can be call to diff two Business Template in template tool
      bt2 = kw.get('compare_to', None)
      if  bt2 is not None:
        installed_bt = bt2
      else:
        installed_bt = self.portal_templates.getInstalledBusinessTemplate(title=bt_title)
3612 3613 3614
      if installed_bt is None:
        installed_bt_format = 0 # that will not check for modification
      else:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3615
        installed_bt_format = installed_bt.getTemplateFormatVersion()
3616

3617
      # if reinstall business template, must compare to object in ZODB
3618 3619 3620
      # and not to those in the installed Business Template because it is itself.
      # same if we make a diff and selected only one business template
      reinstall = 0      
3621 3622
      if installed_bt == self:
        reinstall = 1
3623 3624 3625
        bt2 = self.portal_templates.manage_clone(ob=installed_bt, id='installed_bt_for_diff')
        # update portal types properties to get last modifications
        bt2.getPortalTypesProperties()
3626 3627 3628 3629
        bt2.edit(description='tmp bt generated for diff')
        bt2.build()
        installed_bt = bt2
      
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3630
      new_bt_format = self.getTemplateFormatVersion()
3631 3632 3633 3634 3635 3636
      if installed_bt_format == 0 and new_bt_format == 0:
        # still use old format, so install everything, no choice
        return modified_object_list
      elif installed_bt_format == 0 and new_bt_format == 1:
        # return list of all object in bt
        for item_name in self._item_name_list:
3637
          item = getattr(self, item_name, None)
3638 3639 3640 3641
          if item is not None:
            for path in item._objects.keys():
              modified_object_list.update({path : ['New', item.__class__.__name__[:-12]]})
        return modified_object_list
3642

3643 3644 3645 3646
      # get the list of modified and new object
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
      for item_name in self._item_name_list:
3647
        new_item = getattr(self, item_name, None)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3648
        old_item = getattr(installed_bt, item_name, None)
3649
        if new_item is not None:
3650
          if old_item is not None and hasattr(old_item, '_objects'):
3651 3652 3653 3654 3655 3656 3657 3658
            modified_object = new_item.preinstall(context=local_configuration, installed_bt=old_item)
            if len(modified_object) > 0:
              modified_object_list.update(modified_object)
          else:
            for path in new_item._objects.keys():
              modified_object_list.update({path : ['New', new_item.__class__.__name__[:-12]]})

      if reinstall:
3659
        self.portal_templates.manage_delObjects(ids=['installed_bt_for_diff'])
3660
      
3661 3662 3663 3664 3665 3666 3667
      return modified_object_list

    def _install(self, force=1, object_to_update={}, **kw):
      """
        Install a new Business Template, if force, all we be upgrade or installed
        otherwise depends of dict object_to_update
      """
3668
      
3669 3670
      installed_bt = self.portal_templates.getInstalledBusinessTemplate(
                                                           self.getTitle())
3671
      if installed_bt is not None:        
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3672
        if installed_bt.getTemplateFormatVersion() == 0:
3673
          force = 1
3674
        installed_bt.replace(self)
3675

3676 3677 3678 3679
      trash_tool = getToolByName(self, 'portal_trash', None)
      if trash_tool is None and self.getTemplateFormatVersion() == 1:
        raise AttributeError, 'Trash Tool is not installed'

3680 3681 3682
      # Check the format of business template, if old, force install
      if self.getTemplateFormatVersion() == 0:
        force = 1
3683
        
3684 3685 3686 3687 3688 3689
      site = self.getPortalObject()
      from Products.ERP5.ERP5Site import ERP5Generator
      gen = ERP5Generator()
      # update activity tool first if necessary
      if self.getTitle() == 'erp5_core' and self.getTemplateUpdateTool():
        LOG('Business Template', 0, 'Updating Activity Tool')
3690
        gen.setupLastTools(site, update=1, create_activities=1)             
3691 3692
      if not force:
        if len(object_to_update) == 0:
3693 3694 3695 3696 3697
          # check if we have to update tools
          if self.getTitle() == 'erp5_core' and self.getTemplateUpdateTool():
            LOG('Business Template', 0, 'Updating Tools')
            gen.setup(site, 0, update=1)
          if self.getTitle() == 'erp5_core' and self.getTemplateUpdateBusinessTemplateWorkflow():
3698
            LOG('Business Template', 0, 'Updating Business Template Workflows')
3699
            gen.setupWorkflow(site)
3700
          return
3701 3702 3703 3704

      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
3705
      local_configuration = self.portal_templates.getLocalConfiguration(self)
3706 3707 3708 3709

      # update catalog if necessary
      update_catalog=0
      catalog_method = getattr(self, '_catalog_method_item', None)
3710
      if catalog_method is not None and self.getTemplateFormatVersion() == 1:
3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729
        for id in catalog_method._objects.keys():
          if id in object_to_update.keys() or force:
            if not force:
              action = object_to_update[id]
              if action == 'nothing':
                continue
            if 'related' not in id:
              # must update catalog
              update_catalog = 1
              break            
      if update_catalog:
        catalog = local_configuration.portal_catalog.getSQLCatalog()
        if catalog is None:
          LOG('Business Template', 0, 'no SQL Catalog available')
          update_catalog = 0
        else:
          LOG('Business Template', 0, 'Updating SQL Catalog')
          catalog.manage_catalogClear()
              
3730 3731
      # always created a trash bin because we may to save object already present
      # but not in a previous business templates apart at creation of a new site
3732
      if trash_tool is not None and (len(object_to_update) > 0 or len(self.portal_templates.objectIds()) > 1):
3733
        trashbin = trash_tool.newTrashBin(self.getTitle(), self)
3734 3735
      else:
        trashbin = None
3736
        
3737 3738 3739 3740 3741 3742 3743
      # Install everything
      if len(object_to_update) > 0 or force:
        for item_name in self._item_name_list:
          item = getattr(self, item_name, None)
          if item is not None:
            item.install(local_configuration, force=force, object_to_update=object_to_update, trashbin=trashbin)

3744
      # get objects to remove
3745
      # do remove after because we may need backup object from installation
3746 3747 3748 3749 3750 3751 3752 3753 3754
      remove_object_dict = {}
      for path in object_to_update.keys():
        action = object_to_update[path]
        if action == 'remove' or action == 'save_and_remove':
          remove_object_dict[path] = action
          object_to_update.pop(path)
      # remove object from old business template
      if len(remove_object_dict) > 0:
        for item_name in installed_bt._item_name_list:
3755
          item = getattr(installed_bt, item_name, None)
3756 3757
          if item is not None:
            item.remove(local_configuration, remove_object_dict=remove_object_dict, trashbin=trashbin)
3758

3759 3760 3761 3762 3763 3764 3765 3766

      # update tools if necessary
      if self.getTitle() == 'erp5_core' and self.getTemplateUpdateTool():
        LOG('Business Template', 0, 'Updating Tools')
        gen.setup(site, 0, update=1)

      # check if we have to updater business template workflow
      if self.getTitle() == 'erp5_core' and self.getTemplateUpdateBusinessTemplateWorkflow():
3767
        LOG('Business Template', 0, 'Updating Business Template Workflows')
3768 3769 3770 3771 3772 3773 3774 3775
        gen.setupWorkflow(site)
        # XXX keep TM in case update of workflow doesn't work
        #         self._v_txn = WorkflowUpdateTM()
        #         self._v_txn.register(update=1, gen=gen, site=site)

      if update_catalog:
        site.ERP5Site_reindexAll()
       
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3776 3777 3778
      # It is better to clear cache because the installation of a template
      # adds many new things into the portal.
      clearCache()
3779

3780
    security.declareProtected(Permissions.ManagePortal, 'install')
3781 3782 3783
    def install(self, **kw):
      """
        For install based on paramaters provided in **kw
3784
      """      
3785
      return self._install(**kw)
3786

3787
    install = WorkflowMethod(install)
3788

3789
    security.declareProtected(Permissions.ManagePortal, 'reinstall')
3790
    def reinstall(self, **kw):
3791 3792 3793 3794
      """Reinstall Business Template.
      """
      return self._install(**kw)

3795
    reinstall = WorkflowMethod(reinstall)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3796

3797
    security.declareProtected(Permissions.ManagePortal, 'trash')
3798 3799
    def trash(self, new_bt, **kw):
      """
3800
        Trash unnecessary items before upgrading to a new business
3801
        template.
3802
        This is similar to uninstall, but different in that this does
3803
        not remove all items.
3804 3805 3806 3807 3808
      """
      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
3809 3810
      # Trash everything
      for item_name in self._item_name_list[::-1]:
3811
        item = getattr(self, item_name, None)
3812 3813 3814 3815
        if item is not None:
          item.trash(
                local_configuration,
                getattr(new_bt, item_name))
3816

3817
    security.declareProtected(Permissions.ManagePortal, 'uninstall')
3818
    def uninstall(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3819
      """
3820
        For uninstall based on paramaters provided in **kw
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3821
      """
3822 3823 3824 3825
      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
3826 3827 3828
      # Uninstall everything
      # Trash everything
      for item_name in self._item_name_list[::-1]:
3829
        item = getattr(self, item_name, None)
3830 3831
        if item is not None:
          item.uninstall(local_configuration)
3832
      # It is better to clear cache because the uninstallation of a
3833
      # template deletes many things from the portal.
3834
      clearCache()
3835

3836 3837
    uninstall = WorkflowMethod(uninstall)

3838
    security.declareProtected(Permissions.ManagePortal, 'clean')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3839
    def _clean(self):
3840
      """
3841
        Clean built information.
3842
      """
3843
      # First, remove obsolete attributes if present.
3844 3845 3846 3847
      for attr in ( '_action_archive',
                    '_document_archive',
                    '_extension_archive',
                    '_test_archive',
3848
                    '_module_archive',
3849 3850 3851
                    '_object_archive',
                    '_portal_type_archive',
                    '_property_archive',
3852
                    '_property_sheet_archive'):
3853 3854 3855
        if hasattr(self, attr):
          delattr(self, attr)
      # Secondly, make attributes empty.
3856 3857
      for item_name in self._item_name_list:
        item = setattr(self, item_name, None)
3858

Yoshinori Okuji's avatar
Yoshinori Okuji committed
3859
    clean = WorkflowMethod(_clean)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3860

3861
    security.declareProtected(Permissions.AccessContentsInformation,
3862
                              'getBuildingState')
3863
    def getBuildingState(self, id_only=1):
3864
      """
3865
        Returns the current state in building
3866
      """
3867
      portal_workflow = getToolByName(self, 'portal_workflow')
3868 3869
      wf = portal_workflow.getWorkflowById(
                          'business_template_building_workflow')
3870
      return wf._getWorkflowStateOf(self, id_only=id_only )
3871

3872
    security.declareProtected(Permissions.AccessContentsInformation,
3873
                              'getInstallationState')
3874
    def getInstallationState(self, id_only=1):
3875
      """
3876
        Returns the current state in installation
3877
      """
3878
      portal_workflow = getToolByName(self, 'portal_workflow')
3879 3880
      wf = portal_workflow.getWorkflowById(
                           'business_template_installation_workflow')
3881
      return wf._getWorkflowStateOf(self, id_only=id_only )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3882

Yoshinori Okuji's avatar
Yoshinori Okuji committed
3883 3884 3885 3886 3887 3888
    security.declareProtected(Permissions.AccessContentsInformation, 'toxml')
    def toxml(self):
      """
        Return this Business Template in XML
      """
      portal_templates = getToolByName(self, 'portal_templates')
3889
      export_string = portal_templates.manage_exportObject(
3890 3891
                                               id=self.getId(),
                                               toxml=1,
3892
                                               download=1)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3893
      return export_string
3894

3895
    def _getOrderedList(self, id):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3896
      """
3897 3898
        We have to set this method because we want an
        ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3899
      """
3900
      result = getattr(self, id, ())
3901 3902 3903 3904 3905 3906
      if result is None: result = ()
      if result != ():
        result = list(result)
        result.sort()
        result = tuple(result)
      return result
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3907

3908
    def getTemplateCatalogMethodIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3909
      """
3910 3911
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3912
      """
3913
      return self._getOrderedList('template_catalog_method_id')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3914

3915
    def getTemplateBaseCategoryList(self):
3916
      """
3917 3918
      We have to set this method because we want an
      ordered list
3919
      """
3920
      return self._getOrderedList('template_base_category')
3921

3922
    def getTemplateWorkflowIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3923
      """
3924 3925
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3926
      """
3927
      return self._getOrderedList('template_workflow_id')
3928

3929
    def getTemplatePortalTypeIdList(self):
3930
      """
3931 3932
      We have to set this method because we want an
      ordered list
3933
      """
3934
      return self._getOrderedList('template_portal_type_id')
3935

3936 3937 3938 3939 3940 3941 3942
    def getTemplatePortalTypeWorkflowChainList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_portal_type_workflow_chain')

Alexandre Boeglin's avatar
Alexandre Boeglin committed
3943 3944 3945 3946 3947 3948 3949
    def getTemplatePathList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_path')

3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977
    def getTemplatePortalTypeAllowedContentTypeList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_portal_type_allowed_content_type')
    
    def getTemplatePortalTypeHiddenContentTypeList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_portal_type_hidden_content_type')

    def getTemplatePortalTypePropertySheetList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_portal_type_property_sheet')

    def getTemplatePortalTypeBaseCategoryList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_portal_type_base_category')

3978
    def getTemplateActionPathList(self):
3979
      """
3980 3981
      We have to set this method because we want an
      ordered list
3982
      """
3983
      return self._getOrderedList('template_action_path')
3984

3985 3986 3987 3988 3989 3990 3991
    def getTemplatePortalTypeRolesList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_portal_type_roles')

3992
    def getTemplateSkinIdList(self):
3993
      """
3994 3995
      We have to set this method because we want an
      ordered list
3996
      """
3997
      return self._getOrderedList('template_skin_id')
3998

3999
    def getTemplateModuleIdList(self):
4000
      """
4001 4002
      We have to set this method because we want an
      ordered list
4003
      """
4004
      return self._getOrderedList('template_module_id')
4005 4006 4007 4008 4009 4010 4011

    def getTemplateMessageTranslationList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_message_translation')
4012

4013
    security.declareProtected(Permissions.ManagePortal, 'export')
Aurel's avatar
Aurel committed
4014 4015 4016 4017
    def export(self, path=None, local=0, **kw):
      """
        Export this Business Template
      """
4018 4019
      if self.getBuildingState() != 'built':
        raise TemplateConditionError, 'Business Template must be build before export'
4020
      
Aurel's avatar
Aurel committed
4021 4022 4023 4024 4025 4026 4027
      if local:
        # we export into a folder tree
        bta = BusinessTemplateFolder(creation=1, path=path)
      else:
        # We export BT into a tarball file
        bta = BusinessTemplateTarball(creation=1, path=path)

4028
      # export bt
4029
      bta.addFolder(path+os.sep+'bt')
Aurel's avatar
Aurel committed
4030
      for prop in self.propertyMap():
4031
        prop_type = prop['type']
Aurel's avatar
Aurel committed
4032
        id = prop['id']
4033
        if id in ('id', 'uid', 'rid', 'sid', 'id_group', 'last_id', 'install_object_list_list'):
4034
          continue
Aurel's avatar
Aurel committed
4035 4036
        if id in ('template_update_business_template_workflow', 'template_update_tool') and self.getTitle() != 'erp5_core':
          continue
Aurel's avatar
Aurel committed
4037
        value = self.getProperty(id)
4038
        if prop_type in ('text', 'string', 'int', 'boolean'):
4039
          bta.addObject(obj=value, name=id, path=path+os.sep+'bt', ext='')
4040
        elif prop_type in ('lines', 'tokens'):
4041
          bta.addObject(obj=str(os.linesep).join(value), name=id, path=path+os.sep+'bt', ext='')
4042

Aurel's avatar
Aurel committed
4043 4044 4045
      # Export each part
      for item_name in self._item_name_list:
        getattr(self, item_name).export(context=self, bta=bta)
4046
        
Aurel's avatar
Aurel committed
4047 4048
      return bta.finishCreation()

4049
    security.declareProtected(Permissions.ManagePortal, 'importFile')
Aurel's avatar
Aurel committed
4050 4051
    def importFile(self, dir = 0, file=None, root_path=None):
      """
4052
        Import all xml files in Business Template
Aurel's avatar
Aurel committed
4053 4054 4055 4056 4057
      """
      if dir:
        bta = BusinessTemplateFolder(importing=1, file=file, path=root_path)
      else:
        bta = BusinessTemplateTarball(importing=1, file=file)
4058

Aurel's avatar
Aurel committed
4059 4060
      self._portal_type_item = \
          PortalTypeTemplateItem(self.getTemplatePortalTypeIdList())
4061
      self._portal_type_workflow_chain_item = \
4062
          PortalTypeWorkflowChainTemplateItem(self.getTemplatePortalTypeWorkflowChainList())
Aurel's avatar
Aurel committed
4063 4064 4065 4066 4067 4068 4069 4070 4071 4072
      self._workflow_item = \
          WorkflowTemplateItem(self.getTemplateWorkflowIdList())
      self._skin_item = \
          SkinTemplateItem(self.getTemplateSkinIdList())
      self._category_item = \
          CategoryTemplateItem(self.getTemplateBaseCategoryList())
      self._catalog_method_item = \
          CatalogMethodTemplateItem(self.getTemplateCatalogMethodIdList())
      self._action_item = \
          ActionTemplateItem(self.getTemplateActionPathList())
4073 4074
      self._portal_type_roles_item = \
          PortalTypeRolesTemplateItem(self.getTemplatePortalTypeRolesList())
Aurel's avatar
Aurel committed
4075 4076 4077 4078 4079 4080 4081 4082
      self._site_property_item = \
          SitePropertyTemplateItem(self.getTemplateSitePropertyIdList())
      self._module_item = \
          ModuleTemplateItem(self.getTemplateModuleIdList())
      self._document_item = \
          DocumentTemplateItem(self.getTemplateDocumentIdList())
      self._property_sheet_item = \
          PropertySheetTemplateItem(self.getTemplatePropertySheetIdList())
4083 4084
      self._constraint_item = \
          ConstraintTemplateItem(self.getTemplateConstraintIdList())
Aurel's avatar
Aurel committed
4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106
      self._extension_item = \
          ExtensionTemplateItem(self.getTemplateExtensionIdList())
      self._test_item = \
          TestTemplateItem(self.getTemplateTestIdList())
      self._product_item = \
          ProductTemplateItem(self.getTemplateProductIdList())
      self._role_item = \
          RoleTemplateItem(self.getTemplateRoleList())
      self._catalog_result_key_item = \
          CatalogResultKeyTemplateItem(
               self.getTemplateCatalogResultKeyList())
      self._catalog_related_key_item = \
          CatalogRelatedKeyTemplateItem(
               self.getTemplateCatalogRelatedKeyList())
      self._catalog_result_table_item = \
          CatalogResultTableTemplateItem(
               self.getTemplateCatalogResultTableList())
      self._message_translation_item = \
          MessageTranslationTemplateItem(
               self.getTemplateMessageTranslationList())
      self._path_item = \
               PathTemplateItem(self.getTemplatePathList())
4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133
      self._portal_type_allowed_content_type_item = \
           PortalTypeAllowedContentTypeTemplateItem(
               self.getTemplatePortalTypeAllowedContentTypeList())
      self._portal_type_hidden_content_type_item = \
           PortalTypeHiddenContentTypeTemplateItem(
               self.getTemplatePortalTypeHiddenContentTypeList())
      self._portal_type_property_sheet_item = \
           PortalTypePropertySheetTemplateItem(
               self.getTemplatePortalTypePropertySheetList())
      self._portal_type_base_category_item = \
           PortalTypeBaseCategoryTemplateItem(
               self.getTemplatePortalTypeBaseCategoryList())
      self._catalog_keyword_key_item = \
          CatalogKeywordKeyTemplateItem(
               self.getTemplateCatalogKeywordKeyList())      
      self._catalog_full_text_key_item = \
          CatalogFullTextKeyTemplateItem(
               self.getTemplateCatalogFullTextKeyList())      
      self._catalog_request_key_item = \
          CatalogRequestKeyTemplateItem(
               self.getTemplateCatalogRequestKeyList())      
      self._catalog_multivalue_key_item = \
          CatalogMultivalueKeyTemplateItem(
               self.getTemplateCatalogMultivalueKeyList())      
      self._catalog_topic_key_item = \
          CatalogTopicKeyTemplateItem(
               self.getTemplateCatalogTopicKeyList())
Aurel's avatar
Aurel committed
4134 4135 4136
      self._local_roles_item = \
          LocalRolesTemplateItem(
               self.getTemplateLocalRolesList())
4137
      
Aurel's avatar
Aurel committed
4138 4139
      for item_name in self._item_name_list:
        getattr(self, item_name).importFile(bta)
4140 4141


Aurel's avatar
Aurel committed
4142
    def diffObject(self, REQUEST, **kw):
4143 4144
      """
        Make a diff between an object in the Business Template
4145
        and the same in the Business Template installed in the site
4146 4147 4148 4149 4150
      """

      class_name_dict = {
        'Product' : '_product_item',
        'PropertySheet' : '_property_sheet_item', 
4151
        'Constraint' : '_constraint_item',
4152 4153 4154 4155 4156 4157 4158 4159 4160
        'Document' : '_document_item',
        'Extension' : '_extension_item',
        'Test' : '_test_item',
        'Role' : '_role_item',
        'MessageTranslation' : '_message_translation_item',
        'Workflow' : '_workflow_item',
        'CatalogMethod' : '_catalog_method_item',
        'SiteProperty' : '_site_property_item',
        'PortalType' : '_portal_type_item',
4161
        'PortalTypeWorkflowChain' : '_portal_type_workflow_chain_item',
4162 4163 4164 4165
        'PortalTypeAllowedContentType' : '_portal_type_allowed_content_type_item',
        'PortalHiddenAllowedContentType' : '_portal_type_hidden_content_type_item',
        'PortalTypePropertySheet' : '_portal_type_property_sheet_item',
        'PortalTypeBaseCategory' : '_portal_type_base_category_item',
4166 4167 4168 4169 4170
        'Category' : '_category_item',
        'Module' : '_module_item',
        'Skin' : '_skin_item',
        'Path' : '_path_item',
        'Action' : '_action_item',
4171
        'PortalTypeRoles' : '_portal_type_roles_item',
Aurel's avatar
Aurel committed
4172
        'LocalRoles' : '_local_roles_item',
4173 4174 4175
        'CatalogResultKey' : '_catalog_result_key_item',
        'CatalogRelatedKey' : '_catalog_related_key_item',
        'CatalogResultTable' : '_catalog_result_table_item',
4176 4177 4178 4179 4180
        'CatalogKeywordKey' : '_catalog_keyword_key_item',
        'CatalogFullTextKey' : '_catalog_full_text_key_item',
        'CatalogRequestKey' : '_catalog_request_key_item',
        'CatalogMultivalueKey' : '_catalog_multivalue_key_item',
        'CatalogTopicKey' : '_catalog_topic_key_item',
4181
        }
Aurel's avatar
Aurel committed
4182
      
4183 4184 4185 4186
      object_id = REQUEST.object_id
      object_class = REQUEST.object_class
      # get objects
      item_name = class_name_dict[object_class]
Aurel's avatar
Aurel committed
4187

Aurel's avatar
Aurel committed
4188
      new_bt =self
Aurel's avatar
Aurel committed
4189
      # compare with a given business template
4190
      compare_to_zodb = 0
Aurel's avatar
Aurel committed
4191 4192
      bt2_id = kw.get('compare_with', None)
      if bt2_id is not None:
4193 4194 4195 4196
        if bt2_id == self.getId():
          compare_to_zodb = 1
        else:
          installed_bt = self.portal_templates._getOb(bt2_id)
Aurel's avatar
Aurel committed
4197 4198 4199
      else:
        installed_bt = self.getInstalledBusinessTemplate(title=self.getTitle())
        if installed_bt == new_bt:
Aurel's avatar
Aurel committed
4200 4201 4202 4203 4204 4205 4206 4207
          compare_to_zodb = 1
      if compare_to_zodb:
        bt2 = self.portal_templates.manage_clone(ob=installed_bt, id='installed_bt_for_diff')
        # update portal types properties to get last modifications
        bt2.getPortalTypesProperties()
        bt2.edit(description='tmp bt generated for diff')
        installed_bt = bt2
        
4208 4209
      new_item = getattr(new_bt, item_name)
      installed_item = getattr(installed_bt, item_name)
4210
      if compare_to_zodb:
Aurel's avatar
Aurel committed
4211
        # XXX maybe only build for the given object to gain time
4212
        installed_item.build(self)
4213 4214 4215 4216
      new_object = new_item._objects[object_id]
      installed_object = installed_item._objects[object_id]
      # make diff
      diff_msg = ''
4217 4218 4219 4220 4221 4222
      item_list_1 = ['_product_item', '_workflow_item', '_portal_type_item', '_category_item', '_path_item',
                     '_skin_item', '_action_item']
      item_list_2 = ['_site_property_item', '_module_item', '_catalog_result_key_item', '_catalog_related_key_item',
                     '_catalog_result_table_item',   '_catalog_keyword_key_item', '_catalog_full_text_key_item',
                     '_catalog_request_key_item', '_catalog_multivalue_key_item', '_catalog_topic_key_item',
                     '_portal_type_allowed_content_type_item', '_portal_type_hidden_content_type_item',
4223
                     '_portal_type_property_sheet_item', '_portal_type_roles_item',
4224 4225
                     '_portal_type_base_category_item', '_local_roles_item',
                     '_portal_type_workflow_chain_item']
4226 4227 4228
      item_list_3 = ['_document_item', '_property_sheet_item',
          '_constraint_item', '_extension_item', '_test_item',
          '_message_translation_item']
4229
      
4230 4231 4232
      if item_name in item_list_1:
        f1 = StringIO()
        f2 = StringIO()
Aurel's avatar
Aurel committed
4233
        # remove uneeded property
4234 4235
        new_object = new_item.removeProperties(new_object)
        installed_object = installed_item.removeProperties(installed_object)
4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249
        OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, f1)
        OFS.XMLExportImport.exportXML(installed_object._p_jar, installed_object._p_oid, f2)
        new_obj_xml = f1.getvalue()
        installed_obj_xml = f2.getvalue()
        f1.close()
        f2.close()
        new_ob_xml_lines = new_obj_xml.splitlines()
        installed_ob_xml_lines = installed_obj_xml.splitlines()
        diff_list = list(unified_diff(installed_ob_xml_lines, new_ob_xml_lines, tofile=new_bt.getId(), fromfile=installed_bt.getId(), lineterm=''))
        if len(diff_list) != 0:
          diff_msg += '\n\nObject %s diff :\n' %( object_id)
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'
Aurel's avatar
Aurel committed
4250
          
4251
      elif item_name in item_list_2:
4252 4253
        new_obj_xml = new_item.generateXml(path= object_id)
        installed_obj_xml = installed_item.generateXml(path= object_id)
4254 4255 4256 4257 4258 4259 4260 4261
        new_obj_xml_lines = new_obj_xml.splitlines()
        installed_obj_xml_lines = installed_obj_xml.splitlines()
        diff_list = list(unified_diff(installed_obj_xml_lines, new_obj_xml_lines, tofile=new_bt.getId(), fromfile=installed_bt.getId(), lineterm=''))
        if len(diff_list) != 0:
          diff_msg += '\n\nObject %s diff :\n' %( object_id)
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'
Aurel's avatar
Aurel committed
4262
          
4263 4264 4265 4266 4267 4268 4269 4270 4271
      elif item_name in item_list_3:
        new_obj_lines = new_object.splitlines()
        installed_obj_lines = installed_object.splitlines()
        diff_list = list(unified_diff(installed_obj_lines, new_obj_lines, tofile=new_bt.getId(), fromfile=installed_bt.getId(), lineterm=''))
        if len(diff_list) != 0:
          diff_msg += '\n\nObject %s diff :\n' %( object_id)
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'                
4272 4273 4274

      if compare_to_zodb:
        self.portal_templates.manage_delObjects(ids=['installed_bt_for_diff'])
4275 4276
      
      return diff_msg
4277 4278 4279 4280 4281
    
    def getPortalTypesProperties(self, **kw):
      """
      Fill field about properties for each portal type
      """
4282 4283 4284 4285 4286 4287 4288
      bt_allowed_content_type_list = list(getattr(self, 'template_portal_type_allowed_content_type', []) or [])
      bt_hidden_content_type_list = list(getattr(self, 'template_portal_type_hidden_content_type', []) or [])
      bt_property_sheet_list = list(getattr(self, 'template_portal_type_property_sheet', []) or [])
      bt_base_category_list = list(getattr(self, 'template_portal_type_base_category', []) or [])
      bt_action_list = list(getattr(self, 'template_action_path', []) or [])
      bt_portal_types_id_list = list(self.getTemplatePortalTypeIdList())
      bt_portal_type_roles_list =  list(getattr(self, 'template_portal_type_roles', []) or [])
4289 4290 4291 4292 4293 4294 4295
      bt_wf_chain_list = list(getattr(self, 'template_portal_type_workflow_chain', []) or [])

      pt_wf_item = getattr(self, '_portal_type_workflow_chain_item', None)
      if pt_wf_item is not None:
        chain_dict = getChainByType(self.getPortalObject())[1]
      else:
        chain_dict = {}
4296

4297 4298 4299 4300 4301 4302
      p = self.getPortalObject()
      for id in bt_portal_types_id_list:        
        try:
          portal_type = p.unrestrictedTraverse('portal_types/'+id)
        except KeyError:
          continue
4303
        if len(getattr(portal_type, '_roles', ())) > 0:
4304 4305
          if id not in bt_portal_type_roles_list:
            bt_portal_type_roles_list.append(id)
4306

4307 4308 4309 4310
        allowed_content_type_list = []
        hidden_content_type_list = []
        property_sheet_list = []
        base_category_list = []
4311
        action_list = []
4312
        wf_list = []
4313 4314 4315 4316 4317 4318 4319 4320
        if hasattr(portal_type, 'allowed_content_types'):
          allowed_content_type_list = portal_type.allowed_content_types
        if hasattr(portal_type, 'hidden_content_type_list'):
          hidden_content_type_list = portal_type.hidden_content_type_list
        if hasattr(portal_type, 'property_sheet_list'):
          property_sheet_list = portal_type.property_sheet_list
        if hasattr(portal_type, 'base_category_list'):
          base_category_list = portal_type.base_category_list       
4321 4322
        if hasattr(portal_type, 'listActions'):
          action_list = [x.getId() for x in portal_type.listActions()]
4323
        if chain_dict.has_key('chain_%s' % id):
Aurel's avatar
Aurel committed
4324 4325 4326
          chain = chain_dict['chain_%s' % id]
          if chain != '' and chain != '(Default)':
            wf_list = chain.split(', ')
4327
        
4328 4329 4330 4331 4332
        for a_id in allowed_content_type_list:
          allowed_id = id+' | '+a_id
          if allowed_id not in bt_allowed_content_type_list:
            bt_allowed_content_type_list.append(allowed_id)
            
4333
        for h_id in hidden_content_type_list:
4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347
          hidden_id = id+' | '+h_id
          if hidden_id not in bt_hidden_content_type_list:
            bt_hidden_content_type_list.append(hidden_id)
          
        for ps_id in property_sheet_list:
          p_sheet_id = id+' | '+ps_id
          if p_sheet_id not in bt_property_sheet_list:
            bt_property_sheet_list.append(p_sheet_id)
          
        for bc_id in base_category_list:
          base_cat_id = id+' | '+bc_id
          if base_cat_id not in bt_base_category_list:
            bt_base_category_list.append(base_cat_id)
            
4348 4349 4350 4351
        for act_id in action_list:
          action_id = id+' | '+act_id
          if action_id not in bt_action_list:
            bt_action_list.append(action_id)
4352 4353 4354 4355 4356 4357

        for workflow_id in wf_list:
          wf_id = id+' | '+workflow_id
          if wf_id not in bt_wf_chain_list:
            bt_wf_chain_list.append(wf_id)
                    
4358 4359 4360 4361
      bt_allowed_content_type_list.sort()
      bt_hidden_content_type_list.sort()
      bt_property_sheet_list.sort()
      bt_base_category_list.sort()
4362
      bt_action_list.sort()
4363
      bt_wf_chain_list.sort()
4364

4365
      setattr(self, 'template_portal_type_workflow_chain', bt_wf_chain_list) 
4366
      setattr(self, 'template_portal_type_roles', bt_portal_type_roles_list)
4367 4368 4369
      setattr(self, 'template_portal_type_allowed_content_type', bt_allowed_content_type_list)
      setattr(self, 'template_portal_type_hidden_content_type', bt_hidden_content_type_list)
      setattr(self, 'template_portal_type_property_sheet', bt_property_sheet_list)
4370 4371
      setattr(self, 'template_portal_type_base_category', bt_base_category_list)
      setattr(self, 'template_action_path', bt_action_list)  
4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492
      return


    def guessPortalTypes(self, **kw):
      """
      This method guesses portal types based on modules define in the Business Template
      """
      bt_module_id_list = list(self.getTemplateModuleIdList())
      if len(bt_module_id_list) == 0:
        raise TemplateConditionError, 'No module defined in business template'    
      
      bt_portal_types_id_list = list(self.getTemplatePortalTypeIdList())

      def getChildPortalType(type_id):
        type_list = {}
        p = self.getPortalObject()
        try:
          portal_type = p.unrestrictedTraverse('portal_types/'+type_id)
        except KeyError:
          return type_list        

        allowed_content_type_list = []
        hidden_content_type_list = []
        if hasattr(portal_type, 'allowed_content_types'):
          allowed_content_type_list = portal_type.allowed_content_types
        if hasattr(portal_type, 'hidden_content_type_list'):
          hidden_content_type_list = portal_type.hidden_content_type_list
        type_list[type_id] = ()
        # get same info for allowed portal types and hidden portal types
        for allowed_ptype_id in allowed_content_type_list:
          if allowed_ptype_id not in type_list.keys():
            type_list.update(getChildPortalType(allowed_ptype_id))
        for hidden_ptype_id in hidden_content_type_list:
          if hidden_ptype_id not in type_list.keys():
            type_list.update(getChildPortalType(hidden_ptype_id))        
        return type_list
      
      p = self.getPortalObject()
      portal_dict = {}
      for module_id in bt_module_id_list:
        module = p.unrestrictedTraverse(module_id)
        portal_type_id = module.getPortalType()
        try:
          portal_type = p.unrestrictedTraverse('portal_types/'+portal_type_id)
        except KeyError:
          continue
        allowed_content_type_list = []
        hidden_content_type_list = []
        if hasattr(portal_type, 'allowed_content_types'):
          allowed_content_type_list = portal_type.allowed_content_types
        if hasattr(portal_type, 'hidden_content_type_list'):
          hidden_content_type_list = portal_type.hidden_content_type_list

        portal_dict[portal_type_id] = ()

        for allowed_type_id in allowed_content_type_list:
          if allowed_type_id not in portal_dict.keys():
            portal_dict.update(getChildPortalType(allowed_type_id))

        for hidden_type_id in hidden_content_type_list:
          if hidden_type_id not in portal_dict.keys():
            portal_dict.update(getChildPortalType(hidden_type_id))

      # construct portal type list, keep already present portal types
      for id in portal_dict.keys():
        if id not in bt_portal_types_id_list:
          bt_portal_types_id_list.append(id)

      bt_portal_types_id_list.sort()

      setattr(self, 'template_portal_type_id', bt_portal_types_id_list)
      return

    def clearPortalTypes(self, **kw):
      """
      clear id list register for portal types
      """
      setattr(self, 'template_portal_type_id', ())
      setattr(self, 'template_portal_type_allowed_content_type', ())
      setattr(self, 'template_portal_type_hidden_content_type', ())
      setattr(self, 'template_portal_type_property_sheet', ())
      setattr(self, 'template_portal_type_base_category', ())
      return

# Transaction Manager used for update of business template workflow
# XXX update seems to works without it

# from Shared.DC.ZRDB.TM import TM

# class WorkflowUpdateTM(TM):

#   _p_oid=_p_changed=_registered=None
#   _update = 0

#   def __init__(self, ):
#     LOG('init TM', 0, '')

#   def register(self, update=0, gen=None, site=None):
#     LOG('register TM', 0, update)
#     self._gen = gen
#     self._site = site
#     self._update = update
#     self._register()

#   def tpc_prepare(self, *d, **kw):
#     LOG("tpc_prepare", 0, self._update)
#     if self._update:
#       # do it one time
#       self._update = 0
#       LOG('call update of wf', 0, '')
#       self._gen.setupWorkflow(self._site)
      

#   def _finish(self, **kw):
#     LOG('finish TM', 0, '')
#     pass

#   def _abort(self, **kw):
#     LOG('abort TM', 0, '')
#     pass