BusinessTemplate.py 185 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
4
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

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

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


82 83
# those attributes from CatalogMethodTemplateItem are kept for
# backward compatibility
Aurel's avatar
Aurel committed
84 85
catalog_method_list = ('_is_catalog_list_method_archive',
                       '_is_uncatalog_method_archive',
86 87
                       '_is_clear_method_archive',
                       '_is_filtered_archive',)
88

89 90 91
catalog_method_filter_list = ('_filter_expression_archive',
                              '_filter_expression_instance_archive',
                              '_filter_type_archive',)
92

93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
def removeAll(entry):
  '''
    Remove all files and directories under 'entry'.
    XXX: This is defined here, because os.removedirs() is buggy.
  '''
  try:
    if os.path.isdir(entry) and not os.path.islink(entry):
      pwd = os.getcwd()
      os.chmod(entry, 0755)
      os.chdir(entry)
      for e in os.listdir(os.curdir):
        removeAll(e)
      os.chdir(pwd)
      os.rmdir(entry)
    else:
      if not os.path.islink(entry):
        os.chmod(entry, 0644)
      os.remove(entry)
  except OSError:
    pass

Aurel's avatar
Aurel committed
114

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

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
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]

Aurel's avatar
Aurel committed
159 160 161
class BusinessTemplateArchive:
  """
    This is the base class for all Business Template archives
162
  """
Aurel's avatar
Aurel committed
163 164 165 166 167 168 169 170 171 172 173 174

  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
175

Aurel's avatar
Aurel committed
176 177 178 179 180
  def finishCreation(self, **kw):
    pass

class BusinessTemplateFolder(BusinessTemplateArchive):
  """
Christophe Dumez's avatar
Christophe Dumez committed
181
    Class archiving business template into a folder tree
182
  """
Aurel's avatar
Aurel committed
183 184 185 186 187 188
  def _initCreation(self, path):
    self.path = path
    try:
      os.makedirs(self.path)
    except OSError:
      # folder already exists, remove it
189
      removeAll(self.path)
Aurel's avatar
Aurel committed
190 191 192
      os.makedirs(self.path)

  def addFolder(self, name=''):
193
     if name !='':
Aurel's avatar
Aurel committed
194
      path = os.path.join(self.path, name)
195
      if not os.path.exists(path):
Aurel's avatar
Aurel committed
196 197 198
        os.makedirs(path)
      return path

199
  def addObject(self, obj, name, path=None, ext='.xml'):
200
    name = pathname2url(name)
Aurel's avatar
Aurel committed
201 202 203
    if path is None:
      object_path = os.path.join(self.path, name)
    else:
204 205
      if '%' not in path:
        path = pathname2url(path)
Aurel's avatar
Aurel committed
206 207
      object_path = os.path.join(path, name)
    f = open(object_path+ext, 'wt')
208
    f.write(str(obj))
Aurel's avatar
Aurel committed
209 210 211
    f.close()

  def _initImport(self, file=None, path=None, **kw):
212 213 214
    # Normalize the paths to eliminate the effect of double-slashes.
    self.file_list = [os.path.normpath(f) for f in file]
    path = os.path.normpath(path)
215
    # to make id consistent, must remove a part of path while importing
216
    self.root_path_len = len(string.split(path, os.sep)) + 1
Aurel's avatar
Aurel committed
217

218
  def importFiles(self, klass, **kw):
Aurel's avatar
Aurel committed
219 220 221 222
    """
      Import file from a local folder
    """
    class_name = klass.__class__.__name__
223
    for file_path in self.file_list:
224
      if class_name in file_path.split(os.sep):
225 226
        if os.path.isfile(file_path):
          file = open(file_path, 'r')
Aurel's avatar
Aurel committed
227
          # get object id
228 229
          folders = file_path.split(os.sep)
          file_name = string.join(folders[self.root_path_len:], os.sep)
230 231
          if '%' in file_name:
            file_name = url2pathname(file_name)
232
          klass._importFile(file_name, file)
Aurel's avatar
Aurel committed
233
          # close file
234
          file.close()
235

Aurel's avatar
Aurel committed
236 237 238 239 240 241
class BusinessTemplateTarball(BusinessTemplateArchive):
  """
    Class archiving businnes template into a tarball file
  """

  def _initCreation(self, path):
242
    # make tmp dir, must use stringIO instead
Aurel's avatar
Aurel committed
243 244 245 246 247
    self.path = path
    try:
      os.makedirs(self.path)
    except OSError:
      # folder already exists, remove it
248
      removeAll(self.path)
Aurel's avatar
Aurel committed
249 250 251 252 253 254
      os.makedirs(self.path)
    # init tarfile obj
    self.fobj = StringIO()
    self.tar = tarfile.open('', 'w:gz', self.fobj)

  def addFolder(self, name=''):
Aurel's avatar
Aurel committed
255
    if not os.path.exists(name):
Aurel's avatar
Aurel committed
256 257
      os.makedirs(name)

258
  def addObject(self, obj, name, path=None, ext='.xml'):
259
    name = pathname2url(name)
Aurel's avatar
Aurel committed
260 261 262
    if path is None:
      object_path = os.path.join(self.path, name)
    else:
263 264
      if '%' not in path:
        path = pathname2url(path)
Aurel's avatar
Aurel committed
265 266
      object_path = os.path.join(path, name)
    f = open(object_path+ext, 'wt')
267
    f.write(str(obj))
Aurel's avatar
Aurel committed
268 269 270 271 272
    f.close()

  def finishCreation(self):
    self.tar.add(self.path)
    self.tar.close()
273
    removeAll(self.path)
Aurel's avatar
Aurel committed
274 275 276 277 278
    return self.fobj

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

279
  def importFiles(self, klass, **kw):
Aurel's avatar
Aurel committed
280 281
    """
      Import all file from the archive to the site
282
    """
Aurel's avatar
Aurel committed
283 284 285 286 287 288
    class_name = klass.__class__.__name__
    self.f.seek(0)
    data = GzipFile(fileobj=self.f).read()
    io = StringIO(data)
    tar = tarfile.TarFile(fileobj=io)
    for info in tar.getmembers():
Yoshinori Okuji's avatar
Yoshinori Okuji committed
289 290
      if 'CVS' in info.name.split('/'):
        continue
Yoshinori Okuji's avatar
Yoshinori Okuji committed
291 292
      if '.svn' in info.name.split('/'):
        continue
293
      if class_name in info.name.split('/'):
Aurel's avatar
Aurel committed
294 295
        if info.isreg():
          file = tar.extractfile(info)
296 297 298
          tar_file_name = info.name.startswith('./') and info.name[2:] or \
              info.name
          folders = string.split(tar_file_name, os.sep)
299 300 301 302
          file_name = (os.sep).join(folders[2:])
          if '%' in file_name:
            file_name = url2pathname(file_name)
          klass._importFile(file_name, file)
Aurel's avatar
Aurel committed
303 304 305
          file.close()
    tar.close()
    io.close()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
306

307
class TemplateConditionError(Exception): pass
308
class TemplateConflictError(Exception): pass
309
class BusinessTemplateMissingDependency(Exception): pass
310

311
class BaseTemplateItem(Implicit, Persistent):
312
  """
313
    This class is the base class for all template items.
314
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
315

316
  def __init__(self, id_list, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
317
    self.__dict__.update(kw)
318
    self._archive = PersistentMapping()
Aurel's avatar
Aurel committed
319
    self._objects = PersistentMapping()
320
    for id in id_list:
321 322
      if id is not None and id != '':
        self._archive[id] = None
323 324 325 326

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

327
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
328 329
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
330
      new_keys = self._objects.keys()
331
      for path in new_keys:
332 333
        if installed_bt._objects.has_key(path):
          # compare object to see it there is changes
334 335
          new_obj_xml = self.generateXml(path=path)
          old_obj_xml = installed_bt.generateXml(path=path)
336 337 338 339 340 341 342 343 344 345 346 347
          if new_obj_xml != old_obj_xml:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
348
    pass
349 350 351

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

353 354 355 356
  def remove(self, context, **kw):
    remove_dict = kw.get('remove_object_dict', {})
    keys = self._objects.keys()
    keys.sort()
357
    keys.reverse()
358 359 360 361 362 363 364 365 366 367 368
    # 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)
369

370

371 372 373 374
  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
375
  def export(self, context, bta, **kw):
376
    pass
Aurel's avatar
Aurel committed
377

378 379
  def getKeys(self):
    return self._objects.keys()
380

Aurel's avatar
Aurel committed
381
  def importFile(self, bta, **kw):
382
    bta.importFiles(klass=self)
383

384 385 386
  def removeProperties(self, obj):
    """
    Remove unneeded properties for export
387
    """
388 389
    if hasattr(aq_base(obj), '_dav_writelocks'):
      del aq_base(obj)._dav_writelocks
390 391 392 393 394 395 396
    if hasattr(obj, '__ac_local_roles__'):
      # remove local roles
      obj.__ac_local_roles__ = None
    if hasattr(obj, '_owner'):
      obj._owner = None
    if hasattr(aq_base(obj), 'uid'):
      obj.uid = None
397 398
    if hasattr(aq_base(obj), '_filepath'):
      obj._filepath = None
399
    if hasattr(aq_base(obj), 'workflow_history'):
400 401 402 403
      if hasattr(obj.__class__, 'workflow_history'):
        obj.workflow_history = None
      else:
        del obj.workflow_history
404 405 406
    if getattr(obj, 'meta_type', None) == 'Script (Python)':
      if hasattr(aq_base(obj), '_code'):
        obj._code = None
407 408
      if hasattr(aq_base(obj), 'Python_magic'):
        obj.Python_magic = None
409 410 411
    elif getattr(obj, 'meta_type', None) == 'ERP5 PDF Form' :
      if not obj.getProperty('business_template_include_content', 1) :
        obj.deletePdfContent()
412 413
    return obj

414 415 416
class ObjectTemplateItem(BaseTemplateItem):
  """
    This class is used for generic objects and as a subclass.
417
  """
418

419 420 421 422
  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()
423
      self._archive.clear()
424 425 426
      for id in id_list :
        if id != '':
          self._archive["%s/%s" % (tool_id, id)] = None
427

428 429 430 431 432
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    for key in self._objects.keys():
433
      obj = self._objects[key]
434 435
      # create folder and subfolders
      folders, id = os.path.split(key)
Aurel's avatar
Aurel committed
436 437 438 439 440 441 442
      encode_folders = []
      for folder in folders.split('/'):
        if '%' not in folder:
          encode_folders.append(pathname2url(folder))
        else:
          encode_folders.append(folder)
      path = os.path.join(root_path, (os.sep).join(encode_folders))
443 444 445
      bta.addFolder(name=path)
      # export object in xml
      f=StringIO()
446 447
      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
      bta.addObject(obj=f.getvalue(), name=id, path=path)
448

Aurel's avatar
Aurel committed
449 450 451 452
  def build_sub_objects(self, context, id_list, url, **kw):
    p = context.getPortalObject()
    sub_list = {}
    for id in id_list:
453
      relative_url = '/'.join([url,id])
454 455
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
456
      obj = self.removeProperties(obj)
457
      id_list = obj.objectIds()
458
      if hasattr(aq_base(obj), 'groups'):
Aurel's avatar
Aurel committed
459
        # we must keep groups because it's ereased when we delete subobjects
460
        groups = deepcopy(obj.groups)
461
      if id_list:
Aurel's avatar
Aurel committed
462
        self.build_sub_objects(context, id_list, relative_url)
463
        for id_ in list(id_list):
464
          obj._delObject(id_)
465
      if hasattr(aq_base(obj), 'groups'):
466 467 468
        obj.groups = groups
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
Aurel's avatar
Aurel committed
469 470
    return sub_list

471 472 473 474
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
475 476
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
477
      obj = self.removeProperties(obj)
478
      id_list = obj.objectIds()
479
      if hasattr(aq_base(obj), 'groups'):
Christophe Dumez's avatar
Christophe Dumez committed
480
        # we must keep groups because it's erased when we delete subobjects
481
        groups = deepcopy(obj.groups)
Aurel's avatar
Aurel committed
482 483
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
484
        for id_ in list(id_list):
485
          obj._delObject(id_)
486
      if hasattr(aq_base(obj), 'groups'):
487 488 489
        obj.groups = groups
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
490

491 492 493 494 495 496 497
  def _importFile(self, file_name, file):
    # import xml file
    obj = self
    connection = None
    while connection is None:
      obj=obj.aq_parent
      connection=obj._p_jar
498 499
    __traceback_info__ = 'Importing %s' % file_name
    obj = connection.importFile(file, customImporters=customImporters)
500 501
    self._objects[file_name[:-4]] = obj

502
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
503 504
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
505 506 507 508 509 510 511
      portal = context.getPortalObject()
      new_keys = self._objects.keys()
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see it there is changes
          new_object = self._objects[path]
          old_object = installed_bt._objects[path]
512 513
          new_object = self.removeProperties(new_object)
          old_object = self.removeProperties(old_object)
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
          new_io = StringIO()
          old_io = StringIO()
          OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, new_io)
          OFS.XMLExportImport.exportXML(old_object._p_jar, old_object._p_oid, old_io)
          new_obj_xml = new_io.getvalue()
          old_obj_xml = old_io.getvalue()
          new_io.close()
          old_io.close()
          if new_obj_xml != old_obj_xml:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

533
  def _backupObject(self, action, trashbin, container_path, object_id, **kw):
534 535 536
    """
      Backup the object in portal trash if necessery and return its subobjects
    """
537
    subobjects_dict = {}
538
    if trashbin is None: # must return subobjects
539 540 541 542 543 544
      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)
545
        subobjects_dict[subobject_id] = subobject_copy
546
      return subobjects_dict
547 548
    # XXX btsave is for backward compatibility
    if action == 'backup' or action == 'btsave':
549
      subobjects_dict = self.portal_trash.backupObject(trashbin, container_path, object_id, save=1, **kw)
550
    elif action == 'install':
551
      subobjects_dict = self.portal_trash.backupObject(trashbin, container_path, object_id, save=0, **kw)
552
    return subobjects_dict
553

554 555 556
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
557
    if context.getTemplateFormatVersion() == 1:
558
      groups = {}
559
      old_groups = {}
560 561 562 563 564
      portal = context.getPortalObject()
      # sort to add objects before their subobjects
      keys = self._objects.keys()
      keys.sort()
      for path in keys:
565 566 567 568 569 570
        if update_dict.has_key(path) or force:
          # get action for the oject
          if not force:
            action = update_dict[path]
            if action == 'nothing':
              continue
571
          action = 'backup'
572
          # get subobjects in path
573 574 575
          path_list = path.split('/')
          container_path = path_list[:-1]
          object_id = path_list[-1]
576 577 578 579
          try:
            container = portal.unrestrictedTraverse(container_path)
          except KeyError:
            # parent object can be set to nothing, in this case just go on
580
            container_url = '/'.join(container_path)
581 582 583 584 585 586 587 588 589 590 591 592
            if update_dict.get(container_url) == 'nothing':
              continue
            # If container's container is portal_catalog,
            # then automatically create the container.
            elif container_path[-2] == 'portal_catalog':
              container_container = portal.unrestrictedTraverse(container_path[:-1])
              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)
            else:
              raise
593 594 595
          container_ids = container.objectIds()
          subobjects_dict = {}
          # Object already exists
596
          if object_id in container_ids:     
597
            old_obj = container._getOb(object_id)
598 599 600 601 602 603
            if hasattr(aq_base(old_obj), 'groups'):
              # 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)
604
            subobjects_dict = self._backupObject(action, trashbin,
605
                                                 container_path, object_id)
606 607
            container.manage_delObjects([object_id])
          # install object
608
          obj = self._objects[path]
609 610 611
          if getattr(obj, 'meta_type', None) == 'Script (Python)':
            if getattr(obj, '_code') is None:
              obj._compile()
612
          if hasattr(aq_base(obj), 'groups'):
613
            # we must keep original order groups
614
            # because they change when we add subobjects
615
            groups[path] = deepcopy(obj.groups)
616
          # copy the object
617 618 619 620 621
          obj = obj._getCopy(container)
          container._setObject(object_id, obj)
          obj = container._getOb(object_id)
          obj.manage_afterClone(obj)
          obj.wl_clearLocks()
622
          # if portal types upgrade, set backup properties
623 624
          if getattr(obj, 'meta_type', None) == 'ERP5 Type Information' and \
              len(subobjects_dict) > 0:
625
            setattr(obj, 'allowed_content_types',
626
                    subobjects_dict['allowed_content_type_list'] or [])
627
            setattr(obj, 'hidden_content_type_list',
628
                    subobjects_dict['hidden_content_type_list'] or [])
629
            setattr(obj, 'property_sheet_list',
630
                    subobjects_dict['property_sheet_list'] or [])
631
            setattr(obj, 'base_category_list',
632
                    subobjects_dict['base_category_list'] or [])
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
            setattr(obj, '_roles', subobjects_dict['roles_list'] or [])
            # set actions
            action_list = subobjects_dict['action_list']
            for action in action_list:
              obj.addAction(id = action.id
                            , name = action.title
                            , action = action.action.text
                            , condition = action.getCondition()
                            , permission = action.permissions
                            , category = action.category
                            , visible = action.visible
                            , icon = getattr(action, 'icon', None) and action.icon.text or ''
                            , priority = action.priority
                            )
            # set workflow chain
            wf_chain = subobjects_dict['workflow_chain']
            chain_dict = getChainByType(context)[1]
            default_chain = ''
651
            chain_dict['chain_%s' % (object_id)] = wf_chain
652
            context.portal_workflow.manage_changeWorkflows(default_chain, props=chain_dict)
653
          # import sub objects if there is
654
          elif len(subobjects_dict) > 0:
655
            # get a jar
656 657
            connection = obj._p_jar
            o = obj
658
            while connection is None:
659 660
              o = o.aq_parent
              connection = o._p_jar
661 662 663 664 665
            # import subobjects
            for subobject_id in subobjects_dict.keys():
              subobject_data = subobjects_dict[subobject_id]
              subobject_data.seek(0)
              subobject = connection.importFile(subobject_data)
666
              if subobject_id not in obj.objectIds():
667
                obj._setObject(subobject_id, subobject)
668
          if obj.meta_type in ('Z SQL Method',):
669
            fixZSQLMethod(portal, obj)
670
      # now put original order group
671 672
      # we remove object not added in forms
      # we put old objects we have kept
673
      for path in groups.keys():
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
        new_groups_dict = groups[path]        
        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
          # excetp the one that had to be removed          
          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'):
              continue              
            widget_in_form = 0            
            for group_id in new_groups_dict.keys():
              group_values = new_groups_dict[group_id]
              if widget_id in group_values:
                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:
              for old_group_id in old_groups_dict.keys():
                old_group_values = old_groups_dict[old_group_id]
                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,]
713
                  obj.group_list = list(obj.group_list) + ['not_assigned']
714 715 716 717 718 719 720 721
          # second check all widget_id in order are in form
          for group_id in new_groups_dict.keys():
            for widget_id in new_groups_dict[group_id]:
              if widget_id not in widget_id_list:
                # if we don't find the widget id in the form
                # remove it fro the group
                new_groups_dict[group_id].remove(widget_id)
          # now set new group object
722
          obj.groups = new_groups_dict
Aurel's avatar
Aurel committed
723
    else:
724 725
      # for old business template format
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
726
      portal = context.getPortalObject()
727
      for relative_url in self._archive.keys():
728
        obj = self._archive[relative_url]
Aurel's avatar
Aurel committed
729 730 731 732
        container_path = relative_url.split('/')[0:-1]
        object_id = relative_url.split('/')[-1]
        container = portal.unrestrictedTraverse(container_path)
        container_ids = container.objectIds()
733
        if object_id in container_ids:    # Object already exists
734
          self._backupObject('backup', trashbin, container_path, object_id)
735
          container.manage_delObjects([object_id])
Aurel's avatar
Aurel committed
736
        # Set a hard link
737 738 739 740 741 742
        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',):
743
          fixZSQLMethod(portal, obj)
744 745 746

  def uninstall(self, context, **kw):
    portal = context.getPortalObject()
747
    trash = kw.get('trash', 0)
748 749 750 751 752
    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
753
      object_keys = self._archive.keys()
754
    for relative_url in object_keys:
755 756
      container_path = relative_url.split('/')[0:-1]
      object_id = relative_url.split('/')[-1]
757
      try:
758
        container = portal.unrestrictedTraverse(container_path)
759
        if trash and trashbin is not None:
760 761
          self.portal_trash.backupObject(trashbin, container_path, object_id, save=1, keep_subobjects=1)
        container.manage_delObjects([object_id])
762
      except (NotFound, KeyError, BadRequest):
763
        # object is already backup and/or removed
764
        pass
765 766
    BaseTemplateItem.uninstall(self, context, **kw)

767 768 769 770 771 772 773 774 775 776 777 778
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

779 780 781 782 783 784 785 786 787
  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()
788 789
    object_keys.sort()
    object_keys.reverse()
790 791
    p = context.getPortalObject()
    for path in object_keys:
792 793 794 795
      path_list = self._resolvePath(p, [], path.split('/'))
      path_list.sort()
      path_list.reverse()
      for relative_url in path_list:
796
        try:
Aurel's avatar
Aurel committed
797 798
          container_path = relative_url.split('/')[0:-1]
          object_id = relative_url.split('/')[-1]
799 800
          container = portal.unrestrictedTraverse(container_path)
          if trash and trashbin is not None:
801 802
            self.portal_trash.backupObject(trashbin, container_path,
                                           object_id, save=1,
803
                                           keep_subobjects=1)
804 805 806 807 808 809
          container.manage_delObjects([object_id])
        except (NotFound, KeyError):
          # object is already backup and/or removed
          pass
    BaseTemplateItem.uninstall(self, context, **kw)

810 811 812
  def _resolvePath(self, folder, relative_url_list, id_list):
    """
      This method calls itself recursively.
813

814 815 816 817 818 819 820 821 822
      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.
823 824 825
      obj = folder._getOb(id, None)
      if obj is None:
        raise AttributeError, "Could not resolve '%s' during business template processing." % id
826
      return self._resolvePath(obj, relative_url_list + [id], id_list[1:])
827 828
    path_list = []
    for object_id in fnmatch.filter(folder.objectIds(), id):
829
      if object_id != "":
830
        path_list.extend(self._resolvePath(
831
            folder._getOb(object_id),
832
            relative_url_list + [object_id], id_list[1:]))
833
    return path_list
Aurel's avatar
Aurel committed
834

835 836 837
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
Aurel's avatar
Aurel committed
838
    keys = self._path_archive.keys()
839
    keys.sort()
Aurel's avatar
Aurel committed
840
    for path in keys:
841 842 843
      include_subobjects = 0
      if '**' in path:
        include_subobjects = 1
844
      for relative_url in self._resolvePath(p, [], path.split('/')):
845 846
        obj = p.unrestrictedTraverse(relative_url)
        obj = obj._getCopy(context)
847
        obj = obj.__of__(context)
848
        id_list = obj.objectIds()
849
        obj = self.removeProperties(obj)
850
        if hasattr(aq_base(obj), 'groups'):
851
          # we must keep groups because it's ereased when we delete subobjects
852
          groups = deepcopy(obj.groups)
853
        if len(id_list) > 0:
854 855
          if include_subobjects:
            self.build_sub_objects(context, id_list, relative_url)
856
          for id_ in list(id_list):
857
            obj._delObject(id_)
858
        if hasattr(aq_base(obj), 'groups'):
859 860 861
          obj.groups = groups
        self._objects[relative_url] = obj
        obj.wl_clearLocks()
862

863 864 865 866 867 868 869 870 871 872 873 874 875 876 877
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")
878
    if len(id_list) != 1:
879 880 881 882 883 884 885 886 887 888 889 890
      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():
891
      pref = portal.unrestrictedTraverse(object_path)
892
      # XXX getPreferenceState is a bad name
893 894 895
      if pref.getPreferenceState() == 'disabled':
        portal.portal_workflow.doActionFor(
                      pref,
896 897 898 899 900
                      'enable_action',
                      wf_id='preference_workflow',
                      comment="Initialized during Business Template " \
                              "installation.")

901 902
class CategoryTemplateItem(ObjectTemplateItem):

903 904
  def __init__(self, id_list, tool_id='portal_categories', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
905

906 907 908 909
  def build_sub_objects(self, context, id_list, url, **kw):
    p = context.getPortalObject()
    for id in id_list:
      relative_url = '/'.join([url,id])
910 911
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
912
      obj = self.removeProperties(obj)
913
      id_list = obj.objectIds()
914
      if id_list:
915
        self.build_sub_objects(context, id_list, relative_url)
916
        for id_ in list(id_list):
917
          obj._delObject(id_)
918 919
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
920 921 922 923 924

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
925 926
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
927
      obj = self.removeProperties(obj)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
928
      include_sub_categories = obj.__of__(context).getProperty('business_template_include_sub_categories', 0)
929
      id_list = obj.objectIds()
930 931
      if len(id_list) > 0 and include_sub_categories:
        self.build_sub_objects(context, id_list, relative_url)
932
        for id_ in list(id_list):
933
          obj._delObject(id_)
934
      else:
935
        for id_ in list(id_list):
936
          obj._delObject(id_)
937 938
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
939

940
  def install(self, context, trashbin, **kw):
941 942
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
943
    if context.getTemplateFormatVersion() == 1:
944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968
      portal = context.getPortalObject()
      category_tool = portal.portal_categories
      tool_id = self.tool_id
      keys = self._objects.keys()
      keys.sort()
      for path in keys:
        if update_dict.has_key(path) or force:
          if not force:
            action = update_dict[path]
            if action == 'nothing':
              continue
          else:
            action = 'backup'
          # Wrap the object by an aquisition wrapper for _aq_dynamic.
          obj = self._objects[path]
          obj = obj.__of__(category_tool)
          container_path = path.split('/')[:-1]
          category_id = path.split('/')[-1]
          try:
            container = category_tool.unrestrictedTraverse(container_path)
          except KeyError:
            # parent object can be set to nothing, in this case just go on
            container_url = '/'.join(container_path)
            if update_dict.has_key(container_url):
              if update_dict[container_url] == 'nothing':
969
                continue
970 971 972 973 974 975 976 977 978 979 980 981
            raise
          container_ids = container.objectIds()
          # Object already exists
          object_uid = None
          subobjects_dict = {}
          if category_id in container_ids:
            object_uid = container[category_id].getUid()
            subobjects_dict = self._backupObject(action, trashbin, container_path, category_id)
            container.manage_delObjects([category_id])
          category = container.newContent(portal_type=obj.getPortalType(), id=category_id)
          if object_uid is not None:
            category.setUid(object_uid)
982 983 984 985
          for prop in obj.propertyIds():
            if prop not in ('id', 'uid'):
              try:
                prop_value = obj.getProperty(prop, evaluate=0)
986
              except TypeError: # the getter doesn't support evaluate=
987 988
                prop_value = obj.getProperty(prop)
              category.setProperty(prop, prop_value)
989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
          # import sub objects if there is
          if len(subobjects_dict) > 0:
            # get a jar
            connection = obj._p_jar
            o = category
            while connection is None:
              o = o.aq_parent
              connection = o._p_jar
            # import subobjects
            for subobject_id in subobjects_dict.keys():
              subobject_data = subobjects_dict[subobject_id]
              subobject_data.seek(0)
              subobject = connection.importFile(subobject_data)
              if subobject_id not in category.objectIds():
                category._setObject(subobject_id, subobject)
1004
    else:
1005
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
1006 1007 1008 1009
      portal = context.getPortalObject()
      category_tool = portal.portal_categories
      tool_id = self.tool_id
      if light_install==0:
1010
        ObjectTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
1011
      else:
1012
        for relative_url in self._archive.keys():
1013
          obj = self._archive[relative_url]
Aurel's avatar
Aurel committed
1014
          # Wrap the object by an aquisition wrapper for _aq_dynamic.
1015
          obj = obj.__of__(category_tool)
Aurel's avatar
Aurel committed
1016 1017 1018 1019 1020
          container_path = relative_url.split('/')[0:-1]
          category_id = relative_url.split('/')[-1]
          container = category_tool.unrestrictedTraverse(container_path)
          container_ids = container.objectIds()
          if category_id in container_ids:    # Object already exists
1021 1022
            # XXX call backup here
            subobjects_dict = self._backupObject('backup', trashbin, container_path, category_id)
1023
            container.manage_delObjects([category_id])
1024
          category = container.newContent(portal_type=obj.getPortalType(), id=category_id)
1025 1026 1027 1028
          for prop in obj.propertyIds():
            if prop not in ('id', 'uid'):
              try:
                prop_value = obj.getProperty(prop, evaluate=0)
1029
              except TypeError: # the getter doesn't support evaluate=
1030 1031
                prop_value = obj.getProperty(prop)
              category.setProperty(prop, prop_value)
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
          # import sub objects if there is
          if len(subobjects_dict) > 0:
            # get a jar
            connection = obj._p_jar
            o = category
            while connection is None:
              o = o.aq_parent
              connection = o._p_jar
            # import subobjects
            for subobject_id in subobjects_dict.keys():
              subobject_data = subobjects_dict[subobject_id]
              subobject_data.seek(0)
              subobject = connection.importFile(subobject_data)
              if subobject_id not in category.objectIds():
                category._setObject(subobject_id, subobject)

1048

1049 1050
class SkinTemplateItem(ObjectTemplateItem):

1051 1052
  def __init__(self, id_list, tool_id='portal_skins', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1053

1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
  def preinstall(self, context, installed_bt, **kw):
    modified_object_list = ObjectTemplateItem.preinstall(self, context, installed_bt, **kw)
    # 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

1069
  def install(self, context, trashbin, **kw):
1070
    ObjectTemplateItem.install(self, context, trashbin, **kw)
1071 1072
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1073 1074 1075
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
      folder = p.unrestrictedTraverse(relative_url)
1076
      for obj in folder.objectValues(spec=('Z SQL Method',)):
1077 1078
        fixZSQLMethod(p, obj)

1079 1080 1081 1082 1083 1084
    # Add new folders into skin paths.
    ps = p.portal_skins
    for skin_name, selection in ps.getSkinPaths():
      new_selection = []
      selection = selection.split(',')
      for relative_url in self._archive.keys():
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1085
        if context.getTemplateFormatVersion() == 1:
1086 1087
          if update_dict.has_key(relative_url) or force:
            if not force:
1088
              if update_dict[relative_url] == 'nothing':
1089
                continue
1090
          obj = self._objects[relative_url]
1091
        else:
1092
          obj = self._archive[relative_url]
1093
        skin_id = relative_url.split('/')[-1]
1094
        selection_list = obj.getProperty('business_template_registered_skin_selections', None)
1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
        if selection_list is None or skin_name in selection_list:
          if skin_id not in selection:
            new_selection.append(skin_id)
      new_selection.extend(selection)
      # sort the layer according to skin priorities
      new_selection.sort(lambda a, b : cmp(
        b in ps.objectIds() and ps[b].getProperty(
            'business_template_skin_layer_priority', 0) or 0,
        a in ps.objectIds() and ps[a].getProperty(
            'business_template_skin_layer_priority', 0) or 0))
      ps.manage_skinLayers(skinpath = tuple(new_selection), skinname = skin_name, add_skin = 1)
    # Make sure that skin data is up-to-date (see CMFCore/Skinnable.py).
    p.changeSkin(None)
1108 1109 1110

  def uninstall(self, context, **kw):
    # Remove folders from skin paths.
1111 1112 1113 1114
    object_path = kw.get('object_path', None)
    if object_path is not None:
      object_keys = [object_path]
    else:
1115
      object_keys = self._archive.keys()
1116
    ps = context.portal_skins
1117
    skin_id_list = [relative_url.split('/')[-1] for relative_url in object_keys]
1118 1119 1120 1121 1122 1123 1124
    for skin_name, selection in ps.getSkinPaths():
      new_selection = []
      selection = selection.split(',')
      for skin_id in selection:
        if skin_id not in skin_id_list:
          new_selection.append(skin_id)
      ps.manage_skinLayers(skinpath = tuple(new_selection), skinname = skin_name, add_skin = 1)
1125
    # Make sure that skin data is up-to-date (see CMFCore/Skinnable.py).
1126
    context.getPortalObject().changeSkin(None)
1127 1128 1129
    ObjectTemplateItem.uninstall(self, context, **kw)


1130
class WorkflowTemplateItem(ObjectTemplateItem):
1131

1132 1133
  def __init__(self, id_list, tool_id='portal_workflow', **kw):
    return ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1134

1135
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1136 1137
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
1138 1139 1140
      portal = context.getPortalObject()
      new_keys = self._objects.keys()
      for path in new_keys:
1141
        if installed_bt._objects.has_key(path):
1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
          # compare object to see it there is changes
          new_object = self._objects[path]
          old_object = installed_bt._objects[path]
          new_io = StringIO()
          old_io = StringIO()
          OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, new_io)
          OFS.XMLExportImport.exportXML(old_object._p_jar, old_object._p_oid, old_io)
          new_obj_xml = new_io.getvalue()
          old_obj_xml = old_io.getvalue()
          new_io.close()
          old_io.close()
          if new_obj_xml != old_obj_xml:
            wf_id = path.split('/')[:2]
            modified_object_list.update({'/'.join(wf_id) : ['Modified', 'Workflow']})
        else: # new object
          modified_object_list.update({path : ['New', 'Workflow']})
1158 1159 1160 1161 1162 1163 1164 1165
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1166
    if context.getTemplateFormatVersion() == 1:
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
      portal = context.getPortalObject()
      # sort to add objects before their subobjects
      keys = self._objects.keys()
      keys.sort()
      update_dict = kw.get('object_to_update')
      force = kw.get('force')
      for path in keys:
        wf_path = '/'.join(path.split('/')[:2])
        if wf_path in update_dict or force:
          if not force:
            action = update_dict[wf_path]
            if action == 'nothing':
              continue
          else:
1181
            action = 'backup'
1182 1183 1184 1185 1186 1187
          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
1188
            container_url = '/'.join(container_path)
1189 1190 1191 1192 1193 1194
            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
1195
            self._backupObject(action, trashbin, container_path, object_id, keep_subobjects=1)
1196
            container.manage_delObjects([object_id])
1197
          obj = self._objects[path]
1198 1199 1200
          if getattr(obj, 'meta_type', None) == 'Script (Python)':
            if getattr(obj, '_code') is None:
              obj._compile()
1201 1202 1203 1204 1205
          obj = obj._getCopy(container)
          container._setObject(object_id, obj)
          obj = container._getOb(object_id)
          obj.manage_afterClone(obj)
          obj.wl_clearLocks()
1206 1207 1208 1209
    else:
      ObjectTemplateItem.install(self, context, trashbin, **kw)


1210 1211
class PortalTypeTemplateItem(ObjectTemplateItem):

1212 1213
  def __init__(self, id_list, tool_id='portal_types', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1214 1215
    # XXX : this statement can be removed once all bt5 have separated
    # workflow-chain information
1216 1217 1218
    self._workflow_chain_archive = PersistentMapping()

  def build(self, context, **kw):
1219 1220
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
1221 1222
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
1223 1224 1225
      # remove actions and properties
      action_len = len(obj.listActions())
      obj.deleteActions(selections=range(action_len))
1226
      obj = self.removeProperties(obj)
1227 1228 1229 1230 1231 1232 1233 1234 1235
      # remove some properties
      if hasattr(obj, 'allowed_content_types'):
        setattr(obj, 'allowed_content_types', ())
      if hasattr(obj, 'hidden_content_type_list'):
        setattr(obj, 'hidden_content_type_list', ())
      if hasattr(obj, 'property_sheet_list'):
        setattr(obj, 'property_sheet_list', ())
      if hasattr(obj, 'base_category_list'):
        setattr(obj, 'base_category_list', ())
Alexandre Boeglin's avatar
Alexandre Boeglin committed
1236 1237
      if hasattr(obj, '_roles'):
        setattr(obj, '_roles', [])
1238 1239
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
1240

1241 1242
  # XXX : this method is kept temporarily, but can be removed once all bt5 are
  # re-exported with separated workflow-chain information
1243 1244 1245 1246
  def install(self, context, trashbin, **kw):
    ObjectTemplateItem.install(self, context, trashbin, **kw)
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1247 1248
    # We now need to setup the list of workflows corresponding to
    # each portal type
1249
    (default_chain, chain_dict) = getChainByType(context)
1250
    # Set the default chain to the empty string is probably the
1251
    # best solution, by default it is 'default_workflow', which is
1252
    # not very usefull
1253
    default_chain = ''
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1254
    if context.getTemplateFormatVersion() == 1:
1255
      object_list = self._objects
1256
    else:
1257 1258
      object_list = self._archive
    for path in object_list.keys():
1259 1260 1261 1262
      if update_dict.has_key(path) or force:
        if not force:
          action = update_dict[path]
          if action == 'nothing':
1263
            continue
1264 1265
        obj = object_list[path]
        portal_type = obj.id
1266 1267 1268
        if self._workflow_chain_archive.has_key(portal_type):
          chain_dict['chain_%s' % portal_type] = \
              self._workflow_chain_archive[portal_type]
1269 1270
    context.portal_workflow.manage_changeWorkflows(default_chain,
                                                   props=chain_dict)
1271

1272 1273
  # XXX : this method is kept temporarily, but can be removed once all bt5 are
  # re-exported with separated workflow-chain information
1274 1275 1276 1277 1278 1279 1280
  def _importFile(self, file_name, file):
    if 'workflow_chain_type.xml' in file_name:
      # import workflow chain for portal_type
      dict = {}
      xml = parse(file)
      chain_list = xml.getElementsByTagName('chain')
      for chain in chain_list:
1281
        ptype = chain.getElementsByTagName('type')[0].childNodes[0].data
1282 1283 1284 1285 1286
        workflow_list = chain.getElementsByTagName('workflow')[0].childNodes
        if len(workflow_list) == 0:
          workflow = ''
        else:
          workflow = workflow_list[0].data
1287
        dict[str(ptype)] = str(workflow)
1288 1289 1290 1291
      self._workflow_chain_archive = dict
    else:
      ObjectTemplateItem._importFile(self, file_name, file)

1292
class PortalTypeWorkflowChainTemplateItem(BaseTemplateItem):
1293 1294

  def build(self, context, **kw):
1295 1296 1297 1298 1299
    # 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
1300
    p = context.getPortalObject()
1301
    (default_chain, chain_dict) = getChainByType(context)
Aurel's avatar
Aurel committed
1302
    for key in self._archive.keys():
1303 1304 1305
      wflist = key.split(' | ')
      if len(wflist) == 2:
        portal_type = wflist[0]
Aurel's avatar
Aurel committed
1306
        workflow = wflist[1]
1307
      else:
1308
        # portal type with no workflow defined
Aurel's avatar
Aurel committed
1309
        portal_type = wflist[0][:-2]
1310 1311
        workflow = ''
      if chain_dict.has_key('chain_%s' % portal_type):
1312 1313 1314 1315
        if workflow[0] in ['+', '-', '=']:
          workflow_name = workflow[1:]
        else:
          workflow_name = workflow
1316
        if workflow[0] != '-' and \
1317
            workflow_name not in chain_dict['chain_%s' % portal_type]:
Alexandre Boeglin's avatar
Alexandre Boeglin committed
1318
          raise NotFound, 'workflow %s not found in chain for portal_type %s'\
1319
                % (workflow, portal_type)
1320
        if self._objects.has_key(portal_type):
1321 1322
          # other workflow id already defined for this portal type
          self._objects[portal_type].append(workflow)
1323
        else:
1324
          self._objects[portal_type] = [workflow,]
1325
      else:
1326 1327
        raise NotFound, 'portal type %s not found in workflow chain'\
                                                    % portal_type
1328

Christophe Dumez's avatar
Christophe Dumez committed
1329
  # Function to generate XML Code Manually
1330 1331 1332 1333 1334
  def generateXml(self, path=None):
    xml_data = '<workflow_chain>'
    keys = self._objects.keys()
    keys.sort()
    for key in keys:
1335
      workflow_list = self._objects[key]
1336 1337 1338
      # XXX Not always a list
      if isinstance(workflow_list, str):
        workflow_list = [workflow_list]
1339 1340
      xml_data += os.linesep+' <chain>'
      xml_data += os.linesep+'  <type>%s</type>' %(key,)
1341
      xml_data += os.linesep+'  <workflow>%s</workflow>' %(', '.join(workflow_list))
1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359
      xml_data += os.linesep+' </chain>'
    xml_data += os.linesep+'</workflow_chain>'
    return xml_data

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

  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    # We now need to setup the list of workflows corresponding to
    # each portal type
1360
    (default_chain, chain_dict) = getChainByType(context)
1361
    # Set the default chain to the empty string is probably the
Christophe Dumez's avatar
Christophe Dumez committed
1362
    # best solution, by default it is 'default_workflow', which is
1363 1364 1365 1366 1367 1368 1369
    # not very usefull
    default_chain = ''
    for path in self._objects.keys():
      if update_dict.has_key(path) or force:
        if not force:
          action = update_dict[path]
          if action == 'nothing':
1370
            continue
Jérome Perrin's avatar
Jérome Perrin committed
1371 1372 1373 1374
        path_splitted = path.split('/', 1)
        # XXX: to avoid crashing when no portal_type
        if len(path_splitted) < 2:
          continue
1375
        portal_type = path_splitted[1]
1376
        if chain_dict.has_key('chain_%s' % portal_type):
Aurel's avatar
Aurel committed
1377 1378 1379
          old_chain_dict = chain_dict['chain_%s' % portal_type]
          # XXX we don't use the chain (Default) in erp5 so don't keep it
          if old_chain_dict != '(Default)' and old_chain_dict != '':
1380
            old_chain_workflow_id_set = {}
1381
            # get existent workflow id list
1382 1383
            for wf_id in old_chain_dict.split(', '):
              old_chain_workflow_id_set[wf_id] = 1
1384
            # get new workflow id list
1385
            for wf_id in self._objects[path].split(', '):
1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399
              if wf_id[0] == '-':
                # remove wf id if already present
                if old_chain_workflow_id_set.has_key(wf_id[1:]):
                  old_chain_workflow_id_set.pop(wf_id[1:])
              elif wf_id[0] == '=':
                # replace existing chain by this one
                old_chain_workflow_id_set = {}
                old_chain_workflow_id_set[wf_id[1:]] = 1
              # then either '+' or nothing, add wf id to the list
              elif wf_id[0] == '+':
                old_chain_workflow_id_set[wf_id[1:]] = 1
              else:
                old_chain_workflow_id_set[wf_id] = 1
            # create the new chain
1400 1401
            chain_dict['chain_%s' % portal_type] = ', '.join(
                                              old_chain_workflow_id_set.keys())
Aurel's avatar
Aurel committed
1402
          else:
1403
            chain_dict['chain_%s' % portal_type] = self._objects[path]
Aurel's avatar
Aurel committed
1404 1405
        else:
          chain_dict['chain_%s' % portal_type] = self._objects[path]
1406 1407
    context.portal_workflow.manage_changeWorkflows(default_chain,
                                                   props=chain_dict)
1408

1409 1410
  def uninstall(self, context, **kw):
    (default_chain, chain_dict) = getChainByType(context)
1411 1412 1413 1414 1415 1416
    object_path = kw.get('object_path', None)
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._objects.keys()
    for path in object_keys:
1417 1418 1419 1420 1421 1422 1423 1424
      path_splitted = path.split('/', 1)
      if len(path_splitted) < 2:
        continue
      portal_type = path_splitted[1]
      id = 'chain_%s' % portal_type
      if id in chain_dict.keys():
        del chain_dict[id]
    context.portal_workflow.manage_changeWorkflows('', props=chain_dict)
1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437

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

1443 1444
# just for backward compatibility
PortalTypeTemplateWorkflowChainItem = PortalTypeWorkflowChainTemplateItem
1445

1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460
class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):

  xml_tag = 'allowed_content_type_list'
  class_property = 'allowed_content_types'

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

Christophe Dumez's avatar
Christophe Dumez committed
1461
  # Function to generate XML Code Manually
1462
  def generateXml(self, path=None):
1463
    dictio = self._objects
1464
    xml_data = '<%s>' %(self.xml_tag,)
1465
    keys = dictio.keys()
1466 1467
    keys.sort()
    for key in keys:
1468
      allowed_list = dictio[key]
1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484
      xml_data += os.linesep+' <portal_type id="%s">' %(key,)
      for allowed_item in allowed_list:
        xml_data += os.linesep+'  <item>%s</item>' %(allowed_item,)
      xml_data += os.linesep+' </portal_type>'
    xml_data += os.linesep+'</%s>' %(self.xml_tag,)
    return xml_data

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

1485 1486 1487 1488 1489 1490
  def preinstall(self, context, installed_bt, **kw):
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
      portal = context.getPortalObject()
      new_keys = self._objects.keys()
      if installed_bt.id == 'installed_bt_for_diff':
1491
        #must rename keys in dict if reinstall
1492 1493 1494 1495 1496 1497 1498 1499 1500
        new_dict = PersistentMapping()
        for key in installed_bt._objects.keys():
          new_key = self.class_property+'/'+key
          new_dict[new_key] = installed_bt._objects[key]
        installed_bt._objects = new_dict
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see it there is changes
          new_object = self._objects[path]
1501
          old_object = installed_bt._objects[path]
1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512
          if new_object != old_object:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539
  def _importFile(self, file_name, file):
    path, name = os.path.split(file_name)
    id = string.split(name, '.')[0]
    xml = parse(file)
    portal_type_list = xml.getElementsByTagName('portal_type')
    for portal_type in portal_type_list:
      id = portal_type.getAttribute('id')
      item_type_list = []
      item_list = portal_type.getElementsByTagName('item')
      for item in item_list:
        item_type_list.append(str(item.childNodes[0].data))
      self._objects[self.class_property+'/'+id] = item_type_list

  def install(self, context, trashbin, **kw):
    p = context.getPortalObject()
    pt = p.unrestrictedTraverse('portal_types')
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    for key in self._objects.keys():
      if update_dict.has_key(key) or force:
        if not force:
          action = update_dict[key]
          if action == 'nothing':
            continue
        try:
          portal_id = key.split('/')[-1]
          portal_type = pt._getOb(portal_id)
1540
        except AttributeError:
1541 1542 1543 1544 1545 1546 1547 1548
          LOG("portal types not found : ", 100, portal_id)
          continue
        property_list = self._objects[key]
        object_property_list = getattr(portal_type, self.class_property, ())
        if len(object_property_list) > 0:
          # merge differences between portal types properties
          # only add new, do not remove
          for id in object_property_list:
1549 1550
            if id not in property_list:
              property_list.append(id)
1551 1552
        setattr(portal_type, self.class_property, list(property_list))

Aurel's avatar
Aurel committed
1553
  def uninstall(self, context, **kw):
1554
    object_path = kw.get('object_path', None)
1555
    p = context.getPortalObject()
1556
    pt = p.unrestrictedTraverse('portal_types')
1557 1558 1559 1560 1561 1562 1563 1564
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._objects.keys()
    for key in object_keys:
      try:
        portal_id = key.split('/')[-1]
        portal_type = pt._getOb(portal_id)
1565
      except AttributeError:
1566 1567 1568
        LOG("portal types not found : ", 100, portal_id)
        continue
      property_list = self._objects[key]
1569 1570
      original_property_list = list(getattr(portal_type,
                                    self.class_property, ()))
Aurel's avatar
Aurel committed
1571 1572
      for id in property_list:
        if id in original_property_list:
1573
          original_property_list.remove(id)
1574
      setattr(portal_type, self.class_property, list(original_property_list))
1575

1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590
class PortalTypeHiddenContentTypeTemplateItem(PortalTypeAllowedContentTypeTemplateItem):

  xml_tag = 'hidden_content_type_list'
  class_property = 'hidden_content_type_list'

class PortalTypePropertySheetTemplateItem(PortalTypeAllowedContentTypeTemplateItem):

  xml_tag = 'property_sheet_list'
  class_property = 'property_sheet_list'

class PortalTypeBaseCategoryTemplateItem(PortalTypeAllowedContentTypeTemplateItem):

  xml_tag = 'base_category_list'
  class_property = 'base_category_list'

1591
class CatalogMethodTemplateItem(ObjectTemplateItem):
1592
  """Template Item for catalog methods.
1593

1594 1595 1596 1597 1598
    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.
  """
1599

1600 1601
  def __init__(self, id_list, tool_id='portal_catalog', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1602
    # a mapping to store properties of methods.
1603 1604 1605 1606 1607
    # 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()
1608

1609 1610 1611 1612 1613
    self._is_filtered_archive = PersistentMapping()
    self._filter_expression_archive = PersistentMapping()
    self._filter_expression_instance_archive = PersistentMapping()
    self._filter_type_archive = PersistentMapping()

1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627
  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

1628 1629
  def build(self, context, **kw):
    ObjectTemplateItem.build(self, context, **kw)
1630

1631 1632
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1633 1634
    except KeyError:
      catalog = None
1635
    if catalog is None:
1636
      LOG('BusinessTemplate build', 0, 'catalog not found')
1637
      return
1638

1639 1640 1641
    # upgrade old
    if not hasattr(self, '_method_properties'):
      self._method_properties = PersistentMapping()
1642

1643 1644
    for obj in self._objects.values():
      method_id = obj.id
1645 1646
      self._method_properties[method_id] = self._extractMethodProperties(
                                                          catalog, method_id)
1647
      self._is_filtered_archive[method_id] = 0
1648
      if catalog.filter_dict.has_key(method_id):
1649 1650 1651 1652 1653 1654 1655 1656 1657
        if catalog.filter_dict[method_id]['filtered']:
          self._is_filtered_archive[method_id] = \
                      catalog.filter_dict[method_id]['filtered']
          self._filter_expression_archive[method_id] = \
                      catalog.filter_dict[method_id]['expression']
          self._filter_expression_instance_archive[method_id] = \
                      catalog.filter_dict[method_id]['expression_instance']
          self._filter_type_archive[method_id] = \
                      catalog.filter_dict[method_id]['type']
1658

Aurel's avatar
Aurel committed
1659
  def export(self, context, bta, **kw):
1660 1661 1662 1663 1664 1665 1666 1667
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate, export', 0, 'no SQL catalog was available')
      return

Aurel's avatar
Aurel committed
1668 1669 1670 1671
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    for key in self._objects.keys():
1672
      obj = self._objects[key]
Aurel's avatar
Aurel committed
1673 1674 1675 1676 1677 1678
      # create folder and subfolders
      folders, id = os.path.split(key)
      path = os.path.join(root_path, folders)
      bta.addFolder(name=path)
      # export object in xml
      f=StringIO()
1679 1680
      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
      bta.addObject(obj=f.getvalue(), name=id, path=path)
Aurel's avatar
Aurel committed
1681 1682
      # add all datas specific to catalog inside one file
      catalog = context.portal_catalog.getSQLCatalog()
1683
      method_id = obj.id
1684 1685
      object_path = os.path.join(path, method_id+'.catalog_keys.xml')

Aurel's avatar
Aurel committed
1686
      f = open(object_path, 'wt')
1687
      xml_data = '<catalog_method>'
1688

1689 1690 1691
      for method_property, value in self._method_properties[method_id].items():
        xml_data += os.linesep+' <item key="%s" type="int">' %(method_property,)
        xml_data += os.linesep+'  <value>%s</value>' %(value,)
1692
        xml_data += os.linesep+' </item>'
1693

Aurel's avatar
Aurel committed
1694
      if catalog.filter_dict.has_key(method_id):
1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710
        if catalog.filter_dict[method_id]['filtered']:
          xml_data += os.linesep+' <item key="_is_filtered_archive" type="int">'
          xml_data += os.linesep+'  <value>1</value>'
          xml_data += os.linesep+' </item>'
          for method in catalog_method_filter_list:
            value = getattr(self, method, '')[method_id]
            if method != '_filter_expression_instance_archive':
              if type(value) in (type(''), type(u'')):
                xml_data += os.linesep+' <item key="%s" type="str">' %(method,)
                xml_data += os.linesep+'  <value>%s</value>' %(str(value))
                xml_data += os.linesep+' </item>'
              elif type(value) in (type(()), type([])):
                xml_data += os.linesep+' <item key="%s" type="tuple">'%(method)
                for item in value:
                  xml_data += os.linesep+'  <value>%s</value>' %(str(item))
                xml_data += os.linesep+' </item>'
1711
      xml_data += os.linesep+'</catalog_method>'
1712
      f.write(xml_data)
Aurel's avatar
Aurel committed
1713
      f.close()
1714

Christophe Dumez's avatar
Christophe Dumez committed
1715 1716 1717 1718 1719 1720 1721 1722 1723
  # 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 += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data
1724

1725 1726
  def install(self, context, trashbin, **kw):
    ObjectTemplateItem.install(self, context, trashbin, **kw)
1727 1728
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1729
    except KeyError:
1730 1731 1732 1733 1734 1735 1736 1737
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    # Make copies of attributes of the default catalog of portal_catalog.
    sql_catalog_object_list = list(catalog.sql_catalog_object_list)
    sql_uncatalog_object = list(catalog.sql_uncatalog_object)
    sql_clear_catalog = list(catalog.sql_clear_catalog)
1738

1739 1740 1741
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    values = []
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1742
    new_bt_format = context.getTemplateFormatVersion()
1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758

    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':
1759
              continue
1760 1761 1762 1763
          if new_bt_format:
            values.append(self._objects[key])
          else:
            values.append(self._archive[key])
1764

1765 1766
    for obj in values:
      method_id = obj.id
1767

1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778
      # 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)
          elif isinstance(old_value, list) or isinstance(old_value, tuple):
            if method_id not in old_value:
              new_value = list(old_value) + [method_id]
              new_value.sort()
              setattr(catalog, key, tuple(new_value))
1779

1780 1781
      # Restore filter
      if self._is_filtered_archive.get(method_id, 0):
1782
        expression = self._filter_expression_archive[method_id]
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1783
        if context.getTemplateFormatVersion() == 1:
1784 1785 1786
          expr_instance = Expression(expression)
        else:
          expr_instance = self._filter_expression_instance_archive[method_id]
1787
        filter_type = self._filter_type_archive[method_id]
1788 1789 1790
        catalog.filter_dict[method_id] = PersistentMapping()
        catalog.filter_dict[method_id]['filtered'] = 1
        catalog.filter_dict[method_id]['expression'] = expression
1791
        catalog.filter_dict[method_id]['expression_instance'] = expr_instance
1792
        catalog.filter_dict[method_id]['type'] = filter_type
1793
      elif method_id in catalog.filter_dict.keys():
1794
        catalog.filter_dict[method_id]['filtered'] = 0
1795

1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821
      # 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)
1822

1823 1824 1825 1826 1827 1828
        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)
1829

1830
  def uninstall(self, context, **kw):
1831 1832
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1833
    except KeyError:
1834 1835 1836 1837
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
1838

1839 1840 1841 1842
    values = []
    object_path = kw.get('object_path', None)
    # get required values
    if object_path is None:
1843 1844 1845 1846
      if context.getTemplateFormatVersion() == 1:
        values = self._objects.values()
      else:
        values = self._archive.values()
1847 1848 1849 1850
    else:
      value = self._archive[object_path]
      if value is not None:
        values.append(value)
1851

1852
    for obj in values:
1853
      method_id = obj.id
1854 1855 1856 1857 1858 1859 1860 1861 1862
      # 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)
1863

Yoshinori Okuji's avatar
Yoshinori Okuji committed
1864
      if catalog.filter_dict.has_key(method_id):
1865
        del catalog.filter_dict[method_id]
1866

1867
    # uninstall objects
1868
    ObjectTemplateItem.uninstall(self, context, **kw)
1869

1870
  def _importFile(self, file_name, file):
1871
    if not '.catalog_keys' in file_name:
1872 1873 1874 1875 1876 1877 1878 1879
      # just import xml object
      obj = self
      connection = None
      while connection is None:
        obj=obj.aq_parent
        connection=obj._p_jar
      obj = connection.importFile(file, customImporters=customImporters)
      self._objects[file_name[:-4]] = obj
1880
    else:
1881 1882 1883 1884
      # recreate data mapping specific to catalog method
      path, name = os.path.split(file_name)
      id = string.split(name, '.')[0]
      xml = parse(file)
1885
      method_list = xml.getElementsByTagName('item')
1886
      for method in method_list:
1887
        key = method.getAttribute('key')
1888 1889
        key_type = str(method.getAttribute('type'))
        if key_type == "str":
1890 1891 1892 1893
          if len(method.getElementsByTagName('value')[0].childNodes):
            value = str(method.getElementsByTagName('value')[0].childNodes[0].data)
          else:
            value = ''
1894
          key = str(key)
1895
        elif key_type == "int":
1896
          value = int(method.getElementsByTagName('value')[0].childNodes[0].data)
1897
          key = str(key)
1898
        elif key_type == "tuple":
1899 1900 1901 1902
          value = []
          value_list = method.getElementsByTagName('value')
          for item in value_list:
            value.append(item.childNodes[0].data)
1903
        else:
1904
          LOG('BusinessTemplate import CatalogMethod, type unknown', 0, key_type)
1905
          continue
1906
        if key in catalog_method_list or key in catalog_method_filter_list:
1907
          dict = getattr(self, key, {})
1908
          dict[id] = value
1909 1910 1911
        else:
          # new style key
          self._method_properties.setdefault(id, PersistentMapping())[key] = 1
1912

1913
class ActionTemplateItem(ObjectTemplateItem):
1914 1915

  def __init__(self, id_list, **kw):
1916
    # XXX It's look like ObjectTemplateItem __init__
1917 1918 1919 1920 1921 1922
    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

1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943
  def _splitPath(self, path):
    """
      Split path tries to split a complexe path such as:

      "foo/bar[id=zoo]"

      into

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

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

1944 1945 1946 1947
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
1948
      relative_url, value = id.split(' | ')
1949 1950
      obj = p.unrestrictedTraverse(relative_url)
      for ai in obj.listActions():
1951
        if getattr(ai, 'id') == value:
1952
          url = os.path.split(relative_url)
Aurel's avatar
Aurel committed
1953
          key = os.path.join(url[-2], url[-1], value)
1954
          action = ai._getCopy(context)
1955
          action = self.removeProperties(action)
1956
          self._objects[key] = action
Aurel's avatar
Aurel committed
1957
          self._objects[key].wl_clearLocks()
1958 1959
          break
      else:
1960
        raise NotFound, 'Action %r not found' %(id,)
Aurel's avatar
Aurel committed
1961

1962 1963 1964
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1965
    if context.getTemplateFormatVersion() == 1:
Aurel's avatar
Aurel committed
1966 1967
      p = context.getPortalObject()
      for id in self._objects.keys():
1968 1969 1970 1971 1972 1973
        if update_dict.has_key(id) or force:
          if not force:
            action = update_dict[id]
            if action == 'nothing':
              continue
          path = id.split(os.sep)
1974 1975
          obj = p.unrestrictedTraverse(path[:-1])
          action_list = obj.listActions()
1976
          for index in range(len(action_list)):
1977
            if getattr(action_list[index], 'id') == path[-1]:
1978
              # remove previous action
1979
              obj.deleteActions(selections=(index,))
1980
          action = self._objects[id]
1981 1982 1983
          action_text = action.action
          if isinstance(action_text, Expression):
            action_text = action_text.text
1984
          obj.addAction(
1985 1986
                        id = action.id
                      , name = action.title
1987
                      , action = action_text
1988 1989 1990 1991
                      , condition = action.getCondition()
                      , permission = action.permissions
                      , category = action.category
                      , visible = action.visible
1992 1993
                      , icon = getattr(action, 'icon', None)\
                                and action.icon.text or ''
1994
                      , priority = action.priority
Aurel's avatar
Aurel committed
1995
                    )
1996
          # sort action based on the priority define on it
1997 1998 1999 2000 2001 2002 2003 2004 2005
          # XXX suppose that priority are properly on actions
          new_priority = action.priority
          action_list = obj.listActions()
          move_down_list = []
          for index in range(len(action_list)):
            action = action_list[index]
            if action.priority > new_priority:
              move_down_list.append(str(index))
          obj.moveDownActions(selections=tuple(move_down_list))
Aurel's avatar
Aurel committed
2006
    else:
2007
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
2008
      p = context.getPortalObject()
2009 2010
      for id in self._archive.keys():
        action = self._archive[id]
Aurel's avatar
Aurel committed
2011
        relative_url, key, value = self._splitPath(id)
2012 2013
        obj = p.unrestrictedTraverse(relative_url)
        for ai in obj.listActions():
Aurel's avatar
Aurel committed
2014
          if getattr(ai, key) == value:
2015
            raise TemplateConflictError, 'the portal type %s already has the action %s' % (obj.id, value)
2016 2017 2018
        action_text = action.action
        if isinstance(action_text, Expression):
          action_text = action_text.text
2019
        obj.addAction(
Aurel's avatar
Aurel committed
2020 2021
                      id = action.id
                    , name = action.title
2022
                    , action = action_text
Aurel's avatar
Aurel committed
2023 2024 2025 2026
                    , condition = action.getCondition()
                    , permission = action.permissions
                    , category = action.category
                    , visible = action.visible
2027 2028
                    , icon = getattr(action, 'icon', None) \
                                      and action.icon.text or ''
Aurel's avatar
Aurel committed
2029
                    )
Aurel's avatar
Aurel committed
2030 2031 2032 2033 2034 2035 2036
        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))
2037 2038
          obj.moveDownActions(selections=tuple(move_down_list))

2039 2040
  def uninstall(self, context, **kw):
    p = context.getPortalObject()
2041 2042 2043 2044
    object_path = kw.get("object_path", None)
    if object_path is not None:
      keys = [object_path]
    else:
2045
      keys = self._archive.keys()
2046
    for id in keys:
2047 2048 2049 2050
      if  '|' in id:
        relative_url, value = id.split(' | ')
        key = 'id'
      else:
2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061
        relative_url, key, value = self._splitPath(id)
      obj = p.unrestrictedTraverse(relative_url, None)
      if obj is not None:
        action_list = obj.listActions()
        for index in range(len(action_list)):
          if getattr(action_list[index], key) == value:
            obj.deleteActions(selections=(index,))
            break
      else :
        LOG('BusinessTemplate', 100,
            'unable to uninstall action at %s, ignoring' % relative_url )
2062 2063
    BaseTemplateItem.uninstall(self, context, **kw)

2064 2065 2066
class PortalTypeRolesTemplateItem(BaseTemplateItem):

  def __init__(self, id_list, **kw):
2067
    id_list = ['portal_type_roles/%s' % id for id in id_list if id != '']
2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079
    BaseTemplateItem.__init__(self, id_list, **kw)

  def build(self, context, **kw):
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
      obj = p.unrestrictedTraverse("portal_types/%s" %
          relative_url.split('/', 1)[1])
      type_roles_obj = getattr(obj, '_roles', ())
      type_role_list = []
      for role in type_roles_obj:
        type_role_dict = {}
        # uniq
2080
        for property in ('id', 'title', 'description',
2081 2082 2083 2084
            'priority', 'base_category_script'):
          prop_value = getattr(role, property)
          if prop_value:
            type_role_dict[property] = prop_value
2085 2086 2087 2088
        # condition
        prop_value = getattr(role, 'condition')
        if prop_value:
          type_role_dict['condition'] = prop_value.text
2089 2090 2091 2092 2093 2094 2095 2096 2097
        # multi
        for property in ('category', 'base_category'):
          prop_value_list = []
          for prop_value in getattr(role, property):
            prop_value_list.append(prop_value)
          type_role_dict[property] = prop_value_list
        type_role_list.append(type_role_dict)
      self._objects[relative_url] = type_role_list

Christophe Dumez's avatar
Christophe Dumez committed
2098
  # Function to generate XML Code Manually
2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112
  def generateXml(self, path=None):
    type_role_list = self._objects[path]
    xml_data = '<type_roles>'
    for role in type_role_list:
      xml_data += os.linesep+"  <role id='%s'>" % role['id']
      # uniq
      for property in ('title', 'description', 'condition', 'priority',
          'base_category_script'):
        prop_value = role.get(property)
        if prop_value:
          xml_data += os.linesep+"   <property id='%s'>%s</property>" % \
              (property, prop_value)
      # multi
      for property in ('category', 'base_category'):
2113
        for prop_value in role.get(property, []):
2114 2115 2116 2117 2118
          xml_data += os.linesep+"   <multi_property "\
          "id='%s'>%s</multi_property>" % (property, prop_value)
      xml_data += os.linesep+"  </role>"
    xml_data += os.linesep+'</type_roles>'
    return xml_data
2119

2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
    for key in self._objects.keys():
      xml_data = self.generateXml(key)
      name = key.split('/', 1)[1]
      bta.addObject(obj=xml_data, name=name, path=root_path)

  def _importFile(self, file_name, file):
    type_roles_list = []
    xml = parse(file)
2133
    xml_type_roles_list = xml.getElementsByTagName('role')
2134 2135 2136 2137 2138 2139 2140 2141
    for role in xml_type_roles_list:
      id = role.getAttribute('id')
      type_role_property_dict = {'id':id}
      # uniq
      property_list = role.getElementsByTagName('property')
      for property in property_list:
        property_id = property.getAttribute('id').encode()
        if property.hasChildNodes():
2142
          property_value = property.childNodes[0].data.encode('utf_8', 'backslashreplace')
2143 2144 2145 2146 2147 2148 2149 2150 2151 2152
          if property_id == 'priority':
            property_value = float(property_value)
          type_role_property_dict[property_id] = property_value
      # multi
      multi_property_list = role.getElementsByTagName('multi_property')
      for property in multi_property_list:
        property_id = property.getAttribute('id').encode()
        if not type_role_property_dict.has_key(property_id):
          type_role_property_dict[property_id] = []
        if property.hasChildNodes():
2153
          property_value = property.childNodes[0].data.encode('utf_8', 'backslashreplace')
2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168
          type_role_property_dict[property_id].append(property_value)
      type_roles_list.append(type_role_property_dict)
    self._objects['portal_type_roles/'+file_name[:-4]] = type_roles_list

  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    p = context.getPortalObject()
    for roles_path in self._objects.keys():
      if update_dict.has_key(roles_path) or force:
        if not force:
          action = update_dict[roles_path]
          if action == 'nothing':
            continue
      path = 'portal_types/%s' % roles_path.split('/', 1)[1]
2169 2170 2171 2172 2173 2174
      obj = p.unrestrictedTraverse(path, None)
      if obj is not None:
        setattr(obj, '_roles', []) # reset roles before applying
        type_roles_list = self._objects[roles_path] or []
        for type_role_property_dict in type_roles_list:
          obj._roles.append(RoleInformation(**type_role_property_dict))
2175 2176 2177

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
Aurel's avatar
Aurel committed
2178 2179 2180 2181 2182 2183
    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:
2184
      path = 'portal_types/%s' % roles_path.split('/', 1)[1]
2185 2186 2187 2188 2189 2190
      try:
        obj = p.unrestrictedTraverse(path)
        setattr(obj, '_roles', [])
      except (NotFound, KeyError):
        pass
      
2191 2192 2193 2194 2195 2196
class SitePropertyTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
2197 2198
      for property in p.propertyMap():
        if property['id'] == id:
2199
          obj = p.getProperty(id)
2200
          prop_type = property['type']
2201 2202
          break
      else:
2203 2204
        obj = None
      if obj is None:
2205
        raise NotFound, 'the property %s is not found' % id
2206
      self._objects[id] = (prop_type, obj)
Aurel's avatar
Aurel committed
2207

2208 2209 2210 2211 2212 2213
  def _importFile(self, file_name, file):
    # recreate list of site property from xml file
    xml = parse(file)
    property_list = xml.getElementsByTagName('property')
    for prop in property_list:
      id = prop.getElementsByTagName('id')[0].childNodes[0].data
2214 2215
      prop_type = prop.getElementsByTagName('type')[0].childNodes[0].data
      if prop_type in ('lines', 'tokens'):
2216 2217 2218 2219 2220 2221 2222
        value = []
        values = prop.getElementsByTagName('value')[0]
        items = values.getElementsByTagName('item')
        for item in items:
          i = item.childNodes[0].data
          value.append(str(i))
      else:
2223
        value = str(prop.getElementsByTagName('value')[0].childNodes[0].data)
2224
      self._objects[str(id)] = (str(prop_type), value)
2225

2226 2227 2228
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2229
    if context.getTemplateFormatVersion() == 1:
Aurel's avatar
Aurel committed
2230 2231
      p = context.getPortalObject()
      for path in self._objects.keys():
2232 2233 2234 2235
        if update_dict.has_key(path) or force:
          if not force:
            action = update_dict[path]
            if action == 'nothing':
2236
              continue
2237 2238 2239
          dir, id = os.path.split(path)
          if p.hasProperty(id):
            continue
2240 2241
          prop_type, property = self._objects[path]
          p._setProperty(id, property, type=prop_type)
Aurel's avatar
Aurel committed
2242
    else:
2243
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
2244
      p = context.getPortalObject()
2245 2246
      for id,property in self._archive.keys():
        property = self._archive[id]
Aurel's avatar
Aurel committed
2247 2248 2249 2250 2251
        if p.hasProperty(id):
          continue
          # Too much???
          #raise TemplateConflictError, 'the property %s already exists' % id
        p._setProperty(id, property['value'], type=property['type'])
2252 2253 2254

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
2255 2256 2257 2258 2259 2260
    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:
2261 2262 2263 2264
      if p.hasProperty(id):
        p._delProperty(id)
    BaseTemplateItem.uninstall(self, context, **kw)

Christophe Dumez's avatar
Christophe Dumez committed
2265
  # Function to generate XML Code Manually
2266
  def generateXml(self, path=None):
2267
    xml_data = ''
2268
    prop_type, obj = self._objects[path]
2269 2270
    xml_data += os.linesep+' <property>'
    xml_data += os.linesep+'  <id>%s</id>' %(path,)
2271 2272
    xml_data += os.linesep+'  <type>%s</type>' %(prop_type,)
    if prop_type in ('lines', 'tokens'):
2273
      xml_data += os.linesep+'  <value>'
2274
      for item in obj:
2275 2276 2277 2278
        if item != '':
          xml_data += os.linesep+'   <item>%s</item>' %(item,)
      xml_data += os.linesep+'  </value>'
    else:
2279
      xml_data += os.linesep+'  <value>%r</value>' %((os.linesep).join(obj),)
2280
    xml_data += os.linesep+' </property>'
2281 2282
    return xml_data

Aurel's avatar
Aurel committed
2283 2284 2285 2286 2287
  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)
2288 2289 2290 2291
    xml_data = '<site_property>'
    keys = self._objects.keys()
    keys.sort()
    for path in keys:
2292
      xml_data += self.generateXml(path)
2293
    xml_data += os.linesep+'</site_property>'
2294
    bta.addObject(obj=xml_data, name='properties', path=root_path)
2295

2296 2297 2298 2299 2300 2301 2302
class ModuleTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
      module = p.unrestrictedTraverse(id)
2303 2304 2305 2306 2307 2308 2309 2310
      dict = {}
      dict['id'] = module.getId()
      dict['title'] = module.getTitle()
      dict['portal_type'] = module.getPortalType()
      permission_list = []
      # use show permission
      dict['permission_list'] = module.showPermissions()
      self._objects[id] = dict
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2311

Christophe Dumez's avatar
Christophe Dumez committed
2312
  # Function to generate XML Code Manually
2313
  def generateXml(self, path=None):
2314 2315
    dict = self._objects[path]
    xml_data = '<module>'
2316 2317 2318 2319
    # sort key
    keys = dict.keys()
    keys.sort()
    for key in keys:
2320 2321 2322 2323 2324
      if key =='permission_list':
        # separe permission dict into xml
        xml_data += os.linesep+' <%s>' %(key,)
        permission_list = dict[key]
        for perm in permission_list:
2325 2326 2327 2328 2329 2330
          # the type of the permission defined if we use acquired or not
          if type(perm[1]) == type([]):
            ptype = "list"
          else:
            ptype = "tuple"
          xml_data += os.linesep+"  <permission type='%s'>" %(ptype,)
2331
          xml_data += os.linesep+'   <name>%s</name>' %(perm[0])
Aurel's avatar
Aurel committed
2332
          role_list = list(perm[1])
2333
          role_list.sort()
2334 2335 2336 2337 2338 2339 2340 2341 2342
          for role in role_list:
            xml_data += os.linesep+'   <role>%s</role>' %(role)
          xml_data += os.linesep+'  </permission>'
        xml_data += os.linesep+' </%s>' %(key,)
      else:
        xml_data += os.linesep+' <%s>%s</%s>' %(key, dict[key], key)
    xml_data += os.linesep+'</module>'
    return xml_data

2343 2344 2345 2346 2347
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(path)
2348 2349 2350
    keys = self._objects.keys()
    keys.sort()
    for id in keys:
2351
      # expor module one by one
2352
      xml_data = self.generateXml(path=id)
2353
      bta.addObject(obj=xml_data, name=id, path=path)
2354

2355
  def install(self, context, trashbin, **kw):
2356
    portal = context.getPortalObject()
2357 2358
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2359
    if context.getTemplateFormatVersion() == 1:
2360
      items = self._objects
2361
    else:
2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373
      items = self._archive

    for id in items.keys():
      if update_dict.has_key(id) or force:
        if not force:
          action = update_dict[id]
          if action == 'nothing':
            continue
        mapping = items[id]
        path, id = os.path.split(id)
        if id in portal.objectIds():
          module = portal._getOb(id)
2374
          module.portal_type = str(mapping['portal_type'])
2375 2376 2377 2378 2379 2380 2381 2382 2383 2384
        else:
          module = portal.newContent(id=id, portal_type=str(mapping['portal_type']))
        module.setTitle(str(mapping['title']))
        for name,role_list in list(mapping['permission_list']):
          acquire = (type(role_list) == type([]))
          try:
            module.manage_permission(name, roles=role_list, acquire=acquire)
          except ValueError:
            # Ignore a permission not present in this system.
            pass
2385 2386 2387 2388 2389 2390 2391 2392 2393 2394

  def _importFile(self, file_name, file):
    dict = {}
    xml = parse(file)
    for id in ('portal_type', 'id', 'title', 'permission_list'):
      elt = xml.getElementsByTagName(id)[0]
      if id == 'permission_list':
        plist = []
        perm_list = elt.getElementsByTagName('permission')
        for perm in perm_list:
2395
          perm_type = perm.getAttribute('type').encode() or None
2396 2397 2398 2399 2400 2401 2402 2403 2404
          name_elt = perm.getElementsByTagName('name')[0]
          name_node = name_elt.childNodes[0]
          name = name_node.data
          role_list = perm.getElementsByTagName('role')
          rlist = []
          for role in role_list:
            role_node = role.childNodes[0]
            role = role_node.data
            rlist.append(str(role))
2405 2406 2407 2408
          if perm_type == "list" or perm_type is None:
            perm_tuple = (str(name), list(rlist))
          else:
            perm_tuple = (str(name), tuple(rlist))
2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419
          plist.append(perm_tuple)
        dict[id] = plist
      else:
        node_list = elt.childNodes
        if len(node_list) == 0:
          value=''
        else:
          value = node_list[0].data
        dict[id] = str(value)
    self._objects[file_name[:-4]] = dict

2420
  def uninstall(self, context, **kw):
Aurel's avatar
Aurel committed
2421 2422 2423
    trash = kw.get('trash', 0)
    if trash:
      return
2424 2425 2426 2427 2428 2429
    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]
2430 2431
    p = context.getPortalObject()
    id_list = p.objectIds()
2432
    for id in keys:
2433
      if id in id_list:
2434
        try:
2435
          if trash and trashbin is not None:
2436 2437
            container_path = id.split('/')
            self.portal_trash.backupObject(trashbin, container_path, id, save=1, keep_subobjects=1)
2438
          p.manage_delObjects([id])
2439
        except NotFound:
2440
          pass
2441 2442
    BaseTemplateItem.uninstall(self, context, **kw)

2443 2444 2445
  def trash(self, context, new_item, **kw):
    # Do not remove any module for safety.
    pass
2446 2447

class DocumentTemplateItem(BaseTemplateItem):
2448 2449 2450 2451
  local_file_reader_name = 'readLocalDocument'
  local_file_writer_name = 'writeLocalDocument'
  local_file_importer_name = 'importLocalDocument'
  local_file_remover_name = 'removeLocalDocument'
2452 2453 2454 2455

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

2458
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2459 2460
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480
      new_keys = self._objects.keys()
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see if there is changes
          new_obj_code = self._objects[path]
          old_obj_code = installed_bt._objects[path]
          if new_obj_code != old_obj_code:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
          # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2481
    if context.getTemplateFormatVersion() == 1:
Aurel's avatar
Aurel committed
2482
      for id in self._objects.keys():
2483 2484 2485 2486 2487 2488 2489 2490 2491
        if update_dict.has_key(id) or force:
          if not force:
            action = update_dict[id]
            if action == 'nothing':
              continue
          text = self._objects[id]
          path, name = os.path.split(id)
          # This raises an exception if the file already exists.
          try:
2492
            globals()[self.local_file_writer_name](name, text, create=0)
2493
          except IOError, error:
2494
            LOG("BusinessTemplate.py", WARNING, "Cannot install class %s on file system" %(name,))
2495 2496
            if error.errno :
              raise
2497 2498 2499
            continue
          if self.local_file_importer_name is not None:
            globals()[self.local_file_importer_name](name)
Aurel's avatar
Aurel committed
2500
    else:
2501 2502 2503
      BaseTemplateItem.install(self, context, trashbin, **kw)
      for id in self._archive.keys():
        text = self._archive[id]
Aurel's avatar
Aurel committed
2504
        # This raises an exception if the file exists.
2505
        globals()[self.local_file_writer_name](id, text, create=1)
Aurel's avatar
Aurel committed
2506 2507
        if self.local_file_importer_name is not None:
          globals()[self.local_file_importer_name](id)
2508 2509

  def uninstall(self, context, **kw):
2510
    object_path = kw.get('object_path', None)
2511 2512 2513 2514 2515
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for id in object_keys:
2516
      globals()[self.local_file_remover_name](id)
2517 2518
    BaseTemplateItem.uninstall(self, context, **kw)

Aurel's avatar
Aurel committed
2519 2520 2521 2522 2523 2524
  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():
2525 2526
      obj = self._objects[path]
      bta.addObject(obj=obj, name=path, path=None, ext='.py')
Aurel's avatar
Aurel committed
2527

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

2532 2533 2534 2535 2536 2537
class PropertySheetTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalPropertySheet'
  local_file_writer_name = 'writeLocalPropertySheet'
  local_file_importer_name = 'importLocalPropertySheet'
  local_file_remover_name = 'removeLocalPropertySheet'

2538 2539 2540 2541 2542
class ConstraintTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalConstraint'
  local_file_writer_name = 'writeLocalConstraint'
  local_file_importer_name = 'importLocalConstraint'
  local_file_remover_name = 'removeLocalConstraint'
2543

2544 2545 2546
class ExtensionTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalExtension'
  local_file_writer_name = 'writeLocalExtension'
2547 2548
  # Extension needs no import
  local_file_importer_name = None
2549 2550 2551 2552 2553
  local_file_remover_name = 'removeLocalExtension'

class TestTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalTest'
  local_file_writer_name = 'writeLocalTest'
2554
  # Test needs no import
2555 2556 2557
  local_file_importer_name = None
  local_file_remover_name = 'removeLocalTest'

Aurel's avatar
Aurel committed
2558

2559 2560 2561
class ProductTemplateItem(BaseTemplateItem):
  # XXX Not implemented yet
  pass
2562 2563 2564

class RoleTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2565 2566 2567 2568
  def build(self, context, **kw):
    role_list = []
    for key in self._archive.keys():
      role_list.append(key)
2569 2570
    if len(role_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'role_list'] = role_list
Aurel's avatar
Aurel committed
2571

2572
  def preinstall(self, context, installed_bt, **kw):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2573 2574
    modified_object_list = {}
    if context.getTemplateFormatVersion() == 1:
2575
      new_roles = self._objects.keys()
2576 2577 2578
      if installed_bt.id == 'installed_bt_for_diff':
        #must rename keys in dict if reinstall
        new_dict = PersistentMapping()
Aurel's avatar
Aurel committed
2579 2580 2581
        old_keys = ()
        if len(installed_bt._objects.values()) > 0:
          old_keys = installed_bt._objects.values()[0]
2582 2583
        for key in old_keys:
          new_dict[key] = ''
2584
        installed_bt._objects = new_dict
2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597
      for role in new_roles:
        if installed_bt._objects.has_key(role):
          continue
        else: # only show new roles
          modified_object_list.update({role : ['New', 'Role']})
      # get removed roles
      old_roles = installed_bt._objects.keys()
      for role in old_roles:
        if role not in new_roles:
          modified_object_list.update({role : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
2598
    p = context.getPortalObject()
2599
    # get roles
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2600
    if context.getTemplateFormatVersion() == 1:
2601
      role_list = self._objects.keys()
2602
    else:
2603
      role_list = self._archive.keys()
2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615
    # set roles in PAS
    if p.acl_users.meta_type == 'Pluggable Auth Service':
      role_manager_list = p.acl_users.objectValues('ZODB Role Manager')
      for role_manager in role_manager_list:
        existing_role_list = role_manager.listRoleIds()
        for role in role_list:
          if role not in existing_role_list:
            role_manager.addRole(role)
    # set roles on portal
    roles = {}
    for role in p.__ac_roles__:
      roles[role] = 1
2616
    for role in role_list:
2617 2618 2619 2620 2621 2622 2623 2624 2625 2626
      roles[role] = 1
    p.__ac_roles__ = tuple(roles.keys())

  def _importFile(self, file_name, file):
    xml = parse(file)
    role_list = xml.getElementsByTagName('role')
    for role in role_list:
      node = role.childNodes[0]
      value = node.data
      self._objects[str(value)] = 1
2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638

  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)

2639 2640 2641 2642 2643 2644 2645 2646 2647
  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
2648
      if role in roles and role not in new_roles:
2649 2650 2651
        del roles[role]
    p.__ac_roles__ = tuple(roles.keys())

Christophe Dumez's avatar
Christophe Dumez committed
2652
  # Function to generate XML Code Manually
2653
  def generateXml(self, path):
2654
    obj = self._objects[path]
2655
    xml_data = '<role_list>'
2656 2657
    obj.sort()
    for role in obj:
2658 2659 2660 2661
      xml_data += os.linesep+' <role>%s</role>' %(role)
    xml_data += os.linesep+'</role_list>'
    return xml_data

Aurel's avatar
Aurel committed
2662 2663 2664 2665 2666 2667
  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():
2668
      xml_data = self.generateXml(path=path)
2669
      bta.addObject(obj=xml_data, name=path, path=None,)
2670

2671 2672
class CatalogResultKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2673
  def build(self, context, **kw):
2674 2675
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2676
    except KeyError:
2677 2678 2679 2680
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2681
    sql_search_result_keys = list(catalog.sql_search_result_keys)
2682
    key_list = []
2683
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
2684
      if key in sql_search_result_keys:
2685
        key_list.append(key)
Aurel's avatar
Aurel committed
2686 2687
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
2688
    if len(key_list) > 0:
Aurel's avatar
Aurel committed
2689
      self._objects[self.__class__.__name__+os.sep+'result_key_list'] = key_list
2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700

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

2701
  def install(self, context, trashbin, **kw):
2702 2703 2704 2705 2706 2707 2708
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2709

2710
    sql_search_result_keys = list(catalog.sql_search_result_keys)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2711
    if context.getTemplateFormatVersion() == 1:
2712 2713
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
2714 2715 2716
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
2717
    else:
2718
      keys = self._archive.keys()
2719 2720
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2721
    # XXX same as related key
Aurel's avatar
Aurel committed
2722
    if update_dict.has_key('result_key_list') or force:
2723
      if not force:
Aurel's avatar
Aurel committed
2724
        action = update_dict['result_key_list']
2725
        if action == 'nothing':
Aurel's avatar
Aurel committed
2726
          return
2727
      for key in keys:
2728 2729
        if key not in sql_search_result_keys:
          sql_search_result_keys.append(key)
2730
      catalog.sql_search_result_keys = sql_search_result_keys
2731

2732
  def uninstall(self, context, **kw):
2733 2734
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2735
    except KeyError:
2736 2737 2738 2739 2740
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_search_result_keys = list(catalog.sql_search_result_keys)
2741
    object_path = kw.get('object_path', None)
2742 2743 2744 2745 2746
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
2747 2748
      if key in sql_search_result_keys:
        sql_search_result_keys.remove(key)
2749
    catalog.sql_search_result_keys = sql_search_result_keys
2750 2751
    BaseTemplateItem.uninstall(self, context, **kw)

Christophe Dumez's avatar
Christophe Dumez committed
2752
  # Function to generate XML Code Manually
2753
  def generateXml(self, path=None):
2754
    obj = self._objects[path]
2755
    xml_data = '<key_list>'
2756 2757
    obj.sort()
    for key in obj:
2758 2759 2760 2761
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

Aurel's avatar
Aurel committed
2762 2763 2764 2765 2766
  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)
2767
    for path in self._objects.keys():
2768
      xml_data = self.generateXml(path=path)
2769
      bta.addObject(obj=xml_data, name=path, path=None)
2770

2771 2772
class CatalogRelatedKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2773
  def build(self, context, **kw):
2774 2775
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2776
    except KeyError:
2777 2778 2779 2780
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
2781
    sql_search_related_keys = list(catalog.sql_catalog_related_keys)
2782
    key_list = []
2783
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
2784
      if key in sql_search_related_keys:
2785
        key_list.append(key)
Aurel's avatar
Aurel committed
2786 2787
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
2788
    if len(key_list) > 0:
Aurel's avatar
Aurel committed
2789
      self._objects[self.__class__.__name__+os.sep+'related_key_list'] = key_list
2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800

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

2801
  def install(self, context, trashbin, **kw):
2802 2803 2804 2805 2806 2807 2808
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2809

2810
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2811
    if context.getTemplateFormatVersion() == 1:
2812
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
2813
        return
2814 2815 2816
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
2817
    else:
2818
      keys = self._archive.keys()
2819 2820
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
Aurel's avatar
Aurel committed
2821
    # XXX must a find a better way to manage related key
Aurel's avatar
Aurel committed
2822
    if update_dict.has_key('related_key_list') or update_dict.has_key('key_list') or force:
Aurel's avatar
Aurel committed
2823
      if not force:
Aurel's avatar
Aurel committed
2824 2825 2826 2827
        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
2828 2829 2830
        if action == 'nothing':
          return
      for key in keys:
2831 2832
        if key not in sql_catalog_related_keys:
          sql_catalog_related_keys.append(key)
Aurel's avatar
Aurel committed
2833
      catalog.sql_catalog_related_keys = tuple(sql_catalog_related_keys)
2834

2835 2836 2837
  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2838
    except KeyError:
2839 2840 2841 2842
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2843
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
2844
    object_path = kw.get('object_path', None)
2845 2846 2847 2848 2849
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
2850 2851 2852 2853 2854
      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
2855
  # Function to generate XML Code Manually
2856
  def generateXml(self, path=None):
2857
    obj = self._objects[path]
2858
    xml_data = '<key_list>'
2859 2860
    obj.sort()
    for key in obj:
2861 2862 2863 2864
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

Aurel's avatar
Aurel committed
2865 2866 2867 2868 2869 2870
  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():
2871
      xml_data = self.generateXml(path=path)
2872
      bta.addObject(obj=xml_data, name=path, path=None)
2873

2874 2875
class CatalogResultTableTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2876
  def build(self, context, **kw):
2877 2878
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2879
    except KeyError:
2880 2881 2882 2883
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
2884
    sql_search_result_tables = list(catalog.sql_search_tables)
2885
    key_list = []
2886
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
2887
      if key in sql_search_result_tables:
2888
        key_list.append(key)
Aurel's avatar
Aurel committed
2889 2890
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
2891
    if len(key_list) > 0:
2892
      self._objects[self.__class__.__name__+os.sep+'result_table_list'] = key_list
2893 2894 2895 2896 2897 2898 2899 2900 2901 2902

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

2904
  def install(self, context, trashbin, **kw):
2905 2906 2907 2908 2909 2910 2911
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2912

2913
    sql_search_tables = list(catalog.sql_search_tables)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2914
    if context.getTemplateFormatVersion() == 1:
2915
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
2916
        return
2917 2918 2919
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
2920
    else:
2921
      keys = self._archive.keys()
2922 2923
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2924
    # XXX same as related keys
Aurel's avatar
Aurel committed
2925
    if update_dict.has_key('result_table_list') or force:
2926
      if not force:
Aurel's avatar
Aurel committed
2927
        action = update_dict['result_table_list']
2928
        if action == 'nothing':
Aurel's avatar
Aurel committed
2929
          return
2930
      for key in keys:
2931 2932
        if key not in sql_search_tables:
          sql_search_tables.append(key)
2933
      catalog.sql_search_tables = tuple(sql_search_tables)
2934 2935

  def uninstall(self, context, **kw):
2936 2937
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2938
    except KeyError:
2939 2940 2941 2942 2943
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_search_tables = list(catalog.sql_search_tables)
2944
    object_path = kw.get('object_path', None)
2945 2946 2947 2948 2949
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
2950 2951
      if key in sql_search_tables:
        sql_search_tables.remove(key)
2952
    catalog.sql_search_tables = sql_search_tables
2953 2954
    BaseTemplateItem.uninstall(self, context, **kw)

Christophe Dumez's avatar
Christophe Dumez committed
2955
  # Function to generate XML Code Manually
2956
  def generateXml(self, path=None):
2957
    obj = self._objects[path]
2958
    xml_data = '<key_list>'
2959 2960
    obj.sort()
    for key in obj:
2961
      xml_data += os.linesep+' <key>%s</key>' %(key)
2962
    xml_data += os.linesep+'</key_list>'
2963 2964
    return xml_data

Aurel's avatar
Aurel committed
2965 2966 2967 2968 2969 2970
  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():
2971
      xml_data = self.generateXml(path=path)
2972
      bta.addObject(obj=xml_data, name=path, path=None)
2973

2974 2975
# keyword
class CatalogKeywordKeyTemplateItem(BaseTemplateItem):
2976 2977

  def build(self, context, **kw):
2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_keyword_keys = list(catalog.sql_catalog_keyword_search_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_keyword_keys:
        key_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'keyword_key_list'] = key_list
Aurel's avatar
Aurel committed
2994

2995 2996 2997 2998 2999 3000 3001 3002 3003
  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list
3004 3005

  def install(self, context, trashbin, **kw):
3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

    sql_keyword_keys = list(catalog.sql_catalog_keyword_search_keys)
    if context.getTemplateFormatVersion() == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
    else:
      keys = self._archive.keys()
3023 3024
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044
    # XXX same as related key
    if update_dict.has_key('keyword_key_list') or force:
      if not force:
        action = update_dict['keyword_key_list']
        if action == 'nothing':
          return
      for key in keys:
        if key not in sql_keyword_keys:
          sql_keyword_keys.append(key)
      catalog.sql_catalog_keyword_search_keys = sql_keyword_keys

  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_keyword_keys = list(catalog.sql_catalog_keyword_search_keys)
3045
    object_path = kw.get('object_path', None)
3046 3047
    if object_path is not None:
      object_keys = [object_path]
Aurel's avatar
Aurel committed
3048
    else:
3049 3050 3051 3052 3053 3054 3055
      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
3056
  # Function to generate XML Code Manually
3057 3058 3059 3060 3061 3062 3063 3064
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data
Aurel's avatar
Aurel committed
3065 3066 3067 3068

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
3069 3070 3071 3072 3073
    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)
3074

3075 3076
# full text
class CatalogFullTextKeyTemplateItem(BaseTemplateItem):
3077

3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093
  def build(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_full_text_keys = list(catalog.sql_catalog_full_text_search_keys)
    key_list = []
    for key in self._archive.keys():
      if key in sql_full_text_keys:
        key_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
    if len(key_list) > 0:
3094
      self._objects[self.__class__.__name__+os.sep+'full_text_key_list'] = key_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3095

3096 3097 3098 3099 3100 3101 3102 3103 3104
  def _importFile(self, file_name, file):
    list = []
    xml = parse(file)
    key_list = xml.getElementsByTagName('key')
    for key in key_list:
      node = key.childNodes[0]
      value = node.data
      list.append(str(value))
    self._objects[file_name[:-4]] = list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3105

3106 3107 3108 3109 3110 3111 3112 3113
  def install(self, context, trashbin, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3114

3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135
    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
3136

3137 3138 3139 3140 3141 3142 3143 3144 3145
  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_full_text_keys = list(catalog.sql_catalog_full_text_search_keys)
3146
    object_path = kw.get('object_path', None)
3147 3148 3149 3150 3151 3152 3153 3154 3155 3156
    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
3157
  # Function to generate XML Code Manually
3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

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


# request
class CatalogRequestKeyTemplateItem(BaseTemplateItem):

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

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

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

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

  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_request_keys = list(catalog.sql_catalog_request_keys)
3248
    object_path = kw.get('object_path', None)
3249 3250 3251 3252 3253 3254 3255 3256 3257 3258
    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
3259
  # Function to generate XML Code Manually
3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

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

# multivalue
class CatalogMultivalueKeyTemplateItem(BaseTemplateItem):

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

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

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

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

  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_multivalue_keys = list(catalog.sql_catalog_multivalue_keys)
3348
    object_path = kw.get('object_path', None)
3349 3350 3351 3352 3353 3354 3355 3356 3357 3358
    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
3359
  # Function to generate XML Code Manually
3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

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

# topic
class CatalogTopicKeyTemplateItem(BaseTemplateItem):

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

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

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

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

  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_catalog_topic_search_keys = list(catalog.sql_catalog_topic_search_keys)
3449
    object_path = kw.get('object_path', None)
3450 3451 3452 3453 3454 3455 3456 3457 3458 3459
    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
3460
  # Function to generate XML Code Manually
3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482
  def generateXml(self, path=None):
    obj = self._objects[path]
    xml_data = '<key_list>'
    obj.sort()
    for key in obj:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

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

class MessageTranslationTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    localizer = context.getPortalObject().Localizer
3483 3484 3485 3486 3487 3488 3489 3490 3491
    for lang_key in self._archive.keys():
      if '|' in lang_key:
        lang, catalog = lang_key.split(' | ')
      else: # XXX backward compatibilty
        lang = lang_key
        catalog = 'erp5_ui'
      path = os.path.join(lang, catalog)
      mc = localizer._getOb(catalog)
      self._objects[path] = mc.manage_export(lang)
3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522

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

  def install(self, context, trashbin, **kw):
    localizer = context.getPortalObject().Localizer
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    if context.getTemplateFormatVersion() == 1:
      for path, po in self._objects.items():
        if update_dict.has_key(path) or force:
          if not force:
            action = update_dict[path]
            if action == 'nothing':
3523
              continue
3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552
          path = string.split(path, '/')
          lang = path[-3]
          catalog = path[-2]
          if lang not in localizer.get_languages():
            localizer.manage_addLanguage(lang)
          mc = localizer._getOb(catalog)
          if lang not in mc.get_languages():
            mc.manage_addLanguage(lang)
          mc.manage_import(lang, po)
    else:
      BaseTemplateItem.install(self, context, trashbin, **kw)
      for lang, catalogs in self._archive.items():
        if lang not in localizer.get_languages():
          localizer.manage_addLanguage(lang)
        for catalog, po in catalogs.items():
          mc = localizer._getOb(catalog)
          if lang not in mc.get_languages():
            mc.manage_addLanguage(lang)
          mc.manage_import(lang, po)

  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
    for key in self._objects.keys():
      obj = self._objects[key]
      path = os.path.join(root_path, key)
      bta.addFolder(name=path)
Aurel's avatar
Aurel committed
3553
      f = open(path+os.sep+'translation.po', 'wt')
3554 3555 3556 3557
      f.write(str(obj))
      f.close()

  def _importFile(self, file_name, file):
3558 3559 3560
    if os.path.split(file_name)[1] == 'translation.po':
      text = file.read()
      self._objects[file_name[:-3]] = text
3561

Aurel's avatar
Aurel committed
3562 3563
class LocalRolesTemplateItem(BaseTemplateItem):

3564
  def __init__(self, id_list, **kw):
3565
    id_list = ['local_roles/%s' % id for id in id_list if id != '']
3566 3567
    BaseTemplateItem.__init__(self, id_list, **kw)

Aurel's avatar
Aurel committed
3568 3569 3570
  def build(self, context, **kw):
    p = context.getPortalObject()
    for path in self._archive.keys():
3571
      obj = p.unrestrictedTraverse(path.split('/', 1)[1])
3572 3573 3574 3575
      local_roles_dict = getattr(obj, '__ac_local_roles__',
                                        {}) or {}
      group_local_roles_dict = getattr(obj, '__ac_local_group_roles__',
                                        {}) or {}
Aurel's avatar
Aurel committed
3576 3577
      self._objects[path] = (local_roles_dict, group_local_roles_dict)

Christophe Dumez's avatar
Christophe Dumez committed
3578
  # Function to generate XML Code Manually
Aurel's avatar
Aurel committed
3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605
  def generateXml(self, path=None):
    local_roles_dict, group_local_roles_dict = self._objects[path]
    local_roles_keys = local_roles_dict.keys()
    group_local_roles_keys = group_local_roles_dict.keys()
    local_roles_keys.sort()
    group_local_roles_keys.sort()
    # local roles
    xml_data = '<local_roles_item>'
    xml_data += os.linesep+' <local_roles>'
    for key in local_roles_keys:
      xml_data += os.linesep+"  <role id='%s'>" %(key,)
      tuple = local_roles_dict[key]
      for item in tuple:
        xml_data += os.linesep+"   <item>%s</item>" %(item,)
      xml_data += os.linesep+"  </role>"
    xml_data += os.linesep+' </local_roles>'
    # group local roles
    xml_data += os.linesep+' <group_local_roles>'
    for key in group_local_roles_keys:
      xml_data += os.linesep+"  <role id='%s'>" %(key,)
      tuple = group_local_roles_dict[key]
      for item in tuple:
        xml_data += os.linesep+"   <item>%s</item>" %(item,)
      xml_data += os.linesep+"  </role>"
    xml_data += os.linesep+' </group_local_roles>'
    xml_data += os.linesep+'</local_roles_item>'
    return xml_data
3606

Aurel's avatar
Aurel committed
3607 3608 3609 3610 3611 3612 3613 3614 3615 3616
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
    bta.addFolder(name=root_path)
    for key in self._objects.keys():
      xml_data = self.generateXml(key)

      folders, id = os.path.split(key)
      encode_folders = []
3617
      for folder in folders.split('/')[1:]:
Aurel's avatar
Aurel committed
3618 3619 3620 3621
        if '%' not in folder:
          encode_folders.append(pathname2url(folder))
        else:
          encode_folders.append(folder)
3622
      path = os.path.join(root_path, (os.sep).join(encode_folders))
Aurel's avatar
Aurel committed
3623 3624 3625 3626 3627 3628 3629
      bta.addFolder(name=path)
      bta.addObject(obj=xml_data, name=id, path=path)

  def _importFile(self, file_name, file):
    xml = parse(file)
    # local roles
    local_roles = xml.getElementsByTagName('local_roles')[0]
3630
    local_roles_list = local_roles.getElementsByTagName('role')
Aurel's avatar
Aurel committed
3631 3632 3633 3634 3635 3636 3637 3638 3639 3640
    local_roles_dict = {}
    for role in local_roles_list:
      id = role.getAttribute('id')
      item_type_list = []
      item_list = role.getElementsByTagName('item')
      for item in item_list:
        item_type_list.append(str(item.childNodes[0].data))
      local_roles_dict[id] = item_type_list
    # group local roles
    group_local_roles = xml.getElementsByTagName('group_local_roles')[0]
3641
    local_roles_list = group_local_roles.getElementsByTagName('role')
Aurel's avatar
Aurel committed
3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674
    group_local_roles_dict = {}
    for role in local_roles_list:
      id = role.getAttribute('id')
      item_type_list = []
      item_list = role.getElementsByTagName('item')
      for item in item_list:
        item_type_list.append(str(item.childNodes[0].data))
      group_local_roles_dict[id] = item_type_list
    self._objects['local_roles/'+file_name[:-4]] = (local_roles_dict, group_local_roles_dict)

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

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
    for roles_path in self._objects.keys():
      path = roles_path.split('/')[1:]
      obj = p.unrestrictedTraverse(path)
      setattr(obj, '__ac_local_roles__', {})
      setattr(obj, '__ac_local_group_roles__', {})
3675

3676 3677 3678
class BusinessTemplate(XMLObject):
    """
    A business template allows to construct ERP5 modules
Christophe Dumez's avatar
Christophe Dumez committed
3679
    in part or completely. Each object is separated from its
3680 3681 3682 3683 3684 3685 3686 3687 3688 3689
    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
3690 3691
      - object without optimal actions
      - list of relation between portal type and workflow
3692 3693 3694 3695 3696 3697 3698 3699 3700 3701

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

    - site property definition
      - id
      - type
3702
      - value
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3703

3704 3705
    - document/propertysheet/extension/test definition
      - copy of the local file
3706

3707
    - message transalation definition
3708
      - .po file
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3709

3710 3711
    The Business Template properties are exported to the bt folder with
    one property per file
3712

Jean-Paul Smets's avatar
Jean-Paul Smets committed
3713 3714
    Technology:

3715 3716
    - 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
3717

3718
    - install files to the right location (install)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3719 3720 3721 3722

    Use case:

    - install core ERP5 (the minimum)
3723

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

3726 3727
    - go to "BT" menu. Create new BT.
      Define BT elements (workflow, methods, attributes, etc.).
3728
      Build BT and export or save it
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3729 3730 3731 3732 3733
      Done.
    """

    meta_type = 'ERP5 Business Template'
    portal_type = 'Business Template'
3734
    add_permission = Permissions.AddPortalContent
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3735 3736 3737 3738 3739
    isPortalContent = 1
    isRADContent = 1

    # Declarative security
    security = ClassSecurityInfo()
3740
    security.declareObjectProtected(Permissions.AccessContentsInformation)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3741 3742 3743 3744 3745 3746 3747

    # Declarative interfaces
    __implements__ = ( Interface.Variated, )

    # Declarative properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
3748
                      , PropertySheet.SimpleItem
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3749
                      , PropertySheet.CategoryCore
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3750
                      , PropertySheet.Version
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3751 3752 3753
                      , PropertySheet.BusinessTemplate
                      )

3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770
    # Factory Type Information
    factory_type_information = \
      {    'id'             : portal_type
         , 'meta_type'      : meta_type
         , 'description'    : """\
Business Template is a set of definitions, such as skins, portal types and categories. This is used to set up a new ERP5 site very efficiently."""
         , 'icon'           : 'order_line_icon.gif'
         , 'product'        : 'ERP5Type'
         , 'factory'        : 'addBusinessTemplate'
         , 'immediate_view' : 'BusinessTemplate_view'
         , 'allow_discussion'     : 1
         , 'allowed_content_types': (
                                      )
         , 'filter_content_types' : 1
         , 'global_allow'   : 1
      }

3771 3772
    # This is a global variable
    # Order is important for installation
3773 3774 3775 3776 3777 3778
    # 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 )
3779 3780 3781
    _item_name_list = [
      '_product_item',
      '_property_sheet_item',
3782
      '_constraint_item',
3783 3784 3785 3786 3787 3788 3789 3790
      '_document_item',
      '_extension_item',
      '_test_item',
      '_role_item',
      '_message_translation_item',
      '_workflow_item',
      '_site_property_item',
      '_portal_type_item',
3791
      '_portal_type_workflow_chain_item',
3792 3793 3794 3795
      '_portal_type_allowed_content_type_item',
      '_portal_type_hidden_content_type_item',
      '_portal_type_property_sheet_item',
      '_portal_type_base_category_item',
3796 3797
      '_category_item',
      '_module_item',
3798
      '_path_item',
3799
      '_skin_item',
3800
      '_preference_item',
3801
      '_action_item',
3802
      '_portal_type_roles_item',
3803
      '_local_roles_item',
3804
      '_catalog_method_item',
3805 3806 3807
      '_catalog_result_key_item',
      '_catalog_related_key_item',
      '_catalog_result_table_item',
3808 3809 3810 3811 3812
      '_catalog_keyword_key_item',
      '_catalog_full_text_key_item',
      '_catalog_request_key_item',
      '_catalog_multivalue_key_item',
      '_catalog_topic_key_item',
3813 3814 3815 3816
    ]

    def __init__(self, *args, **kw):
      XMLObject.__init__(self, *args, **kw)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3817 3818 3819 3820 3821 3822
      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
3823
        self._baseGetTemplateFormatVersion()
3824

Yoshinori Okuji's avatar
Yoshinori Okuji committed
3825 3826 3827
      # the attribute _objects in BaseTemplateItem was added in the new format.
      if hasattr(self._path_item, '_objects'):
        return 1
3828

Yoshinori Okuji's avatar
Yoshinori Okuji committed
3829
      return 0
3830

3831
    security.declareProtected(Permissions.ManagePortal, 'manage_afterAdd')
3832 3833 3834 3835 3836 3837 3838
    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".
3839 3840
        if portal_workflow.getStatusOf(
                'business_template_installation_workflow', self) is not None:
3841
          # XXX Not good to access the attribute directly,
3842 3843 3844
          # but there is no API for clearing the history.
          self.workflow_history[
                            'business_template_installation_workflow'] = None
3845

3846 3847
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getRevision')
3848 3849 3850 3851 3852 3853
    def getRevision(self):
      """returns the revision property.
      This is a workaround for #461.
      """
      return self._baseGetRevision()

3854
    def updateRevisionNumber(self):
Jérome Perrin's avatar
Jérome Perrin committed
3855 3856 3857 3858 3859 3860 3861 3862
        """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)
3863

3864
    security.declareProtected(Permissions.ManagePortal, 'build')
3865
    def build(self, no_action=0):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3866 3867 3868
      """
        Copy existing portal objects to self
      """
3869
      if no_action: return
3870
        # this is use at import of Business Template to get the status built
3871 3872
      # Make sure that everything is sane.
      self.clean()
3873

3874 3875 3876
      # Update revision number
      # <christophe@nexedi.com>
      self.updateRevisionNumber()
3877

Yoshinori Okuji's avatar
Yoshinori Okuji committed
3878 3879
      self._setTemplateFormatVersion(1)

3880
      # Store all data
3881 3882
      self._portal_type_item = \
          PortalTypeTemplateItem(self.getTemplatePortalTypeIdList())
3883
      self._portal_type_workflow_chain_item = \
3884
          PortalTypeWorkflowChainTemplateItem(self.getTemplatePortalTypeWorkflowChainList())
3885 3886 3887 3888 3889 3890 3891 3892 3893 3894
      self._workflow_item = \
          WorkflowTemplateItem(self.getTemplateWorkflowIdList())
      self._skin_item = \
          SkinTemplateItem(self.getTemplateSkinIdList())
      self._category_item = \
          CategoryTemplateItem(self.getTemplateBaseCategoryList())
      self._catalog_method_item = \
          CatalogMethodTemplateItem(self.getTemplateCatalogMethodIdList())
      self._action_item = \
          ActionTemplateItem(self.getTemplateActionPathList())
3895 3896
      self._portal_type_roles_item = \
          PortalTypeRolesTemplateItem(self.getTemplatePortalTypeRolesList())
3897 3898 3899 3900 3901 3902 3903 3904
      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())
3905 3906
      self._constraint_item = \
          ConstraintTemplateItem(self.getTemplateConstraintIdList())
3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926
      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())
3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938
      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())
3939 3940
      self._path_item = \
               PathTemplateItem(self.getTemplatePathList())
3941 3942
      self._preference_item = \
               PreferenceTemplateItem(self.getTemplatePreferenceList())
3943 3944
      self._catalog_keyword_key_item = \
          CatalogKeywordKeyTemplateItem(
3945
               self.getTemplateCatalogKeywordKeyList())
3946 3947
      self._catalog_full_text_key_item = \
          CatalogFullTextKeyTemplateItem(
3948
               self.getTemplateCatalogFullTextKeyList())
3949 3950
      self._catalog_request_key_item = \
          CatalogRequestKeyTemplateItem(
3951
               self.getTemplateCatalogRequestKeyList())
3952 3953
      self._catalog_multivalue_key_item = \
          CatalogMultivalueKeyTemplateItem(
3954
               self.getTemplateCatalogMultivalueKeyList())
3955 3956 3957
      self._catalog_topic_key_item = \
          CatalogTopicKeyTemplateItem(
               self.getTemplateCatalogTopicKeyList())
Aurel's avatar
Aurel committed
3958 3959 3960
      self._local_roles_item = \
          LocalRolesTemplateItem(
               self.getTemplateLocalRolesList())
3961

3962 3963 3964
      # Build each part
      for item_name in self._item_name_list:
        getattr(self, item_name).build(self)
3965

3966
    build = WorkflowMethod(build)
3967 3968

    def publish(self, url, username=None, password=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3969
      """
3970
        Publish in a format or another
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3971
      """
3972
      return self.portal_templates.publish(self, url, username=username,
3973
                                           password=password)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3974

3975
    def update(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3976
      """
3977
        Update template: download new template definition
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3978
      """
3979
      return self.portal_templates.update(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
3980

3981 3982 3983 3984
    def preinstall(self, **kw):
      """
        Return the list of modified/new/removed object between a Business Template
        and the one installed if exists
Aurel's avatar
Aurel committed
3985
      """
3986

3987 3988
      modified_object_list = {}
      bt_title = self.getTitle()
3989 3990 3991 3992 3993 3994 3995

      #  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)
3996 3997 3998
      if installed_bt is None:
        installed_bt_format = 0 # that will not check for modification
      else:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
3999
        installed_bt_format = installed_bt.getTemplateFormatVersion()
4000

4001
      # if reinstall business template, must compare to object in ZODB
4002
      # and not to those in the installed Business Template because it is itself.
Christophe Dumez's avatar
Christophe Dumez committed
4003
      # same if we make a diff and select only one business template
4004
      reinstall = 0
4005 4006
      if installed_bt == self:
        reinstall = 1
4007 4008 4009
        bt2 = self.portal_templates.manage_clone(ob=installed_bt, id='installed_bt_for_diff')
        # update portal types properties to get last modifications
        bt2.getPortalTypesProperties()
4010 4011 4012
        bt2.edit(description='tmp bt generated for diff')
        bt2.build()
        installed_bt = bt2
4013

Yoshinori Okuji's avatar
Yoshinori Okuji committed
4014
      new_bt_format = self.getTemplateFormatVersion()
4015 4016 4017 4018 4019 4020
      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:
4021
          item = getattr(self, item_name, None)
4022 4023 4024 4025
          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
4026

4027 4028 4029 4030
      # get the list of modified and new object
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
      for item_name in self._item_name_list:
4031
        new_item = getattr(self, item_name, None)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
4032
        old_item = getattr(installed_bt, item_name, None)
4033
        if new_item is not None:
4034
          if old_item is not None and hasattr(old_item, '_objects'):
4035 4036 4037 4038 4039 4040 4041 4042
            modified_object = new_item.preinstall(context=local_configuration, installed_bt=old_item)
            if len(modified_object) > 0:
              modified_object_list.update(modified_object)
          else:
            for path in new_item._objects.keys():
              modified_object_list.update({path : ['New', new_item.__class__.__name__[:-12]]})

      if reinstall:
4043
        self.portal_templates.manage_delObjects(ids=['installed_bt_for_diff'])
4044

4045 4046
      return modified_object_list

4047
    def _install(self, force=1, object_to_update=None, **kw):
4048
      """
Christophe Dumez's avatar
Christophe Dumez committed
4049
        Install a new Business Template, if force, all will be upgraded or installed
4050 4051
        otherwise depends of dict object_to_update
      """
4052 4053 4054 4055
      if object_to_update is not None:
        force=0
      else:
        object_to_update = {}
4056

4057 4058
      installed_bt = self.portal_templates.getInstalledBusinessTemplate(
                                                           self.getTitle())
4059
      if installed_bt is not None:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
4060
        if installed_bt.getTemplateFormatVersion() == 0:
4061
          force = 1
4062
        installed_bt.replace(self)
4063

4064 4065 4066 4067
      trash_tool = getToolByName(self, 'portal_trash', None)
      if trash_tool is None and self.getTemplateFormatVersion() == 1:
        raise AttributeError, 'Trash Tool is not installed'

4068 4069 4070
      # Check the format of business template, if old, force install
      if self.getTemplateFormatVersion() == 0:
        force = 1
4071

4072 4073 4074 4075 4076 4077
      site = self.getPortalObject()
      from Products.ERP5.ERP5Site import ERP5Generator
      gen = ERP5Generator()
      # update activity tool first if necessary
      if self.getTitle() == 'erp5_core' and self.getTemplateUpdateTool():
        LOG('Business Template', 0, 'Updating Activity Tool')
4078
        gen.setupLastTools(site, update=1, create_activities=1)
4079 4080
      if not force:
        if len(object_to_update) == 0:
4081 4082 4083 4084 4085
          # 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():
4086
            LOG('Business Template', 0, 'Updating Business Template Workflows')
4087
            gen.setupWorkflow(site)
4088
          return
4089 4090 4091 4092

      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
4093
      local_configuration = self.portal_templates.getLocalConfiguration(self)
4094

4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107
      # 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
      if trash_tool is not None and (len(object_to_update) > 0 or len(self.portal_templates.objectIds()) > 1):
        trashbin = trash_tool.newTrashBin(self.getTitle(), self)
      else:
        trashbin = None

      # Install everything
      if len(object_to_update) > 0 or force:
        for item_name in self._item_name_list:
          item = getattr(self, item_name, None)
          if item is not None:
            item.install(local_configuration, force=force, object_to_update=object_to_update, trashbin=trashbin)
4108

4109 4110 4111
      # update catalog if necessary
      update_catalog=0
      catalog_method = getattr(self, '_catalog_method_item', None)
4112
      if catalog_method is not None and self.getTemplateFormatVersion() == 1:
4113 4114 4115 4116 4117 4118 4119 4120 4121
        for id in catalog_method._objects.keys():
          if id in object_to_update.keys() or force:
            if not force:
              action = object_to_update[id]
              if action == 'nothing':
                continue
            if 'related' not in id:
              # must update catalog
              update_catalog = 1
4122
              break
4123 4124
      if update_catalog:
        catalog = local_configuration.portal_catalog.getSQLCatalog()
4125
        if (catalog is None) or (not site.isIndexable):
4126 4127 4128 4129 4130
          LOG('Business Template', 0, 'no SQL Catalog available')
          update_catalog = 0
        else:
          LOG('Business Template', 0, 'Updating SQL Catalog')
          catalog.manage_catalogClear()
4131

4132
      # get objects to remove
4133
      # do remove after because we may need backup object from installation
4134 4135 4136 4137 4138 4139
      remove_object_dict = {}
      for path in object_to_update.keys():
        action = object_to_update[path]
        if action == 'remove' or action == 'save_and_remove':
          remove_object_dict[path] = action
          object_to_update.pop(path)
4140

4141 4142 4143
      # remove object from old business template
      if len(remove_object_dict) > 0:
        for item_name in installed_bt._item_name_list:
4144
          item = getattr(installed_bt, item_name, None)
4145 4146
          if item is not None:
            item.remove(local_configuration, remove_object_dict=remove_object_dict, trashbin=trashbin)
4147

4148 4149 4150 4151 4152 4153

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

4154
      # check if we have to update business template workflow
4155
      if self.getTitle() == 'erp5_core' and self.getTemplateUpdateBusinessTemplateWorkflow():
4156
        LOG('Business Template', 0, 'Updating Business Template Workflows')
4157 4158 4159 4160 4161
        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)

4162 4163 4164 4165 4166
      # remove trashbin if empty
      if trashbin is not None:
        if len(trashbin.objectIds()) == 0:
          trash_tool.manage_delObjects([trashbin.getId(),])

4167 4168
      if update_catalog:
        site.ERP5Site_reindexAll()
4169

Yoshinori Okuji's avatar
Yoshinori Okuji committed
4170 4171 4172
      # It is better to clear cache because the installation of a template
      # adds many new things into the portal.
      clearCache()
4173

4174
    security.declareProtected(Permissions.ManagePortal, 'install')
4175 4176 4177
    def install(self, **kw):
      """
        For install based on paramaters provided in **kw
4178
      """
4179
      return self._install(**kw)
4180

4181
    install = WorkflowMethod(install)
4182

4183
    security.declareProtected(Permissions.ManagePortal, 'reinstall')
4184
    def reinstall(self, **kw):
4185 4186 4187 4188
      """Reinstall Business Template.
      """
      return self._install(**kw)

4189
    reinstall = WorkflowMethod(reinstall)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4190

4191
    security.declareProtected(Permissions.ManagePortal, 'trash')
4192 4193
    def trash(self, new_bt, **kw):
      """
4194
        Trash unnecessary items before upgrading to a new business
4195
        template.
4196
        This is similar to uninstall, but different in that this does
4197
        not remove all items.
4198 4199 4200 4201 4202
      """
      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
4203 4204
      # Trash everything
      for item_name in self._item_name_list[::-1]:
4205
        item = getattr(self, item_name, None)
4206 4207 4208 4209
        if item is not None:
          item.trash(
                local_configuration,
                getattr(new_bt, item_name))
4210

4211
    security.declareProtected(Permissions.ManagePortal, 'uninstall')
4212
    def uninstall(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4213
      """
4214
        For uninstall based on paramaters provided in **kw
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4215
      """
4216 4217 4218 4219
      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
4220 4221 4222
      # Uninstall everything
      # Trash everything
      for item_name in self._item_name_list[::-1]:
4223
        item = getattr(self, item_name, None)
4224 4225
        if item is not None:
          item.uninstall(local_configuration)
4226
      # It is better to clear cache because the uninstallation of a
4227
      # template deletes many things from the portal.
4228
      clearCache()
4229

4230 4231
    uninstall = WorkflowMethod(uninstall)

4232
    security.declareProtected(Permissions.ManagePortal, 'clean')
Yoshinori Okuji's avatar
Yoshinori Okuji committed
4233
    def _clean(self):
4234
      """
4235
        Clean built information.
4236
      """
4237
      # First, remove obsolete attributes if present.
4238 4239 4240 4241
      for attr in ( '_action_archive',
                    '_document_archive',
                    '_extension_archive',
                    '_test_archive',
4242
                    '_module_archive',
4243 4244 4245
                    '_object_archive',
                    '_portal_type_archive',
                    '_property_archive',
4246
                    '_property_sheet_archive'):
4247 4248 4249
        if hasattr(self, attr):
          delattr(self, attr)
      # Secondly, make attributes empty.
4250 4251
      for item_name in self._item_name_list:
        item = setattr(self, item_name, None)
4252

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

4255
    security.declareProtected(Permissions.AccessContentsInformation,
4256
                              'getBuildingState')
4257
    def getBuildingState(self, default=None, id_only=1):
4258
      """
4259
        Returns the current state in building
4260
      """
4261
      portal_workflow = getToolByName(self, 'portal_workflow')
4262 4263
      wf = portal_workflow.getWorkflowById(
                          'business_template_building_workflow')
4264
      return wf._getWorkflowStateOf(self, id_only=id_only )
4265

4266
    security.declareProtected(Permissions.AccessContentsInformation,
4267
                              'getInstallationState')
4268
    def getInstallationState(self, default=None, id_only=1):
4269
      """
4270
        Returns the current state in installation
4271
      """
4272
      portal_workflow = getToolByName(self, 'portal_workflow')
4273 4274
      wf = portal_workflow.getWorkflowById(
                           'business_template_installation_workflow')
4275
      return wf._getWorkflowStateOf(self, id_only=id_only )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4276

Yoshinori Okuji's avatar
Yoshinori Okuji committed
4277 4278 4279 4280 4281 4282
    security.declareProtected(Permissions.AccessContentsInformation, 'toxml')
    def toxml(self):
      """
        Return this Business Template in XML
      """
      portal_templates = getToolByName(self, 'portal_templates')
4283
      export_string = portal_templates.manage_exportObject(
4284 4285
                                               id=self.getId(),
                                               toxml=1,
4286
                                               download=1)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
4287
      return export_string
4288

4289
    def _getOrderedList(self, id):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4290
      """
4291 4292
        We have to set this method because we want an
        ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4293
      """
4294
      result = getattr(self, id, ())
4295 4296 4297 4298
      if result is None: result = ()
      if result != ():
        result = list(result)
        result.sort()
4299
        # XXX Why do we need to return a tuple ?
4300 4301
        result = tuple(result)
      return result
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4302

4303
    def getTemplateCatalogMethodIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4304
      """
4305 4306
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4307
      """
4308
      return self._getOrderedList('template_catalog_method_id')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4309

4310
    def getTemplateBaseCategoryList(self):
4311
      """
4312 4313
      We have to set this method because we want an
      ordered list
4314
      """
4315
      return self._getOrderedList('template_base_category')
4316

4317
    def getTemplateWorkflowIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4318
      """
4319 4320
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4321
      """
4322
      return self._getOrderedList('template_workflow_id')
4323

4324
    def getTemplatePortalTypeIdList(self):
4325
      """
4326 4327
      We have to set this method because we want an
      ordered list
4328
      """
4329
      return self._getOrderedList('template_portal_type_id')
4330

4331 4332 4333 4334 4335 4336 4337
    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
4338 4339 4340 4341 4342 4343 4344
    def getTemplatePathList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_path')

4345 4346 4347 4348 4349 4350 4351
    def getTemplatePreferenceList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_preference')

4352 4353 4354 4355 4356 4357
    def getTemplatePortalTypeAllowedContentTypeList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_portal_type_allowed_content_type')
4358

4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379
    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')

4380
    def getTemplateActionPathList(self):
4381
      """
4382 4383
      We have to set this method because we want an
      ordered list
4384
      """
4385
      return self._getOrderedList('template_action_path')
4386

4387 4388 4389 4390 4391 4392 4393
    def getTemplatePortalTypeRolesList(self):
      """
      We have to set this method because we want an
      ordered list
      """
      return self._getOrderedList('template_portal_type_roles')

4394
    def getTemplateSkinIdList(self):
4395
      """
4396 4397
      We have to set this method because we want an
      ordered list
4398
      """
4399
      return self._getOrderedList('template_skin_id')
4400

4401
    def getTemplateModuleIdList(self):
4402
      """
4403 4404
      We have to set this method because we want an
      ordered list
4405
      """
4406
      return self._getOrderedList('template_module_id')
4407 4408 4409 4410 4411 4412 4413

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

4415
    security.declareProtected(Permissions.ManagePortal, 'export')
Aurel's avatar
Aurel committed
4416 4417 4418 4419
    def export(self, path=None, local=0, **kw):
      """
        Export this Business Template
      """
4420
      if self.getBuildingState() != 'built':
4421
        raise TemplateConditionError, \
Christophe Dumez's avatar
Christophe Dumez committed
4422
              'Business Template must be built before export'
4423 4424 4425
      if self.getInstallationState() == 'installed':
        raise TemplateConditionError, \
              'Can not export installed Business Template'
4426

Aurel's avatar
Aurel committed
4427 4428 4429 4430 4431 4432 4433
      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)

4434
      # export bt
4435
      bta.addFolder(path+os.sep+'bt')
Aurel's avatar
Aurel committed
4436
      for prop in self.propertyMap():
4437
        prop_type = prop['type']
Aurel's avatar
Aurel committed
4438
        id = prop['id']
4439
        if id in ('id', 'uid', 'rid', 'sid', 'id_group', 'last_id',
4440
                  'install_object_list_list', 'id_generator'):
4441
          continue
Aurel's avatar
Aurel committed
4442
        value = self.getProperty(id)
4443
        if prop_type in ('text', 'string', 'int', 'boolean'):
4444
          bta.addObject(obj=value, name=id, path=path+os.sep+'bt', ext='')
4445
        elif prop_type in ('lines', 'tokens'):
4446
          bta.addObject(obj=str(os.linesep).join(value), name=id,
4447
                        path=path+os.sep+'bt', ext='')
4448

Aurel's avatar
Aurel committed
4449 4450 4451
      # Export each part
      for item_name in self._item_name_list:
        getattr(self, item_name).export(context=self, bta=bta)
4452

Aurel's avatar
Aurel committed
4453 4454
      return bta.finishCreation()

4455
    security.declareProtected(Permissions.ManagePortal, 'importFile')
Aurel's avatar
Aurel committed
4456 4457
    def importFile(self, dir = 0, file=None, root_path=None):
      """
4458
        Import all xml files in Business Template
Aurel's avatar
Aurel committed
4459 4460 4461 4462 4463
      """
      if dir:
        bta = BusinessTemplateFolder(importing=1, file=file, path=root_path)
      else:
        bta = BusinessTemplateTarball(importing=1, file=file)
4464

Aurel's avatar
Aurel committed
4465 4466
      self._portal_type_item = \
          PortalTypeTemplateItem(self.getTemplatePortalTypeIdList())
4467
      self._portal_type_workflow_chain_item = \
4468
          PortalTypeWorkflowChainTemplateItem(self.getTemplatePortalTypeWorkflowChainList())
Aurel's avatar
Aurel committed
4469 4470 4471 4472 4473 4474 4475 4476 4477 4478
      self._workflow_item = \
          WorkflowTemplateItem(self.getTemplateWorkflowIdList())
      self._skin_item = \
          SkinTemplateItem(self.getTemplateSkinIdList())
      self._category_item = \
          CategoryTemplateItem(self.getTemplateBaseCategoryList())
      self._catalog_method_item = \
          CatalogMethodTemplateItem(self.getTemplateCatalogMethodIdList())
      self._action_item = \
          ActionTemplateItem(self.getTemplateActionPathList())
4479 4480
      self._portal_type_roles_item = \
          PortalTypeRolesTemplateItem(self.getTemplatePortalTypeRolesList())
Aurel's avatar
Aurel committed
4481 4482 4483 4484 4485 4486 4487 4488
      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())
4489 4490
      self._constraint_item = \
          ConstraintTemplateItem(self.getTemplateConstraintIdList())
Aurel's avatar
Aurel committed
4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512
      self._extension_item = \
          ExtensionTemplateItem(self.getTemplateExtensionIdList())
      self._test_item = \
          TestTemplateItem(self.getTemplateTestIdList())
      self._product_item = \
          ProductTemplateItem(self.getTemplateProductIdList())
      self._role_item = \
          RoleTemplateItem(self.getTemplateRoleList())
      self._catalog_result_key_item = \
          CatalogResultKeyTemplateItem(
               self.getTemplateCatalogResultKeyList())
      self._catalog_related_key_item = \
          CatalogRelatedKeyTemplateItem(
               self.getTemplateCatalogRelatedKeyList())
      self._catalog_result_table_item = \
          CatalogResultTableTemplateItem(
               self.getTemplateCatalogResultTableList())
      self._message_translation_item = \
          MessageTranslationTemplateItem(
               self.getTemplateMessageTranslationList())
      self._path_item = \
               PathTemplateItem(self.getTemplatePathList())
4513 4514
      self._preference_item = \
               PreferenceTemplateItem(self.getTemplatePreferenceList())
4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528
      self._portal_type_allowed_content_type_item = \
           PortalTypeAllowedContentTypeTemplateItem(
               self.getTemplatePortalTypeAllowedContentTypeList())
      self._portal_type_hidden_content_type_item = \
           PortalTypeHiddenContentTypeTemplateItem(
               self.getTemplatePortalTypeHiddenContentTypeList())
      self._portal_type_property_sheet_item = \
           PortalTypePropertySheetTemplateItem(
               self.getTemplatePortalTypePropertySheetList())
      self._portal_type_base_category_item = \
           PortalTypeBaseCategoryTemplateItem(
               self.getTemplatePortalTypeBaseCategoryList())
      self._catalog_keyword_key_item = \
          CatalogKeywordKeyTemplateItem(
4529
               self.getTemplateCatalogKeywordKeyList())
4530 4531
      self._catalog_full_text_key_item = \
          CatalogFullTextKeyTemplateItem(
4532
               self.getTemplateCatalogFullTextKeyList())
4533 4534
      self._catalog_request_key_item = \
          CatalogRequestKeyTemplateItem(
4535
               self.getTemplateCatalogRequestKeyList())
4536 4537
      self._catalog_multivalue_key_item = \
          CatalogMultivalueKeyTemplateItem(
4538
               self.getTemplateCatalogMultivalueKeyList())
4539 4540 4541
      self._catalog_topic_key_item = \
          CatalogTopicKeyTemplateItem(
               self.getTemplateCatalogTopicKeyList())
Aurel's avatar
Aurel committed
4542 4543 4544
      self._local_roles_item = \
          LocalRolesTemplateItem(
               self.getTemplateLocalRolesList())
4545

Aurel's avatar
Aurel committed
4546 4547
      for item_name in self._item_name_list:
        getattr(self, item_name).importFile(bta)
4548

4549 4550 4551 4552 4553 4554
    #By christophe Dumez <christophe@nexedi.com>
    def getItemsList(self):
      """Return list of items in business template
      """
      items_list = []
      for item_name in self._item_name_list:
4555 4556 4557
        item = getattr(self, item_name, None)
        if item is not None:
          items_list.extend(item.getKeys())
4558
      return items_list
4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572
    
    #By christophe Dumez <christophe@nexedi.com>
    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:
          dependency_couple_list = dependency_couple.split(' ')
          dependency = dependency_couple_list[0]
4573 4574
          if dependency in (None, ''):
            continue
4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585
          version_restriction = None
          if len(dependency_couple_list) > 1:
            version_restriction = dependency_couple_list[1][1:-1]
          installed_bt = self.portal_templates.getInstalledBusinessTemplate(dependency)
          if (not self.portal_templates.IsOneProviderInstalled(dependency)) \
             and ((installed_bt is None) \
                  or (version_restriction is not None and 
                     (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)
4586

Aurel's avatar
Aurel committed
4587
    def diffObject(self, REQUEST, **kw):
4588 4589
      """
        Make a diff between an object in the Business Template
4590
        and the same in the Business Template installed in the site
4591 4592 4593 4594
      """

      class_name_dict = {
        'Product' : '_product_item',
4595
        'PropertySheet' : '_property_sheet_item',
4596
        'Constraint' : '_constraint_item',
4597 4598 4599 4600 4601 4602 4603 4604 4605
        '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',
4606
        'PortalTypeWorkflowChain' : '_portal_type_workflow_chain_item',
4607 4608 4609 4610
        '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',
4611 4612 4613 4614
        'Category' : '_category_item',
        'Module' : '_module_item',
        'Skin' : '_skin_item',
        'Path' : '_path_item',
4615
        'Preference' : '_preference_item',
4616
        'Action' : '_action_item',
4617
        'PortalTypeRoles' : '_portal_type_roles_item',
Aurel's avatar
Aurel committed
4618
        'LocalRoles' : '_local_roles_item',
4619 4620 4621
        'CatalogResultKey' : '_catalog_result_key_item',
        'CatalogRelatedKey' : '_catalog_related_key_item',
        'CatalogResultTable' : '_catalog_result_table_item',
4622 4623 4624 4625 4626
        'CatalogKeywordKey' : '_catalog_keyword_key_item',
        'CatalogFullTextKey' : '_catalog_full_text_key_item',
        'CatalogRequestKey' : '_catalog_request_key_item',
        'CatalogMultivalueKey' : '_catalog_multivalue_key_item',
        'CatalogTopicKey' : '_catalog_topic_key_item',
4627
        }
4628

4629 4630
      object_id = REQUEST.object_id
      object_class = REQUEST.object_class
4631

Christophe Dumez's avatar
Christophe Dumez committed
4632
      # Get objects
4633
      item_name = class_name_dict[object_class]
Aurel's avatar
Aurel committed
4634

Aurel's avatar
Aurel committed
4635
      new_bt =self
Christophe Dumez's avatar
Christophe Dumez committed
4636
      # Compare with a given business template
4637
      compare_to_zodb = 0
Aurel's avatar
Aurel committed
4638 4639
      bt2_id = kw.get('compare_with', None)
      if bt2_id is not None:
4640 4641
        if bt2_id == self.getId():
          compare_to_zodb = 1
4642
          installed_bt = self.getInstalledBusinessTemplate(title=self.getTitle())
4643 4644
        else:
          installed_bt = self.portal_templates._getOb(bt2_id)
Aurel's avatar
Aurel committed
4645 4646 4647
      else:
        installed_bt = self.getInstalledBusinessTemplate(title=self.getTitle())
        if installed_bt == new_bt:
Aurel's avatar
Aurel committed
4648 4649 4650
          compare_to_zodb = 1
      if compare_to_zodb:
        bt2 = self.portal_templates.manage_clone(ob=installed_bt, id='installed_bt_for_diff')
Christophe Dumez's avatar
Christophe Dumez committed
4651
        # Update portal types properties to get last modifications
Aurel's avatar
Aurel committed
4652 4653 4654
        bt2.getPortalTypesProperties()
        bt2.edit(description='tmp bt generated for diff')
        installed_bt = bt2
4655

4656 4657
      new_item = getattr(new_bt, item_name)
      installed_item = getattr(installed_bt, item_name)
4658
      if compare_to_zodb:
Aurel's avatar
Aurel committed
4659
        # XXX maybe only build for the given object to gain time
4660
        installed_item.build(self)
4661 4662 4663
      new_object = new_item._objects[object_id]
      installed_object = installed_item._objects[object_id]
      diff_msg = ''
4664

Christophe Dumez's avatar
Christophe Dumez committed
4665
      # Real Zope Objects (can be exported into XML directly by Zope)
4666 4667
      # XXX Bad naming
      item_list_1 = ['_product_item', '_workflow_item', '_portal_type_item',
4668
                     '_category_item', '_path_item', '_preference_tem',
Christophe Dumez's avatar
Christophe Dumez committed
4669
                     '_skin_item', '_action_item',]
4670

Christophe Dumez's avatar
Christophe Dumez committed
4671
      # Not considered as objects by Zope (will be exported into XML manually)
4672
      # XXX Bad naming
4673
      item_list_2 = ['_site_property_item', '_module_item',
4674
                     '_catalog_result_key_item', '_catalog_related_key_item',
4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686
                     '_catalog_result_table_item',
                     '_catalog_keyword_key_item',
                     '_catalog_full_text_key_item',
                     '_catalog_request_key_item',
                     '_catalog_multivalue_key_item',
                     '_catalog_topic_key_item',
                     '_portal_type_allowed_content_type_item',
                     '_portal_type_hidden_content_type_item',
                     '_portal_type_property_sheet_item',
                     '_portal_type_roles_item',
                     '_portal_type_base_category_item',
                     '_local_roles_item',
4687
                     '_portal_type_workflow_chain_item',]
4688

Christophe Dumez's avatar
Christophe Dumez committed
4689
      # Text objects (no need to export them into XML)
4690
      # XXX Bad naming
4691 4692
      item_list_3 = ['_document_item', '_property_sheet_item',
                     '_constraint_item', '_extension_item',
4693
                     '_test_item', '_message_translation_item',]
4694

4695
      if item_name in item_list_1:
Christophe Dumez's avatar
Christophe Dumez committed
4696 4697 4698
        f1 = StringIO() # for XML export of New Object
        f2 = StringIO() # For XML export of Installed Object
        # Remove unneeded properties
4699 4700
        new_object = new_item.removeProperties(new_object)
        installed_object = installed_item.removeProperties(installed_object)
4701
        # XML Export in memory
4702 4703 4704
        OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, f1)
        OFS.XMLExportImport.exportXML(installed_object._p_jar, installed_object._p_oid, f2)
        new_obj_xml = f1.getvalue()
4705
        f1.close()
4706 4707 4708 4709
        installed_obj_xml = f2.getvalue()
        f2.close()
        new_ob_xml_lines = new_obj_xml.splitlines()
        installed_ob_xml_lines = installed_obj_xml.splitlines()
4710
        # End of XML export
4711

4712
        # Diff between XML objects
4713 4714
        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
4715
          diff_msg += '\n\nObject %s diff :\n' % (object_id,)
4716 4717 4718
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'
4719

4720
      elif item_name in item_list_2:
4721
        # Generate XML code manually
4722 4723
        new_obj_xml = new_item.generateXml(path= object_id)
        installed_obj_xml = installed_item.generateXml(path= object_id)
4724 4725
        new_obj_xml_lines = new_obj_xml.splitlines()
        installed_obj_xml_lines = installed_obj_xml.splitlines()
4726
        # End of XML Code Generation
4727

4728
        # Diff between XML objects
4729 4730
        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
4731
          diff_msg += '\n\nObject %s diff :\n' % (object_id,)
4732 4733 4734
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'
4735

4736
      elif item_name in item_list_3:
4737
        # Diff between text objects
4738 4739 4740 4741
        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
4742
          diff_msg += '\n\nObject %s diff :\n' % (object_id,)
4743 4744
          diff_msg += '\n'.join(diff_list)
        else:
4745
          diff_msg = 'No diff'
4746

Christophe Dumez's avatar
Christophe Dumez committed
4747
      else: # Added By <christophe@nexedi.com>
4748
        diff_msg += 'Unsupported file !'
Christophe Dumez's avatar
Christophe Dumez committed
4749

4750 4751
      if compare_to_zodb:
        self.portal_templates.manage_delObjects(ids=['installed_bt_for_diff'])
4752

4753
      return diff_msg
4754

4755

4756 4757 4758 4759
    def getPortalTypesProperties(self, **kw):
      """
      Fill field about properties for each portal type
      """
4760 4761
      wtool = self.getPortalObject().portal_workflow
      ttool = self.getPortalObject().portal_types
4762 4763 4764 4765 4766 4767 4768
      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 [])
4769 4770
      bt_wf_chain_list = list(getattr(self, 'template_portal_type_workflow_chain', []) or [])

4771
      p = self.getPortalObject()
4772
      for id in bt_portal_types_id_list:
4773 4774
        portal_type = ttool.getTypeInfo(id)
        if portal_type is None:
4775
          continue
4776
        if len(getattr(portal_type, '_roles', ())) > 0:
4777 4778
          if id not in bt_portal_type_roles_list:
            bt_portal_type_roles_list.append(id)
4779

4780 4781 4782 4783
        allowed_content_type_list = []
        hidden_content_type_list = []
        property_sheet_list = []
        base_category_list = []
4784
        action_list = []
4785 4786 4787 4788 4789 4790 4791
        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'):
4792
          base_category_list = portal_type.base_category_list
4793 4794
        if hasattr(portal_type, 'listActions'):
          action_list = [x.getId() for x in portal_type.listActions()]
4795

4796 4797 4798 4799
        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)
4800

4801
        for h_id in hidden_content_type_list:
4802 4803 4804
          hidden_id = id+' | '+h_id
          if hidden_id not in bt_hidden_content_type_list:
            bt_hidden_content_type_list.append(hidden_id)
4805

4806 4807 4808 4809
        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)
4810

4811 4812 4813 4814
        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)
4815

4816 4817 4818 4819
        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)
4820

4821
        for workflow_id in [chain for chain in wtool.getChainFor(id)
4822
                                    if chain != '(Default)']:
4823 4824 4825
          wf_id = id+' | '+workflow_id
          if wf_id not in bt_wf_chain_list:
            bt_wf_chain_list.append(wf_id)
4826

4827 4828 4829 4830
      bt_allowed_content_type_list.sort()
      bt_hidden_content_type_list.sort()
      bt_property_sheet_list.sort()
      bt_base_category_list.sort()
4831
      bt_action_list.sort()
4832
      bt_wf_chain_list.sort()
4833

4834 4835 4836 4837 4838 4839 4840
      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)
4841 4842 4843 4844 4845 4846 4847 4848 4849
      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:
4850 4851
        raise TemplateConditionError, 'No module defined in business template'

4852 4853 4854 4855 4856 4857 4858 4859
      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:
4860
          return type_list
4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874

        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():
4875
            type_list.update(getChildPortalType(hidden_ptype_id))
4876
        return type_list
4877

4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951
      p = self.getPortalObject()
      portal_dict = {}
      for module_id in bt_module_id_list:
        module = p.unrestrictedTraverse(module_id)
        portal_type_id = module.getPortalType()
        try:
          portal_type = p.unrestrictedTraverse('portal_types/'+portal_type_id)
        except KeyError:
          continue
        allowed_content_type_list = []
        hidden_content_type_list = []
        if hasattr(portal_type, 'allowed_content_types'):
          allowed_content_type_list = portal_type.allowed_content_types
        if hasattr(portal_type, 'hidden_content_type_list'):
          hidden_content_type_list = portal_type.hidden_content_type_list

        portal_dict[portal_type_id] = ()

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

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

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

      bt_portal_types_id_list.sort()

      setattr(self, 'template_portal_type_id', bt_portal_types_id_list)
      return

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

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

# from Shared.DC.ZRDB.TM import TM

# class WorkflowUpdateTM(TM):

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

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

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

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

4953 4954 4955 4956 4957 4958 4959 4960

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

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