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

30
import fnmatch, gc, imp, os, re, shutil, sys
31
from Shared.DC.ZRDB.Connection import Connection as RDBConnection
32
from Products.ERP5Type.DiffUtils import DiffFile
33
from Products.ERP5Type.Globals import Persistent, PersistentMapping
34
from Acquisition import Implicit, aq_base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
35 36
from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
37
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
38
from Products.ERP5Type.Base import WorkflowMethod, _aq_reset
Nicolas Dumazet's avatar
Nicolas Dumazet committed
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
from Products.ERP5Type.Utils import readLocalDocument, \
                                    writeLocalDocument, \
                                    importLocalDocument, \
                                    removeLocalDocument
from Products.ERP5Type.Utils import readLocalPropertySheet, \
                                    writeLocalPropertySheet, \
                                    importLocalPropertySheet, \
                                    removeLocalPropertySheet
from Products.ERP5Type.Utils import readLocalConstraint, \
                                    writeLocalConstraint, \
                                    importLocalConstraint, \
                                    removeLocalConstraint
from Products.ERP5Type.Utils import readLocalExtension, \
                                    writeLocalExtension, \
                                    removeLocalExtension
from Products.ERP5Type.Utils import readLocalTest, \
                                    writeLocalTest, \
                                    removeLocalTest
57
from Products.ERP5Type.Utils import convertToUpperCase
58
from Products.ERP5Type import Permissions, PropertySheet
Jean-Paul Smets's avatar
Jean-Paul Smets committed
59
from Products.ERP5Type.XMLObject import XMLObject
Aurel's avatar
Aurel committed
60
from OFS.Traversable import NotFound
61
from OFS import SimpleItem, XMLExportImport
62
from cStringIO import StringIO
Aurel's avatar
Aurel committed
63
from copy import deepcopy
64
from zExceptions import BadRequest
Aurel's avatar
Aurel committed
65
import OFS.XMLExportImport
66
from Products.ERP5Type.patches.ppml import importXML
Aurel's avatar
Aurel committed
67
customImporters={
68
    XMLExportImport.magic: importXML,
Aurel's avatar
Aurel committed
69
    }
Jean-Paul Smets's avatar
Jean-Paul Smets committed
70

71
from zLOG import LOG, WARNING
72
from warnings import warn
Aurel's avatar
Aurel committed
73
from gzip import GzipFile
74
from lxml.etree import parse
75
from xml.sax.saxutils import escape
76
from Products.CMFCore.Expression import Expression
77
from Products.ERP5Type import tarfile
78
from urllib import quote, unquote
79
from difflib import unified_diff
80
import posixpath
Julien Muchembled's avatar
Julien Muchembled committed
81
import transaction
82

83 84 85 86 87 88
import threading

CACHE_DATABASE_PATH = None
try:
  if int(os.getenv('ERP5_BT5_CACHE', 0)):
    from App.config import getConfiguration
89
    import gdbm
90 91 92 93 94 95
    instancehome = getConfiguration().instancehome
    CACHE_DATABASE_PATH = os.path.join(instancehome, 'bt5cache.db')
except TypeError:
  pass
cache_database = threading.local()

96 97
# those attributes from CatalogMethodTemplateItem are kept for
# backward compatibility
Aurel's avatar
Aurel committed
98 99
catalog_method_list = ('_is_catalog_list_method_archive',
                       '_is_uncatalog_method_archive',
100 101
                       '_is_clear_method_archive',
                       '_is_filtered_archive',)
102

103 104
catalog_method_filter_list = ('_filter_expression_archive',
                              '_filter_expression_instance_archive',
105
                              '_filter_expression_cache_key_archive',
106
                              '_filter_type_archive',)
107

108 109
INSTALLED_BT_FOR_DIFF = 'installed_bt_for_diff'

110 111 112 113 114 115
def _getCatalog(acquisition_context):
  """
    Return the id of the SQLCatalog which correspond to the current BT.
  """
  catalog_method_id_list = acquisition_context.getTemplateCatalogMethodIdList()
  if len(catalog_method_id_list) == 0:
116 117 118 119
    try:
      return acquisition_context.getPortalObject().portal_catalog.objectIds('SQLCatalog')[0]
    except IndexError:
      return None
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
  catalog_method_id = catalog_method_id_list[0]
  return catalog_method_id.split('/')[0]

def _getCatalogValue(acquisition_context):
  """
    Returns the catalog object which correspond to the ZSQLMethods
    stored/to store in the business template.

    NB: acquisition_context must make possible to reach portal object
        and getTemplateCatalogMethodIdList.
  """
  catalog_id = _getCatalog(acquisition_context)
  if catalog_id is None:
    return None
  try:
    return acquisition_context.getPortalObject().portal_catalog[catalog_id]
  except KeyError:
    return None

139 140 141 142 143
def _recursiveRemoveUid(obj):
  """Recusivly set uid to None, to prevent (un)indexing.
  This is used to prevent unindexing real objects when we delete subobjects on
  a copy of this object.
  """
144 145
  if hasattr(aq_base(obj), 'uid'):
    obj.uid = None
146 147 148
  for subobj in obj.objectValues():
    _recursiveRemoveUid(subobj)

149
def removeAll(entry):
150 151 152
  warn('removeAll is deprecated; use shutil.rmtree instead.',
       DeprecationWarning)
  shutil.rmtree(entry, True)
Aurel's avatar
Aurel committed
153

154 155 156 157 158 159 160
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
  """
161
  pw = context.getPortalObject().portal_workflow
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
  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)

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
def fixZSQLMethod(portal, method):
  """Make sure the ZSQLMethod uses a valid connection.
  """
  if not isinstance(getattr(portal, method.connection_id, None),
                      RDBConnection):
    # if not valid, we assign to the first valid connection found
    sql_connection_list = portal.objectIds(
                          spec=('Z MySQL Database Connection',))
    if (method.connection_id not in sql_connection_list) and \
       (len(sql_connection_list) != 0):
      LOG('BusinessTemplate', WARNING,
          'connection_id for Z SQL Method %s is invalid, using %s' % (
                    method.getId(), sql_connection_list[0]))
      method.connection_id = sql_connection_list[0]

198 199
def registerSkinFolder(skin_tool, skin_folder):
  request = skin_tool.REQUEST
200 201 202
  # XXX: Getting parameter from request instead of dialog is bad
  # XXX: This is even non consistent with rest of parameters selected by user
  #      (like update_translation or update_catalog)
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
  register_skin_selection = request.get('your_register_skin_selection', 1)
  reorder_skin_selection = request.get('your_reorder_skin_selection', 1)
  skin_layer_list = request.get('your_skin_layer_list', 
                                skin_tool.getSkinSelections()) 

  skin_folder_id = skin_folder.getId()

  try:
    skin_selection_list = skin_folder.getProperty(
                 'business_template_registered_skin_selections', 
                 skin_tool.getSkinSelections()
                 )
  except AttributeError:
    skin_selection_list = skin_tool.getSkinSelections()

218 219 220
  if isinstance(skin_selection_list, basestring):
    skin_selection_list = skin_selection_list.split()

221 222 223 224 225 226 227 228 229
  for skin_name in skin_selection_list:

    if (skin_name not in skin_tool.getSkinSelections()) and \
                                          register_skin_selection:
      createSkinSelection(skin_tool, skin_name)
      # add newly created skins to list of skins we care for 
      skin_layer_list.append(skin_name)

    selection = skin_tool.getSkinPath(skin_name) or ''
230
    selection_list = selection.split(',')
231
    if (skin_folder_id not in selection_list):
232
      selection_list.insert(0, skin_folder_id)
233 234
    if reorder_skin_selection:
      selection_list.sort(
235 236
        key=lambda x: x in skin_tool.objectIds() and skin_tool[x].getProperty(
        'business_template_skin_layer_priority', skin_tool[x].meta_type == 'Filesystem Directory View' and -1 or 0) or 0, reverse=True)
237
    if (skin_name in skin_layer_list):
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
      skin_tool.manage_skinLayers(skinpath=selection_list,
                                  skinname=skin_name, add_skin=1)
      skin_tool.getPortalObject().changeSkin(None)

def createSkinSelection(skin_tool, skin_name):
  # This skin selection does not exist, so we create a new one.
  # We'll initialize it with all skin folders, unless:
  #  - they explictly define a list of
  #    "business_template_registered_skin_selections", and we
  #    are not in this list.
  #  - they are not registred in the default skin selection
  skin_path = ''
  for skin_folder in skin_tool.objectValues():
    if skin_name in skin_folder.getProperty(
             'business_template_registered_skin_selections',
             (skin_name, )):
      if skin_folder.getId() in \
          skin_tool.getSkinPath(skin_tool.getDefaultSkin()):
        if skin_path:
          skin_path = '%s,%s' % (skin_path, skin_folder.getId())
        else:
          skin_path= skin_folder.getId()
  # add newly created skins to list of skins we care for 
  skin_tool.addSkinSelection(skin_name, skin_path)
  skin_tool.getPortalObject().changeSkin(None)

def deleteSkinSelection(skin_tool, skin_name):
  # Do not delete default skin
  if skin_tool.getDefaultSkin() != skin_name:

    skin_selection_registered = False
    for skin_folder in skin_tool.objectValues():
      try:
        skin_selection_list = skin_folder.getProperty(
               'business_template_registered_skin_selections', ())
        if skin_name in skin_selection_list:
          skin_selection_registered = True
          break
      except AttributeError:
        pass

    if (not skin_selection_registered):
      skin_tool.manage_skinLayers(chosen=[skin_name], 
                                  del_skin=1)
      skin_tool.getPortalObject().changeSkin(None)

def unregisterSkinFolder(skin_tool, skin_folder, skin_selection_list):
  skin_folder_id = skin_folder.getId()

  for skin_selection in skin_selection_list:
    selection = skin_tool.getSkinPath(skin_selection)
    selection = selection.split(',')
    if (skin_folder_id in selection):
      selection.remove(skin_folder_id)
      skin_tool.manage_skinLayers(skinpath=tuple(selection),
                                  skinname=skin_selection, add_skin=1)
      deleteSkinSelection(skin_tool, skin_selection)
      skin_tool.getPortalObject().changeSkin(None)

Aurel's avatar
Aurel committed
297 298 299
class BusinessTemplateArchive:
  """
    This is the base class for all Business Template archives
300
  """
Aurel's avatar
Aurel committed
301 302 303 304 305 306 307 308 309 310 311 312

  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
313

314
  def finishCreation(self, name=None, **kw):
Aurel's avatar
Aurel committed
315 316 317 318
    pass

class BusinessTemplateFolder(BusinessTemplateArchive):
  """
Christophe Dumez's avatar
Christophe Dumez committed
319
    Class archiving business template into a folder tree
320
  """
Aurel's avatar
Aurel committed
321 322 323 324 325 326
  def _initCreation(self, path):
    self.path = path
    try:
      os.makedirs(self.path)
    except OSError:
      # folder already exists, remove it
327
      shutil.rmtree(self.path)
Aurel's avatar
Aurel committed
328 329 330
      os.makedirs(self.path)

  def addFolder(self, name=''):
Jérome Perrin's avatar
Jérome Perrin committed
331
    if name != '':
332
      name = os.path.normpath(name)
Aurel's avatar
Aurel committed
333
      path = os.path.join(self.path, name)
334
      if not os.path.exists(path):
Aurel's avatar
Aurel committed
335 336 337
        os.makedirs(path)
      return path

338
  def addObject(self, obj, name, path=None, ext='.xml'):
339 340 341
    name = name.replace('\\', '/')
    name = quote(name)
    name = os.path.normpath(name)
Aurel's avatar
Aurel committed
342 343 344
    if path is None:
      object_path = os.path.join(self.path, name)
    else:
345
      if '%' not in path:
346 347 348 349
        tail, path = os.path.splitdrive(path)
        path = path.replace('\\', '/')
        path = tail + quote(path)
      path = os.path.normpath(path)
Aurel's avatar
Aurel committed
350
      object_path = os.path.join(path, name)
351
    f = open(object_path+ext, 'wb')
352 353 354 355
    try:
      f.write(str(obj))
    finally:
      f.close()
Aurel's avatar
Aurel committed
356 357

  def _initImport(self, file=None, path=None, **kw):
358
    # Normalize the paths to eliminate the effect of double-slashes.
359 360 361 362 363 364 365 366
    root_path_len = len(os.path.normpath(path)) + len(os.sep)
    self.root_path_len = root_path_len
    d = {}
    for f in file:
      f = os.path.normpath(f)
      klass = f[root_path_len:].split(os.sep, 1)[0]
      d.setdefault(klass, []).append(f)
    self.file_list_dict = d
Aurel's avatar
Aurel committed
367

368
  def importFiles(self, item, **kw):
Aurel's avatar
Aurel committed
369 370 371
    """
      Import file from a local folder
    """
372
    class_name = item.__class__.__name__
373 374
    root_path_len = self.root_path_len
    prefix_len = root_path_len + len(class_name) + len(os.sep)
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
    if CACHE_DATABASE_PATH:
      try:
        cache_database.db = gdbm.open(CACHE_DATABASE_PATH, 'cf')
      except gdbm.error:
        cache_database.db = gdbm.open(CACHE_DATABASE_PATH, 'nf')
    try:
      for file_path in self.file_list_dict.get(class_name, ()):
        if os.path.isfile(file_path):
          file = open(file_path, 'rb')
          try:
            file_name = file_path[prefix_len:]
            if '%' in file_name:
              file_name = unquote(file_name)
            item._importFile(file_name, file)
          finally:
            file.close()
    finally:
      if hasattr(cache_database, 'db'):
        cache_database.db.close()
        del cache_database.db
395

Aurel's avatar
Aurel committed
396 397 398 399 400 401
class BusinessTemplateTarball(BusinessTemplateArchive):
  """
    Class archiving businnes template into a tarball file
  """

  def _initCreation(self, path):
402
    # make tmp dir, must use stringIO instead
Aurel's avatar
Aurel committed
403 404 405 406 407
    self.path = path
    try:
      os.makedirs(self.path)
    except OSError:
      # folder already exists, remove it
408
      shutil.rmtree(self.path)
Aurel's avatar
Aurel committed
409 410 411 412 413 414
      os.makedirs(self.path)
    # init tarfile obj
    self.fobj = StringIO()
    self.tar = tarfile.open('', 'w:gz', self.fobj)

  def addFolder(self, name=''):
415
    name = os.path.normpath(name)
Aurel's avatar
Aurel committed
416
    if not os.path.exists(name):
Aurel's avatar
Aurel committed
417 418
      os.makedirs(name)

419
  def addObject(self, obj, name, path=None, ext='.xml'):
420 421 422
    name = name.replace('\\', '/')
    name = quote(name)
    name = os.path.normpath(name)
Aurel's avatar
Aurel committed
423 424 425
    if path is None:
      object_path = os.path.join(self.path, name)
    else:
426
      if '%' not in path:
427 428 429 430
        tail, path = os.path.splitdrive(path)
        path = path.replace('\\', '/')
        path = tail + quote(path)
      path = os.path.normpath(path)
Aurel's avatar
Aurel committed
431
      object_path = os.path.join(path, name)
432
    f = open(object_path+ext, 'wb')
433 434 435 436
    try:
      f.write(str(obj))
    finally:
      f.close()
Aurel's avatar
Aurel committed
437

438 439
  def finishCreation(self, name):
    self.tar.add(name)
Aurel's avatar
Aurel committed
440
    self.tar.close()
441
    shutil.rmtree(name)
Aurel's avatar
Aurel committed
442 443 444 445 446
    return self.fobj

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

447
  def importFiles(self, item, **kw):
Aurel's avatar
Aurel committed
448 449
    """
      Import all file from the archive to the site
450
    """
451
    class_name = item.__class__.__name__
Aurel's avatar
Aurel committed
452 453 454 455 456
    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
457 458
      if 'CVS' in info.name.split('/'):
        continue
Yoshinori Okuji's avatar
Yoshinori Okuji committed
459 460
      if '.svn' in info.name.split('/'):
        continue
461
      if class_name in info.name.split('/'):
Aurel's avatar
Aurel committed
462 463
        if info.isreg():
          file = tar.extractfile(info)
464 465
          tar_file_name = info.name.startswith('./') and info.name[2:] or \
              info.name
466
          folders = tar_file_name.split('/')
467
          file_name = ('/').join(folders[2:])
468
          if '%' in file_name:
469
            file_name = unquote(file_name)
470
          item._importFile(file_name, file)
Aurel's avatar
Aurel committed
471 472 473
          file.close()
    tar.close()
    io.close()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
474

475
class TemplateConditionError(Exception): pass
476
class TemplateConflictError(Exception): pass
477
class BusinessTemplateMissingDependency(Exception): pass
478

479
class BaseTemplateItem(Implicit, Persistent):
480
  """
481
    This class is the base class for all template items.
482
    is_bt_for_diff means This BT is used to compare self temporary BT with installed BT
483
  """
484
  is_bt_for_diff = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
485

486
  def __init__(self, id_list, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
487
    self.__dict__.update(kw)
488
    self._archive = PersistentMapping()
Aurel's avatar
Aurel committed
489
    self._objects = PersistentMapping()
490
    for id in id_list:
491 492
      if id is not None and id != '':
        self._archive[id] = None
493 494 495 496

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

497
  def preinstall(self, context, installed_item, **kw):
Vincent Pelletier's avatar
Vincent Pelletier committed
498 499
    """
      Build a list of added/removed/changed files between the BusinessTemplate
500
      being installed (self) and the installed one (installed_item).
Vincent Pelletier's avatar
Vincent Pelletier committed
501 502 503 504 505
      Note : we compare files between BTs, *not* between the installed BT and
      the objects in the DataFS.

      XXX: -12 used here is -len('TemplateItem')
    """
Yoshinori Okuji's avatar
Yoshinori Okuji committed
506 507
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
508
      new_keys = self._objects.keys()
509
      for path in new_keys:
510
        if installed_item._objects.has_key(path):
Vincent Pelletier's avatar
Vincent Pelletier committed
511
          # compare objects to see it there are changes
512
          new_obj_xml = self.generateXml(path=path)
513
          old_obj_xml = installed_item.generateXml(path=path)
514 515
          if new_obj_xml != old_obj_xml:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
Vincent Pelletier's avatar
Vincent Pelletier committed
516
          # else, compared versions are identical, don't overwrite the old one
517 518
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
Vincent Pelletier's avatar
Vincent Pelletier committed
519
      # list removed objects
520
      old_keys = installed_item._objects.keys()
521 522 523 524 525 526
      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):
527
    pass
528 529 530

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

532
  def remove(self, context, **kw):
Vincent Pelletier's avatar
Vincent Pelletier committed
533 534 535 536 537 538
    """
      If 'remove' is chosen on an object containing subobjects, all the
      subobjects will be removed too, even if 'backup' or 'keep' was chosen for
      the subobjects.
      Likewise, for 'save_and_remove' : subobjects will get saved too.
    """
539 540 541
    remove_dict = kw.get('remove_object_dict', {})
    keys = self._objects.keys()
    keys.sort()
542
    keys.reverse()
543 544 545 546 547 548 549 550 551 552 553
    # 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)
554 555 556 557
        else:
          # As the list of available actions is not strictly defined,
          # prevent mistake if an action is not handled
          raise ValueError, 'Unknown action "%s"' % action
558

559

560 561 562 563
  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
564
  def export(self, context, bta, **kw):
565
    pass
Aurel's avatar
Aurel committed
566

567 568
  def getKeys(self):
    return self._objects.keys()
569

Aurel's avatar
Aurel committed
570
  def importFile(self, bta, **kw):
571
    bta.importFiles(item=self)
572

573 574 575
  def removeProperties(self, obj):
    """
    Remove unneeded properties for export
576
    """
577 578 579 580 581 582 583 584 585 586 587 588 589 590
    meta_type = getattr(aq_base(obj), 'meta_type', None)

    attr_list = [ '_dav_writelocks', '_filepath', '_owner', 'uid',
                  'workflow_history', '__ac_local_roles__' ]
    attr_list += {
        'Script (Python)': ('_lazy_compilation', 'Python_magic'),
      }.get(meta_type, ())

    for attr in attr_list:
      if attr in obj.__dict__:
        delattr(obj, attr)

    if meta_type == 'ERP5 PDF Form':
      if not obj.getProperty('business_template_include_content', 1):
591
        obj.deletePdfContent()
592 593
    elif meta_type == 'Script (Python)':
      obj._code = None
594 595
    return obj

596 597 598 599 600 601 602 603 604
  def getTemplateTypeName(self):
    """
     Get a meaningfull class Name without 'TemplateItem'. Used to 
     present to the user.

     XXX: -12 used here is -len('TemplateItem')
    """
    return self.__class__.__name__[:-12]

605 606 607
class ObjectTemplateItem(BaseTemplateItem):
  """
    This class is used for generic objects and as a subclass.
608
  """
609

610 611 612 613
  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()
614
      self._archive.clear()
615 616 617
      for id in id_list :
        if id != '':
          self._archive["%s/%s" % (tool_id, id)] = None
618

619
  def export(self, context, bta, **kw):
Vincent Pelletier's avatar
Vincent Pelletier committed
620 621 622 623
    """
      Export the business template : fill the BusinessTemplateArchive with
      objects exported as XML, hierarchicaly organised.
    """
624 625 626
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
Vincent Pelletier's avatar
Vincent Pelletier committed
627
    for key, obj in self._objects.iteritems():
628
      # create folder and subfolders
629
      folders, id = posixpath.split(key)
Aurel's avatar
Aurel committed
630 631 632
      encode_folders = []
      for folder in folders.split('/'):
        if '%' not in folder:
633
          encode_folders.append(quote(folder))
Aurel's avatar
Aurel committed
634 635 636
        else:
          encode_folders.append(folder)
      path = os.path.join(root_path, (os.sep).join(encode_folders))
637 638
      bta.addFolder(name=path)
      # export object in xml
639
      f = StringIO()
640 641
      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
      bta.addObject(obj=f.getvalue(), name=id, path=path)
642

Aurel's avatar
Aurel committed
643
  def build_sub_objects(self, context, id_list, url, **kw):
Vincent Pelletier's avatar
Vincent Pelletier committed
644
    # XXX duplicates code from build
Aurel's avatar
Aurel committed
645 646 647
    p = context.getPortalObject()
    sub_list = {}
    for id in id_list:
648
      relative_url = '/'.join([url,id])
649 650
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
651
      obj = self.removeProperties(obj)
Vincent Pelletier's avatar
Vincent Pelletier committed
652 653 654
      id_list = obj.objectIds() # FIXME duplicated variable name
      if hasattr(aq_base(obj), 'groups'): # XXX should check metatype instead
        # we must keep groups because they are deleted along with subobjects
655
        groups = deepcopy(obj.groups)
656
      if id_list:
Aurel's avatar
Aurel committed
657
        self.build_sub_objects(context, id_list, relative_url)
658
        for id_ in list(id_list):
659
          obj._delObject(id_)
660
      if hasattr(aq_base(obj), 'groups'):
661 662 663
        obj.groups = groups
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
Aurel's avatar
Aurel committed
664 665
    return sub_list

666 667 668 669
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
670 671 672 673
      try:
        obj = p.unrestrictedTraverse(relative_url)
      except ValueError:
        raise ValueError, "Can not access to %s" % relative_url
674 675 676 677
      try:
        obj = obj._getCopy(context)
      except AttributeError:
        raise AttributeError, "Could not find object '%s' during business template processing." % relative_url
678
      _recursiveRemoveUid(obj)
679
      obj = self.removeProperties(obj)
680
      id_list = obj.objectIds()
Vincent Pelletier's avatar
Vincent Pelletier committed
681 682
      if hasattr(aq_base(obj), 'groups'): # XXX should check metatype instead
        # we must keep groups because they are deleted along with subobjects
683
        groups = deepcopy(obj.groups)
Aurel's avatar
Aurel committed
684 685
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
686
        for id_ in list(id_list):
687
          obj._delObject(id_)
688
      if hasattr(aq_base(obj), 'groups'):
689 690 691
        obj.groups = groups
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
692

693
  def _compileXML(self, file):
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
    # This method converts XML to ZEXP. Because the conversion
    # is quite heavy, a persistent cache database is used to
    # store ZEXP, so the second run wouldn't have to re-generate
    # identical data again.
    #
    # For now, a pair of the path to an XML file and its modification time
    # are used as a unique key. In theory, a checksum of the content could
    # be used instead, and it could be more reliable, as modification time
    # might not be updated in some insane filesystems correctly. However,
    # in practice, checksums consume a lot of CPU time, so when the cache
    # does not hit, the increased overhead is significant. In addition, it
    # does rarely happen that two XML files in Business Templates contain
    # the same data, so it may not be expected to have more cache hits
    # with this approach.
    #
    # The disadvantage is that this wouldn't work with the archive format,
    # because each entry in an archive does not have a mtime in itself.
    # However, the plan is to have an archive to retain ZEXP directly
    # instead of XML, so the idea of caching would be completely useless
    # with the archive format.
    name = file.name
    mtime = os.path.getmtime(file.name)
    key = '%s:%s' % (name, mtime)

    try:
      return StringIO(cache_database.db[key])
    except:
      pass

    #LOG('Business Template', 0, 'Compiling %s...' % (name,))
    from Shared.DC.xml import ppml
    from OFS.XMLExportImport import start_zopedata, save_record, save_zopedata
    import xml.parsers.expat
    outfile=StringIO()
    try:
      data=file.read()
      F=ppml.xmlPickler()
      F.end_handlers['record'] = save_record
      F.end_handlers['ZopeData'] = save_zopedata
      F.start_handlers['ZopeData'] = start_zopedata
      F.binary=1
      F.file=outfile
      p=xml.parsers.expat.ParserCreate('utf-8')
      p.returns_unicode = False
      p.CharacterDataHandler=F.handle_data
      p.StartElementHandler=F.unknown_starttag
      p.EndElementHandler=F.unknown_endtag
      r=p.Parse(data)

      try:
        cache_database.db[key] = outfile.getvalue()
      except:
        pass

      outfile.seek(0)
      return outfile
    except:
      outfile.close()
      raise
753

754
  def _importFile(self, file_name, file_obj):
755
    # import xml file
756
    if not file_name.endswith('.xml'):
757
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
758
      return
759 760 761 762 763
    obj = self
    connection = None
    while connection is None:
      obj=obj.aq_parent
      connection=obj._p_jar
764
    __traceback_info__ = 'Importing %s' % file_name
765 766
    if hasattr(cache_database, 'db') and isinstance(file_obj, file):
      obj = connection.importFile(self._compileXML(file_obj))
767
    else:
768 769
      # FIXME: Why not use the importXML function directly? Are there any BT5s
      # with actual .zexp files on the wild?
770
      obj = connection.importFile(file_obj, customImporters=customImporters)
771
    self.removeProperties(obj)
772 773
    self._objects[file_name[:-4]] = obj

774
  def preinstall(self, context, installed_item, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
775 776
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
Julien Muchembled's avatar
Julien Muchembled committed
777 778 779
      upgrade_list = []
      type_name = self.__class__.__name__.split('TemplateItem')[-2]
      for path in self._objects:
780
        if installed_item._objects.has_key(path):
Julien Muchembled's avatar
Julien Muchembled committed
781
          upgrade_list.append((path,
782
            self.removeProperties(installed_item._objects[path])))
783
        else: # new object
Julien Muchembled's avatar
Julien Muchembled committed
784 785 786 787 788 789 790 791 792 793
          modified_object_list[path] = 'New', type_name
      # update _p_jar property of objects cleaned by removeProperties
      transaction.savepoint(optimistic=True)
      for path, old_object in upgrade_list:
        # compare object to see it there is changes
        new_object = self._objects[path]
        new_io = StringIO()
        old_io = StringIO()
        OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, new_io)
        new_obj_xml = new_io.getvalue()
794 795 796 797 798
        try:
          OFS.XMLExportImport.exportXML(old_object._p_jar, old_object._p_oid, old_io)
          old_obj_xml = old_io.getvalue()
        except ImportError, e: # module is already removed etc.
          old_obj_xml = '(%s: %s)' % (e.__class__.__name__, e)
Julien Muchembled's avatar
Julien Muchembled committed
799 800 801 802
        new_io.close()
        old_io.close()
        if new_obj_xml != old_obj_xml:
          modified_object_list[path] = 'Modified', type_name
803
      # get removed object
804
      for path in set(installed_item._objects) - set(self._objects):
Julien Muchembled's avatar
Julien Muchembled committed
805
        modified_object_list[path] = 'Removed', type_name
806 807
    return modified_object_list

808
  def _backupObject(self, action, trashbin, container_path, object_id, **kw):
809 810 811
    """
      Backup the object in portal trash if necessery and return its subobjects
    """
812
    subobjects_dict = {}
813
    if trashbin is None: # must return subobjects
814 815 816 817 818 819
      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)
820
        subobjects_dict[subobject_id] = subobject_copy
821
      return subobjects_dict
822
    # XXX btsave is for backward compatibility
823
    if action == 'backup' or action == 'btsave' or action == 'save_and_remove':
824 825 826
      subobjects_dict = self.portal_trash.backupObject(trashbin, 
                                                container_path, object_id, 
                                                save=1, **kw)
827
    elif action == 'install':
828 829 830
      subobjects_dict = self.portal_trash.backupObject(trashbin, 
                                                container_path, object_id, 
                                                save=0, **kw)
831 832 833 834
    else:
      # As the list of available actions is not strictly defined,
      # prevent mistake if an action is not handled
      raise ValueError, 'Unknown action "%s"' % action
835
    return subobjects_dict
836

837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
  def beforeInstall(self):
    """
      Installation hook.
      Called right at the begining of "install" method.
      Can be overridden by subclasses.
    """
    pass

  def afterInstall(self):
    """
      Installation hook.
      Called right before returning in "install" method.
      Can be overridden by subclasses.
    """
    pass

  def onNewObject(self):
    """
      Installation hook.
      Called when installation process determined that object to install is
      new on current site (it's not replacing an existing object).
      Can be overridden by subclasses.
    """
    pass

862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
  def setSafeReindexationMode(self, context):
    """
      Postpone indexations after unindexations.
      This avoids alarming error messages about a single uid being used
      by "deleted" path and reindexed object. This can happen here for
      objects on which the uid was restored: previous object was deleted,
      hence the "deleted" path, and new object does have the same uid.
    """
    original_reindex_parameters = context.getPlacelessDefaultReindexParameters()
    if original_reindex_parameters is None:
      original_reindex_parameters = {}
    activate_kw = original_reindex_parameters.get('activate_kw', {}).copy()
    activate_kw['after_method_id'] = 'unindexObject'
    context.setPlacelessDefaultReindexParameters(activate_kw=activate_kw, \
                                                 **original_reindex_parameters)
    return original_reindex_parameters

879
  def install(self, context, trashbin, **kw):
880
    self.beforeInstall()
881 882
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
883
    if context.getTemplateFormatVersion() == 1:
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
      def recurse(hook, document, prefix=''):
        my_prefix = '%s/%s' % (prefix, document.id)
        if (hook(document, my_prefix)):
          for subdocument in document.objectValues():
            recurse(hook, subdocument, my_prefix)
      def saveHook(document, prefix):
        uid = getattr(document, 'uid', None)
        if uid is None:
          return 0
        else:
          saved_uid_dict[prefix] = uid
          return 1
      def restoreHook(document, prefix):
        uid = saved_uid_dict.get(prefix)
        if uid is None:
          return 0
        else:
          document.uid = uid
          return 1
903
      groups = {}
904
      old_groups = {}
905 906 907 908
      portal = context.getPortalObject()
      # sort to add objects before their subobjects
      keys = self._objects.keys()
      keys.sort()
909 910
      # set safe activities execution order
      original_reindex_parameters = self.setSafeReindexationMode(context)
911
      for path in keys:
912 913
        if update_dict.has_key(path) or force:
          # get action for the oject
Yoshinori Okuji's avatar
Yoshinori Okuji committed
914
          action = 'backup'
915 916 917 918 919
          if not force:
            action = update_dict[path]
            if action == 'nothing':
              continue
          # get subobjects in path
920 921 922
          path_list = path.split('/')
          container_path = path_list[:-1]
          object_id = path_list[-1]
923 924 925 926
          try:
            container = portal.unrestrictedTraverse(container_path)
          except KeyError:
            # parent object can be set to nothing, in this case just go on
927
            container_url = '/'.join(container_path)
928 929 930 931
            if update_dict.get(container_url) == 'nothing':
              continue
            # If container's container is portal_catalog,
            # then automatically create the container.
932
            elif len(container_path) > 1 and container_path[-2] == 'portal_catalog':
933 934
              # The id match, but better double check with the meta type
              # while avoiding the impact of systematic check
935
              container_container = portal.unrestrictedTraverse(container_path[:-1])
936 937 938 939 940
              if container_container.meta_type == 'ERP5 Catalog':
                container_container.manage_addProduct['ZSQLCatalog'].manage_addSQLCatalog(id=container_path[-1], title='')
                if len(container_container.objectIds()) == 1:
                  container_container.default_sql_catalog_id = container_path[-1]
                container = portal.unrestrictedTraverse(container_path)
941 942
            else:
              raise
943
          saved_uid_dict = {}
944
          subobjects_dict = {}
Julien Muchembled's avatar
Julien Muchembled committed
945
          portal_type_dict = {}
946
          # Object already exists
947 948
          old_obj = container._getOb(object_id, None)
          if old_obj is not None:
949
            recurse(saveHook, old_obj)
950
            if getattr(aq_base(old_obj), 'groups', None) is not None:
951 952 953 954 955
              # we must keep original order groups
              # from old form in case we keep some
              # old widget, thus we can readd them in
              # the right order group
              old_groups[path] = deepcopy(old_obj.groups)
956
            subobjects_dict = self._backupObject(action, trashbin,
957
                                                 container_path, object_id)
Julien Muchembled's avatar
Julien Muchembled committed
958
            # in case of portal types, we want to keep some properties
959
            if getattr(old_obj, 'meta_type', None) == 'ERP5 Base Type':
960
              for attr in ('allowed_content_types',
Julien Muchembled's avatar
Julien Muchembled committed
961 962 963 964 965 966
                           'hidden_content_type_list',
                           'property_sheet_list',
                           'base_category_list'):
                portal_type_dict[attr] = getattr(old_obj, attr, ())
              portal_type_dict['workflow_chain'] = \
                getChainByType(context)[1].get('chain_' + object_id, '')
967
            container.manage_delObjects([object_id])
968 969
          else:
            self.onNewObject()
970
          # install object
971
          obj = self._objects[path]
972 973 974
          if getattr(obj, 'meta_type', None) == 'Script (Python)':
            if getattr(obj, '_code') is None:
              obj._compile()
975
          if getattr(aq_base(obj), 'groups', None) is not None:
976
            # we must keep original order groups
977
            # because they change when we add subobjects
978
            groups[path] = deepcopy(obj.groups)
979
          # copy the object
980 981 982 983 984 985 986
          if (getattr(aq_base(obj), '_mt_index', None) is not None and
              obj._count() == 0):
            # some btrees were exported in a corrupted state. They're empty but
            # their metadata-index (._mt_index) contains entries which in
            # Zope 2.12 are used for .objectIds(), .objectValues() and
            # .objectItems(). In these cases, force the 
            LOG('Products.ERP5.Document.BusinessTemplate', WARNING,
Łukasz Nowak's avatar
Łukasz Nowak committed
987
                'Cleaning corrupted BTreeFolder2 object at %r.' % (path,))
988
            obj._initBTrees()
989
          obj = obj._getCopy(container)
990 991 992 993 994
          try:
            container._setObject(object_id, obj)
          except AttributeError:
            LOG("BT, install", 0, object_id)
            raise
995
          obj = container._getOb(object_id)
996 997
          # mark a business template installation so in 'PortalType_afterClone' scripts
          # we can implement logical for reseting or not attributes (i.e reference).
998
          self.REQUEST.set('is_business_template_installation', 1)
999 1000 1001 1002
          # We set isIndexable to 0 before calling
          # manage_afterClone in order to not call recursiveReindex, this is
          # useless because we will already reindex every created object, so
          # we avoid duplication of reindexation
1003
          obj.isIndexable = ConstantGetter('isIndexable', value=False)
1004
          obj.manage_afterClone(obj)
1005 1006 1007
          del obj.isIndexable
          if getattr(aq_base(obj), 'reindexObject', None) is not None:
            obj.reindexObject()
1008
          obj.wl_clearLocks()
Julien Muchembled's avatar
Julien Muchembled committed
1009
          if portal_type_dict:
1010
            # set workflow chain
Julien Muchembled's avatar
Julien Muchembled committed
1011
            wf_chain = portal_type_dict.pop('workflow_chain')
1012 1013
            chain_dict = getChainByType(context)[1]
            default_chain = ''
1014
            chain_dict['chain_%s' % (object_id)] = wf_chain
1015
            context.portal_workflow.manage_changeWorkflows(default_chain, props=chain_dict)
Julien Muchembled's avatar
Julien Muchembled committed
1016 1017
            # restore some other properties
            obj.__dict__.update(portal_type_dict)
1018
          # import sub objects if there is
Julien Muchembled's avatar
Julien Muchembled committed
1019
          if subobjects_dict:
1020
            # get a jar
1021 1022
            connection = obj._p_jar
            o = obj
1023
            while connection is None:
1024 1025
              o = o.aq_parent
              connection = o._p_jar
1026
            # import subobjects
1027
            for subobject_id, subobject_data in subobjects_dict.iteritems():
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
              try:
                if obj._getOb(subobject_id, None) is None:
                  subobject_data.seek(0)
                  subobject = connection.importFile(subobject_data)
                  obj._setObject(subobject_id, subobject)
              except AttributeError:
                # XXX this may happen when an object which can contain
                # sub-objects (e.g. ERP5 Form) has been replaced with
                # an object which cannot (e.g. External Method).
                LOG('BusinessTemplate', WARNING,
                    'could not restore %r in %r' % (subobject_id, obj))
1039
          if obj.meta_type in ('Z SQL Method',):
1040
            fixZSQLMethod(portal, obj)
1041 1042 1043
          # portal transforms specific initialization
          elif obj.meta_type in ('Transform', 'TransformsChain'):
            assert container.meta_type == 'Portal Transforms'
1044 1045 1046
            # skip transforms that couldn't have been initialized
            if obj.title != 'BROKEN':
              container._mapTransform(obj)
1047
          elif obj.meta_type in ('ERP5 Ram Cache',
1048
                                 'ERP5 Distributed Ram Cache',):
1049 1050
            assert container.meta_type == 'ERP5 Cache Factory'
            container.getParentValue().updateCache()
1051 1052
          elif (container.meta_type == 'CMF Skins Tool') and \
              (old_obj is not None):
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
            # Keep compatibility with previous export format of
            # business_template_registered_skin_selections
            # and do not modify exported value
            if obj.getProperty('business_template_registered_skin_selections', 
                               None) is None:
              # Keep previous value of register skin selection for skin folder
              skin_selection_list = old_obj.getProperty(
                  'business_template_registered_skin_selections', None)
              if skin_selection_list is not None:
                if isinstance(skin_selection_list, basestring):
                  skin_selection_list = skin_selection_list.split(' ')
                obj._setProperty(
                    'business_template_registered_skin_selections',
                    skin_selection_list, type='tokens')
1067

1068
          recurse(restoreHook, obj)
1069
      # now put original order group
1070 1071
      # we remove object not added in forms
      # we put old objects we have kept
1072
      for path, new_groups_dict in groups.iteritems():
1073 1074 1075 1076 1077 1078 1079 1080 1081
        if not old_groups.has_key(path):
          # installation of a new form
          obj = portal.unrestrictedTraverse(path)
          obj.groups = new_groups_dict
        else:
          # upgrade of a form
          old_groups_dict = old_groups[path]
          obj = portal.unrestrictedTraverse(path)
          # first check that all widgets are in new order
1082
          # excetp the one that had to be removed
1083 1084 1085 1086
          widget_id_list = obj.objectIds()
          for widget_id in widget_id_list:
            widget_path = path+'/'+widget_id
            if update_dict.has_key(widget_path) and update_dict[widget_path] in ('remove', 'save_and_remove'):
1087 1088
              continue
            widget_in_form = 0
1089 1090
            for group_id, group_value_list in new_groups_dict.iteritems():
              if widget_id in group_value_list:
1091 1092 1093 1094 1095 1096
                widget_in_form = 1
                break
            # if not, add it in the same groups
            # defined on the former form
            previous_group_id = None
            if not widget_in_form:
1097
              for old_group_id, old_group_values in old_groups_dict.iteritems():
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
                if widget_id in old_group_values:
                  previous_group_id = old_group_id
              # if we find same group in new one, add widget to it
              if previous_group_id is not None and new_groups_dict.has_key(previous_group_id):
                new_groups_dict[previous_group_id].append(widget_id)
              # otherwise use a specific group
              else:
                if new_groups_dict.has_key('not_assigned'):
                  new_groups_dict['not_assigned'].append(widget_id)
                else:
                  new_groups_dict['not_assigned'] = [widget_id,]
1109
                  obj.group_list = list(obj.group_list) + ['not_assigned']
1110
          # second check all widget_id in order are in form
1111 1112
          for group_id, group_value_list in new_groups_dict.iteritems():
            for widget_id in tuple(group_value_list):
1113 1114 1115
              if widget_id not in widget_id_list:
                # if we don't find the widget id in the form
                # remove it fro the group
1116
                group_value_list.remove(widget_id)
1117
          # now set new group object
1118
          obj.groups = new_groups_dict
1119
      # restore previous activities execution order
1120
      context.setPlacelessDefaultReindexParameters(**original_reindex_parameters)
1121
      # Do not forget to delete all remaining objects if asked by user
1122 1123 1124 1125 1126 1127
      # Fetch all sub objects path recursively
      recursive_path_list = []
      def fillRecursivePathList(from_path_list):
        for from_path in from_path_list:
          container = portal.unrestrictedTraverse(from_path, None)
          if container is not None:
1128 1129
            if from_path in recursive_path_list:
              continue
1130 1131 1132 1133 1134 1135
            recursive_path_list.append(from_path)
            # Check that container support iteration of sub_content_id
            if getattr(aq_base(container), 'objectIds', None) is not None:
              fillRecursivePathList(['%s/%s' % (from_path, sub_content_id) for\
                                        sub_content_id in container.objectIds()])
      fillRecursivePathList(self._objects.keys())
1136 1137 1138 1139
      for recursive_path in recursive_path_list:
        if recursive_path in update_dict:
          action = update_dict[recursive_path]
          if action in ('remove', 'save_and_remove'):
1140 1141 1142 1143
            document = portal.restrictedTraverse(recursive_path, None)
            if document is None:
              # It happens if the parent of target path is removed before
              continue
1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
            if getattr(aq_base(document), 'getParentValue', None) is not None:
              # regular ERP5 object
              parent = document.getParentValue()
            else:
              parent = document.aq_parent
            document_id = document.getId()
            container_path_list = recursive_path.split('/')[:-1]
            self._backupObject(action, trashbin, container_path_list,
                               document_id)
            parent.manage_delObjects([document_id])
Aurel's avatar
Aurel committed
1154
    else:
1155 1156
      # for old business template format
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
1157
      portal = context.getPortalObject()
1158
      for relative_url in self._archive.keys():
1159
        obj = self._archive[relative_url]
Aurel's avatar
Aurel committed
1160 1161 1162 1163
        container_path = relative_url.split('/')[0:-1]
        object_id = relative_url.split('/')[-1]
        container = portal.unrestrictedTraverse(container_path)
        container_ids = container.objectIds()
1164
        if object_id in container_ids:    # Object already exists
1165
          self._backupObject('backup', trashbin, container_path, object_id)
1166
          container.manage_delObjects([object_id])
Aurel's avatar
Aurel committed
1167
        # Set a hard link
1168 1169 1170 1171 1172 1173
        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',):
1174
          fixZSQLMethod(portal, obj)
1175
    self.afterInstall()
1176 1177 1178

  def uninstall(self, context, **kw):
    portal = context.getPortalObject()
1179
    trash = kw.get('trash', 0)
1180 1181 1182 1183 1184
    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
1185
      object_keys = self._archive.keys()
1186
    for relative_url in object_keys:
1187 1188
      container_path = relative_url.split('/')[0:-1]
      object_id = relative_url.split('/')[-1]
1189
      try:
1190
        container = portal.unrestrictedTraverse(container_path)
1191
        object = container._getOb(object_id) # We force access to the object to be sure
1192 1193
                                        # that appropriate exception is thrown
                                        # in case object is already backup and/or removed
1194
        if trash and trashbin is not None:
1195
          self.portal_trash.backupObject(trashbin, container_path, object_id, save=1, keep_subobjects=1)
1196
        if container.meta_type == 'CMF Skins Tool':
1197 1198
          # we are removing a skin folder, check and 
          # remove if registered skin selection
1199
          skin_folder = container[object_id]
1200 1201 1202
          unregisterSkinFolder(container, skin_folder,
              container.getSkinSelections())

1203
        container.manage_delObjects([object_id])
1204
        if container.aq_parent.meta_type == 'ERP5 Catalog' and not len(container):
1205
          # We are removing a ZSQLMethod, remove the SQLCatalog if empty
1206
          container.getParentValue().manage_delObjects([container.id])
1207
      except (NotFound, KeyError, BadRequest, AttributeError):
1208
        # object is already backup and/or removed
1209
        pass
1210 1211
    BaseTemplateItem.uninstall(self, context, **kw)

1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223
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

1224 1225 1226 1227 1228 1229 1230 1231 1232
  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()
1233 1234
    object_keys.sort()
    object_keys.reverse()
1235 1236
    p = context.getPortalObject()
    for path in object_keys:
1237 1238 1239 1240 1241
      try:
        path_list = self._resolvePath(p, [], path.split('/'))
      except AttributeError:
        # path seems to not exist anymore
        continue
1242 1243 1244
      path_list.sort()
      path_list.reverse()
      for relative_url in path_list:
1245
        try:
Aurel's avatar
Aurel committed
1246 1247
          container_path = relative_url.split('/')[0:-1]
          object_id = relative_url.split('/')[-1]
1248 1249
          container = portal.unrestrictedTraverse(container_path)
          if trash and trashbin is not None:
1250 1251
            self.portal_trash.backupObject(trashbin, container_path,
                                           object_id, save=1,
1252
                                           keep_subobjects=1)
1253 1254 1255 1256 1257 1258
          container.manage_delObjects([object_id])
        except (NotFound, KeyError):
          # object is already backup and/or removed
          pass
    BaseTemplateItem.uninstall(self, context, **kw)

1259 1260 1261
  def _resolvePath(self, folder, relative_url_list, id_list):
    """
      This method calls itself recursively.
1262

1263 1264 1265 1266 1267 1268 1269 1270 1271
      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.
1272 1273 1274
      obj = folder._getOb(id, None)
      if obj is None:
        raise AttributeError, "Could not resolve '%s' during business template processing." % id
1275
      return self._resolvePath(obj, relative_url_list + [id], id_list[1:])
1276 1277
    path_list = []
    for object_id in fnmatch.filter(folder.objectIds(), id):
1278
      if object_id != "":
1279
        path_list.extend(self._resolvePath(
1280
            folder._getOb(object_id),
1281
            relative_url_list + [object_id], id_list[1:]))
1282
    return path_list
Aurel's avatar
Aurel committed
1283

1284 1285 1286
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
Aurel's avatar
Aurel committed
1287
    keys = self._path_archive.keys()
1288
    keys.sort()
Aurel's avatar
Aurel committed
1289
    for path in keys:
1290 1291 1292
      include_subobjects = 0
      if '**' in path:
        include_subobjects = 1
1293
      for relative_url in self._resolvePath(p, [], path.split('/')):
1294 1295
        obj = p.unrestrictedTraverse(relative_url)
        obj = obj._getCopy(context)
1296
        obj = obj.__of__(context)
1297
        _recursiveRemoveUid(obj)
1298
        id_list = obj.objectIds()
1299
        obj = self.removeProperties(obj)
1300
        if hasattr(aq_base(obj), 'groups'):
1301
          # we must keep groups because it's ereased when we delete subobjects
1302
          groups = deepcopy(obj.groups)
1303
        if len(id_list) > 0:
1304 1305
          if include_subobjects:
            self.build_sub_objects(context, id_list, relative_url)
1306
          for id_ in list(id_list):
1307
            obj._delObject(id_)
1308
        if hasattr(aq_base(obj), 'groups'):
1309 1310 1311
          obj.groups = groups
        self._objects[relative_url] = obj
        obj.wl_clearLocks()
1312

Yoshinori Okuji's avatar
Yoshinori Okuji committed
1313 1314 1315 1316 1317 1318 1319 1320
class ToolTemplateItem(PathTemplateItem):
  """This class is used only for making a distinction between other objects
  and tools, because tools may not be backed up."""
  def _backupObject(self, action, trashbin, container_path, object_id, **kw):
    """Fake as if a trashbin is not available."""
    return PathTemplateItem._backupObject(self, action, None, container_path,
                                          object_id, **kw)

1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335
class PreferenceTemplateItem(PathTemplateItem):
  """
  This class is used to store preference objects
  """
  def _resolvePath(self, folder, relative_url_list, id_list):
    """
    This method calls itself recursively.

    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 relative_url_list != []:
      LOG("PreferenceTemplateItem, _resolvePath", WARNING,
          "Should be empty")
1336
    if len(id_list) != 1:
1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348
      LOG("PreferenceTemplateItem, _resolvePath", WARNING,
          "Should contain only one element")
    # XXX hardcoded
    return ['portal_preferences/%s' % id_list[0]]

  def install(self, context, trashbin, **kw):
    """
    Enable Preference
    """
    PathTemplateItem.install(self, context, trashbin, **kw)
    portal = context.getPortalObject()
    for object_path in self._objects.keys():
1349
      pref = portal.unrestrictedTraverse(object_path)
1350
      # XXX getPreferenceState is a bad name
1351
      if pref.getPreferenceState() == 'disabled':
1352 1353
        # set safe activities execution order
        original_reindex_parameters = self.setSafeReindexationMode(context)
1354 1355
        portal.portal_workflow.doActionFor(
                      pref,
1356 1357 1358
                      'enable_action',
                      comment="Initialized during Business Template " \
                              "installation.")
1359 1360
        # restore previous activities execution order
        context.setPlacelessDefaultReindexParameters(**original_reindex_parameters) 
1361

1362 1363
class CategoryTemplateItem(ObjectTemplateItem):

1364 1365
  def __init__(self, id_list, tool_id='portal_categories', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1366

1367 1368 1369 1370
  def build_sub_objects(self, context, id_list, url, **kw):
    p = context.getPortalObject()
    for id in id_list:
      relative_url = '/'.join([url,id])
1371 1372
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
1373
      obj = self.removeProperties(obj)
1374
      id_list = obj.objectIds()
1375
      if id_list:
1376
        self.build_sub_objects(context, id_list, relative_url)
1377
        for id_ in list(id_list):
1378
          obj._delObject(id_)
1379 1380
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
1381 1382 1383 1384 1385

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
1386 1387
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
1388
      _recursiveRemoveUid(obj)
1389
      obj = self.removeProperties(obj)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1390
      include_sub_categories = obj.__of__(context).getProperty('business_template_include_sub_categories', 0)
1391
      id_list = obj.objectIds()
1392 1393
      if len(id_list) > 0 and include_sub_categories:
        self.build_sub_objects(context, id_list, relative_url)
1394
        for id_ in list(id_list):
1395
          obj._delObject(id_)
1396
      else:
1397
        for id_ in list(id_list):
1398
          obj._delObject(id_)
1399 1400
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
1401

1402 1403 1404 1405 1406 1407 1408 1409
  def beforeInstall(self):
    self._installed_new_category = False

  def onNewObject(self):
    self._installed_new_category = True

  def afterInstall(self):
    if self._installed_new_category:
1410 1411
      # reset accessors if we installed a new category
      _aq_reset()
1412

1413 1414
class SkinTemplateItem(ObjectTemplateItem):

1415 1416
  def __init__(self, id_list, tool_id='portal_skins', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1417

1418 1419 1420 1421 1422 1423 1424 1425 1426 1427
  def build(self, context, **kw):
    ObjectTemplateItem.build(self, context, **kw)
    for relative_url in self._objects.keys():
      obj = self._objects[relative_url]
      if (getattr(obj, 'meta_type', None) == 'Folder') and \
        (obj.getProperty('business_template_registered_skin_selections', None) \
            is not None):
          obj._delProperty(
              'business_template_registered_skin_selections')

1428 1429
  def preinstall(self, context, installed_item, **kw):
    modified_object_list = ObjectTemplateItem.preinstall(self, context, installed_item, **kw)
1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442
    # We must install/update an ERP5 Form if one of its widget is modified.
    # This allow to keep the widget order and the form layout after an update
    #   from a BT to another one.
    for (bt_obj_path, bt_obj) in self._objects.items():
      if getattr(bt_obj, 'meta_type', None) == 'ERP5 Form':
        # search sub-objects of ERP5 Forms that are marked as "modified"
        for upd_obj_path in modified_object_list.keys():
          if upd_obj_path.startswith(bt_obj_path):
            # a child of the ERP5 Form must be updated, so the form too
            if not modified_object_list.has_key(bt_obj_path):
              modified_object_list.update({bt_obj_path: ['Modified', self.__class__.__name__[:-12]]})
    return modified_object_list

1443
  def install(self, context, trashbin, **kw):
1444
    ObjectTemplateItem.install(self, context, trashbin, **kw)
1445 1446
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1447
    p = context.getPortalObject()
1448
    skin_tool = p.portal_skins
1449 1450
    for relative_url in self._archive.keys():
      folder = p.unrestrictedTraverse(relative_url)
1451
      for obj in folder.objectValues(spec=('Z SQL Method',)):
1452 1453
        fixZSQLMethod(p, obj)

1454
      # Do not register skin which were explicitely ask not to be installed
1455 1456 1457 1458 1459
      if context.getTemplateFormatVersion() == 1:
        if update_dict.has_key(relative_url) or force:
          if not force:
            if update_dict[relative_url] == 'nothing':
              continue
1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475
      if folder.aq_parent.meta_type == 'CMF Skins Tool':
        registerSkinFolder(skin_tool, folder)

class RegisteredSkinSelectionTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    portal = context.getPortalObject()
    skin_tool = getToolByName(portal, 'portal_skins')

    for key in self._archive.keys():
      skin_folder_id, skin_selection_id = key.split(' | ')

      skin_folder = skin_tool[skin_folder_id]
      selection_list = skin_folder.getProperty(
          'business_template_registered_skin_selections',
          [])
1476 1477 1478
      # Backward compatibility, some values can be string
      if isinstance(selection_list, str):
        selection_list = selection_list.replace(',', ' ').split(' ')
1479
      if skin_selection_id in selection_list:
1480
        self._objects.setdefault(skin_folder_id, []).append(skin_selection_id)
1481
      else:
1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500
        raise NotFound, 'No skin selection %s found for skin folder %s.' \
                          % (skin_selection_id, skin_folder_id)

  # Function to generate XML Code Manually
  def generateXml(self, path=None):
    xml_data = '<registered_skin_selection>'
    keys = self._objects.keys()
    keys.sort()
    for key in keys:
      skin_selection_list = self._objects[key]
      xml_data += '\n <skin_folder_selection>'
      xml_data += '\n  <skin_folder>%s</skin_folder>' % key
      xml_data += '\n  <skin_selection>%s</skin_selection>' \
                      % ','.join(skin_selection_list)
      xml_data += '\n </skin_folder_selection>'
    xml_data += '\n</registered_skin_selection>'
    return xml_data

  def export(self, context, bta, **kw):
1501
    if not self._objects:
1502 1503 1504 1505
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
    # export workflow chain
1506 1507 1508
    bta.addObject(obj=self.generateXml(), 
                  name='registered_skin_selection',  
                  path=root_path)
1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523

  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    portal = context.getPortalObject()
    skin_tool = getToolByName(portal, 'portal_skins')

    for skin_folder_id in self._objects.keys():

      if update_dict.has_key(skin_folder_id) or force:
        if not force:
          action = update_dict[skin_folder_id]
          if action == 'nothing':
            continue
        skin_folder = skin_tool[skin_folder_id]
1524 1525 1526
        default_value = []
        selection_list = skin_folder.getProperty(
          'business_template_registered_skin_selections', default_value)
1527

1528
        if selection_list is default_value:
1529
          create_property = True
1530
          selection_list = self._objects[skin_folder_id]
1531
        else:
1532
          create_property = False
1533 1534 1535 1536 1537
          if isinstance(selection_list, basestring):
            selection_list = selection_list.replace(',', ' ').split(' ')
          elif isinstance(selection_list, tuple):
            selection_list = list(selection_list)
          selection_list.extend(self._objects[skin_folder_id])
1538 1539

        # Remove duplicate
1540
        selection_list = list(set(selection_list))
1541 1542 1543
        if create_property:
          skin_folder._setProperty(
              'business_template_registered_skin_selections',
1544
              selection_list, type='tokens')
1545 1546 1547
        else:
          skin_folder._updateProperty(
              'business_template_registered_skin_selections',
1548
              selection_list)
1549 1550

        unregisterSkinFolder(skin_tool, skin_folder,
1551
                             skin_tool.getSkinSelections())
1552
        registerSkinFolder(skin_tool, skin_folder)
1553 1554

  def uninstall(self, context, **kw):
1555 1556 1557
    portal = context.getPortalObject()
    skin_tool = getToolByName(portal, 'portal_skins')

1558 1559 1560 1561
    object_path = kw.get('object_path', None)
    if object_path is not None:
      object_keys = [object_path]
    else:
1562 1563 1564 1565
      object_keys = self._objects.keys()

    for skin_folder_id in object_keys:
      skin_folder = skin_tool[skin_folder_id]
1566
      current_selection_list = skin_folder.getProperty(
1567
        'business_template_registered_skin_selections', [])
1568
      current_selection_set = set(current_selection_list)
1569

1570 1571 1572
      skin_selection_list = workflow_id = self._objects[skin_folder_id]
      if isinstance(skin_selection_list, str):
        skin_selection_list = skin_selection_list.replace(',', ' ').split(' ')
1573 1574 1575 1576 1577
      for skin_selection in skin_selection_list:
        current_selection_set.remove(skin_selection)

      current_selection_list = list(current_selection_set)
      if current_selection_list:
1578
        skin_folder._updateProperty(
1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592
            'business_template_registered_skin_selections',
            current_selection_list)

        # Unregister skin folder from skin selection
        unregisterSkinFolder(skin_tool, skin_folder, skin_selection_list)
      else:
        delattr(skin_folder, 'business_template_registered_skin_selections')

        # Delete all skin selection
        for skin_selection in skin_selection_list:
          deleteSkinSelection(skin_tool, skin_selection)
        # Register to all other skin selection
        registerSkinFolder(skin_tool, skin_folder)

1593
  def preinstall(self, context, installed_item, **kw):
1594 1595 1596 1597 1598
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
      new_keys = self._objects.keys()
      new_dict = PersistentMapping()
      for path in new_keys:
1599
        if installed_item._objects.has_key(path):
1600 1601
          # compare object to see it there is changes
          new_object = self._objects[path]
1602
          old_object = installed_item._objects[path]
1603 1604 1605 1606 1607
          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
1608
      old_keys = installed_item._objects.keys()
1609 1610 1611 1612 1613 1614 1615
      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 _importFile(self, file_name, file):
    if not file_name.endswith('.xml'):
1616
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
1617 1618 1619 1620
      return
    # import workflow chain for portal_type
    skin_selection_dict = {}
    xml = parse(file)
1621 1622 1623
    for skin_folder_selection in xml.getroot():
      skin_folder_id = skin_folder_selection.find('skin_folder').text
      selection_string = skin_folder_selection.find('skin_selection').text
1624 1625
      if not selection_string:
        selection_list = []
1626
      else:
1627 1628
        selection_list = selection_string.split(',')
      skin_selection_dict[skin_folder_id] = selection_list
1629
    self._objects = skin_selection_dict
1630 1631


1632
class WorkflowTemplateItem(ObjectTemplateItem):
1633

1634 1635
  def __init__(self, id_list, tool_id='portal_workflow', **kw):
    return ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1636

1637 1638 1639 1640 1641
  # When the root object of a workflow is modified, the entire workflow is
  # recreated: all subobjects are discarded and must be reinstalled.
  # So we hide modified subobjects to the user and we always reinstall
  # (or remove) everything.

1642
  def preinstall(self, context, installed_item, installed_bt, **kw):
1643
    modified_object_dict = ObjectTemplateItem.preinstall(self, context,
1644
                                                         installed_item, **kw)
1645 1646 1647 1648
    modified_workflow_dict = {}
    for modified_object, state in modified_object_dict.iteritems():
      path = modified_object.split('/')
      if len(path) > 2:
1649
        modified_workflow_dict.setdefault('/'.join(path[:2]), ('Modified', state[1]))
1650 1651
      else:
        modified_workflow_dict[modified_object] = state
1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673
    removed_workflow_id_list = [x[0].split('/', 1)[1] \
                                for x in modified_workflow_dict.iteritems() \
                                if x[1][0] == 'Removed']
    if len(removed_workflow_id_list) > 0:
      installed_chain_list = [[y.strip() for y in x.split('|')] for x in \
                                installed_bt.getTemplatePortalTypeWorkflowChainList()]
      new_chain_list = [[y.strip() for y in x.split('|')] for x in \
                          context.getTemplatePortalTypeWorkflowChainList()]
      chain_dict = getChainByType(context)[1]
      for workflow_id in removed_workflow_id_list:
        affected_portal_type_set = set([x[0][6:] for x in \
                                        chain_dict.iteritems() \
                                        if workflow_id in \
                                        [y.strip() for y in x[1].split(',')]])
        safe_portal_type_set = set([x[0] for x in installed_chain_list \
                                    if x[1] == workflow_id]) - \
                               set([x[0] for x in new_chain_list \
                                    if x[1] == workflow_id])
        if affected_portal_type_set - safe_portal_type_set:
          value = modified_workflow_dict['portal_workflow/%s' % workflow_id]
          modified_workflow_dict['portal_workflow/%s' % workflow_id] = \
              ('Removed but used', value[1])
1674 1675
    return modified_workflow_dict

1676
  def install(self, context, trashbin, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1677
    if context.getTemplateFormatVersion() == 1:
1678 1679 1680
      portal = context.getPortalObject()
      update_dict = kw.get('object_to_update')
      force = kw.get('force')
Julien Muchembled's avatar
Julien Muchembled committed
1681 1682 1683
      # sort to add objects before their subobjects
      for path in sorted(self._objects):
          if force:
1684
            action = 'backup'
Julien Muchembled's avatar
Julien Muchembled committed
1685
          else:
1686
            action = update_dict.get('/'.join(path.split('/')[:2]))
Julien Muchembled's avatar
Julien Muchembled committed
1687 1688
            if action in (None, 'nothing'):
              continue
1689 1690 1691 1692 1693 1694
          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
1695
            container_url = '/'.join(container_path)
1696 1697 1698 1699 1700 1701
            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
1702
            self._backupObject(action, trashbin, container_path, object_id, keep_subobjects=1)
1703
            container.manage_delObjects([object_id])
1704
          obj = self._objects[path]
1705 1706 1707
          if getattr(obj, 'meta_type', None) == 'Script (Python)':
            if getattr(obj, '_code') is None:
              obj._compile()
1708 1709 1710 1711 1712
          obj = obj._getCopy(container)
          container._setObject(object_id, obj)
          obj = container._getOb(object_id)
          obj.manage_afterClone(obj)
          obj.wl_clearLocks()
1713 1714 1715
    else:
      ObjectTemplateItem.install(self, context, trashbin, **kw)

1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730
  def uninstall(self, context, **kw):
    object_path = kw.get('object_path', None)
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    removed_workflow_id_list = set([x.split('/', 1)[1] for x in object_keys])
    (default_chain, chain_dict) = getChainByType(context)
    for portal_type, workflow_ids in chain_dict.iteritems():
      workflow_ids = set([x.strip() for x in workflow_ids.split(',')]) - \
                     removed_workflow_id_list
      chain_dict[portal_type] = ', '.join(workflow_ids)
    context.portal_workflow.manage_changeWorkflows(default_chain,
                                                   props=chain_dict)
    ObjectTemplateItem.uninstall(self, context, **kw)
1731

1732 1733
class PortalTypeTemplateItem(ObjectTemplateItem):

1734 1735
  def __init__(self, id_list, tool_id='portal_types', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1736 1737
    # XXX : this statement can be removed once all bt5 have separated
    # workflow-chain information
1738 1739 1740
    self._workflow_chain_archive = PersistentMapping()

  def build(self, context, **kw):
1741 1742
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
1743 1744
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
1745 1746
      # obj is in ghost state and an attribute must be accessed
      # so that obj.__dict__ does not return an empty dict
1747
      obj.meta_type
1748
      for attr in obj.__dict__.keys():
1749 1750 1751 1752 1753 1754 1755 1756
        if attr == '_property_domain_dict':
          continue
        if attr[0] == '_' or attr in ('allowed_content_types',
                                      'hidden_content_type_list',
                                      'property_sheet_list',
                                      'base_category_list',
                                      'last_id', 'uid', 'workflow_history'):
          delattr(obj, attr)
1757 1758
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
1759

1760 1761
  # XXX : this method is kept temporarily, but can be removed once all bt5 are
  # re-exported with separated workflow-chain information
1762 1763 1764 1765
  def install(self, context, trashbin, **kw):
    ObjectTemplateItem.install(self, context, trashbin, **kw)
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1766 1767
    # We now need to setup the list of workflows corresponding to
    # each portal type
1768
    (default_chain, chain_dict) = getChainByType(context)
1769
    # Set the default chain to the empty string is probably the
1770
    # best solution, by default it is 'default_workflow', which is
1771
    # not very usefull
1772
    default_chain = ''
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1773
    if context.getTemplateFormatVersion() == 1:
1774
      object_list = self._objects
1775
    else:
1776 1777
      object_list = self._archive
    for path in object_list.keys():
1778 1779 1780 1781
      if update_dict.has_key(path) or force:
        if not force:
          action = update_dict[path]
          if action == 'nothing':
1782
            continue
1783 1784
        obj = object_list[path]
        portal_type = obj.id
1785 1786 1787
        if self._workflow_chain_archive.has_key(portal_type):
          chain_dict['chain_%s' % portal_type] = \
              self._workflow_chain_archive[portal_type]
1788 1789
    context.portal_workflow.manage_changeWorkflows(default_chain,
                                                   props=chain_dict)
1790

1791 1792
  # XXX : this method is kept temporarily, but can be removed once all bt5 are
  # re-exported with separated workflow-chain information
1793 1794 1795
  def _importFile(self, file_name, file):
    if 'workflow_chain_type.xml' in file_name:
      # import workflow chain for portal_type
1796
      result_dict = {}
1797
      xml = parse(file)
1798
      chain_list = xml.findall('//chain')
1799
      for chain in chain_list:
1800 1801 1802 1803
        portal_type = chain.find('type').text
        workflow = chain.find('workflow').text or ''
        result_dict[portal_type] = workflow
      self._workflow_chain_archive = result_dict
1804 1805 1806
    else:
      ObjectTemplateItem._importFile(self, file_name, file)

1807
class PortalTypeWorkflowChainTemplateItem(BaseTemplateItem):
1808

1809 1810 1811
  _chain_string_prefix = 'chain_'
  _chain_string_separator = ', '

1812
  def build(self, context, **kw):
1813 1814 1815 1816 1817
    # we can either specify nothing, +, - or = before the chain
    # this is used to know how to manage the chain
    # if nothing or +, chain is added to the existing one
    # if - chain is removed from the exisiting one
    # if = chain replaced the existing one
1818
    p = context.getPortalObject()
1819
    (default_chain, chain_dict) = getChainByType(context)
Aurel's avatar
Aurel committed
1820
    for key in self._archive.keys():
1821 1822 1823
      wflist = key.split(' | ')
      if len(wflist) == 2:
        portal_type = wflist[0]
Aurel's avatar
Aurel committed
1824
        workflow = wflist[1]
1825
      else:
1826
        # portal type with no workflow defined
Aurel's avatar
Aurel committed
1827
        portal_type = wflist[0][:-2]
1828
        workflow = ''
1829 1830 1831 1832 1833
      portal_type_key = '%s%s' % (self._chain_string_prefix, portal_type)
      if portal_type_key in chain_dict:
        workflow_name = workflow.lstrip('+-=')
        if workflow[0] != '-' and workflow_name not in \
           chain_dict[portal_type_key].split(self._chain_string_separator):
1834
          if not self.is_bt_for_diff:
1835 1836 1837
            # here, we use 'LOG' instead of 'raise', because it can
            # happen when a workflow is removed from the chain by
            # another business template.
Jérome Perrin's avatar
Jérome Perrin committed
1838
            LOG('BusinessTemplate', WARNING, 'workflow %s not found '\
1839
                       'in chain for portal_type %s' % (workflow_name, portal_type))
1840
        self._objects.setdefault(portal_type, []).append(workflow)
1841
      elif not self.is_bt_for_diff:
1842
        raise NotFound, 'No workflow chain found for portal type %s. This '\
Vincent Pelletier's avatar
Vincent Pelletier committed
1843
                        'is probably a sign of a missing dependency.'\
1844
                                                    % portal_type
1845

Christophe Dumez's avatar
Christophe Dumez committed
1846
  # Function to generate XML Code Manually
1847 1848
  def generateXml(self, path=None):
    xml_data = '<workflow_chain>'
1849 1850 1851
    key_list = self._objects.keys()
    key_list.sort()
    for key in key_list:
1852
      workflow_list = self._objects[key]
1853 1854
      xml_data += '\n <chain>'
      xml_data += '\n  <type>%s</type>' %(key,)
1855 1856
      xml_data += '\n  <workflow>%s</workflow>' %(
        self._chain_string_separator.join(sorted(workflow_list)))
1857 1858
      xml_data += '\n </chain>'
    xml_data += '\n</workflow_chain>'
1859 1860 1861
    return xml_data

  def export(self, context, bta, **kw):
1862
    if not self._objects:
1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874
      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
1875
    (default_chain, chain_dict) = getChainByType(context)
1876 1877 1878 1879
    # First convert all workflow_ids into list.
    for key in chain_dict:
      chain_dict.update({key: chain_dict[key].\
                                          split(self._chain_string_separator)})
1880
    # Set the default chain to the empty string is probably the
Christophe Dumez's avatar
Christophe Dumez committed
1881
    # best solution, by default it is 'default_workflow', which is
1882 1883
    # not very usefull
    default_chain = ''
1884 1885
    for path in self._objects:
      if path in update_dict or force:
1886 1887 1888
        if not force:
          action = update_dict[path]
          if action == 'nothing':
1889
            continue
Jérome Perrin's avatar
Jérome Perrin committed
1890 1891
        path_splitted = path.split('/', 1)
        # XXX: to avoid crashing when no portal_type
1892
        if not path_splitted:
Jérome Perrin's avatar
Jérome Perrin committed
1893
          continue
1894
        portal_type = path_splitted[-1]
1895 1896
        chain_key = '%s%s' % (self._chain_string_prefix, portal_type)
        if chain_key in chain_dict:
Aurel's avatar
Aurel committed
1897
          # XXX we don't use the chain (Default) in erp5 so don't keep it
1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917
          old_chain_list = [workflow_id for workflow_id in\
                            chain_dict[chain_key] if workflow_id not in\
                            ('(Default)', '',)]
          old_chain_workflow_id_set = set(old_chain_list)
          # get new workflow id list
          workflow_id_list = self._objects[path]
          for wf_id in workflow_id_list:
            if wf_id[0] == '-':
              # remove wf id if already present
              if wf_id[1:] in old_chain_workflow_id_set:
                old_chain_workflow_id_set.remove(wf_id[1:])
            elif wf_id[0] == '=':
              # replace existing chain by this one
              old_chain_workflow_id_set = set()
              old_chain_workflow_id_set.add(wf_id[1:])
            # then either '+' or nothing, add wf id to the list
            elif wf_id[0] == '+':
              old_chain_workflow_id_set.add(wf_id[1:])
            else:
              old_chain_workflow_id_set.add(wf_id)
1918
            # create the new chain
1919 1920
            chain_dict[chain_key] = list(old_chain_workflow_id_set)
          if not workflow_id_list:
Romain Courteaud's avatar
Romain Courteaud committed
1921 1922
            # Check if it has normally to remove a workflow chain, in order to
            # improve the error message
1923
            for wf_id in self._objects[path]:
Romain Courteaud's avatar
Romain Courteaud committed
1924 1925 1926
              if wf_id.startswith('-'):
                raise ValueError, '"%s" is not a workflow ID for %s' % \
                                  (wf_id, portal_type)
1927
            chain_dict[chain_key] = self._objects[path]
Aurel's avatar
Aurel committed
1928
        else:
1929 1930
          if portal_type not in context.portal_types.objectIds():
            raise ValueError('Cannot chain workflow %r to non existing '
1931 1932 1933 1934 1935 1936 1937 1938
                           'portal type %r' % (self._chain_string_separator\
                                                     .join(self._objects[path])
                                               , portal_type))
          chain_dict[chain_key] = self._objects[path]
    # convert workflow list into string only at the end.
    for key in chain_dict:
      chain_dict.update({key: self._chain_string_separator.\
                                                        join(chain_dict[key])})
1939 1940
    context.portal_workflow.manage_changeWorkflows(default_chain,
                                                   props=chain_dict)
1941

1942 1943
  def uninstall(self, context, **kw):
    (default_chain, chain_dict) = getChainByType(context)
1944 1945
    object_path = kw.get('object_path', None)
    if object_path is not None:
1946
      object_key_list = [object_path]
1947
    else:
1948 1949 1950
      object_key_list = self._objects.keys()
    for object_key in object_key_list:
      path_splitted = object_key.split('/', 1)
1951 1952 1953
      if len(path_splitted) < 2:
        continue
      portal_type = path_splitted[1]
1954 1955 1956 1957 1958 1959 1960 1961 1962 1963
      path = '%s%s' % (self._chain_string_prefix, portal_type)
      if path in chain_dict:
        workflow_id_list = chain_dict[path].\
                                            split(self._chain_string_separator)
        removed_workflow_id_list = self._objects[object_key]
        for workflow_id in removed_workflow_id_list:
          for i in range(workflow_id_list.count(workflow_id)):
            workflow_id_list.remove(workflow_id)
        if not workflow_id_list:
          del chain_dict[path]
1964
        else:
1965 1966 1967 1968
          chain_dict[path] = self._chain_string_separator.\
                                                  join(workflow_id_list)
    context.getPortalObject().portal_workflow.\
                                   manage_changeWorkflows('', props=chain_dict)
1969

1970
  def preinstall(self, context, installed_item, **kw):
1971 1972
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
1973
      new_key_list = self._objects.keys()
1974 1975
      new_dict = PersistentMapping()
      # Fix key from installed bt if necessary
1976
      for key, value in installed_item._objects.iteritems():
1977 1978
        if not 'portal_type_workflow_chain/' in key:
          key = 'portal_type_workflow_chain/%s' % (key)
1979
        new_dict[key] = value
1980
      if new_dict:
1981
        installed_item._objects = new_dict
1982 1983
      for path in new_key_list:
        if path in installed_item._objects:
1984 1985
          # compare object to see it there is changes
          new_object = self._objects[path]
1986
          old_object = installed_item._objects[path]
1987 1988 1989 1990 1991 1992
          if isinstance(new_object, str):
            new_object = new_object.split(self._chain_string_separator)
          if isinstance(old_object, str):
            old_object = old_object.split(self._chain_string_separator)
          new_object.sort()
          old_object.sort()
1993
          if new_object != old_object:
1994
            modified_object_list.update({path : ['Modified', self.getTemplateTypeName()]})
1995
        else: # new object
1996
          modified_object_list.update({path : ['New', self.getTemplateTypeName()]})
1997
      # get removed object
1998 1999 2000
      for path in installed_item._objects:
        if path not in new_key_list:
          modified_object_list.update({path : ['Removed', self.getTemplateTypeName()]})
2001 2002
    return modified_object_list

2003
  def _importFile(self, file_name, file):
2004
    if not file_name.endswith('.xml'):
2005
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
2006
      return
2007
    # import workflow chain for portal_type
2008
    result_dict = {}
2009
    xml = parse(file)
2010
    chain_list = xml.findall('chain')
2011
    for chain in chain_list:
2012
      portal_type = chain.find('type').text
2013
      workflow_chain = chain.find('workflow').text or ''
2014
      if 'portal_type_workflow_chain/' not in portal_type:
2015 2016 2017 2018
        key = 'portal_type_workflow_chain/%s' % (portal_type,)
      else:
        key = portal_type
      result_dict[key] = workflow_chain.split(self._chain_string_separator)
2019
    self._objects = result_dict
2020

2021 2022
# just for backward compatibility
PortalTypeTemplateWorkflowChainItem = PortalTypeWorkflowChainTemplateItem
2023

2024
class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
2025 2026 2027
  # XXX This class is subclassed for hidden types, propertysheets, base
  # categories ...
  name = 'Allowed Content Type'
2028 2029
  xml_tag = 'allowed_content_type_list'
  class_property = 'allowed_content_types'
2030
  business_template_class_property = '_portal_type_allowed_content_type_item'
2031 2032

  def build(self, context, **kw):
2033
    types_tool = getToolByName(self.getPortalObject(), 'portal_types')
2034
    types_list = list(types_tool.objectIds())
2035
    for key in self._archive.keys():
2036 2037 2038 2039
      try:
        portal_type, allowed_type = key.split(' | ')
      except ValueError:
        raise ValueError('Invalid item %r in %s' % (key, self.name))
2040 2041 2042 2043 2044
      # check properties corresponds to what is defined in site
      if not portal_type in types_list:
        raise ValueError, "Portal Type %s not found in site" %(portal_type,)
      ob = types_tool._getOb(portal_type)
      prop_value = getattr(ob, self.class_property, ())
2045
      if not allowed_type in prop_value and not self.is_bt_for_diff:
Jérome Perrin's avatar
Jérome Perrin committed
2046
        raise ValueError, "%s %s not found in portal type %s" % (
2047 2048
                             getattr(self, 'name', self.__class__.__name__),
                             allowed_type, portal_type)
2049 2050
      if self.class_property not in portal_type:
        key = '%s/%s' % (self.class_property, portal_type)
2051
      else:
2052 2053
        key = portal_type
      self._objects.setdefault(key, []).append(allowed_type)
2054

Christophe Dumez's avatar
Christophe Dumez committed
2055
  # Function to generate XML Code Manually
2056 2057
  def generateXml(self, path=None):
    xml_data = '<%s>' %(self.xml_tag,)
2058 2059 2060
    key_list = self._objects.keys()
    key_list.sort()
    for key in key_list:
2061
      id_value = key.replace('%s/' % self.class_property, '')
2062
      allowed_item_list = sorted(self._objects[key])
2063
      xml_data += '\n <portal_type id="%s">' % (id_value)
2064
      for allowed_item in allowed_item_list:
2065 2066 2067
        xml_data += '\n  <item>%s</item>' %(allowed_item,)
      xml_data += '\n </portal_type>'
    xml_data += '\n</%s>' %(self.xml_tag,)
2068 2069 2070
    return xml_data

  def export(self, context, bta, **kw):
2071
    if not self._objects:
2072 2073 2074
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
2075
    path = os.sep.join((self.__class__.__name__, self.class_property,))
2076 2077 2078
    xml_data = self.generateXml(path=None)
    bta.addObject(obj=xml_data, name=path, path=None)

2079
  def preinstall(self, context, installed_item, **kw):
2080 2081 2082
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
      portal = context.getPortalObject()
2083
      new_key_list = self._objects.keys()
2084 2085
      new_dict = PersistentMapping()
      # fix key if necessary in installed bt for diff
Nicolas Delaby's avatar
Nicolas Delaby committed
2086
      for key, value in installed_item._objects.iteritems():
2087
        if self.class_property not in key:
2088
          key = '%s/%s' % (self.class_property, key)
Nicolas Delaby's avatar
Nicolas Delaby committed
2089
        new_dict[key] = value
2090
      if new_dict:
2091
        installed_item._objects = new_dict
2092 2093
      for path in new_key_list:
        if path in installed_item._objects:
2094 2095
          # compare object to see it there is changes
          new_object = self._objects[path]
2096
          old_object = installed_item._objects[path]
2097 2098
          new_object.sort()
          old_object.sort()
2099
          if new_object != old_object:
2100
            modified_object_list.update({path : ['Modified', self.getTemplateTypeName()]})
2101
        else: # new object
2102
          modified_object_list.update({path : ['New', self.getTemplateTypeName()]})
2103
      # get removed object
2104 2105 2106
      for path in installed_item._objects:
        if path not in new_key_list:
          modified_object_list.update({path : ['Removed', self.getTemplateTypeName()]})
2107 2108
    return modified_object_list

2109
  def _importFile(self, file_name, file):
2110
    if not file_name.endswith('.xml'):
2111
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
2112
      return
2113
    path, name = posixpath.split(file_name)
2114
    xml = parse(file)
2115
    portal_type_list = xml.findall('portal_type')
2116
    for portal_type in portal_type_list:
2117 2118
      id = portal_type.get('id')
      item_type_list = [item.text for item in portal_type.findall('item')]
Nicolas Delaby's avatar
typo  
Nicolas Delaby committed
2119
      if self.class_property not in id:
2120 2121 2122 2123
        key = '%s/%s' % (self.class_property, id,)
      else:
        key = id
      self._objects[key] = item_type_list
2124 2125

  def install(self, context, trashbin, **kw):
Nicolas Delaby's avatar
Nicolas Delaby committed
2126 2127
    portal = context.getPortalObject()
    types_tool = getToolByName(portal, 'portal_types')
2128 2129
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2130 2131 2132 2133 2134 2135 2136
    installed_bt = kw.get('installed_bt')
    if installed_bt is not None:
      old_objects = getattr(installed_bt,
                            self.business_template_class_property)._objects
    else:
      old_objects = {}
    for key in set(self._objects.keys()).union(set(old_objects.keys())):
Nicolas Delaby's avatar
Nicolas Delaby committed
2137
      if key in update_dict or force:
2138 2139 2140 2141
        if not force:
          action = update_dict[key]
          if action == 'nothing':
            continue
2142 2143
        try:
          portal_id = key.split('/')[-1]
Nicolas Delaby's avatar
Nicolas Delaby committed
2144
          portal_type = types_tool._getOb(portal_id)
2145
        except (AttributeError, KeyError):
2146 2147
          raise AttributeError, "Portal type '%s' not found while " \
              "installing %s" % (portal_id, self.getTitle())
2148 2149
        property_list = self._objects.get(key, [])
        old_property_list = old_objects.get(key, ())
2150
        object_property_list = getattr(portal_type, self.class_property, ())
Nicolas Delaby's avatar
Nicolas Delaby committed
2151 2152 2153 2154 2155 2156 2157 2158 2159
        # merge differences between portal types properties
        # for example:
        # * current value : [A,B,C]
        # * in new BT : [A,D]
        # * in old BT : [A,B]
        # -> [A,D,C] i.e. C is merged but B is not merged
        for id in object_property_list:
          if id not in property_list and id not in old_property_list:
            property_list.append(id)
2160
        setattr(portal_type, self.class_property, tuple(property_list))
2161

Aurel's avatar
Aurel committed
2162
  def uninstall(self, context, **kw):
2163
    object_path = kw.get('object_path', None)
Nicolas Delaby's avatar
Nicolas Delaby committed
2164 2165
    portal = context.getPortalObject()
    types_tool = getToolByName(portal, 'portal_types')
2166
    if object_path is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
2167
      object_key_list = [object_path]
2168
    else:
Nicolas Delaby's avatar
Nicolas Delaby committed
2169 2170
      object_key_list = self._objects.keys()
    for key in object_key_list:
2171 2172
      try:
        portal_id = key.split('/')[-1]
Nicolas Delaby's avatar
Nicolas Delaby committed
2173
        portal_type = types_tool._getOb(portal_id)
2174
      except (AttributeError,  KeyError):
2175 2176 2177
        LOG("portal types not found : ", 100, portal_id)
        continue
      property_list = self._objects[key]
2178 2179
      original_property_list = list(getattr(portal_type,
                                    self.class_property, ()))
Aurel's avatar
Aurel committed
2180 2181
      for id in property_list:
        if id in original_property_list:
2182
          original_property_list.remove(id)
2183
      setattr(portal_type, self.class_property, tuple(original_property_list))
2184

2185

2186 2187
class PortalTypeHiddenContentTypeTemplateItem(PortalTypeAllowedContentTypeTemplateItem):

2188
  name = 'Hidden Content Type'
2189 2190
  xml_tag = 'hidden_content_type_list'
  class_property = 'hidden_content_type_list'
2191
  business_template_class_property = '_portal_type_hidden_content_type_item'
2192

2193

2194 2195
class PortalTypePropertySheetTemplateItem(PortalTypeAllowedContentTypeTemplateItem):

2196
  name = 'Property Sheet'
2197 2198
  xml_tag = 'property_sheet_list'
  class_property = 'property_sheet_list'
2199
  business_template_class_property = '_portal_type_property_sheet_item'
2200

2201

2202 2203
class PortalTypeBaseCategoryTemplateItem(PortalTypeAllowedContentTypeTemplateItem):

2204
  name = 'Base Category'
2205 2206
  xml_tag = 'base_category_list'
  class_property = 'base_category_list'
2207
  business_template_class_property = '_portal_type_base_category_item'
2208

2209

2210
class CatalogMethodTemplateItem(ObjectTemplateItem):
2211
  """Template Item for catalog methods.
2212

2213 2214 2215 2216 2217
    This template item stores catalog method and install them in the
    default catalog.
    The use Catalog makes for methods is saved as well and recreated on
    installation.
  """
2218

2219 2220
  def __init__(self, id_list, tool_id='portal_catalog', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
2221
    # a mapping to store properties of methods.
2222 2223 2224 2225 2226
    # the mapping contains an entry for each method, and this entry is
    # another mapping having the id of the catalog property as key and a
    # boolean value to say wether the method is part of this catalog
    # configuration property.
    self._method_properties = PersistentMapping()
2227

2228
    self._is_filtered_archive = PersistentMapping()
2229 2230
    for method in catalog_method_filter_list:
      setattr(self, method, PersistentMapping())
2231

2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245
  def _extractMethodProperties(self, catalog, method_id):
    """Extracts properties for a given method in the catalog.
    Returns a mapping of property name -> boolean """
    method_properties = PersistentMapping()
    for prop in catalog._properties:
      if prop.get('select_variable') == 'getCatalogMethodIds':
        if prop['type'] == 'selection' and \
            getattr(catalog, prop['id']) == method_id:
          method_properties[prop['id']] = 1
        elif prop['type'] == 'multiple selection' and \
            method_id in getattr(catalog, prop['id']):
          method_properties[prop['id']] = 1
    return method_properties

2246 2247
  def build(self, context, **kw):
    ObjectTemplateItem.build(self, context, **kw)
2248

2249
    catalog = _getCatalogValue(self)
2250
    if catalog is None:
2251
      LOG('BusinessTemplate build', 0, 'catalog not found')
2252
      return
2253

2254 2255 2256
    # upgrade old
    if not hasattr(self, '_method_properties'):
      self._method_properties = PersistentMapping()
2257

2258 2259
    for obj in self._objects.values():
      method_id = obj.id
2260 2261
      self._method_properties[method_id] = self._extractMethodProperties(
                                                          catalog, method_id)
2262 2263 2264 2265 2266 2267
      filter = catalog.filter_dict.get(method_id, {})
      self._is_filtered_archive[method_id] = filter.get('filtered', 0)
      for method in catalog_method_filter_list:
        property = method[8:-8]
        if property in filter:
          getattr(self, method)[method_id] = filter[property]
2268

2269 2270 2271 2272 2273
  def generateXml(self, path):
    obj = self._objects[path]
    method_id = obj.id
    xml_data = '<catalog_method>'
    if self._method_properties.has_key(method_id):
2274
      for method_property, value in self._method_properties[method_id].items():
2275 2276 2277
        xml_data += '\n <item key="%s" type="int">' %(method_property,)
        xml_data += '\n  <value>%s</value>' %(value,)
        xml_data += '\n </item>'
2278

2279
      if self._is_filtered_archive.get(method_id):
2280 2281 2282
          xml_data += '\n <item key="_is_filtered_archive" type="int">'
          xml_data += '\n  <value>1</value>'
          xml_data += '\n </item>'
2283 2284
          for method in catalog_method_filter_list:
            if method != '_filter_expression_instance_archive':
2285 2286
              value = getattr(self, method, {}).get(method_id)
              if isinstance(value, basestring):
2287 2288 2289
                xml_data += '\n <item key="%s" type="str">' %(method,)
                xml_data += '\n  <value>%s</value>' %(str(value))
                xml_data += '\n </item>'
2290
              elif value:
2291
                xml_data += '\n <item key="%s" type="tuple">'%(method)
2292
                for item in value:
2293 2294
                  xml_data += '\n  <value>%s</value>' %(str(item))
                xml_data += '\n </item>'
2295
    xml_data += '\n</catalog_method>\n'
Christophe Dumez's avatar
Christophe Dumez committed
2296
    return xml_data
2297

2298 2299 2300
  def preinstall(self, context, installed_item, **kw):
    modified_object_dict = ObjectTemplateItem.preinstall(self, context, installed_item, **kw)
    modified_object_dict.update(BaseTemplateItem.preinstall(self, context, installed_item, **kw))
2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326
    return modified_object_dict

  def export(self, context, bta, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate, export', 0, 'no SQL catalog was available')
      return

    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    for key in self._objects.keys():
      obj = self._objects[key]
      # create folder and subfolders
      folders, id = posixpath.split(key)
      path = os.path.join(root_path, folders)
      bta.addFolder(name=path)
      # export object in xml
      f=StringIO()
      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
      bta.addObject(obj=f.getvalue(), name=id, path=path)
      # add all datas specific to catalog inside one file
      key_name = os.path.join(obj.id+'.catalog_keys')
      xml_data = self.generateXml(key)
      bta.addObject(obj=xml_data, name=key_name, path=path)

2327 2328
  def install(self, context, trashbin, **kw):
    ObjectTemplateItem.install(self, context, trashbin, **kw)
2329
    catalog = _getCatalogValue(self)
2330 2331 2332 2333 2334 2335 2336
    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)
2337

2338 2339 2340
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    values = []
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2341
    new_bt_format = context.getTemplateFormatVersion()
2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357

    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':
2358
              continue
2359 2360 2361 2362
          if new_bt_format:
            values.append(self._objects[key])
          else:
            values.append(self._archive[key])
2363

2364 2365
    for obj in values:
      method_id = obj.id
2366

2367 2368 2369 2370 2371 2372
      # Restore catalog properties for methods
      if hasattr(self, '_method_properties'):
        for key in self._method_properties.get(method_id, {}).keys():
          old_value = getattr(catalog, key, None)
          if isinstance(old_value, str):
            setattr(catalog, key, method_id)
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
2373
          elif isinstance(old_value, (list, tuple)):
2374 2375 2376 2377
            if method_id not in old_value:
              new_value = list(old_value) + [method_id]
              new_value.sort()
              setattr(catalog, key, tuple(new_value))
2378

2379 2380
      # Restore filter
      if self._is_filtered_archive.get(method_id, 0):
2381
        expression = self._filter_expression_archive[method_id]
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2382
        if context.getTemplateFormatVersion() == 1:
2383 2384 2385 2386 2387
          if expression and expression.strip():
            # only compile non-empty expressions
            expr_instance = Expression(expression)
          else:
            expr_instance = None
2388 2389
        else:
          expr_instance = self._filter_expression_instance_archive[method_id]
2390 2391 2392
        catalog.filter_dict[method_id] = PersistentMapping()
        catalog.filter_dict[method_id]['filtered'] = 1
        catalog.filter_dict[method_id]['expression'] = expression
2393
        catalog.filter_dict[method_id]['expression_instance'] = expr_instance
2394 2395 2396 2397
        catalog.filter_dict[method_id]['expression_cache_key'] = \
          self._filter_expression_cache_key_archive.get(method_id, ())
        catalog.filter_dict[method_id]['type'] = \
          self._filter_type_archive.get(method_id, ())
2398
      elif method_id in catalog.filter_dict.keys():
2399
        catalog.filter_dict[method_id]['filtered'] = 0
2400

2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426
      # backward compatibility
      if hasattr(self, '_is_catalog_list_method_archive'):
        LOG("BusinessTemplate.CatalogMethodTemplateItem", 0,
            "installing old style catalog method configuration")
        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])

        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)

        if is_uncatalog_method and method_id not in sql_uncatalog_object:
          sql_uncatalog_object.append(method_id)
        elif not is_uncatalog_method and method_id in sql_uncatalog_object:
          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)
2427

2428 2429 2430 2431 2432 2433
        sql_catalog_object_list.sort()
        catalog.sql_catalog_object_list = tuple(sql_catalog_object_list)
        sql_uncatalog_object.sort()
        catalog.sql_uncatalog_object = tuple(sql_uncatalog_object)
        sql_clear_catalog.sort()
        catalog.sql_clear_catalog = tuple(sql_clear_catalog)
2434

2435
  def uninstall(self, context, **kw):
2436
    catalog = _getCatalogValue(self)
2437 2438 2439
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2440

2441 2442 2443 2444
    values = []
    object_path = kw.get('object_path', None)
    # get required values
    if object_path is None:
2445 2446 2447 2448
      if context.getTemplateFormatVersion() == 1:
        values = self._objects.values()
      else:
        values = self._archive.values()
2449
    else:
2450
      try:
2451 2452 2453 2454
        if context.getTemplateFormatVersion() == 1:
          value = self._objects[object_path]
        else:
          value = self._archive[object_path]
2455 2456
      except KeyError:
        value = None
2457 2458
      if value is not None:
        values.append(value)
2459
    for obj in values:
2460
      method_id = obj.id
2461 2462 2463 2464 2465 2466 2467 2468 2469
      # remove method references in portal_catalog
      for catalog_prop in catalog._properties:
        if catalog_prop.get('select_variable') == 'getCatalogMethodIds'\
            and catalog_prop['type'] == 'multiple selection':
          old_value = getattr(catalog, catalog_prop['id'], ())
          if method_id in old_value:
            new_value = list(old_value)
            new_value.remove(method_id)
            setattr(catalog, catalog_prop['id'], new_value)
2470

Yoshinori Okuji's avatar
Yoshinori Okuji committed
2471
      if catalog.filter_dict.has_key(method_id):
2472
        del catalog.filter_dict[method_id]
2473

2474
    # uninstall objects
2475
    ObjectTemplateItem.uninstall(self, context, **kw)
2476

2477
  def _importFile(self, file_name, file):
2478
    if file_name.endswith('.catalog_keys.xml'):
2479
      # recreate data mapping specific to catalog method
2480 2481
      name = os.path.basename(file_name)
      id = name.split('.', 1)[0]
2482
      xml = parse(file)
2483
      method_list = xml.findall('item')
2484
      for method in method_list:
2485 2486 2487
        key = method.get('key')
        key_type = method.get('type')
        value_node = method.find('value')
2488
        if key_type == "str":
2489
          value = value_node.text or ''
2490
        elif key_type == "int":
2491
          value = int(value_node.text)
2492
        elif key_type == "tuple":
2493
          value = tuple([value_node.text for value_node in method.findall('value')])
2494
        else:
2495
          LOG('BusinessTemplate import CatalogMethod, type unknown', 0, key_type)
2496
          continue
2497
        if key in catalog_method_list or key in catalog_method_filter_list:
2498
          getattr(self, key)[id] = value
2499 2500 2501
        else:
          # new style key
          self._method_properties.setdefault(id, PersistentMapping())[key] = 1
2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513
    elif file_name.endswith('.xml'):
      # 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.removeProperties(obj)
      self._objects[file_name[:-4]] = obj
    else:
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
2514

2515
class ActionTemplateItem(ObjectTemplateItem):
2516 2517

  def __init__(self, id_list, **kw):
2518
    # XXX It's look like ObjectTemplateItem __init__
2519 2520 2521 2522 2523 2524
    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

2525 2526
  def _splitPath(self, path):
    """
Leonardo Rochael Almeida's avatar
typo  
Leonardo Rochael Almeida committed
2527
      Split path tries to split a complex path such as:
2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545

      "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

2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583
  def _getPortalTypeActionCopy(self, obj, value):
    id_id = 'reference'
    for action in obj.getActionInformationList():
      if getattr(action, id_id, None) == value:
        return obj._exportOldAction(action)

  def _getPortalToolActionCopy(self, obj, context, value):
    try:
      from Products.CMFCore.interfaces import IActionProvider
    except ImportError:
      # BACK:
      # we still don't load ZCML on tests on 2.8, but on 2.8 actions from other
      # tools are not redirected to portal_actions
      pass
    else:
      if not IActionProvider.providedBy(obj):
        # look for the action in portal_actions, instead of the original object
        LOG('Products.ERP5.Document.BusinessTemplate', WARNING,
            'Redirected action export',
            'Attempted to retrieve action %r from %r which is no longer an '
            'IActionProvided. Retrieving action from portal_actions instead' %
            (value, obj.getId()))
        obj = context.getPortalObject().portal_actions
    id_id = 'id'
    for action in obj.listActions():
      if getattr(action, id_id, None) == value:
        return action._getCopy(context)

  def _getActionCopy(self, obj, context, value):
    """
    Gets action copy from action provider given the action id or reference
    """
    # Several tools still use CMF actions
    if obj.getParentId() == 'portal_types':
      return self._getPortalTypeActionCopy(obj, value)
    else:
      return self._getPortalToolActionCopy(obj, context, value)

2584 2585 2586 2587
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
2588 2589 2590
      url, value = id.split(' | ')
      url = posixpath.split(url)
      obj = p.unrestrictedTraverse(url)
2591 2592
      action = self._getActionCopy(obj, context, value)
      if action is None:
2593 2594 2595
        if self.is_bt_for_diff:
          continue
        raise NotFound('Action %r not found' % id)
2596
      key = posixpath.join(url[-2], url[-1], value)
2597
      self._objects[key] = self.removeProperties(action)
2598
      self._objects[key].wl_clearLocks()
Aurel's avatar
Aurel committed
2599

2600 2601 2602
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2603
    if context.getTemplateFormatVersion() == 1:
2604
      portal_type_dict = {}
Aurel's avatar
Aurel committed
2605 2606
      p = context.getPortalObject()
      for id in self._objects.keys():
2607 2608 2609 2610 2611
        if update_dict.has_key(id) or force:
          if not force:
            action = update_dict[id]
            if action == 'nothing':
              continue
2612 2613 2614
          obj = self._objects[id]
          path, id = id.rsplit('/', 1)
          container = p.unrestrictedTraverse(path)
2615

2616
          if container.getParentId() == 'portal_types':
2617 2618 2619
            # XXX future BT should use 'reference' instead of 'id'
            reference = getattr(obj, 'reference', None) or obj.id
            portal_type_dict.setdefault(path, {})[reference] = obj
2620 2621
            continue

Julien Muchembled's avatar
Julien Muchembled committed
2622 2623
          # Following code is for actions outside Types Tool.
          # It will be removed when they are also converted to ERP5 actions.
2624 2625 2626
          try:
            from Products.CMFCore.interfaces import IActionProvider
          except ImportError:
2627
              # BACK:
2628 2629 2630 2631 2632 2633 2634 2635
              # we still don't load ZCML on tests on 2.8, but on 2.8 we don't
              # need to redirect actions to portal_actions.
              pass
          else:
            if not IActionProvider.providedBy(container):
              # some tools stopped being ActionProviders in CMF 2.x. Drop the
              # action into portal_actions.
              LOG('Products.ERP5.Document.BusinessTemplate', WARNING,
2636
                  'Redirected action import',
2637 2638 2639 2640
                  'Attempted to store action %r in %r which is no longer an '
                  'IActionProvided. Storing action on portal_actions instead' %
                  (id, path))
              container = p.portal_actions
2641
          obj, action = container, obj
2642
          action_list = obj.listActions()
2643
          for index in range(len(action_list)):
2644
            if action_list[index].id == id:
2645
              # remove previous action
2646
              obj.deleteActions(selections=(index,))
2647 2648 2649
          action_text = action.action
          if isinstance(action_text, Expression):
            action_text = action_text.text
2650
          obj.addAction(
2651 2652
                        id = action.id
                      , name = action.title
2653
                      , action = action_text
2654 2655 2656 2657
                      , condition = action.getCondition()
                      , permission = action.permissions
                      , category = action.category
                      , visible = action.visible
2658 2659
                      , icon = getattr(action, 'icon', None)\
                                and action.icon.text or ''
2660
                      , priority = action.priority
2661
                      , description = action.description
Aurel's avatar
Aurel committed
2662
                    )
2663
          # sort action based on the priority define on it
2664 2665 2666 2667 2668 2669 2670 2671 2672
          # 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))
2673 2674
      for path, action_dict in portal_type_dict.iteritems():
        container = p.unrestrictedTraverse(path)
2675
        container.manage_delObjects([obj.id
2676
          for obj in container.getActionInformationList()
2677
          if obj.getReference() in action_dict])
2678 2679
        for obj in action_dict.itervalues():
          container._importOldAction(obj)
Aurel's avatar
Aurel committed
2680
    else:
2681
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
2682
      p = context.getPortalObject()
2683 2684
      for id in self._archive.keys():
        action = self._archive[id]
Aurel's avatar
Aurel committed
2685
        relative_url, key, value = self._splitPath(id)
2686 2687
        obj = p.unrestrictedTraverse(relative_url)
        for ai in obj.listActions():
Aurel's avatar
Aurel committed
2688
          if getattr(ai, key) == value:
2689
            raise TemplateConflictError, 'the portal type %s already has the action %s' % (obj.id, value)
2690 2691 2692
        action_text = action.action
        if isinstance(action_text, Expression):
          action_text = action_text.text
2693
        obj.addAction(
Aurel's avatar
Aurel committed
2694 2695
                      id = action.id
                    , name = action.title
2696
                    , action = action_text
Aurel's avatar
Aurel committed
2697 2698 2699 2700
                    , condition = action.getCondition()
                    , permission = action.permissions
                    , category = action.category
                    , visible = action.visible
2701 2702
                    , icon = getattr(action, 'icon', None) \
                                      and action.icon.text or ''
Aurel's avatar
Aurel committed
2703
                    )
Aurel's avatar
Aurel committed
2704 2705 2706 2707 2708 2709 2710
        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))
2711 2712
          obj.moveDownActions(selections=tuple(move_down_list))

2713 2714
  def uninstall(self, context, **kw):
    p = context.getPortalObject()
2715 2716
    object_path = kw.get("object_path", None)
    if object_path is not None:
2717 2718 2719 2720 2721 2722 2723 2724
      if '/' in object_path:
        # here object_path is the path of the actions, something like
        # portal_type/Person/view
        ti, action_id = object_path.rsplit('/', 1)
        keys = ['%s | %s' % (ti, action_id)]
      else:
        # compatibility ?
        keys = [object_path]
2725
    else:
2726
      keys = self._archive.keys()
2727
    for id in keys:
2728 2729
      if  '|' in id:
        relative_url, value = id.split(' | ')
2730 2731 2732 2733 2734
        obj = p.unrestrictedTraverse(relative_url, None)
        # Several tools still use CMF actions
        if obj is not None:
          is_new_action = obj.getParentId() == 'portal_types'
          key = is_new_action and 'reference' or 'id'
2735
      else:
2736
        relative_url, key, value = self._splitPath(id)
2737
        obj = p.unrestrictedTraverse(relative_url, None)
2738 2739 2740
      if obj is not None:
        action_list = obj.listActions()
        for index in range(len(action_list)):
2741
          if getattr(action_list[index], key, None) == value:
2742 2743
            obj.deleteActions(selections=(index,))
            break
2744 2745
      LOG('BusinessTemplate', 100,
          'unable to uninstall action at %s, ignoring' % relative_url )
2746 2747
    BaseTemplateItem.uninstall(self, context, **kw)

2748 2749 2750
class PortalTypeRolesTemplateItem(BaseTemplateItem):

  def __init__(self, id_list, **kw):
2751
    id_list = ['portal_type_roles/%s' % id for id in id_list if id != '']
2752 2753 2754 2755 2756 2757 2758
    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])
2759
      self._objects[relative_url] = type_role_list = []
2760
      for role in obj.getRoleInformationList():
2761
        type_role_dict = {}
2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778
        for k, v in aq_base(role).__getstate__().iteritems():
          if k == 'condition':
            if not v:
              continue
            v = v.text
          elif k in ('role_base_category', 'role_category'):
            k = k[5:]
          elif k == 'role_name':
            k, v = 'id', '; '.join(v)
          elif k not in ('title', 'description'):
            k = {'id': 'object_id', # for stable sort
                 'role_base_category': 'base_category',
                 'role_base_category_script_id': 'base_category_script',
                 'role_category': 'category'}.get(k)
            if not k:
              continue
          type_role_dict[k] = v
2779
        type_role_list.append(type_role_dict)
2780
      type_role_list.sort(key=lambda x: (x.get('title'), x['object_id'],))
2781

Christophe Dumez's avatar
Christophe Dumez committed
2782
  # Function to generate XML Code Manually
2783 2784 2785 2786
  def generateXml(self, path=None):
    type_role_list = self._objects[path]
    xml_data = '<type_roles>'
    for role in type_role_list:
2787
      xml_data += "\n  <role id='%s'>" % role['id']
2788
      # uniq
2789
      for property in ('title', 'description', 'condition',
2790 2791 2792
          'base_category_script'):
        prop_value = role.get(property)
        if prop_value:
Nicolas Delaby's avatar
Nicolas Delaby committed
2793 2794
          if isinstance(prop_value, str):
            prop_value = prop_value.decode('utf-8')
2795
          xml_data += "\n   <property id='%s'>%s</property>" % \
2796 2797 2798
              (property, prop_value)
      # multi
      for property in ('category', 'base_category'):
2799
        for prop_value in role.get(property, []):
Nicolas Delaby's avatar
Nicolas Delaby committed
2800 2801
          if isinstance(prop_value, str):
            prop_value = prop_value.decode('utf-8')
2802
          xml_data += "\n   <multi_property "\
2803
          "id='%s'>%s</multi_property>" % (property, prop_value)
2804 2805
      xml_data += "\n  </role>"
    xml_data += '\n</type_roles>'
2806
    return xml_data
2807

2808 2809 2810 2811 2812 2813 2814
  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)
2815 2816
      if isinstance(xml_data, unicode):
        xml_data = xml_data.encode('utf-8')
2817 2818 2819 2820
      name = key.split('/', 1)[1]
      bta.addObject(obj=xml_data, name=name, path=root_path)

  def _importFile(self, file_name, file):
2821 2822 2823
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
2824 2825
    type_roles_list = []
    xml = parse(file)
2826
    xml_type_roles_list = xml.findall('role')
2827
    for role in xml_type_roles_list:
2828 2829 2830 2831
      id = role.get('id')
      if isinstance(id, unicode):
        id = id.encode('utf_8', 'backslashreplace')
      type_role_property_dict = {'id': id}
2832
      # uniq
2833 2834 2835 2836 2837 2838 2839 2840
      property_list = role.findall('property')
      for property_node in property_list:
        property_id = property_node.get('id')
        if property_node.text:
          value = property_node.text
          if isinstance(value, unicode):
            value = value.encode('utf_8', 'backslashreplace')
          type_role_property_dict[property_id] = value
2841
      # multi
2842 2843 2844 2845 2846 2847 2848 2849
      multi_property_list = role.findall('multi_property')
      for property_node in multi_property_list:
        property_id = property_node.get('id')
        if property_node.text:
          value = property_node.text
          if isinstance(value, unicode):
            value = value.encode('utf_8', 'backslashreplace')
          type_role_property_dict.setdefault(property_id, []).append(value)
2850
      type_roles_list.append(type_role_property_dict)
2851
    self._objects['portal_type_roles/%s' % (file_name[:-4],)] = type_roles_list
2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862

  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
2863 2864 2865
        path = 'portal_types/%s' % roles_path.split('/', 1)[1]
        obj = p.unrestrictedTraverse(path, None)
        if obj is not None:
2866
          # reset roles before applying
2867
          obj.manage_delObjects([x.id for x in obj.getRoleInformationList()])
2868
          type_roles_list = self._objects[roles_path] or []
2869 2870
          for role_property_dict in type_roles_list:
            obj._importRole(role_property_dict)
2871 2872 2873

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
Aurel's avatar
Aurel committed
2874 2875 2876 2877 2878 2879
    object_path = kw.get('object_path', None)
    if object_path is not None:
      keys = [object_path]
    else:
      keys = self._objects.keys()
    for roles_path in keys:
2880
      path = 'portal_types/%s' % roles_path.split('/', 1)[1]
2881 2882 2883 2884 2885
      try:
        obj = p.unrestrictedTraverse(path)
        setattr(obj, '_roles', [])
      except (NotFound, KeyError):
        pass
2886

2887 2888 2889 2890 2891 2892
class SitePropertyTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
2893 2894
      for property in p.propertyMap():
        if property['id'] == id:
2895
          obj = p.getProperty(id)
2896
          prop_type = property['type']
2897 2898
          break
      else:
2899
        obj = None
2900
      if obj is None and not self.is_bt_for_diff:
2901
        raise NotFound, 'the property %s is not found' % id
2902
      self._objects[id] = (prop_type, obj)
Aurel's avatar
Aurel committed
2903

2904 2905
  def _importFile(self, file_name, file):
    # recreate list of site property from xml file
2906
    if not file_name.endswith('.xml'):
2907
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
2908
      return
2909
    xml = parse(file)
2910 2911 2912 2913 2914 2915 2916 2917 2918
    property_node = xml.getroot()[0]
    property_id = property_node.find('id').text
    prop_type = property_node.find('type').text
    value_node = property_node.find('value')
    if prop_type in ('lines', 'tokens'):
      value = [item.text for item in value_node.findall('item')]
    else:
      value = value_node.text
    self._objects[property_id] = (prop_type, value)
2919

2920 2921 2922
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2923
    if context.getTemplateFormatVersion() == 1:
Aurel's avatar
Aurel committed
2924 2925
      p = context.getPortalObject()
      for path in self._objects.keys():
2926 2927 2928 2929
        if update_dict.has_key(path) or force:
          if not force:
            action = update_dict[path]
            if action == 'nothing':
2930
              continue
2931
          dir, id = posixpath.split(path)
2932
          prop_type, property = self._objects[path]
2933 2934 2935 2936 2937 2938 2939 2940
          if p.hasProperty(id):
            if p.getPropertyType(id) != prop_type:
              p._delProperty(id)
              p._setProperty(id, property, type=prop_type)
            else:
              p._updateProperty(id, property)
          else:
            p._setProperty(id, property, type=prop_type)
Aurel's avatar
Aurel committed
2941
    else:
2942
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
2943
      p = context.getPortalObject()
2944
      for id, property in self._archive.keys():
2945
        property = self._archive[id]
Aurel's avatar
Aurel committed
2946
        if p.hasProperty(id):
2947 2948 2949 2950 2951 2952 2953
          if p.getPropertyType(id) != property['type']:
            p._delProperty(id)
            p._setProperty(id, property['value'], type=property['type'])
          else:
            p._updateProperty(id, property['value'])
        else:
          p._setProperty(id, property['value'], type=property['type'])
2954 2955 2956

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
2957 2958 2959 2960 2961 2962
    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:
2963 2964 2965 2966
      if p.hasProperty(id):
        p._delProperty(id)
    BaseTemplateItem.uninstall(self, context, **kw)

Christophe Dumez's avatar
Christophe Dumez committed
2967
  # Function to generate XML Code Manually
2968
  def generateXml(self, path=None):
2969
    xml_data = ''
2970
    prop_type, obj = self._objects[path]
2971
    xml_data += '\n <property>'
2972 2973
    xml_data += '\n  <id>%s</id>' % escape(str(path))
    xml_data += '\n  <type>%s</type>' % escape(str(prop_type))
2974
    if prop_type in ('lines', 'tokens'):
2975
      xml_data += '\n  <value>'
2976
      for item in obj:
2977
        if item != '':
2978
          xml_data += '\n   <item>%s</item>' % escape(str(item))
2979
      xml_data += '\n  </value>'
2980
    else:
2981
      xml_data += '\n  <value>%s</value>' % escape(str(obj))
2982
    xml_data += '\n </property>'
2983 2984
    return xml_data

Aurel's avatar
Aurel committed
2985 2986 2987 2988 2989
  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)
2990 2991 2992 2993
    xml_data = '<site_property>'
    keys = self._objects.keys()
    keys.sort()
    for path in keys:
2994
      xml_data += self.generateXml(path)
2995
    xml_data += '\n</site_property>'
2996
    bta.addObject(obj=xml_data, name='properties', path=root_path)
2997

2998 2999 3000 3001 3002
class ModuleTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
3003 3004 3005 3006 3007 3008
    for module_id in self._archive.keys():
      module = p.unrestrictedTraverse(module_id)
      mapping = {}
      mapping['id'] = module.getId()
      mapping['title'] = module.getTitle()
      mapping['portal_type'] = module.getPortalType()
3009
      permission_list = []
3010
      mapping['permission_list'] = module.showPermissions()
3011
      mapping['category_list'] = module.getCategoryList()
3012
      self._objects[module_id] = mapping
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3013

Christophe Dumez's avatar
Christophe Dumez committed
3014
  # Function to generate XML Code Manually
3015
  def generateXml(self, path=None):
3016
    mapping = self._objects[path]
3017
    xml_data = ['<module>']
3018
    keys = mapping.keys()
3019 3020
    for key in sorted(keys):
      if key == 'permission_list':
3021
        # separe permission dict into xml
3022
        xml_data.append(' <%s>' % (key, ))
3023
        permission_list = mapping[key]
3024
        for perm in permission_list:
3025
          # the type of the permission defined if we use acquired or not
3026
          if isinstance(perm[1], list):
3027 3028 3029
            ptype = "list"
          else:
            ptype = "tuple"
Aurel's avatar
Aurel committed
3030
          role_list = list(perm[1])
3031 3032 3033
          # Skip if permission is not configured (i.e. no role at all
          # with acquire permission, or Manager only without acquire
          # permission).
3034
          if (len(role_list) == 0 and ptype == 'list') or \
3035 3036
                 (role_list == ['Manager'] and ptype == 'tuple'):
            continue
3037
          role_list.sort()
3038 3039
          xml_data.append("  <permission type='%s'>" % (ptype, ))
          xml_data.append('   <name>%s</name>' % (perm[0], ))
3040
          for role in role_list:
3041 3042 3043
            xml_data.append('   <role>%s</role>' % (role, ))
          xml_data.append('  </permission>')
        xml_data.append(' </%s>' % (key, ))
3044 3045 3046 3047 3048 3049 3050 3051
      elif key == 'category_list':
        category_list = mapping[key]
        if not category_list:
          continue
        xml_data.append(' <%s>' % (key, ))
        for category in category_list:
          xml_data.append('  <category>%s</category>' % (category, ))
        xml_data.append(' </%s>' % (key, ))
3052
      else:
3053
        xml_data.append(' <%s>%s</%s>' % (key, mapping[key], key))
3054 3055
    xml_data.append('</module>')
    return '\n'.join(xml_data)
3056

3057
  def export(self, context, bta, **kw):
3058
    if len(self._objects) == 0:
3059 3060 3061
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(path)
3062 3063
    keys = self._objects.keys()
    keys.sort()
3064
    for key in keys:
Jérome Perrin's avatar
typo  
Jérome Perrin committed
3065
      # export modules one by one
3066 3067
      xml_data = self.generateXml(path=key)
      bta.addObject(obj=xml_data, name=key, path=path)
3068

3069
  def install(self, context, trashbin, **kw):
3070
    portal = context.getPortalObject()
3071 3072
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3073
    if context.getTemplateFormatVersion() == 1:
3074
      items = self._objects
3075
    else:
3076 3077
      items = self._archive

3078 3079
    valid_permissions = dict.fromkeys([x[0] for x in
                                       context.ac_inherited_permissions(all=1)])
3080 3081
    for path, mapping in items.iteritems():
      if update_dict.has_key(path) or force:
3082
        if not force:
3083
          action = update_dict[path]
3084 3085
          if action == 'nothing':
            continue
3086 3087
        path, module_id = posixpath.split(path)
        portal_type = str(mapping['portal_type'])
3088
        module = portal._getOb(module_id, None)
3089 3090
        if module is not None:
          module.portal_type = portal_type
3091
        else:
3092
          module = portal.newContent(id=module_id, portal_type=portal_type)
3093
        module.setTitle(str(mapping['title']))
3094 3095
        permission_dict = dict(mapping['permission_list'])
        for name in valid_permissions.iterkeys():
3096
          # By default, Manager only without acquire permission
3097 3098
          role_list = permission_dict.get(name, ('Manager',))
          acquire = isinstance(role_list, list)
3099
          module.manage_permission(name, roles=role_list, acquire=acquire)
3100 3101
        if 'category_list' in mapping:
          module.setCategoryList(mapping['category_list'])
3102 3103

  def _importFile(self, file_name, file):
3104 3105 3106
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
3107
    mapping = {}
3108
    xml = parse(file)
3109
    for key in ('portal_type', 'id', 'title', 'permission_list'):
3110
      key_node = xml.find(key)
3111
      if key == 'permission_list':
3112 3113 3114 3115 3116 3117 3118
        permission_list = []
        for permission in key_node:
          permission_type = permission.get('type', None)
          name = permission.find('name').text
          role_list = [role.text for role in permission.findall('role')]
          if permission_type in ('list', None):
            perm_tuple = (name, list(role_list))
3119
          else:
3120 3121 3122
            perm_tuple = (name, tuple(role_list))
          permission_list.append(perm_tuple)
        mapping[key] = permission_list
3123
      else:
3124
        mapping[key] = key_node.text or ''
3125 3126

    category_list = []
3127 3128 3129 3130
    category_list_node = xml.find('category_list')
    if category_list_node is not None:
      category_list.extend(node.text for node\
                            in category_list_node.findall('category'))
3131 3132
    mapping['category_list'] = category_list

3133
    self._objects[file_name[:-4]] = mapping
3134

3135
  def uninstall(self, context, **kw):
Aurel's avatar
Aurel committed
3136 3137 3138
    trash = kw.get('trash', 0)
    if trash:
      return
3139 3140 3141 3142 3143 3144
    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]
3145 3146
    p = context.getPortalObject()
    id_list = p.objectIds()
3147 3148
    for key in keys:
      if key in id_list:
3149
        try:
3150
          if trash and trashbin is not None:
3151
            container_path = key.split('/')
3152 3153
            self.portal_trash.backupObject(trashbin, container_path, 
                                           key, save=1, keep_subobjects=1)
3154
          p.manage_delObjects([key])
3155
        except NotFound:
3156
          pass
3157 3158
    BaseTemplateItem.uninstall(self, context, **kw)

3159 3160 3161
  def trash(self, context, new_item, **kw):
    # Do not remove any module for safety.
    pass
3162 3163

class DocumentTemplateItem(BaseTemplateItem):
3164 3165 3166 3167
  local_file_reader_name = staticmethod(readLocalDocument)
  local_file_writer_name = staticmethod(writeLocalDocument)
  local_file_importer_name = staticmethod(importLocalDocument)
  local_file_remover_name = staticmethod(removeLocalDocument)
3168 3169 3170

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
3171 3172
    for key in self._archive.iterkeys():
      self._objects[key] = self.local_file_reader_name(key)
Aurel's avatar
Aurel committed
3173

3174
  def preinstall(self, context, installed_item, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3175 3176
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
3177
      new_keys = self._objects.keys()
3178
      # fix key if necessary in installed bt for diff
3179
      extra_prefix = self.__class__.__name__ + '/'
3180
      for key in installed_item._objects.keys():
3181 3182 3183 3184
        if key.startswith(extra_prefix):
          new_key = key[len(extra_prefix):]
          installed_item._objects[new_key] = installed_item._objects[key]
          del installed_item._objects[key]
3185
      for path in new_keys:
3186
        if installed_item._objects.has_key(path):
3187 3188
          # compare object to see if there is changes
          new_obj_code = self._objects[path]
3189
          old_obj_code = installed_item._objects[path]
3190
          if new_obj_code != old_obj_code:
3191 3192
            modified_object_list.update(
                {path : ['Modified', self.__class__.__name__[:-12]]})
3193
        else: # new object
3194 3195
          modified_object_list.update(
                {path : ['New', self.__class__.__name__[:-12]]})
3196
          # get removed object
3197
      old_keys = installed_item._objects.keys()
3198 3199
      for path in old_keys:
        if path not in new_keys:
3200 3201
          modified_object_list.update(
                {path : ['Removed', self.__class__.__name__[:-12]]})
3202 3203 3204 3205 3206
    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
3207
    if context.getTemplateFormatVersion() == 1:
Aurel's avatar
Aurel committed
3208
      for id in self._objects.keys():
3209 3210 3211 3212 3213 3214
        if update_dict.has_key(id) or force:
          if not force:
            action = update_dict[id]
            if action == 'nothing':
              continue
          text = self._objects[id]
3215
          path, name = posixpath.split(id)
3216 3217
          # This raises an exception if the file already exists.
          try:
3218
            self.local_file_writer_name(name, text, create=0)
3219
          except IOError, error:
3220
            LOG("BusinessTemplate.py", WARNING, "Cannot install class %s on file system" %(name,))
3221
            if error.errno:
3222
              raise
3223 3224
            continue
          if self.local_file_importer_name is not None:
3225
            self.local_file_importer_name(name)
3226 3227 3228 3229 3230 3231 3232 3233 3234 3235
            # after any import, flush all ZODB caches to force a DB reload
            # otherwise we could have objects trying to get commited while
            # holding reference to a class that is no longer the same one as
            # the class in its import location and pickle doesn't tolerate it.
            # First we do a savepoint to dump dirty objects to temporary
            # storage, so that all references to them can be freed.
            transaction.savepoint(optimistic=True)
            # Then we need to flush from all caches, not only the one from this
            # connection
            self.getPortalObject()._p_jar.db().cacheMinimize()
Aurel's avatar
Aurel committed
3236
    else:
3237 3238 3239
      BaseTemplateItem.install(self, context, trashbin, **kw)
      for id in self._archive.keys():
        text = self._archive[id]
Aurel's avatar
Aurel committed
3240
        # This raises an exception if the file exists.
3241
        self.local_file_writer_name(id, text, create=1)
Aurel's avatar
Aurel committed
3242
        if self.local_file_importer_name is not None:
3243
          self.local_file_importer_name(id)
3244 3245

  def uninstall(self, context, **kw):
3246
    object_path = kw.get('object_path', None)
3247 3248 3249 3250
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
3251 3252
    for key in object_keys:
      self.local_file_remover_name(key)
3253 3254
    BaseTemplateItem.uninstall(self, context, **kw)

Aurel's avatar
Aurel committed
3255 3256 3257 3258 3259
  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)
3260 3261 3262 3263 3264 3265 3266
    extra_prefix = self.__class__.__name__ + '/'
    for key in self._objects.keys():
      obj = self._objects[key]
      # BBB the prefix was put into each key in the previous implementation.
      if not key.startswith(extra_prefix):
        key = extra_prefix + key
      bta.addObject(obj=obj, name=key, ext='.py')
Aurel's avatar
Aurel committed
3267

3268
  def _importFile(self, file_name, file):
3269 3270 3271
    if not file_name.endswith('.py'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
3272
    text = file.read()
3273
    self._objects[file_name[:-3]] = text
3274

3275
class PropertySheetTemplateItem(DocumentTemplateItem):
3276 3277 3278 3279
  local_file_reader_name = staticmethod(readLocalPropertySheet)
  local_file_writer_name = staticmethod(writeLocalPropertySheet)
  local_file_importer_name = staticmethod(importLocalPropertySheet)
  local_file_remover_name = staticmethod(removeLocalPropertySheet)
3280

3281
class ConstraintTemplateItem(DocumentTemplateItem):
3282 3283 3284 3285
  local_file_reader_name = staticmethod(readLocalConstraint)
  local_file_writer_name = staticmethod(writeLocalConstraint)
  local_file_importer_name = staticmethod(importLocalConstraint)
  local_file_remover_name = staticmethod(removeLocalConstraint)
3286

3287
class ExtensionTemplateItem(DocumentTemplateItem):
3288 3289
  local_file_reader_name = staticmethod(readLocalExtension)
  local_file_writer_name = staticmethod(writeLocalExtension)
3290 3291
  # Extension needs no import
  local_file_importer_name = None
3292
  local_file_remover_name = staticmethod(removeLocalExtension)
3293 3294

class TestTemplateItem(DocumentTemplateItem):
3295 3296
  local_file_reader_name = staticmethod(readLocalTest)
  local_file_writer_name = staticmethod(writeLocalTest)
3297
  # Test needs no import
3298
  local_file_importer_name = None
3299
  local_file_remover_name = staticmethod(removeLocalTest)
3300

Aurel's avatar
Aurel committed
3301

3302 3303 3304
class ProductTemplateItem(BaseTemplateItem):
  # XXX Not implemented yet
  pass
3305 3306 3307

class RoleTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
3308
  def build(self, context, **kw):
3309 3310
    for key in self._archive.iterkeys():
      self._objects[key] = 1
Aurel's avatar
Aurel committed
3311

3312
  def preinstall(self, context, installed_item, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3313 3314
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
3315
      new_roles = self._objects.keys()
3316 3317 3318 3319 3320 3321
      # BBB it might be necessary to change the data structure.
      obsolete_key = self.__class__.__name__ + '/role_list'
      if obsolete_key in installed_item._objects:
        for role in installed_item._objects[obsolete_key]:
          installed_item._objects[role] = 1
        del installed_item._objects[obsolete_key]
3322
      for role in new_roles:
3323
        if installed_item._objects.has_key(role):
3324 3325 3326 3327
          continue
        else: # only show new roles
          modified_object_list.update({role : ['New', 'Role']})
      # get removed roles
3328
      old_roles = installed_item._objects.keys()
3329 3330 3331 3332 3333 3334
      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):
3335
    p = context.getPortalObject()
3336
    # get roles
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3337
    if context.getTemplateFormatVersion() == 1:
3338
      role_set = set(self._objects)
3339
    else:
3340
      role_set = set(self._archive)
3341 3342 3343 3344
    # 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:
3345 3346
        for role in role_set.difference(role_manager.listRoleIds()):
          role_manager.addRole(role)
3347
    # set roles on portal
3348
    p.__ac_roles__ = tuple(role_set.union(p.__ac_roles__))
3349 3350

  def _importFile(self, file_name, file):
3351
    if not file_name.endswith('.xml'):
3352
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
3353
      return
3354
    xml = parse(file)
3355 3356 3357
    for role in xml.getroot():
      value = role.text
      self._objects[value] = 1
3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369

  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)

3370 3371 3372 3373 3374 3375 3376 3377 3378
  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
3379
      if role in roles and role not in new_roles:
3380 3381 3382
        del roles[role]
    p.__ac_roles__ = tuple(roles.keys())

Christophe Dumez's avatar
Christophe Dumez committed
3383
  # Function to generate XML Code Manually
3384 3385
  def generateXml(self):
    role_list = self._objects.keys()
3386
    xml_data = '<role_list>'
3387 3388
    for role in sorted(role_list):
      xml_data += '\n <role>%s</role>' % (role,)
3389
    xml_data += '\n</role_list>'
3390 3391
    return xml_data

Aurel's avatar
Aurel committed
3392
  def export(self, context, bta, **kw):
3393
    if len(self._objects) == 0:
Aurel's avatar
Aurel committed
3394 3395 3396
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=path)
3397 3398 3399 3400 3401 3402 3403 3404 3405
    # BBB it might be necessary to change the data structure.
    obsolete_key = self.__class__.__name__ + '/role_list'
    if obsolete_key in self._objects:
      for role in self._objects[obsolete_key]:
        self._objects[role] = 1
      del self._objects[obsolete_key]
    xml_data = self.generateXml()
    path = obsolete_key
    bta.addObject(obj=xml_data, name=path)
3406

3407 3408
class CatalogResultKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
3409
  def build(self, context, **kw):
3410
    catalog = _getCatalogValue(self)
3411 3412 3413
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
3414
    sql_search_result_keys = list(catalog.sql_search_result_keys)
3415
    key_list = []
3416
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
3417
      if key in sql_search_result_keys:
3418
        key_list.append(key)
3419
      elif not self.is_bt_for_diff:
3420
        raise NotFound, 'Result key "%r" not found in catalog' %(key,)
3421
    if len(key_list) > 0:
3422
      self._objects[self.__class__.__name__+'/'+'result_key_list'] = key_list
3423 3424

  def _importFile(self, file_name, file):
3425 3426 3427
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
3428
    xml = parse(file)
3429 3430
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
3431

3432
  def install(self, context, trashbin, **kw):
3433
    catalog = _getCatalogValue(self)
3434 3435 3436
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
3437

3438
    sql_search_result_keys = list(catalog.sql_search_result_keys)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3439
    if context.getTemplateFormatVersion() == 1:
3440 3441
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
3442 3443 3444
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
3445
    else:
3446
      keys = self._archive.keys()
3447 3448
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
3449
    # XXX same as related key
Aurel's avatar
Aurel committed
3450
    if update_dict.has_key('result_key_list') or force:
3451
      if not force:
Aurel's avatar
Aurel committed
3452
        action = update_dict['result_key_list']
3453
        if action == 'nothing':
Aurel's avatar
Aurel committed
3454
          return
3455
      for key in keys:
3456 3457
        if key not in sql_search_result_keys:
          sql_search_result_keys.append(key)
3458
      catalog.sql_search_result_keys = sql_search_result_keys
3459

3460
  def uninstall(self, context, **kw):
3461
    catalog = _getCatalogValue(self)
3462 3463 3464 3465
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_search_result_keys = list(catalog.sql_search_result_keys)
3466
    object_path = kw.get('object_path', None)
3467 3468 3469 3470 3471
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
3472 3473
      if key in sql_search_result_keys:
        sql_search_result_keys.remove(key)
3474
    catalog.sql_search_result_keys = sql_search_result_keys
3475 3476
    BaseTemplateItem.uninstall(self, context, **kw)

Christophe Dumez's avatar
Christophe Dumez committed
3477
  # Function to generate XML Code Manually
3478
  def generateXml(self, path=None):
3479
    obj = self._objects[path]
3480
    xml_data = '<key_list>'
3481 3482
    obj.sort()
    for key in obj:
3483 3484
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</key_list>'
3485 3486
    return xml_data

Aurel's avatar
Aurel committed
3487 3488 3489 3490 3491
  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)
3492
    for path in self._objects.keys():
3493
      xml_data = self.generateXml(path=path)
3494
      bta.addObject(obj=xml_data, name=path, path=None)
3495

3496 3497
class CatalogRelatedKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
3498
  def build(self, context, **kw):
3499
    catalog = _getCatalogValue(self)
3500 3501 3502
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
3503
    sql_search_related_keys = list(catalog.sql_catalog_related_keys)
3504
    key_list = []
3505
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
3506
      if key in sql_search_related_keys:
3507
        key_list.append(key)
3508
      elif not self.is_bt_for_diff:
3509
        raise NotFound, 'Related key "%r" not found in catalog' %(key,)
3510
    if len(key_list) > 0:
3511
      self._objects[self.__class__.__name__+'/'+'related_key_list'] = key_list
3512 3513

  def _importFile(self, file_name, file):
3514 3515 3516
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
3517
    xml = parse(file)
3518 3519
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
3520

3521
  def install(self, context, trashbin, **kw):
3522
    catalog = _getCatalogValue(self)
3523 3524 3525
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
3526

3527
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3528
    if context.getTemplateFormatVersion() == 1:
3529
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
3530
        return
3531 3532 3533
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
3534
    else:
3535
      keys = self._archive.keys()
3536 3537
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Aurel's avatar
Aurel committed
3538
    # XXX must a find a better way to manage related key
Aurel's avatar
Aurel committed
3539
    if update_dict.has_key('related_key_list') or update_dict.has_key('key_list') or force:
Aurel's avatar
Aurel committed
3540
      if not force:
Aurel's avatar
Aurel committed
3541 3542 3543 3544
        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
3545 3546 3547
        if action == 'nothing':
          return
      for key in keys:
3548 3549
        if key not in sql_catalog_related_keys:
          sql_catalog_related_keys.append(key)
Aurel's avatar
Aurel committed
3550
      catalog.sql_catalog_related_keys = tuple(sql_catalog_related_keys)
3551

3552
  def uninstall(self, context, **kw):
3553
    catalog = _getCatalogValue(self)
3554 3555 3556
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
3557
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
3558
    object_path = kw.get('object_path', None)
3559 3560 3561 3562 3563
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
3564 3565 3566 3567 3568
      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)

Christophe Dumez's avatar
Christophe Dumez committed
3569
  # Function to generate XML Code Manually
3570
  def generateXml(self, path=None):
3571
    obj = self._objects[path]
3572
    xml_data = '<key_list>'
3573 3574
    obj.sort()
    for key in obj:
3575 3576
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</key_list>'
3577 3578
    return xml_data

Aurel's avatar
Aurel committed
3579 3580 3581 3582 3583 3584
  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():
3585
      xml_data = self.generateXml(path=path)
3586
      bta.addObject(obj=xml_data, name=path, path=None)
3587

3588 3589
class CatalogResultTableTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
3590
  def build(self, context, **kw):
3591
    catalog = _getCatalogValue(self)
3592 3593 3594
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
3595
    sql_search_result_tables = list(catalog.sql_search_tables)
3596
    key_list = []
3597
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
3598
      if key in sql_search_result_tables:
3599
        key_list.append(key)
3600
      elif not self.is_bt_for_diff:
3601
        raise NotFound, 'Result table "%r" not found in catalog' %(key,)
3602
    if len(key_list) > 0:
3603
      self._objects[self.__class__.__name__+'/'+'result_table_list'] = key_list
3604 3605

  def _importFile(self, file_name, file):
3606 3607 3608
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
3609
    xml = parse(file)
3610 3611
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
Aurel's avatar
Aurel committed
3612

3613
  def install(self, context, trashbin, **kw):
3614
    catalog = _getCatalogValue(self)
3615 3616 3617
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
3618

3619
    sql_search_tables = list(catalog.sql_search_tables)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3620
    if context.getTemplateFormatVersion() == 1:
3621
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
3622
        return
3623 3624 3625
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
3626
    else:
3627
      keys = self._archive.keys()
3628 3629
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
3630
    # XXX same as related keys
Aurel's avatar
Aurel committed
3631
    if update_dict.has_key('result_table_list') or force:
3632
      if not force:
Aurel's avatar
Aurel committed
3633
        action = update_dict['result_table_list']
3634
        if action == 'nothing':
Aurel's avatar
Aurel committed
3635
          return
3636
      for key in keys:
3637 3638
        if key not in sql_search_tables:
          sql_search_tables.append(key)
3639
      catalog.sql_search_tables = tuple(sql_search_tables)
3640 3641

  def uninstall(self, context, **kw):
3642
    catalog = _getCatalogValue(self)
3643 3644 3645 3646
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_search_tables = list(catalog.sql_search_tables)
3647
    object_path = kw.get('object_path', None)
3648 3649 3650 3651 3652
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
3653 3654
      if key in sql_search_tables:
        sql_search_tables.remove(key)
3655
    catalog.sql_search_tables = sql_search_tables
3656 3657
    BaseTemplateItem.uninstall(self, context, **kw)

Christophe Dumez's avatar
Christophe Dumez committed
3658
  # Function to generate XML Code Manually
3659
  def generateXml(self, path=None):
3660
    obj = self._objects[path]
3661
    xml_data = '<key_list>'
3662 3663
    obj.sort()
    for key in obj:
3664 3665
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</key_list>'
3666 3667
    return xml_data

Aurel's avatar
Aurel committed
3668 3669 3670 3671 3672 3673
  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():
3674
      xml_data = self.generateXml(path=path)
3675
      bta.addObject(obj=xml_data, name=path, path=None)
3676

3677 3678
# keyword
class CatalogKeywordKeyTemplateItem(BaseTemplateItem):
3679 3680

  def build(self, context, **kw):
3681
    catalog = _getCatalogValue(self)
3682 3683 3684 3685 3686 3687 3688 3689
    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)
3690
      elif not self.is_bt_for_diff:
3691
        raise NotFound, 'Keyword key "%r" not found in catalog' %(key,)
3692
    if len(key_list) > 0:
3693
      self._objects[self.__class__.__name__+'/'+'keyword_key_list'] = key_list
Aurel's avatar
Aurel committed
3694

3695
  def _importFile(self, file_name, file):
3696 3697 3698
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
3699
    xml = parse(file)
3700 3701
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
3702 3703

  def install(self, context, trashbin, **kw):
3704
    catalog = _getCatalogValue(self)
3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717
    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()
3718 3719
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731
    # 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):
3732
    catalog = _getCatalogValue(self)
3733 3734 3735 3736
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_keyword_keys = list(catalog.sql_catalog_keyword_search_keys)
3737
    object_path = kw.get('object_path', None)
3738 3739
    if object_path is not None:
      object_keys = [object_path]
Aurel's avatar
Aurel committed
3740
    else:
3741 3742 3743 3744 3745 3746 3747
      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)

Christophe Dumez's avatar
Christophe Dumez committed
3748
  # Function to generate XML Code Manually
3749 3750 3751 3752 3753
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
3754 3755
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</key_list>'
3756
    return xml_data
Aurel's avatar
Aurel committed
3757 3758 3759 3760

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
3761 3762 3763 3764 3765
    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)
3766

3767 3768 3769 3770 3771 3772 3773 3774
# datetime
class CatalogDateTimeKeyTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
3775
    sql_datetime_keys = list(getattr(catalog, 'sql_catalog_datetime_search_keys', []))
3776 3777 3778 3779
    key_list = []
    for key in self._archive.keys():
      if key in sql_datetime_keys:
        key_list.append(key)
3780
      elif not self.is_bt_for_diff:
3781 3782 3783 3784 3785
        raise NotFound, 'DateTime key "%r" not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+'/'+'datetime_key_list'] = key_list

  def _importFile(self, file_name, file):
3786 3787 3788
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
3789
    xml = parse(file)
3790 3791
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
3792 3793 3794 3795 3796 3797 3798

  def install(self, context, trashbin, **kw):
    catalog = _getCatalogValue(context)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

3799
    sql_datetime_keys = list(getattr(catalog, 'sql_catalog_datetime_search_keys', []))
3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825
    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('datetime_key_list') or force:
      if not force:
        action = update_dict['datetime_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_datetime_keys:
          sql_datetime_keys.append(key)
      catalog.sql_catalog_datetime_search_keys = sql_datetime_keys

  def uninstall(self, context, **kw):
    catalog = _getCatalogValue(context)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available - uninstall')
      return
3826
    sql_datetime_keys = list(getattr(catalog, 'sql_catalog_datetime_search_keys', []))
3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856
    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_datetime_keys:
        sql_datetime_keys.remove(key)
    catalog.sql_catalog_datetime_search_keys = sql_datetime_keys
    BaseTemplateItem.uninstall(self, context, **kw)

  # Function to generate XML Code Manually
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</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)      
      
3857 3858
# full text
class CatalogFullTextKeyTemplateItem(BaseTemplateItem):
3859

3860
  def build(self, context, **kw):
3861
    catalog = _getCatalogValue(self)
3862 3863 3864 3865 3866 3867 3868 3869
    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)
3870
      elif not self.is_bt_for_diff:
3871
        raise NotFound, 'Fulltext key "%r" not found in catalog' %(key,)
3872
    if len(key_list) > 0:
3873
      self._objects[self.__class__.__name__+'/'+'full_text_key_list'] = key_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3874

3875
  def _importFile(self, file_name, file):
3876 3877 3878
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
3879
    xml = parse(file)
3880 3881
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3882

3883
  def install(self, context, trashbin, **kw):
3884
    catalog = _getCatalogValue(self)
3885 3886 3887
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3888

3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909
    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
3910

3911
  def uninstall(self, context, **kw):
3912
    catalog = _getCatalogValue(self)
3913 3914 3915 3916
    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)
3917
    object_path = kw.get('object_path', None)
3918 3919 3920 3921 3922 3923 3924 3925 3926 3927
    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)

Christophe Dumez's avatar
Christophe Dumez committed
3928
  # Function to generate XML Code Manually
3929 3930 3931 3932 3933
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
3934 3935
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</key_list>'
3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951
    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):
3952
    catalog = _getCatalogValue(self)
3953 3954 3955 3956 3957 3958 3959 3960
    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)
3961
      elif not self.is_bt_for_diff:
3962
        raise NotFound, 'Request key "%r" not found in catalog' %(key,)
3963
    if len(key_list) > 0:
3964
      self._objects[self.__class__.__name__+'/'+'request_key_list'] = key_list
3965 3966

  def _importFile(self, file_name, file):
3967 3968 3969
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
3970
    xml = parse(file)
3971 3972
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
3973 3974

  def install(self, context, trashbin, **kw):
3975
    catalog = _getCatalogValue(self)
3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002
    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):
4003
    catalog = _getCatalogValue(self)
4004 4005 4006 4007
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_request_keys = list(catalog.sql_catalog_request_keys)
4008
    object_path = kw.get('object_path', None)
4009 4010 4011 4012 4013 4014 4015 4016 4017 4018
    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)

Christophe Dumez's avatar
Christophe Dumez committed
4019
  # Function to generate XML Code Manually
4020 4021 4022 4023 4024
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
4025 4026
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</key_list>'
4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041
    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):
4042
    catalog = _getCatalogValue(self)
4043 4044 4045 4046 4047 4048 4049 4050
    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)
4051
      elif not self.is_bt_for_diff:
4052
        raise NotFound, 'Multivalue key "%r" not found in catalog' %(key,)
4053
    if len(key_list) > 0:
4054
      self._objects[self.__class__.__name__+'/'+'multivalue_key_list'] = key_list
4055 4056

  def _importFile(self, file_name, file):
4057 4058 4059
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
4060
    xml = parse(file)
4061 4062
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
4063 4064

  def install(self, context, trashbin, **kw):
4065
    catalog = _getCatalogValue(self)
4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091
    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):
4092
    catalog = _getCatalogValue(self)
4093 4094 4095 4096
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_multivalue_keys = list(catalog.sql_catalog_multivalue_keys)
4097
    object_path = kw.get('object_path', None)
4098 4099 4100 4101 4102 4103 4104 4105 4106 4107
    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)

Christophe Dumez's avatar
Christophe Dumez committed
4108
  # Function to generate XML Code Manually
4109 4110 4111 4112 4113
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
4114 4115
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</key_list>'
4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130
    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):
4131
    catalog = _getCatalogValue(self)
4132 4133 4134 4135 4136 4137 4138 4139
    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)
4140
      elif not self.is_bt_for_diff:
4141
        raise NotFound, 'Topic key "%r" not found in catalog' %(key,)
4142
    if len(key_list) > 0:
4143
      self._objects[self.__class__.__name__+'/'+'topic_key_list'] = key_list
4144 4145

  def _importFile(self, file_name, file):
4146 4147 4148
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
4149
    xml = parse(file)
4150 4151
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
4152 4153

  def install(self, context, trashbin, **kw):
4154
    catalog = _getCatalogValue(self)
4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181
    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):
4182
    catalog = _getCatalogValue(self)
4183 4184 4185 4186
    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)
4187
    object_path = kw.get('object_path', None)
4188 4189 4190 4191 4192 4193 4194 4195 4196 4197
    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)

Christophe Dumez's avatar
Christophe Dumez committed
4198
  # Function to generate XML Code Manually
4199 4200 4201 4202 4203
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
4204 4205
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</key_list>'
4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216
    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)

4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228
class CatalogScriptableKeyTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_scriptable_keys = list(catalog.sql_catalog_scriptable_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_catalog_scriptable_keys:
        key_list.append(key)
4229
      elif not self.is_bt_for_diff:
4230 4231 4232 4233 4234
        raise NotFound, 'Scriptable key "%r" not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+'/'+'scriptable_key_list'] = key_list

  def _importFile(self, file_name, file):
4235 4236 4237
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
4238
    xml = parse(file)
4239 4240
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306

  def install(self, context, trashbin, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

    sql_catalog_scriptable_keys = list(catalog.sql_catalog_scriptable_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 scriptable key
    if update_dict.has_key('scriptable_key_list') or force:
      if not force:
        if update_dict.has_key('scriptable_key_list'):
          action = update_dict['scriptable_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_catalog_scriptable_keys:
          sql_catalog_scriptable_keys.append(key)
      catalog.sql_catalog_scriptable_keys = tuple(sql_catalog_scriptable_keys)

  def uninstall(self, context, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_scriptable_keys = list(catalog.sql_catalog_scriptable_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_scriptable_keys:
        sql_catalog_scriptable_keys.remove(key)
    catalog.sql_catalog_scriptable_keys = tuple(sql_catalog_scriptable_keys)
    BaseTemplateItem.uninstall(self, context, **kw)

  # Function to generate XML Code Manually
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</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)

4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319
class CatalogRoleKeyTemplateItem(BaseTemplateItem):
  # XXX Copy/paste from CatalogScriptableKeyTemplateItem

  def build(self, context, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_role_keys = list(catalog.sql_catalog_role_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_catalog_role_keys:
        key_list.append(key)
4320
      elif not self.is_bt_for_diff:
4321 4322 4323 4324 4325
        raise NotFound, 'Role key "%r" not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+'/'+'role_key_list'] = key_list

  def _importFile(self, file_name, file):
4326 4327 4328
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
4329
    xml = parse(file)
4330 4331
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 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

  def install(self, context, trashbin, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

    sql_catalog_role_keys = list(catalog.sql_catalog_role_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 scriptable key
    if update_dict.has_key('role_key_list') or force:
      if not force:
        if update_dict.has_key('role_key_list'):
          action = update_dict['role_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_catalog_role_keys:
          sql_catalog_role_keys.append(key)
      catalog.sql_catalog_role_keys = tuple(sql_catalog_role_keys)

  def uninstall(self, context, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_role_keys = list(catalog.sql_catalog_role_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_role_keys:
        sql_catalog_role_keys.remove(key)
    catalog.sql_catalog_role_keys = tuple(sql_catalog_role_keys)
    BaseTemplateItem.uninstall(self, context, **kw)

  # Function to generate XML Code Manually
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</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 CatalogLocalRoleKeyTemplateItem(BaseTemplateItem):
  # XXX Copy/paste from CatalogScriptableKeyTemplateItem

  def build(self, context, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_local_role_keys = list(catalog.sql_catalog_local_role_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_catalog_local_role_keys:
        key_list.append(key)
4411
      elif not self.is_bt_for_diff:
4412 4413 4414 4415 4416
        raise NotFound, 'LocalRole key "%r" not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+'/'+'local_role_key_list'] = key_list

  def _importFile(self, file_name, file):
4417 4418 4419
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
4420
    xml = parse(file)
4421 4422
    key_list = [key.text for key in xml.getroot()]
    self._objects[file_name[:-4]] = key_list
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

  def install(self, context, trashbin, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

    sql_catalog_local_role_keys = list(catalog.sql_catalog_local_role_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 scriptable key
    if update_dict.has_key('local_role_key_list') or force:
      if not force:
        if update_dict.has_key('local_role_key_list'):
          action = update_dict['local_role_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_catalog_local_role_keys:
          sql_catalog_local_role_keys.append(key)
      catalog.sql_catalog_local_role_keys = tuple(sql_catalog_local_role_keys)

  def uninstall(self, context, **kw):
    catalog = _getCatalogValue(self)
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_local_role_keys = list(catalog.sql_catalog_local_role_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_local_role_keys:
        sql_catalog_local_role_keys.remove(key)
    catalog.sql_catalog_local_role_keys = tuple(sql_catalog_local_role_keys)
    BaseTemplateItem.uninstall(self, context, **kw)

  # Function to generate XML Code Manually
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += '\n <key>%s</key>' %(key)
    xml_data += '\n</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)

4489 4490 4491 4492
class MessageTranslationTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    localizer = context.getPortalObject().Localizer
4493 4494 4495
    for lang_key in self._archive.keys():
      if '|' in lang_key:
        lang, catalog = lang_key.split(' | ')
4496
      else: # XXX backward compatibility
4497 4498
        lang = lang_key
        catalog = 'erp5_ui'
4499
      path = posixpath.join(lang, catalog)
4500 4501
      mc = localizer._getOb(catalog)
      self._objects[path] = mc.manage_export(lang)
4502 4503 4504
      if lang not in self._objects:
        name = localizer.get_language_name(lang)
        self._objects[lang] = name
4505

4506
  def preinstall(self, context, installed_item, **kw):
4507 4508 4509 4510
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
      new_keys = self._objects.keys()
      for path in new_keys:
4511
        if installed_item._objects.has_key(path):
4512 4513
          # compare object to see if there is changes
          new_obj_code = self._objects[path]
4514
          old_obj_code = installed_item._objects[path]
4515 4516 4517 4518 4519
          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
4520
      old_keys = installed_item._objects.keys()
4521 4522 4523 4524 4525
      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

4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553
  def _splitKey(self,key):
    path = key.split('/')
    if len(path) == 1:
      lang = path[0]
      catalog = None
    elif len(path) == 2:
      lang = path[0]
      catalog = path[1]
    else:
      lang = path[-3]
      catalog = path[-2]
    return lang, catalog

  def _importCatalogLanguage(self, localizer, catalog, lang, po):
    if catalog not in localizer.objectIds():
      dispatcher = localizer.manage_addProduct['Localizer']
      dispatcher.manage_addMessageCatalog(id=catalog, 
                                          title='Message Catalog',
                                          languages=['en'])
    mc = localizer._getOb(catalog)
    if lang not in mc.get_languages():
      mc.manage_addLanguage(lang)
    mc.manage_import(lang, po)

  def install(self, context, trashbin, localizer=None, **kw):
    if localizer is None:
      localizer = context.getPortalObject().Localizer
    update_dict = kw.get('object_to_update', {})
4554 4555
    force = kw.get('force')
    if context.getTemplateFormatVersion() == 1:
4556 4557
      for key in sorted(self._objects.keys()):
        if update_dict.has_key(key) or force:
4558
          if not force:
4559
            action = update_dict[key]
4560
            if action == 'nothing':
4561
              continue
4562
          lang, catalog = self._splitKey(key)
4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578

          if catalog is None:
            name = self._objects[key]
            for lang_dict in localizer.get_all_languages():
              if lang_dict['code'] == lang:
                # When the Localizer has the language as a user-defined
                # language, make sure that the name is updated.
                old_name = localizer.get_user_defined_language_name(lang)
                if old_name is not None and old_name != name:
                  localizer._del_user_defined_language(lang)
                  localizer._add_user_defined_language(name, lang)
                break
            else:
              # if the Localizer does not know the language code, it must be
              # defined as a user-defined language.
              localizer._add_user_defined_language(name, lang)
4579 4580
            if lang not in localizer.get_languages():
              localizer.manage_addLanguage(lang)
4581 4582 4583 4584
          else:
            po = self._objects[key]
            if lang not in localizer.get_languages():
              localizer.manage_addLanguage(lang)
4585
            self._importCatalogLanguage(localizer, catalog, lang, po)
4586 4587
    else:
      BaseTemplateItem.install(self, context, trashbin, **kw)
4588
      for lang, catalogs in self._archive.iteritems():
4589 4590 4591
        if lang not in localizer.get_languages():
          localizer.manage_addLanguage(lang)
        for catalog, po in catalogs.items():
4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621
          self._importCatalogLanguage(catalog, lang, po)

  def uninstall(self, context, remove_translations=False, **kw):
    if not remove_translations:
      return
    portal = context.getPortalObject()
    localizer = portal.Localizer
    from Products.Localizer.Localizer import Localizer
    fake_localizer = Localizer('Fake Localizer',
                               languages=['en']).__of__(portal)
    # roundabout way of keeping BW compatibility, allow install() to do the
    # heavy lifting so we can extract the original catalogs and messages:
    self.install(context, None, localizer=fake_localizer, force=True, **kw)
    # now scan the actual message_catalog to remove messages present in the
    # fake one.
    for fake_message_catalog in fake_localizer.objectValues():
      message_catalog = localizer._getOb(fake_message_catalog.getId())
      # get list of messages present in both the fake and the real catalog
      # UGH! direct attribute access... but there is no real API to access
      # all messages here.
      messages = set(fake_message_catalog._messages.keys())
      messages.intersection_update(message_catalog._messages.keys())
      for message in messages:
        # delete translations from the real catalog that are present in the
        # fake one
        fake_translations = fake_message_catalog.get_translations(message)
        translations = message_catalog.get_translations(message)
        for lang in fake_translations.keys():
          # XXX: should we check they're still the same before removing?
          translations.pop(lang, None)
4622 4623

  def export(self, context, bta, **kw):
4624
    if len(self._objects) == 0:
4625 4626 4627
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
4628
    for key, obj in self._objects.iteritems():
4629 4630
      path = os.path.join(root_path, key)
      bta.addFolder(name=path)
4631 4632 4633 4634 4635 4636 4637 4638
      if '/' in key:
        bta.addObject(obj, os.path.join(path, 'translation'), ext='.po')
      else:
        xml_data = ['<language>']
        xml_data.append(' <code>%s</code>' % (escape(key), ))
        xml_data.append(' <name>%s</name>' % (escape(obj), ))
        xml_data.append('</language>')
        bta.addObject('\n'.join(xml_data), os.path.join(path, 'language'))
4639 4640

  def _importFile(self, file_name, file):
4641 4642
    name = posixpath.split(file_name)[1]
    if name == 'translation.po':
4643 4644
      text = file.read()
      self._objects[file_name[:-3]] = text
4645 4646
    elif name == 'language.xml':
      xml = parse(file)
4647 4648
      name = xml.find('name').text
      code = xml.find('code').text
4649
      self._objects[code] = name
4650

Aurel's avatar
Aurel committed
4651 4652
class LocalRolesTemplateItem(BaseTemplateItem):

4653
  def __init__(self, id_list, **kw):
4654
    id_list = ['local_roles/%s' % id for id in id_list if id != '']
4655 4656
    BaseTemplateItem.__init__(self, id_list, **kw)

Aurel's avatar
Aurel committed
4657 4658 4659
  def build(self, context, **kw):
    p = context.getPortalObject()
    for path in self._archive.keys():
4660
      obj = p.unrestrictedTraverse(path.split('/', 1)[1])
4661 4662
      local_roles_dict = getattr(obj, '__ac_local_roles__',
                                        {}) or {}
4663
      self._objects[path] = (local_roles_dict, )
Aurel's avatar
Aurel committed
4664

Christophe Dumez's avatar
Christophe Dumez committed
4665
  # Function to generate XML Code Manually
Aurel's avatar
Aurel committed
4666
  def generateXml(self, path=None):
4667
    local_roles_dict = self._objects[path][0]
Aurel's avatar
Aurel committed
4668 4669
    # local roles
    xml_data = '<local_roles_item>'
4670
    xml_data += '\n <local_roles>'
4671
    for key in sorted(local_roles_dict):
4672
      xml_data += "\n  <role id='%s'>" %(key,)
Aurel's avatar
Aurel committed
4673 4674
      tuple = local_roles_dict[key]
      for item in tuple:
4675 4676 4677 4678
        xml_data += "\n   <item>%s</item>" %(item,)
      xml_data += '\n  </role>'
    xml_data += '\n </local_roles>'
    xml_data += '\n</local_roles_item>'
Aurel's avatar
Aurel committed
4679
    return xml_data
4680

Aurel's avatar
Aurel committed
4681 4682 4683 4684 4685 4686 4687 4688
  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)

4689
      folders, id = posixpath.split(key)
Aurel's avatar
Aurel committed
4690
      encode_folders = []
4691
      for folder in folders.split('/')[1:]:
Aurel's avatar
Aurel committed
4692
        if '%' not in folder:
4693
          encode_folders.append(quote(folder))
Aurel's avatar
Aurel committed
4694 4695
        else:
          encode_folders.append(folder)
4696
      path = os.path.join(root_path, (os.sep).join(encode_folders))
Aurel's avatar
Aurel committed
4697 4698 4699 4700
      bta.addFolder(name=path)
      bta.addObject(obj=xml_data, name=id, path=path)

  def _importFile(self, file_name, file):
4701 4702 4703
    if not file_name.endswith('.xml'):
      LOG('Business Template', 0, 'Skipping file "%s"' % (file_name, ))
      return
Aurel's avatar
Aurel committed
4704 4705
    xml = parse(file)
    # local roles
4706
    local_roles_list = xml.findall('//role')
Aurel's avatar
Aurel committed
4707 4708
    local_roles_dict = {}
    for role in local_roles_list:
4709 4710
      id = role.get('id')
      item_type_list = [item.text for item in role]
Aurel's avatar
Aurel committed
4711
      local_roles_dict[id] = item_type_list
4712
    self._objects['local_roles/%s' % (file_name[:-4],)] = (local_roles_dict, )
Aurel's avatar
Aurel committed
4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723

  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
4724 4725
        path = roles_path.split('/')[1:]
        obj = p.unrestrictedTraverse(path)
4726
        local_roles_dict = self._objects[roles_path][0]
4727
        setattr(obj, '__ac_local_roles__', local_roles_dict)
4728
        obj.reindexObject()
Aurel's avatar
Aurel committed
4729

4730
  def uninstall(self, context, object_path=None, **kw):
Aurel's avatar
Aurel committed
4731
    p = context.getPortalObject()
4732 4733 4734 4735 4736
    if object_path is not None:
      keys = [object_path]
    else:
      keys = self._objects.keys()
    for roles_path in keys:
Aurel's avatar
Aurel committed
4737 4738 4739
      path = roles_path.split('/')[1:]
      obj = p.unrestrictedTraverse(path)
      setattr(obj, '__ac_local_roles__', {})
4740
      obj.reindexObject()
4741

4742 4743 4744
class BusinessTemplate(XMLObject):
    """
    A business template allows to construct ERP5 modules
Christophe Dumez's avatar
Christophe Dumez committed
4745
    in part or completely. Each object is separated from its
4746 4747 4748 4749 4750 4751 4752 4753 4754 4755
    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
Christophe Dumez's avatar
Christophe Dumez committed
4756 4757
      - object without optimal actions
      - list of relation between portal type and workflow
4758 4759 4760 4761 4762 4763 4764 4765 4766 4767

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

    - site property definition
      - id
      - type
4768
      - value
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4769

4770 4771
    - document/propertysheet/extension/test definition
      - copy of the local file
4772

4773
    - message transalation definition
4774
      - .po file
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4775

4776 4777
    The Business Template properties are exported to the bt folder with
    one property per file
4778

Jean-Paul Smets's avatar
Jean-Paul Smets committed
4779 4780
    Technology:

4781 4782
    - 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
4783

4784
    - install files to the right location (install)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4785 4786 4787 4788

    Use case:

    - install core ERP5 (the minimum)
4789

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

4792 4793
    - go to "BT" menu. Create new BT.
      Define BT elements (workflow, methods, attributes, etc.).
4794
      Build BT and export or save it
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4795 4796 4797 4798 4799
      Done.
    """

    meta_type = 'ERP5 Business Template'
    portal_type = 'Business Template'
4800
    add_permission = Permissions.AddPortalContent
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4801 4802 4803

    # Declarative security
    security = ClassSecurityInfo()
4804
    security.declareObjectProtected(Permissions.AccessContentsInformation)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4805 4806 4807 4808

    # Declarative properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
4809
                      , PropertySheet.SimpleItem
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4810
                      , PropertySheet.CategoryCore
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4811
                      , PropertySheet.Version
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4812
                      , PropertySheet.BusinessTemplate
4813
                      , PropertySheet.Comment
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4814 4815
                      )

4816 4817 4818 4819 4820 4821
    # 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."""
4822
         , 'icon'           : 'file_icon.gif'
4823 4824 4825 4826 4827 4828 4829 4830 4831
         , 'product'        : 'ERP5Type'
         , 'factory'        : 'addBusinessTemplate'
         , 'immediate_view' : 'BusinessTemplate_view'
         , 'allow_discussion'     : 1
         , 'allowed_content_types': (
                                      )
         , 'filter_content_types' : 1
      }

4832 4833
    # This is a global variable
    # Order is important for installation
4834 4835 4836 4837 4838 4839
    # We want to have:
    #  * path after module, because path can be module content
    #  * path after categories, because path can be categories content
    #  * skin after paths, because we can install a custom connection string as
    #       path and use it with SQLMethods in a skin.
    #    ( and more )
4840 4841
    _item_name_list = [
      '_product_item',
4842
      '_property_sheet_item',
4843
      '_constraint_item',
4844 4845 4846 4847
      '_document_item',
      '_extension_item',
      '_test_item',
      '_role_item',
Yoshinori Okuji's avatar
Yoshinori Okuji committed
4848
      '_tool_item',
4849 4850 4851 4852
      '_message_translation_item',
      '_workflow_item',
      '_site_property_item',
      '_portal_type_item',
4853
      '_portal_type_workflow_chain_item',
4854 4855 4856 4857
      '_portal_type_allowed_content_type_item',
      '_portal_type_hidden_content_type_item',
      '_portal_type_property_sheet_item',
      '_portal_type_base_category_item',
4858 4859
      '_category_item',
      '_module_item',
4860
      '_path_item',
4861
      '_skin_item',
4862
      '_registered_skin_selection_item',
4863
      '_preference_item',
4864
      '_action_item',
4865
      '_portal_type_roles_item',
4866
      '_local_roles_item',
4867
      '_catalog_method_item',
4868 4869 4870
      '_catalog_result_key_item',
      '_catalog_related_key_item',
      '_catalog_result_table_item',
4871
      '_catalog_keyword_key_item',
4872
      '_catalog_datetime_key_item',
4873 4874 4875 4876
      '_catalog_full_text_key_item',
      '_catalog_request_key_item',
      '_catalog_multivalue_key_item',
      '_catalog_topic_key_item',
4877
      '_catalog_scriptable_key_item',
4878 4879
      '_catalog_role_key_item',
      '_catalog_local_role_key_item',
4880 4881 4882 4883
    ]

    def __init__(self, *args, **kw):
      XMLObject.__init__(self, *args, **kw)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
4884 4885 4886 4887 4888 4889
      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
4890
        self._baseGetTemplateFormatVersion()
4891

Yoshinori Okuji's avatar
Yoshinori Okuji committed
4892 4893 4894
      # the attribute _objects in BaseTemplateItem was added in the new format.
      if hasattr(self._path_item, '_objects'):
        return 1
4895

Yoshinori Okuji's avatar
Yoshinori Okuji committed
4896
      return 0
4897

4898
    security.declareProtected(Permissions.ManagePortal, 'manage_afterAdd')
4899 4900 4901 4902 4903 4904 4905
    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".
4906 4907
        if portal_workflow.getStatusOf(
                'business_template_installation_workflow', self) is not None:
4908
          # XXX Not good to access the attribute directly,
4909 4910 4911
          # but there is no API for clearing the history.
          self.workflow_history[
                            'business_template_installation_workflow'] = None
4912

4913 4914
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getRevision')
4915 4916 4917 4918 4919 4920
    def getRevision(self):
      """returns the revision property.
      This is a workaround for #461.
      """
      return self._baseGetRevision()

4921
    def updateRevisionNumber(self):
Jérome Perrin's avatar
Jérome Perrin committed
4922 4923 4924 4925 4926 4927 4928 4929
        """Increment bt revision number.
        """
        revision_number = self.getRevision()
        if revision_number is None or revision_number.strip() == '':
          revision_number = 1
        else:
          revision_number = int(revision_number)+1
        self.setRevision(revision_number)
4930

4931 4932
    security.declareProtected(Permissions.ManagePortal, 'storeTemplateItemData')
    def storeTemplateItemData(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4933
      """
4934
        Instanciate and Store Template items into properties.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4935
      """
4936
      # Store all Data
4937 4938
      self._portal_type_item = \
          PortalTypeTemplateItem(self.getTemplatePortalTypeIdList())
4939
      self._portal_type_workflow_chain_item = \
4940
          PortalTypeWorkflowChainTemplateItem(self.getTemplatePortalTypeWorkflowChainList())
4941 4942 4943 4944
      self._workflow_item = \
          WorkflowTemplateItem(self.getTemplateWorkflowIdList())
      self._skin_item = \
          SkinTemplateItem(self.getTemplateSkinIdList())
4945 4946 4947
      self._registered_skin_selection_item = \
          RegisteredSkinSelectionTemplateItem(
              self.getTemplateRegisteredSkinSelectionList())
4948 4949 4950 4951 4952 4953
      self._category_item = \
          CategoryTemplateItem(self.getTemplateBaseCategoryList())
      self._catalog_method_item = \
          CatalogMethodTemplateItem(self.getTemplateCatalogMethodIdList())
      self._action_item = \
          ActionTemplateItem(self.getTemplateActionPathList())
4954
      self._portal_type_roles_item = \
4955
          PortalTypeRolesTemplateItem(self.getTemplatePortalTypeRoleList())
4956 4957 4958 4959 4960 4961 4962 4963
      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())
4964 4965
      self._constraint_item = \
          ConstraintTemplateItem(self.getTemplateConstraintIdList())
4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985
      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())
4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997
      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())
4998 4999
      self._path_item = \
               PathTemplateItem(self.getTemplatePathList())
5000 5001
      self._preference_item = \
               PreferenceTemplateItem(self.getTemplatePreferenceList())
5002 5003
      self._catalog_keyword_key_item = \
          CatalogKeywordKeyTemplateItem(
5004
               self.getTemplateCatalogKeywordKeyList())
5005 5006 5007
      self._catalog_datetime_key_item = \
          CatalogDateTimeKeyTemplateItem(
               self.getTemplateCatalogDatetimeKeyList())
5008 5009
      self._catalog_full_text_key_item = \
          CatalogFullTextKeyTemplateItem(
5010
               self.getTemplateCatalogFullTextKeyList())
5011 5012
      self._catalog_request_key_item = \
          CatalogRequestKeyTemplateItem(
5013
               self.getTemplateCatalogRequestKeyList())
5014 5015
      self._catalog_multivalue_key_item = \
          CatalogMultivalueKeyTemplateItem(
5016
               self.getTemplateCatalogMultivalueKeyList())
5017 5018 5019
      self._catalog_topic_key_item = \
          CatalogTopicKeyTemplateItem(
               self.getTemplateCatalogTopicKeyList())
Aurel's avatar
Aurel committed
5020 5021
      self._local_roles_item = \
          LocalRolesTemplateItem(
5022
               self.getTemplateLocalRoleList())
Yoshinori Okuji's avatar
Yoshinori Okuji committed
5023 5024 5025
      self._tool_item = \
          ToolTemplateItem(
               self.getTemplateToolIdList())
5026 5027 5028
      self._catalog_scriptable_key_item = \
          CatalogScriptableKeyTemplateItem(
               self.getTemplateCatalogScriptableKeyList())
5029 5030 5031 5032 5033 5034
      self._catalog_role_key_item = \
          CatalogRoleKeyTemplateItem(
               self.getTemplateCatalogRoleKeyList())
      self._catalog_local_role_key_item = \
          CatalogLocalRoleKeyTemplateItem(
               self.getTemplateCatalogLocalRoleKeyList())
5035

5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049
    security.declareProtected(Permissions.ManagePortal, 'build')
    def build(self, no_action=0):
      """
        Copy existing portal objects to self
      """
      if no_action: return
        # this is use at import of Business Template to get the status built
      # Make sure that everything is sane.
      self.clean()

      self.updateRevisionNumber()
      self._setTemplateFormatVersion(1)
      self.storeTemplateItemData()

5050 5051
      # Build each part
      for item_name in self._item_name_list:
5052 5053 5054 5055
        item = getattr(self, item_name)
        if self.getBtForDiff():
          item.is_bt_for_diff = 1
        item.build(self)
5056

5057
    build = WorkflowMethod(build)
5058 5059

    def publish(self, url, username=None, password=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5060
      """
5061
        Publish in a format or another
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5062
      """
5063
      return self.portal_templates.publish(self, url, username=username,
5064
                                           password=password)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5065

5066
    def update(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5067
      """
5068
        Update template: download new template definition
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5069
      """
5070
      return self.portal_templates.update(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5071

5072 5073 5074 5075 5076
    def isCatalogUpdatable(self):
      """
      Return if catalog will be updated or not by business template installation
      """
      catalog_method = getattr(self, '_catalog_method_item', None)
5077 5078 5079 5080 5081 5082 5083 5084 5085 5086
      default_catalog = self.getPortalObject().portal_catalog.getSQLCatalog()
      my_catalog = _getCatalogValue(self)
      if default_catalog is not None and my_catalog is not None \
             and catalog_method is not None and self.getTemplateFormatVersion() == 1:
        if default_catalog.getId() == my_catalog.getId():
          # It is needed to update the catalog only if the default SQLCatalog is modified.
          for method_id in catalog_method._objects.keys():
            if 'related' not in method_id:
              # must update catalog
              return True
5087 5088
      return False

5089
    def preinstall(self, check_dependencies=1, **kw):
5090 5091 5092
      """
        Return the list of modified/new/removed object between a Business Template
        and the one installed if exists
Aurel's avatar
Aurel committed
5093
      """
5094 5095 5096 5097 5098
      if check_dependencies:
        # required because in multi installation, dependencies has already
        # been checked before and it will failed here as dependencies can be
        # installed at the same time
        self.checkDependencies()
5099

5100 5101
      modified_object_list = {}
      bt_title = self.getTitle()
5102 5103 5104 5105 5106 5107 5108

      #  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)
5109 5110 5111
      if installed_bt is None:
        installed_bt_format = 0 # that will not check for modification
      else:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
5112
        installed_bt_format = installed_bt.getTemplateFormatVersion()
5113

5114
      # if reinstall business template, must compare to object in ZODB
5115
      # and not to those in the installed Business Template because it is itself.
Christophe Dumez's avatar
Christophe Dumez committed
5116
      # same if we make a diff and select only one business template
5117
      reinstall = 0
5118 5119
      if installed_bt == self:
        reinstall = 1
5120
        if self.portal_templates._getOb(INSTALLED_BT_FOR_DIFF, None) is None:
5121 5122
          bt2 = self.portal_templates.manage_clone(ob=installed_bt, 
                                                   id=INSTALLED_BT_FOR_DIFF)
5123 5124 5125 5126 5127 5128 5129
          # update portal types properties to get last modifications
          bt2.getPortalTypesProperties()
          bt2.edit(description='tmp bt generated for diff', bt_for_diff=1)
          bt2.build()
          installed_bt = bt2
        else:
          installed_bt = self.portal_templates._getOb(INSTALLED_BT_FOR_DIFF)
5130

Yoshinori Okuji's avatar
Yoshinori Okuji committed
5131
      new_bt_format = self.getTemplateFormatVersion()
5132 5133 5134 5135 5136 5137
      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:
5138
          item = getattr(self, item_name, None)
5139 5140 5141 5142
          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
5143

5144
      for item_name in self._item_name_list:
5145
        new_item = getattr(self, item_name, None)
5146
        installed_item = getattr(installed_bt, item_name, None)
5147
        if new_item is not None:
5148
          if installed_item is not None and hasattr(installed_item, '_objects'):
5149
            modified_object = new_item.preinstall(context=self, 
5150 5151
                                                  installed_item=installed_item,
                                                  installed_bt=installed_bt)
5152 5153 5154 5155 5156 5157 5158
            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:
5159
        self.portal_templates.manage_delObjects(ids=[INSTALLED_BT_FOR_DIFF])
5160

5161 5162
      return modified_object_list

5163 5164
    def _install(self, force=1, object_to_update=None, update_translation=0,
                 update_catalog=0, **kw):
5165
      """
Christophe Dumez's avatar
Christophe Dumez committed
5166
        Install a new Business Template, if force, all will be upgraded or installed
5167 5168
        otherwise depends of dict object_to_update
      """
5169 5170 5171 5172
      if object_to_update is not None:
        force=0
      else:
        object_to_update = {}
5173

5174 5175
      installed_bt = self.portal_templates.getInstalledBusinessTemplate(
                                                           self.getTitle())
5176 5177
      # When reinstalling, installation state should not change to replaced
      if installed_bt not in [None, self]:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
5178
        if installed_bt.getTemplateFormatVersion() == 0:
5179
          force = 1
5180
        installed_bt.replace(self)
5181

5182 5183 5184 5185
      trash_tool = getToolByName(self, 'portal_trash', None)
      if trash_tool is None and self.getTemplateFormatVersion() == 1:
        raise AttributeError, 'Trash Tool is not installed'

5186 5187 5188
      # Check the format of business template, if old, force install
      if self.getTemplateFormatVersion() == 0:
        force = 1
5189

5190 5191 5192
      if not force:
        self.checkDependencies()

5193
      site = self.getPortalObject()
5194 5195 5196
      from Products.ERP5.ERP5Site import ERP5Generator
      generator_class = getattr(site, '_generator_class', ERP5Generator)
      gen = generator_class()
5197 5198
      # update activity tool first if necessary
      if self.getTitle() == 'erp5_core' and self.getTemplateUpdateTool():
5199 5200 5201 5202 5203 5204
        if not site.hasObject('portal_activities'):
          create_activities = True
          LOG('Business Template', 0, 'Updating Activity Tool')
        else:
          create_activities = False
        gen.setupLastTools(site, update=1, create_activities=create_activities)
5205 5206
      if not force:
        if len(object_to_update) == 0:
5207 5208 5209 5210 5211
          # 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():
5212
            LOG('Business Template', 0, 'Updating Business Template Workflows')
5213
            gen.setupWorkflow(site)
5214
          return
5215

5216 5217
      # 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
5218
      if trash_tool is not None and (len(object_to_update) > 0 or len(self.portal_templates) > 1):
5219 5220 5221 5222 5223
        trashbin = trash_tool.newTrashBin(self.getTitle(), self)
      else:
        trashbin = None

      # Install everything
5224
      if len(object_to_update) or force:
5225 5226 5227
        for item_name in self._item_name_list:
          item = getattr(self, item_name, None)
          if item is not None:
5228 5229
            item.install(self, force=force, object_to_update=object_to_update, 
                               trashbin=trashbin, installed_bt=installed_bt)
5230

5231
      # update catalog if necessary
5232 5233
      if force and self.isCatalogUpdatable():
        update_catalog = 1
5234
      if update_catalog:
5235
        catalog = _getCatalogValue(self)
5236
        if (catalog is None) or (not site.isIndexable):
5237 5238 5239 5240 5241
          LOG('Business Template', 0, 'no SQL Catalog available')
          update_catalog = 0
        else:
          LOG('Business Template', 0, 'Updating SQL Catalog')
          catalog.manage_catalogClear()
5242

5243
      # get objects to remove
5244
      # do remove after because we may need backup object from installation
5245
      remove_object_dict = {}
5246 5247
      for path, action in object_to_update.iteritems():
        if action in ('remove', 'save_and_remove'):
5248
          remove_object_dict[path] = action
5249

5250
      # remove object from old business template
5251
      if len(remove_object_dict):
5252
        # XXX: this code assumes that there is an installed_bt
5253
        for item_name in reversed(installed_bt._item_name_list):
5254
          item = getattr(installed_bt, item_name, None)
5255
          if item is not None:
5256
            item.remove(self, remove_object_dict=remove_object_dict, trashbin=trashbin)
5257

5258 5259 5260 5261 5262 5263

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

5264
      # check if we have to update business template workflow
5265
      if self.getTitle() == 'erp5_core' and self.getTemplateUpdateBusinessTemplateWorkflow():
5266
        LOG('Business Template', 0, 'Updating Business Template Workflows')
5267 5268 5269 5270 5271
        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)

5272 5273
      # remove trashbin if empty
      if trashbin is not None:
5274
        if len(trashbin) == 0:
5275 5276
          trash_tool.manage_delObjects([trashbin.getId(),])

5277 5278
      if update_catalog:
        site.ERP5Site_reindexAll()
5279

5280 5281
      # Update translation table, in case we added new portal types or
      # workflow states.
5282 5283
      if update_translation:
        site.ERP5Site_updateTranslationTable()
5284

5285 5286
      # Clear cache to avoid reusing cached values with replaced objects.
      site.portal_caches.clearAllCache()
5287

5288
    security.declareProtected(Permissions.ManagePortal, 'install')
5289 5290 5291
    def install(self, **kw):
      """
        For install based on paramaters provided in **kw
5292
      """
5293
      return self._install(**kw)
5294

5295
    install = WorkflowMethod(install)
5296

5297
    security.declareProtected(Permissions.ManagePortal, 'reinstall')
5298
    def reinstall(self, **kw):
5299 5300 5301 5302
      """Reinstall Business Template.
      """
      return self._install(**kw)

5303
    reinstall = WorkflowMethod(reinstall)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5304

5305
    security.declareProtected(Permissions.ManagePortal, 'trash')
5306 5307
    def trash(self, new_bt, **kw):
      """
5308
        Trash unnecessary items before upgrading to a new business
5309
        template.
5310
        This is similar to uninstall, but different in that this does
5311
        not remove all items.
5312
      """
5313 5314
      # Trash everything
      for item_name in self._item_name_list[::-1]:
5315
        item = getattr(self, item_name, None)
5316 5317
        if item is not None:
          item.trash(
5318
                self,
5319
                getattr(new_bt, item_name))
5320

5321
    security.declareProtected(Permissions.ManagePortal, 'uninstall')
5322
    def uninstall(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5323
      """
5324
        For uninstall based on paramaters provided in **kw
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5325
      """
5326 5327 5328
      # Uninstall everything
      # Trash everything
      for item_name in self._item_name_list[::-1]:
5329
        item = getattr(self, item_name, None)
5330
        if item is not None:
5331
          item.uninstall(self, **kw)
5332
      # It is better to clear cache because the uninstallation of a
5333
      # template deletes many things from the portal.
Aurel's avatar
Aurel committed
5334
      self.getPortalObject().portal_caches.clearAllCache()
5335

5336 5337
    uninstall = WorkflowMethod(uninstall)

5338
    security.declareProtected(Permissions.ManagePortal, 'clean')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
5339
    def _clean(self):
5340
      """
5341
        Clean built information.
5342
      """
5343
      # First, remove obsolete attributes if present.
5344 5345 5346 5347
      for attr in ( '_action_archive',
                    '_document_archive',
                    '_extension_archive',
                    '_test_archive',
5348
                    '_module_archive',
5349 5350 5351
                    '_object_archive',
                    '_portal_type_archive',
                    '_property_archive',
5352
                    '_property_sheet_archive'):
5353 5354 5355
        if hasattr(self, attr):
          delattr(self, attr)
      # Secondly, make attributes empty.
5356 5357
      for item_name in self._item_name_list:
        item = setattr(self, item_name, None)
5358

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

5361
    security.declareProtected(Permissions.AccessContentsInformation,
5362
                              'getBuildingState')
5363
    def getBuildingState(self, default=None, id_only=1):
5364
      """
5365
        Returns the current state in building
5366
      """
5367
      portal_workflow = getToolByName(self, 'portal_workflow')
5368 5369
      wf = portal_workflow.getWorkflowById(
                          'business_template_building_workflow')
5370
      return wf._getWorkflowStateOf(self, id_only=id_only )
5371

5372
    security.declareProtected(Permissions.AccessContentsInformation,
5373
                              'getInstallationState')
5374
    def getInstallationState(self, default=None, id_only=1):
5375
      """
5376
        Returns the current state in installation
5377
      """
5378
      portal_workflow = getToolByName(self.getPortalObject(), 'portal_workflow')
5379 5380
      wf = portal_workflow.getWorkflowById(
                           'business_template_installation_workflow')
5381
      return wf._getWorkflowStateOf(self, id_only=id_only )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5382

Yoshinori Okuji's avatar
Yoshinori Okuji committed
5383 5384 5385 5386 5387 5388
    security.declareProtected(Permissions.AccessContentsInformation, 'toxml')
    def toxml(self):
      """
        Return this Business Template in XML
      """
      portal_templates = getToolByName(self, 'portal_templates')
5389
      export_string = portal_templates.manage_exportObject(
5390 5391
                                               id=self.getId(),
                                               toxml=1,
5392
                                               download=1)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
5393
      return export_string
5394

5395
    def _getOrderedList(self, id):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5396
      """
5397 5398
        We have to set this method because we want an
        ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5399
      """
5400 5401
      method_id = '_baseGet%sList' % convertToUpperCase(id)
      result = getattr(self, method_id)(())
5402 5403 5404 5405
      if result is None: result = ()
      if result != ():
        result = list(result)
        result.sort()
5406
        # XXX Why do we need to return a tuple ?
5407 5408
        result = tuple(result)
      return result
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5409

5410
    def getTemplateCatalogMethodIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5411
      """
5412 5413
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5414
      """
5415
      return self._getOrderedList('template_catalog_method_id')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5416

5417
    def getTemplateBaseCategoryList(self):
5418
      """
5419 5420
      We have to set this method because we want an
      ordered list
5421
      """
5422
      return self._getOrderedList('template_base_category')
5423

5424
    def getTemplateWorkflowIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5425
      """
5426 5427
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5428
      """
5429
      return self._getOrderedList('template_workflow_id')
5430

5431
    def getTemplatePortalTypeIdList(self):
5432
      """
5433 5434
      We have to set this method because we want an
      ordered list
5435
      """
5436
      return self._getOrderedList('template_portal_type_id')
5437

5438 5439 5440 5441 5442 5443 5444
    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
5445 5446 5447 5448 5449 5450 5451
    def getTemplatePathList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_path')

5452 5453 5454 5455 5456 5457 5458
    def getTemplatePreferenceList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_preference')

5459 5460 5461 5462 5463 5464
    def getTemplatePortalTypeAllowedContentTypeList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_portal_type_allowed_content_type')
5465

5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486
    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')

5487
    def getTemplateActionPathList(self):
5488
      """
5489 5490
      We have to set this method because we want an
      ordered list
5491
      """
5492
      return self._getOrderedList('template_action_path')
5493

5494
    def getTemplatePortalTypeRoleList(self):
5495 5496 5497 5498
      """
      We have to set this method because we want an
      ordered list
      """
5499
      return self._getOrderedList('template_portal_type_role')
5500

5501
    def getTemplateLocalRoleList(self):
5502 5503 5504 5505
      """
      We have to set this method because we want an
      ordered list
      """
5506
      return self._getOrderedList('template_local_role')
5507

5508
    def getTemplateSkinIdList(self):
5509
      """
5510 5511
      We have to set this method because we want an
      ordered list
5512
      """
5513
      return self._getOrderedList('template_skin_id')
5514

5515 5516 5517 5518 5519 5520 5521
    def getTemplateRegisteredSkinSelectionList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_registered_skin_selection')

5522
    def getTemplateModuleIdList(self):
5523
      """
5524 5525
      We have to set this method because we want an
      ordered list
5526
      """
5527
      return self._getOrderedList('template_module_id')
5528 5529 5530 5531 5532 5533 5534

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

Yoshinori Okuji's avatar
Yoshinori Okuji committed
5536 5537 5538 5539 5540 5541 5542
    def getTemplateToolIdList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_tool_id')

5543
    security.declareProtected(Permissions.ManagePortal, 'export')
Aurel's avatar
Aurel committed
5544 5545 5546 5547
    def export(self, path=None, local=0, **kw):
      """
        Export this Business Template
      """
5548
      if self.getBuildingState() != 'built':
5549
        raise TemplateConditionError, \
Christophe Dumez's avatar
Christophe Dumez committed
5550
              'Business Template must be built before export'
5551

Aurel's avatar
Aurel committed
5552 5553 5554 5555 5556 5557 5558
      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)

5559
      # export bt
5560
      bta.addFolder(path+os.sep+'bt')
Aurel's avatar
Aurel committed
5561
      for prop in self.propertyMap():
5562
        prop_type = prop['type']
Aurel's avatar
Aurel committed
5563
        id = prop['id']
5564
        if id in ('id', 'uid', 'rid', 'sid', 'id_group', 'last_id',
5565
                  'install_object_list_list', 'id_generator', 'bt_for_diff'):
5566
          continue
Aurel's avatar
Aurel committed
5567
        value = self.getProperty(id)
5568 5569
        if value is None:
          continue
5570
        if prop_type in ('text', 'string', 'int', 'boolean'):
5571
          bta.addObject(obj=value, name=id, path=path+os.sep+'bt', ext='')
5572
        elif prop_type in ('lines', 'tokens'):
5573
          bta.addObject(obj=str('\n').join(value), name=id,
5574
                        path=path+os.sep+'bt', ext='')
5575

Aurel's avatar
Aurel committed
5576 5577 5578
      # Export each part
      for item_name in self._item_name_list:
        getattr(self, item_name).export(context=self, bta=bta)
5579

5580
      return bta.finishCreation(self.getTitle())
Aurel's avatar
Aurel committed
5581

5582
    security.declareProtected(Permissions.ManagePortal, 'importFile')
Aurel's avatar
Aurel committed
5583 5584
    def importFile(self, dir = 0, file=None, root_path=None):
      """
5585
        Import all xml files in Business Template
Aurel's avatar
Aurel committed
5586 5587 5588 5589 5590
      """
      if dir:
        bta = BusinessTemplateFolder(importing=1, file=file, path=root_path)
      else:
        bta = BusinessTemplateTarball(importing=1, file=file)
5591 5592

      self.storeTemplateItemData()
5593

5594 5595
      # Create temporary modules/classes for classes defined by this BT.
      # This is required if the BT contains instances of one of these classes.
5596
      orig_module_dict = {}
5597
      instance_oid_list = []
5598 5599
      for template_id in self.getTemplateDocumentIdList():
          module_id = 'Products.ERP5Type.Document.' + template_id
5600
          orig_module_dict[module_id] = sys.modules.get(module_id)
5601 5602 5603 5604 5605 5606 5607 5608 5609 5610
          # Always redefine the module, so that 'instance_oid_list' contains
          # the full list of oid to remove from pickle cache.
          sys.modules[module_id] = module = imp.new_module(module_id)
          module.SimpleItem = SimpleItem.SimpleItem
          module.instance_oid_list = instance_oid_list
          exec """class %s(SimpleItem):
            def __setstate__(self, state):
              instance_oid_list.append(self._p_oid)
              return SimpleItem.__setstate__(self, state)""" % template_id \
            in module.__dict__
5611

Aurel's avatar
Aurel committed
5612 5613
      for item_name in self._item_name_list:
        getattr(self, item_name).importFile(bta)
5614

5615 5616 5617 5618 5619 5620
      if instance_oid_list:
        # If a temporary class was used, we must force all instances using it
        # to be reloaded (i.e. unpickle) on next access (at installation).
        # Doing a savepoint will pickle them to a temporary storage so that all
        # references to it can be freed.
        transaction.savepoint(optimistic=True)
5621 5622
        self._p_jar.cacheMinimize()
        gc.collect()
5623

5624 5625
      # Remove temporary modules created above to allow import of real modules
      # (during the installation).
5626 5627 5628 5629 5630 5631
      # Restore original module if any, in case the new one is not installed.
      for module_id, module in orig_module_dict.iteritems():
        if module is None:
          del sys.modules[module_id]
        else:
          sys.modules[module_id] = module
5632

5633 5634 5635 5636 5637
    def getItemsList(self):
      """Return list of items in business template
      """
      items_list = []
      for item_name in self._item_name_list:
5638 5639 5640
        item = getattr(self, item_name, None)
        if item is not None:
          items_list.extend(item.getKeys())
5641
      return items_list
5642

5643 5644 5645 5646 5647 5648 5649 5650 5651 5652
    def checkDependencies(self):
      """
       Check if all the dependencies of the business template
       are installed. Raise an exception with the list of
       missing dependencies if some are missing
      """
      missing_dep_list = []
      dependency_list = self.getDependencyList()
      if len(dependency_list)!=0:
        for dependency_couple in dependency_list:
5653
          dependency_couple_list = dependency_couple.strip().split(' ', 1)
5654
          dependency = dependency_couple_list[0]
5655 5656
          if dependency in (None, ''):
            continue
5657 5658
          version_restriction = None
          if len(dependency_couple_list) > 1:
5659
            version_restriction = dependency_couple_list[1]
5660 5661 5662
            if version_restriction.startswith('('):
              # Something like "(>= 1.0rc6)".
              version_restriction = version_restriction[1:-1]
5663 5664 5665
          installed_bt = self.portal_templates.getInstalledBusinessTemplate(dependency)
          if (not self.portal_templates.IsOneProviderInstalled(dependency)) \
             and ((installed_bt is None) \
5666
                  or (version_restriction not in (None, '') and
5667 5668 5669 5670
                     (not self.portal_templates.compareVersionStrings(installed_bt.getVersion(), version_restriction)))):
            missing_dep_list.append((dependency, version_restriction or ''))
      if len(missing_dep_list) != 0:
        raise BusinessTemplateMissingDependency, 'Impossible to install, please install the following dependencies before: %s'%repr(missing_dep_list)
5671

5672 5673 5674 5675 5676 5677 5678 5679
    def diffObjectAsHTML(self, REQUEST, **kw):
      """
        Convert diff into a HTML format before reply
        This is compatible with ERP5Subversion look and feel but
        it is preferred in future we use more difflib python library.
      """
      return DiffFile(self.diffObject(REQUEST, **kw)).toHTML()

Aurel's avatar
Aurel committed
5680
    def diffObject(self, REQUEST, **kw):
5681 5682
      """
        Make a diff between an object in the Business Template
5683
        and the same in the Business Template installed in the site
5684 5685 5686 5687
      """

      class_name_dict = {
        'Product' : '_product_item',
5688
        'PropertySheet' : '_property_sheet_item',
5689
        'Constraint' : '_constraint_item',
5690 5691 5692 5693 5694 5695 5696 5697 5698
        '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',
5699
        'PortalTypeWorkflowChain' : '_portal_type_workflow_chain_item',
5700 5701 5702 5703
        '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',
5704 5705 5706
        'Category' : '_category_item',
        'Module' : '_module_item',
        'Skin' : '_skin_item',
5707
        'RegisteredSkinSelection' : '_registered_skin_selection_item',
5708
        'Path' : '_path_item',
5709
        'Preference' : '_preference_item',
5710
        'Action' : '_action_item',
5711
        'PortalTypeRoles' : '_portal_type_roles_item',
Aurel's avatar
Aurel committed
5712
        'LocalRoles' : '_local_roles_item',
5713 5714 5715
        'CatalogResultKey' : '_catalog_result_key_item',
        'CatalogRelatedKey' : '_catalog_related_key_item',
        'CatalogResultTable' : '_catalog_result_table_item',
5716
        'CatalogKeywordKey' : '_catalog_keyword_key_item',
5717
        'CatalogDateTimeKey' : '_catalog_datetime_key_item',
5718 5719 5720 5721
        'CatalogFullTextKey' : '_catalog_full_text_key_item',
        'CatalogRequestKey' : '_catalog_request_key_item',
        'CatalogMultivalueKey' : '_catalog_multivalue_key_item',
        'CatalogTopicKey' : '_catalog_topic_key_item',
Yoshinori Okuji's avatar
Yoshinori Okuji committed
5722
        'Tool': '_tool_item',
5723
        'CatalogScriptableKey' : '_catalog_scriptable_key_item',
5724 5725
        'CatalogRoleKey' : '_catalog_role_key_item',
        'CatalogLocalRoleKey' : '_catalog_local_role_key_item',
5726
        }
5727

5728 5729
      object_id = REQUEST.object_id
      object_class = REQUEST.object_class
5730

Christophe Dumez's avatar
Christophe Dumez committed
5731
      # Get objects
5732
      item_name = class_name_dict[object_class]
Aurel's avatar
Aurel committed
5733

5734
      new_bt = self
Christophe Dumez's avatar
Christophe Dumez committed
5735
      # Compare with a given business template
5736
      compare_to_zodb = 0
Aurel's avatar
Aurel committed
5737 5738
      bt2_id = kw.get('compare_with', None)
      if bt2_id is not None:
5739 5740
        if bt2_id == self.getId():
          compare_to_zodb = 1
5741
          installed_bt = self.getInstalledBusinessTemplate(title=self.getTitle())
5742 5743
        else:
          installed_bt = self.portal_templates._getOb(bt2_id)
Aurel's avatar
Aurel committed
5744 5745 5746
      else:
        installed_bt = self.getInstalledBusinessTemplate(title=self.getTitle())
        if installed_bt == new_bt:
Aurel's avatar
Aurel committed
5747 5748
          compare_to_zodb = 1
      if compare_to_zodb:
5749 5750
        bt2 = self.portal_templates.manage_clone(ob=installed_bt, 
                                                 id=INSTALLED_BT_FOR_DIFF)
Christophe Dumez's avatar
Christophe Dumez committed
5751
        # Update portal types properties to get last modifications
Aurel's avatar
Aurel committed
5752 5753 5754
        bt2.getPortalTypesProperties()
        bt2.edit(description='tmp bt generated for diff')
        installed_bt = bt2
5755

5756 5757
      new_item = getattr(new_bt, item_name)
      installed_item = getattr(installed_bt, item_name)
5758
      if compare_to_zodb:
Aurel's avatar
Aurel committed
5759
        # XXX maybe only build for the given object to gain time
5760
        installed_item.build(self)
5761 5762 5763
      new_object = new_item._objects[object_id]
      installed_object = installed_item._objects[object_id]
      diff_msg = ''
5764

Christophe Dumez's avatar
Christophe Dumez committed
5765
      # Real Zope Objects (can be exported into XML directly by Zope)
5766 5767
      # XXX Bad naming
      item_list_1 = ['_product_item', '_workflow_item', '_portal_type_item',
5768
                     '_category_item', '_path_item', '_preference_tem',
Yoshinori Okuji's avatar
Yoshinori Okuji committed
5769
                     '_skin_item', '_action_item', '_tool_item', ]
5770

Christophe Dumez's avatar
Christophe Dumez committed
5771
      # Not considered as objects by Zope (will be exported into XML manually)
5772
      # XXX Bad naming
5773 5774 5775 5776
      item_list_2 = ['_site_property_item',
                     '_module_item',
                     '_catalog_result_key_item', 
                     '_catalog_related_key_item',
5777 5778
                     '_catalog_result_table_item',
                     '_catalog_keyword_key_item',
5779
                     '_catalog_datetime_key_item',
5780 5781 5782 5783
                     '_catalog_full_text_key_item',
                     '_catalog_request_key_item',
                     '_catalog_multivalue_key_item',
                     '_catalog_topic_key_item',
5784
                     '_catalog_scriptable_key_item',
5785 5786
                     '_catalog_role_key_item',
                     '_catalog_local_role_key_item',
5787 5788 5789 5790 5791 5792
                     '_portal_type_allowed_content_type_item',
                     '_portal_type_hidden_content_type_item',
                     '_portal_type_property_sheet_item',
                     '_portal_type_roles_item',
                     '_portal_type_base_category_item',
                     '_local_roles_item',
5793
                     '_portal_type_workflow_chain_item',]
5794

Christophe Dumez's avatar
Christophe Dumez committed
5795
      # Text objects (no need to export them into XML)
5796
      # XXX Bad naming
5797 5798
      item_list_3 = ['_document_item', '_property_sheet_item',
                     '_constraint_item', '_extension_item',
5799
                     '_test_item', '_message_translation_item',]
5800

5801
      if item_name in item_list_1:
Christophe Dumez's avatar
Christophe Dumez committed
5802 5803 5804
        f1 = StringIO() # for XML export of New Object
        f2 = StringIO() # For XML export of Installed Object
        # Remove unneeded properties
5805 5806
        new_object = new_item.removeProperties(new_object)
        installed_object = installed_item.removeProperties(installed_object)
5807
        # XML Export in memory
5808
        OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, f1)
5809 5810
        OFS.XMLExportImport.exportXML(installed_object._p_jar, 
                                      installed_object._p_oid, f2)
5811
        new_obj_xml = f1.getvalue()
5812
        f1.close()
5813 5814 5815 5816
        installed_obj_xml = f2.getvalue()
        f2.close()
        new_ob_xml_lines = new_obj_xml.splitlines()
        installed_ob_xml_lines = installed_obj_xml.splitlines()
5817
        # End of XML export
5818

5819
        # Diff between XML objects
5820 5821
        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:
Christophe Dumez's avatar
Christophe Dumez committed
5822
          diff_msg += '\n\nObject %s diff :\n' % (object_id,)
5823 5824 5825
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'
5826

5827
      elif item_name in item_list_2:
5828
        # Generate XML code manually
5829 5830
        new_obj_xml = new_item.generateXml(path= object_id)
        installed_obj_xml = installed_item.generateXml(path= object_id)
5831 5832
        new_obj_xml_lines = new_obj_xml.splitlines()
        installed_obj_xml_lines = installed_obj_xml.splitlines()
5833
        # End of XML Code Generation
5834

5835
        # Diff between XML objects
5836 5837
        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:
Christophe Dumez's avatar
Christophe Dumez committed
5838
          diff_msg += '\n\nObject %s diff :\n' % (object_id,)
5839 5840 5841
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'
5842

5843
      elif item_name in item_list_3:
5844
        # Diff between text objects
5845 5846 5847 5848
        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:
Christophe Dumez's avatar
Christophe Dumez committed
5849
          diff_msg += '\n\nObject %s diff :\n' % (object_id,)
5850 5851
          diff_msg += '\n'.join(diff_list)
        else:
5852
          diff_msg = 'No diff'
5853

5854
      else:
5855
        diff_msg += 'Unsupported file !'
Christophe Dumez's avatar
Christophe Dumez committed
5856

5857
      if compare_to_zodb:
5858
        self.portal_templates.manage_delObjects(ids=[INSTALLED_BT_FOR_DIFF])
5859

5860
      return diff_msg
5861

5862

5863 5864 5865 5866
    def getPortalTypesProperties(self, **kw):
      """
      Fill field about properties for each portal type
      """
5867 5868
      wtool = self.getPortalObject().portal_workflow
      ttool = self.getPortalObject().portal_types
5869 5870 5871 5872 5873 5874 5875
      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 [])
5876 5877
      bt_wf_chain_list = list(getattr(self, 'template_portal_type_workflow_chain', []) or [])

5878
      p = self.getPortalObject()
5879
      for id in bt_portal_types_id_list:
5880 5881
        portal_type = ttool.getTypeInfo(id)
        if portal_type is None:
5882
          continue
Julien Muchembled's avatar
Julien Muchembled committed
5883
        if portal_type.getRoleInformationList():
5884 5885
          if id not in bt_portal_type_roles_list:
            bt_portal_type_roles_list.append(id)
5886

5887 5888 5889 5890
        allowed_content_type_list = []
        hidden_content_type_list = []
        property_sheet_list = []
        base_category_list = []
5891
        action_list = []
5892 5893 5894 5895 5896 5897 5898
        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'):
5899
          base_category_list = portal_type.base_category_list
Julien Muchembled's avatar
Julien Muchembled committed
5900 5901
        for action in portal_type.getActionInformationList():
          action_list.append(action.getReference())
5902

5903 5904 5905 5906
        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)
5907

5908
        for h_id in hidden_content_type_list:
5909 5910 5911
          hidden_id = id+' | '+h_id
          if hidden_id not in bt_hidden_content_type_list:
            bt_hidden_content_type_list.append(hidden_id)
5912

5913 5914 5915 5916
        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)
5917

5918 5919 5920 5921
        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)
5922

5923 5924 5925 5926
        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)
5927

5928
        for workflow_id in [chain for chain in wtool.getChainFor(id)
5929
                                    if chain != '(Default)']:
5930 5931 5932
          wf_id = id+' | '+workflow_id
          if wf_id not in bt_wf_chain_list:
            bt_wf_chain_list.append(wf_id)
5933

5934 5935 5936 5937
      bt_allowed_content_type_list.sort()
      bt_hidden_content_type_list.sort()
      bt_property_sheet_list.sort()
      bt_base_category_list.sort()
5938
      bt_action_list.sort()
5939
      bt_wf_chain_list.sort()
5940

5941 5942 5943 5944 5945 5946 5947
      self.setProperty('template_portal_type_workflow_chain', bt_wf_chain_list)
      self.setProperty('template_portal_type_roles', bt_portal_type_roles_list)
      self.setProperty('template_portal_type_allowed_content_type', bt_allowed_content_type_list)
      self.setProperty('template_portal_type_hidden_content_type', bt_hidden_content_type_list)
      self.setProperty('template_portal_type_property_sheet', bt_property_sheet_list)
      self.setProperty('template_portal_type_base_category', bt_base_category_list)
      self.setProperty('template_action_path', bt_action_list)
5948 5949 5950 5951 5952 5953 5954 5955 5956
      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:
5957 5958
        raise TemplateConditionError, 'No module defined in business template'

5959 5960 5961 5962 5963 5964 5965 5966
      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:
5967
          return type_list
5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981

        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():
5982
            type_list.update(getChildPortalType(hidden_ptype_id))
5983
        return type_list
5984

5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031
      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

6032 6033 6034
# Block acquisition on all _item_name_list properties by setting
# a default class value to None
for key in BusinessTemplate._item_name_list:
6035
  setattr(BusinessTemplate, key, None)
6036

6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063
# 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)
6064

6065 6066 6067 6068 6069 6070 6071 6072

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

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