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

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

from zLOG import LOG
Aurel's avatar
Aurel committed
63 64
from OFS.ObjectManager import customImporters
from gzip import GzipFile
65
from xml.dom.minidom import parse
Aurel's avatar
Aurel committed
66 67 68
import tarfile


69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
catalog_method_list = ('_is_catalog_method_archive', '_is_catalog_list_method_archive',
                       '_is_uncatalog_method_archive', '_is_update_method_archive',
                       '_is_clear_method_archive', '_is_filtered_archive')

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


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

Aurel's avatar
Aurel committed
98 99 100
class BusinessTemplateArchive:
  """
    This is the base class for all Business Template archives
101
  """
Aurel's avatar
Aurel committed
102 103 104 105 106 107 108 109 110 111 112 113

  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
114

Aurel's avatar
Aurel committed
115 116 117 118 119 120
  def finishCreation(self, **kw):
    pass

class BusinessTemplateFolder(BusinessTemplateArchive):
  """
    Class archiving businnes template into a folder tree
121
  """
Aurel's avatar
Aurel committed
122 123 124 125 126 127
  def _initCreation(self, path):
    self.path = path
    try:
      os.makedirs(self.path)
    except OSError:
      # folder already exists, remove it
128
      removeAll(self.path)
Aurel's avatar
Aurel committed
129 130 131 132 133
      os.makedirs(self.path)

  def addFolder(self, name=''):
    if name !='':
      path = os.path.join(self.path, name)
134
      if not os.path.exists(path):
Aurel's avatar
Aurel committed
135 136 137 138 139 140 141 142 143
        os.makedirs(path)
      return path

  def addObject(self, object, name, path=None, ext='.xml'):
    if path is None:
      object_path = os.path.join(self.path, name)
    else:
      object_path = os.path.join(path, name)
    f = open(object_path+ext, 'wt')
144
    f.write(str(object))
Aurel's avatar
Aurel committed
145 146 147 148
    f.close()

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

152
  def importFiles(self, klass, **kw):
Aurel's avatar
Aurel committed
153 154 155 156
    """
      Import file from a local folder
    """
    class_name = klass.__class__.__name__
157 158 159 160
    for file_path in self.file_list:
      if class_name in file_path:
        if os.path.isfile(file_path):
          file = open(file_path, 'r')
Aurel's avatar
Aurel committed
161
          # get object id
162 163
          folders = file_path.split(os.sep)
          file_name = string.join(folders[self.root_path_len:], os.sep)
164
          klass._importFile(file_name, file)
Aurel's avatar
Aurel committed
165
          # close file
166
          file.close()
167

Aurel's avatar
Aurel committed
168 169 170 171 172 173
class BusinessTemplateTarball(BusinessTemplateArchive):
  """
    Class archiving businnes template into a tarball file
  """

  def _initCreation(self, path):
174
    # make tmp dir, must use stringIO instead
Aurel's avatar
Aurel committed
175 176 177 178 179
    self.path = path
    try:
      os.makedirs(self.path)
    except OSError:
      # folder already exists, remove it
180
      removeAll(self.path)
Aurel's avatar
Aurel committed
181 182 183 184 185 186
      os.makedirs(self.path)
    # init tarfile obj
    self.fobj = StringIO()
    self.tar = tarfile.open('', 'w:gz', self.fobj)

  def addFolder(self, name=''):
187
    if not os.path.exists(name):
Aurel's avatar
Aurel committed
188 189 190 191 192 193 194 195
      os.makedirs(name)

  def addObject(self, object, name, path=None, ext='.xml'):
    if path is None:
      object_path = os.path.join(self.path, name)
    else:
      object_path = os.path.join(path, name)
    f = open(object_path+ext, 'wt')
196
    f.write(str(object))
Aurel's avatar
Aurel committed
197 198 199 200 201
    f.close()

  def finishCreation(self):
    self.tar.add(self.path)
    self.tar.close()
202
    removeAll(self.path)
Aurel's avatar
Aurel committed
203 204 205 206 207
    return self.fobj

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

208
  def importFiles(self, klass, **kw):
Aurel's avatar
Aurel committed
209 210
    """
      Import all file from the archive to the site
211
    """
Aurel's avatar
Aurel committed
212 213 214 215 216 217 218 219 220
    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():
      if class_name in info.name:
        if info.isreg():
          file = tar.extractfile(info)
221 222
          folders = string.split(info.name, os.sep)
          klass._importFile((os.sep).join(folders[2:]), file)
Aurel's avatar
Aurel committed
223 224 225
          file.close()
    tar.close()
    io.close()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
226

227 228
class TemplateConditionError(Exception): pass

229 230
class TemplateConflictError(Exception): pass

231
class BaseTemplateItem(Implicit, Persistent):
232
  """
233
    This class is the base class for all template items.
234
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
235

236
  def __init__(self, id_list, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
237
    self.__dict__.update(kw)
238
    self._archive = PersistentMapping()
Aurel's avatar
Aurel committed
239
    self._objects = PersistentMapping()
240 241 242 243 244 245 246
    for id in id_list:
      if not id: continue
      self._archive[id] = None

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

247 248
  def install(self, context, **kw):
    pass
249 250 251

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

253 254 255 256
  def trash(self, context, new_item, **kw):
    # trash is quite similar to uninstall.
    return self.uninstall(context, new_item=new_item, trash=1, **kw)

257 258
  def diff(self, **kw):
    return ''
259

Aurel's avatar
Aurel committed
260
  def export(self, context, bta, **kw):
261
    pass
Aurel's avatar
Aurel committed
262 263

  def importFile(self, bta, **kw):
264
    bta.importFiles(klass=self)
265

266 267 268
class ObjectTemplateItem(BaseTemplateItem):
  """
    This class is used for generic objects and as a subclass.
269
  """
270

271 272 273 274
  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()
275
      self._archive.clear()
276 277 278
      for id in id_list:
        self._archive["%s/%s" % (tool_id, id)] = None

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
  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():
      object=self._objects[key]
      # 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()
      XMLExportImport.exportXML(object._p_jar, object._p_oid, f)
      bta.addObject(object=f.getvalue(), name=id, path=path)

Aurel's avatar
Aurel committed
294 295 296 297
  def build_sub_objects(self, context, id_list, url, **kw):
    p = context.getPortalObject()
    sub_list = {}
    for id in id_list:
298
      relative_url = '/'.join([url,id])
299
      object = p.unrestrictedTraverse(relative_url)
Aurel's avatar
Aurel committed
300 301
      object = object._getCopy(context)
      id_list = object.objectIds()
302 303 304 305 306
      if hasattr(object, '__ac_local_roles__'):
        # remove local roles
        object.__ac_local_roles__ = None
      if hasattr(object, '_owner'):
        object._owner = None
Aurel's avatar
Aurel committed
307 308 309 310 311 312
      if hasattr(object, 'groups'):
        # we must keep groups because it's ereased when we delete subobjects
        groups = deepcopy(object.groups)
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
        object.manage_delObjects(list(id_list))
313
      if hasattr(aq_base(object), 'uid'):
Aurel's avatar
Aurel committed
314 315 316 317 318 319 320
        object.uid = None
      if hasattr(object, 'groups'):
        object.groups = groups
      self._objects[relative_url] = object
      object.wl_clearLocks()
    return sub_list

321 322 323 324 325 326
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
      object = p.unrestrictedTraverse(relative_url)
      object = object._getCopy(context)
Aurel's avatar
Aurel committed
327
      id_list = object.objectIds()
328 329 330 331 332
      if hasattr(object, '__ac_local_roles__'):
        # remove local roles
        object.__ac_local_roles__ = None
      if hasattr(object, '_owner'):
        object._owner = None
Aurel's avatar
Aurel committed
333 334 335 336 337 338
      if hasattr(object, 'groups'):
        # we must keep groups because it's ereased when we delete subobjects
        groups = deepcopy(object.groups)
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
        object.manage_delObjects(list(id_list))
339
      if hasattr(aq_base(object), 'uid'):
Aurel's avatar
Aurel committed
340 341 342 343
        object.uid = None
      if hasattr(object, 'groups'):
        object.groups = groups
      self._objects[relative_url] = object
344 345
      object.wl_clearLocks()

346
  def _backupObject(self, container, object_id, **kw):
347 348 349
    """
    Rename object as a _btsave_
    """
350
    container_ids = container.objectIds()
351 352 353 354 355
    n = 0
    new_object_id = object_id
    while new_object_id in container_ids:
      n = n + 1
      new_object_id = '%s_btsave_%s' % (object_id, n)
356
    # XXX manage_renameObject is not in ERP5 API. Use setId.
357
    container.manage_renameObject(object_id, new_object_id)
358 359
    # Returned ID of the backuped object
    return new_object_id
360

361 362 363 364 365 366 367 368 369 370
  def _importFile(self, file_name, file):
    # import xml file
    obj = self
    connection = None
    while connection is None:
      obj=obj.aq_parent
      connection=obj._p_jar
    obj = connection.importFile(file, customImporters=customImporters)
    self._objects[file_name[:-4]] = obj

371 372 373 374 375 376 377 378
  def install(self, context, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
      groups = {}
      portal = context.getPortalObject()
      # sort to add objects before their subobjects
      keys = self._objects.keys()
      keys.sort()
      for path in keys:
379
        container_path = path.split('/')[:-1]
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
        object_id = path.split('/')[-1]
        container = portal.unrestrictedTraverse(container_path)
        container_ids = container.objectIds()
        if object_id in container_ids:    # Object already exists
          self._backupObject(container, object_id)
        object = self._objects[path]
        if hasattr(object, 'groups'):
          # we must keep original order groups because they change when we add subobjects
          groups[path] = deepcopy(object.groups)
        object = object._getCopy(container)
        container._setObject(object_id, object)
        object = container._getOb(object_id)
        object.manage_afterClone(object)
        object.wl_clearLocks()
        if object.meta_type in ('Z SQL Method',):
395
          # It is necessary to make sure that the sql connection
396 397 398
          # in this method is valid.
          sql_connection_list = portal.objectIds(spec=('Z MySQL Database Connection',))
          if object.connection_id not in sql_connection_list:
399
            object.connection_id = sql_connection_list[0]
400 401
      # now put original order group
      for path in groups.keys():
402
        object = portal.unrestrictedTraverse(path)
403
        object.groups = groups[path]
Aurel's avatar
Aurel committed
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
    else:
      BaseTemplateItem.install(self, context, **kw)
      portal = context.getPortalObject()
      for relative_url,object in self._archive.items():
        container_path = relative_url.split('/')[0:-1]
        object_id = relative_url.split('/')[-1]
        container = portal.unrestrictedTraverse(container_path)
        container_ids = container.objectIds()
        if object_id in container_ids:    # Object already exists
          self._backupObject(container, object_id)
        # Set a hard link
        object = object._getCopy(container)
        container._setObject(object_id, object)
        object = container._getOb(object_id)
        object.manage_afterClone(object)
        object.wl_clearLocks()
        if object.meta_type in ('Z SQL Method',):
421
          # It is necessary to make sure that the sql connection
Aurel's avatar
Aurel committed
422 423 424 425 426
          # in this method is valid.
          sql_connection_list = portal.objectIds(
                                   spec=('Z MySQL Database Connection',))
          if object.connection_id not in sql_connection_list:
            object.connection_id = sql_connection_list[0]
427 428 429

  def uninstall(self, context, **kw):
    portal = context.getPortalObject()
430
    trash = kw.get('trash', 0)
431 432 433
    for relative_url in self._archive.keys():
      container_path = relative_url.split('/')[0:-1]
      object_id = relative_url.split('/')[-1]
434 435 436 437 438 439 440
      try:
        container = portal.unrestrictedTraverse(container_path)
        if trash:
          self._backupObject(container, object_id)
        else:
          if object_id in container.objectIds():
            container.manage_delObjects([object_id])
441
      except NotFound:
442
        pass
443 444
    BaseTemplateItem.uninstall(self, context, **kw)

445 446
  def _compareObjects(self, object1, object2, btsave_object_included=0):
    """
447
      Execute a diff between 2 objects,
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
      and return a string diff.
    """
    xml_dict = {
      str(object1): None,
      str(object2): None,
    }
    # Generate XML
    for object in (object1, object2):
      string_io = StringIO()
      XMLExportImport.exportXML(object._p_jar, object._p_oid, string_io)
      object_xml = string_io.getvalue()
      string_io.close()
      object_xml_lines = object_xml.splitlines(1)
      xml_dict[str(object)] = object_xml_lines
    # Make diff between XML
    diff_instance = difflib.Differ()
    diff_list = list(diff_instance.compare(xml_dict[str(object1)],
                                           xml_dict[str(object2)]))
    diff_list = [x for x in diff_list if x[0] != ' ']
467 468 469 470 471
#     # Dirty patch to remove useless diff message (id different)
#     if btsave_object_included==1:
#       if len(diff_list) == 3:
#         if '_btsave_' in diff_list[1]:
#           diff_list = []
472
    # Return string
473 474 475 476 477 478 479 480 481 482
    result = '%s' % ''.join(diff_list)
    return result

  def diff(self, archive_variable='_archive', max_deep=0, verbose=0):
    """
      Show all __btsave__ created, and make a diff between
      the current and the old version.
    """
    result = ''
    portal = self.getPortalObject()
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
    if (getattr(self, 'template_format_version', 0)) == 0:
      object_list = getattr(self, archive_variable).items()
    else:
      try:
        object_list = []
        keys = self._objects.keys()
        keys.sort()
        for path in keys:
          container_path = path.split('/')[2:-1]
          object_id = path.split('/')[-1]
          container = portal.unrestrictedTraverse(container_path)
          object = self._objects[path]
          object_list.append(('/'.join(path.split('/')[2:]), object))
      except:
        import pdb
        pdb.set_trace()

    for relative_url, object in object_list:
      # Browse all items stored
502 503 504 505 506 507 508 509 510 511 512 513 514 515
      object = portal.unrestrictedTraverse(relative_url)
      container_path = relative_url.split('/')[0:-1]
      object_id = relative_url.split('/')[-1]
      container = portal.unrestrictedTraverse(container_path)
      container_ids = container.objectIds()
      # Search _btsave_ object
      compare_object_couple_list = []
      btsave_id_list = []
      n = 1
      new_object_id = '%s_btsave_%s' % (object_id, n)
      while new_object_id in container_ids:
        # Found _btsave_ object
        btsave_id_list.append(new_object_id)
        compare_object_couple_list.append(
516 517 518 519
              (portal.unrestrictedTraverse(container_path+[object_id]),
               portal.unrestrictedTraverse(container_path+[new_object_id])))
#               (object, portal.unrestrictedTraverse(
#                                   container_path+[new_object_id])))
520 521
        n += 1
        new_object_id = '%s_btsave_%s' % (object_id, n)
522 523 524 525
#       if n == 1:
#         result += "$$$ Added: %s $$$\n" % \
#               ('/'.join(container_path+[object_id]))
#         result += '%s\n' % ('-'*80)
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
      # Find all objects to compare
      deep = 0
      while deep != max_deep:
        new_compare_object_couple_list = []
        for new_object, btsave_object in compare_object_couple_list:
          btsave_object_content_id_list = btsave_object.objectIds()
          for new_object_content_id in new_object.objectIds():
            if new_object_content_id in btsave_object_content_id_list:
              new_compare_object_couple_list.append(
                  (getattr(new_object, new_object_content_id),
                   getattr(btsave_object, new_object_content_id)))
              btsave_object_content_id_list.remove(new_object_content_id)
            else:
              result += "$$$ Added: %s/%s $$$\n" % \
                    (new_object.absolute_url(), new_object_content_id)
              result += '%s\n' % ('-'*80)
          for btsave_object_id in btsave_object_content_id_list:
            result += "$$$ Removed: %s/%s $$$\n" % \
                  (btsave_object.absolute_url(), btsave_object_id)
            result += '%s\n' % ('-'*80)
        if new_compare_object_couple_list == []:
          deep = max_deep
        else:
          compare_object_couple_list = new_compare_object_couple_list
          deep += 1
      # Now, we can compare all objects requested
      for new_object, btsave_object in compare_object_couple_list:
        tmp_diff = self._compareObjects(new_object, btsave_object,
                                        btsave_object_included=1)
        if tmp_diff != '':
          result += "$$$ %s $$$\n$$$ %s $$$\n" % \
              (new_object.absolute_url(),
               btsave_object.absolute_url())
          if verbose == 1:
560
            result += tmp_diff
561 562
          result += '%s\n' % ('-'*80)
    return result
563

564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
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

  def _resolvePath(self, folder, relative_url_list, id_list):
    """
      This method calls itself recursively.
579

580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
      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.
      object = folder._getOb(id)
      return self._resolvePath(object, relative_url_list + [id], id_list[1:])
    path_list = []
    for object_id in fnmatch.filter(folder.objectIds(), id):
      path_list.extend(self._resolvePath(folder._getOb(object_id), relative_url_list + [object_id], id_list[1:]))
    return path_list
Aurel's avatar
Aurel committed
595

596 597 598 599 600 601 602
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for path in self._path_archive.keys():
      for relative_url in self._resolvePath(p, [], path.split('/')):
        object = p.unrestrictedTraverse(relative_url)
        object = object._getCopy(context)
603 604 605 606 607
        if hasattr(object, '__ac_local_roles__'):
          # remove local roles
          object.__ac_local_roles__ = None
        if hasattr(object, '_owner'):
          object._owner = None
Aurel's avatar
Aurel committed
608 609 610
        if hasattr(object, 'uid'):
          object.uid = None
        self._objects[relative_url] = object
611
        object.wl_clearLocks()
612
      
613 614
class CategoryTemplateItem(ObjectTemplateItem):

615 616
  def __init__(self, id_list, tool_id='portal_categories', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
617

618 619 620 621 622
  def build_sub_objects(self, context, id_list, url, **kw):
    p = context.getPortalObject()
    sub_list = {}
    for id in id_list:
      relative_url = '/'.join([url,id])
623
      object = p.unrestrictedTraverse(relative_url)
624 625 626 627 628 629 630
      object_copy = object._getCopy(context)
      include_sub_categories = object.getProperty('business_template_include_sub_categories', 0)
      id_list = object_copy.objectIds()
      if len(id_list) > 0 and include_sub_categories:
        self.build_sub_objects(context, id_list, relative_url)
        object_copy.manage_delObjects(list(id_list))
      else:
631
        object_copy.manage_delObjects(list(id_list))
632 633 634 635 636
      if hasattr(object, '__ac_local_roles__'):
        # remove local roles
        object.__ac_local_roles__ = None
      if hasattr(object, '_owner'):
        object._owner = None
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
      if hasattr(aq_base(object_copy), 'uid'):
        object_copy.uid = None
      self._objects[relative_url] = object_copy
      object.wl_clearLocks()
    return sub_list


  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
      object = p.unrestrictedTraverse(relative_url)
      object_copy = object._getCopy(context)
      include_sub_categories = object.getProperty('business_template_include_sub_categories', 0)
      id_list = object_copy.objectIds()
      if len(id_list) > 0 and include_sub_categories:
        self.build_sub_objects(context, id_list, relative_url)
        object_copy.manage_delObjects(list(id_list))
      else:
        object_copy.manage_delObjects(list(id_list))
657 658 659 660 661
      if hasattr(object, '__ac_local_roles__'):
        # remove local roles
        object.__ac_local_roles__ = None
      if hasattr(object, '_owner'):
        object._owner = None
662 663 664 665 666 667
      if hasattr(aq_base(object_copy), 'uid'):
        object_copy.uid = None
      self._objects[relative_url] = object_copy
      object.wl_clearLocks()


668 669
  def install(self, context, light_install = 0, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
670
      if light_install==0:
671
        ObjectTemplateItem.install(self, context, **kw)
Aurel's avatar
Aurel committed
672 673 674 675 676 677 678 679 680 681
      else:
        portal = context.getPortalObject()
        category_tool = portal.portal_categories
        tool_id = self.tool_id
        keys = self._objects.keys()
        keys.sort()
        for path in keys:
          # Wrap the object by an aquisition wrapper for _aq_dynamic.
          object = self._objects[path]
          object = object.__of__(category_tool)
682
          container_path = path.split('/')[:-1]
Aurel's avatar
Aurel committed
683 684 685 686 687 688 689 690 691
          category_id = path.split('/')[-1]
          container = category_tool.unrestrictedTraverse(container_path)
          container_ids = container.objectIds()
          if category_id in container_ids:    # Object already exists
            self._backupObject(container, category_id)
          category = container.newContent(portal_type=object.getPortalType(), id=category_id)
          for property in object.propertyIds():
            if property not in ('id', 'uid'):
              category.setProperty(property, object.getProperty(property, evaluate=0))
692
    else:
Aurel's avatar
Aurel committed
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
      BaseTemplateItem.install(self, context, **kw)
      portal = context.getPortalObject()
      category_tool = portal.portal_categories
      tool_id = self.tool_id
      if light_install==0:
        ObjectTemplateItem.install(self, context, **kw)
      else:
        for relative_url,object in self._archive.items():
          # Wrap the object by an aquisition wrapper for _aq_dynamic.
          object = object.__of__(category_tool)
          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
            self._backupObject(container, category_id)
          category = container.newContent(portal_type=object.getPortalType(), id=category_id)
          for property in object.propertyIds():
            if property not in ('id', 'uid'):
              category.setProperty(property, object.getProperty(property, evaluate=0))
713

714 715 716

class SkinTemplateItem(ObjectTemplateItem):

717 718
  def __init__(self, id_list, tool_id='portal_skins', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
719

720 721 722
  def install(self, context, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
      ObjectTemplateItem.install(self, context, **kw)
Aurel's avatar
Aurel committed
723 724 725 726 727 728
      p = context.getPortalObject()
      ps = p.portal_skins
      for skin_name, selection in ps.getSkinPaths():
        new_selection = []
        selection = selection.split(',')
        for relative_url, object in self._objects.items():
729
          skin_id = relative_url.split('/')[1]
730
          if hasattr(object, 'getProperty'):
Aurel's avatar
Aurel committed
731
            selection_list = object.getProperty('business_template_registered_skin_selections', None)
732
          else:
Aurel's avatar
Aurel committed
733 734 735 736 737 738
            continue
          if selection_list is None or skin_name in selection_list:
            if skin_id not in selection and skin_id not in new_selection:
              new_selection.append(skin_id)
        new_selection.extend(selection)
        # sort the layer according to skin priorities
739
        new_selection.sort(lambda a, b : cmp( # separate functions here
Aurel's avatar
Aurel committed
740
          b in ps.objectIds() and ps[b].getProperty(
741
              'business_template_skin_layer_priority', 0) or 0,
Aurel's avatar
Aurel committed
742 743 744
          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)
745 746
      # Make sure that skin data is up-to-date (see CMFCore/Skinnable.py).
      p.changeSkin(None)
Aurel's avatar
Aurel committed
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771
    else:
      ObjectTemplateItem.install(self, context, **kw)
      p = context.getPortalObject()
      # It is necessary to make sure that the sql connections in Z SQL Methods are valid.
      sql_connection_list = p.objectIds(spec=('Z MySQL Database Connection',))
      for relative_url in self._archive.keys():
        folder = p.unrestrictedTraverse(relative_url)
        for object in folder.objectValues(spec=('Z SQL Method',)):
          if object.connection_id not in sql_connection_list:
            object.connection_id = sql_connection_list[0]
      # 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, object in self._archive.items():
          skin_id = relative_url.split('/')[-1]
          selection_list = object.getProperty('business_template_registered_skin_selections', None)
          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(
772
              'business_template_skin_layer_priority', 0) or 0,
Aurel's avatar
Aurel committed
773 774 775
          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)
776 777
      # Make sure that skin data is up-to-date (see CMFCore/Skinnable.py).
      p.changeSkin(None)
778 779 780 781 782 783 784 785 786 787 788 789

  def uninstall(self, context, **kw):
    # Remove folders from skin paths.
    ps = context.portal_skins
    skin_id_list = [relative_url.split('/')[-1] for relative_url in self._archive.keys()]
    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)
790
    # Make sure that skin data is up-to-date (see CMFCore/Skinnable.py).
791
    context.getPortalObject().changeSkin(None)
792

793 794
    ObjectTemplateItem.uninstall(self, context, **kw)

795 796
  def diff(self, max_deep=1, **kw):
    return ObjectTemplateItem.diff(self, max_deep=max_deep, **kw)
797

798
class WorkflowTemplateItem(ObjectTemplateItem):
799

800 801
  def __init__(self, id_list, tool_id='portal_workflow', **kw):
    return ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
802 803 804 805 806 807 808 809 810

class PortalTypeTemplateItem(ObjectTemplateItem):

  workflow_chain = None

  def _getChainByType(self, context):
    """
    This is used in order to construct the full list
    of mapping between type and list of workflow associated
811
    This is only useful in order to use
812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
    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)

836 837
  def __init__(self, id_list, tool_id='portal_types', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
838 839 840
    self._workflow_chain_archive = PersistentMapping()

  def build(self, context, **kw):
841 842 843 844
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
      object = p.unrestrictedTraverse(relative_url)
      object = object._getCopy(context)
Aurel's avatar
Aurel committed
845 846
      id_list = object.objectIds()
      # remove optional actions
847 848 849 850 851 852
      optional_action_list = []
      for index,ai in enumerate(object.listActions()):
        if ai.getOption():
          optional_action_list.append(index)
      if len(optional_action_list) > 0:
        object.deleteActions(selections=optional_action_list)
853 854 855 856 857
      if hasattr(object, '__ac_local_roles__'):
        # remove local roles
        object.__ac_local_roles__ = None
      if hasattr(object, '_owner'):
        object._owner = None
Aurel's avatar
Aurel committed
858 859 860
      if hasattr(object, 'uid'):
        object.uid = None
      self._objects[relative_url] = object
861
      object.wl_clearLocks()
Aurel's avatar
Aurel committed
862
    # also export workflow chain
863
    (default_chain, chain_dict) = self._getChainByType(context)
Aurel's avatar
Aurel committed
864
    for object in self._objects.values():
865 866 867
      portal_type = object.id
      self._workflow_chain_archive[portal_type] = chain_dict['chain_%s' % portal_type]

Aurel's avatar
Aurel committed
868 869 870 871
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
872
    # export portal type object
873
    ObjectTemplateItem.export(self, context, bta, **kw)
874 875
    # export workflow chain
    xml_data = '<workflow_chain>'
876 877 878
    keys = self._workflow_chain_archive.keys()
    keys.sort()
    for key in keys:
879 880 881
      xml_data += os.linesep+' <chain>'
      xml_data += os.linesep+'  <type>%s</type>' %(key,)
      xml_data += os.linesep+'  <workflow>%s</workflow>' %(self._workflow_chain_archive[key],)
882
      xml_data += os.linesep+' </chain>'
883 884
    xml_data += os.linesep+'</workflow_chain>'
    bta.addObject(object=xml_data, name='workflow_chain_type',  path=root_path)
885

886
  def install(self, context, **kw):
887 888 889 890 891 892 893
    ObjectTemplateItem.install(self, context, **kw)
    # We now need to setup the list of workflows corresponding to
    # each portal type
    (default_chain, chain_dict) = self._getChainByType(context)
    # Set the default chain to the empty string is probably the
    # best solution, by default it is 'default_workflow', wich is
    # not very usefull
894 895
    default_chain = ''
    if (getattr(self, 'template_format_version', 0)) == 1:
896
      objects = self._objects.values()
897
    else:
898 899 900 901 902
      objects = self._archive.values()
    for object in objects:
      portal_type = object.id
      chain_dict['chain_%s' % portal_type] = \
                            self._workflow_chain_archive[portal_type]
Aurel's avatar
Aurel committed
903 904
      context.portal_workflow.manage_changeWorkflows(default_chain,
                                                     props=chain_dict)
905 906 907 908 909 910 911 912 913

  def _backupObject(self, container, object_id, **kw):
    """
      Backup portal type and keep the workflow chain.
    """
    # Get the chain value
    (default_chain, chain_dict) = self._getChainByType(self)
    chain = chain_dict['chain_%s' % object_id]
    # Backup the portal type
914
    backup_id = ObjectTemplateItem._backupObject(self, container,
915 916 917 918 919 920 921 922 923
                                                 object_id, **kw)
    # Restore the chain to the backuped portal type
    (default_chain, chain_dict) = self._getChainByType(self)
    chain_dict['chain_%s' % backup_id] = chain
    self.portal_workflow.manage_changeWorkflows(default_chain,
                                                props=chain_dict)

  def diff(self, verbose=0, **kw):
    """
924
      Make a diff between portal type.
925 926 927 928 929 930
      Also compare the workflow chain.
    """
    # Compare XML portal type
    result = ObjectTemplateItem.diff(self, verbose=verbose, **kw)
    # Compare chains
    container_ids = self.portal_types.objectIds()
931 932 933 934 935 936 937 938 939
    if (getattr(self, 'template_format_version', 0)) == 0:
      for object in self._archive.values():
        try:
          object_id = object.id
        except:
          import pdb
          pdb.set_trace()
        object_chain = self.portal_workflow.getChainFor(object_id)
        n = 1
940
        new_object_id = '%s_btsave_%s' % (object_id, n)
941 942 943 944 945 946 947 948 949 950 951
        while new_object_id in container_ids:
          backuped_object_chain = self.portal_workflow.getChainFor(new_object_id)
          if object_chain != backuped_object_chain:
            result += "$$$ Workflow chains: " \
                       "%s and %s $$$\n" % \
                        (object_id, new_object_id)
            if verbose:
              result += '"%s" != "%s"\n' % (object_chain, backuped_object_chain)
            result += '%s\n' % ('-'*80)
          n += 1
          new_object_id = '%s_btsave_%s' % (object_id, n)
952
    return result
953

954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972
  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:
        type = 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
        dict[str(type)] = str(workflow)
      self._workflow_chain_archive = dict
    else:
      ObjectTemplateItem._importFile(self, file_name, file)


973 974
class CatalogMethodTemplateItem(ObjectTemplateItem):

975 976
  def __init__(self, id_list, tool_id='portal_catalog', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
977
    self._is_catalog_method_archive = PersistentMapping()
978
    self._is_catalog_list_method_archive = PersistentMapping()
979 980 981 982 983 984 985 986 987 988
    self._is_uncatalog_method_archive = PersistentMapping()
    self._is_update_method_archive = PersistentMapping()
    self._is_clear_method_archive = PersistentMapping()
    self._is_filtered_archive = PersistentMapping()
    self._filter_expression_archive = PersistentMapping()
    self._filter_expression_instance_archive = PersistentMapping()
    self._filter_type_archive = PersistentMapping()

  def build(self, context, **kw):
    ObjectTemplateItem.build(self, context, **kw)
989 990
    try:
      catalog = context.portal_catalog.getSQLCatalog()
991 992
    except KeyError:
      catalog = None
993
    if catalog is None:
994
      LOG('BusinessTemplate build', 0, 'catalog not found')
995
      return
Aurel's avatar
Aurel committed
996
    for object in self._objects.values():
997
      method_id = object.id
998 999 1000 1001 1002
      self._is_catalog_method_archive[method_id] = method_id in catalog.sql_catalog_object
      self._is_catalog_list_method_archive[method_id] = method_id in catalog.sql_catalog_object_list
      self._is_uncatalog_method_archive[method_id] = method_id in catalog.sql_uncatalog_object
      self._is_update_method_archive[method_id] = method_id in catalog.sql_update_object
      self._is_clear_method_archive[method_id] = method_id in catalog.sql_clear_catalog
1003
      self._is_filtered_archive[method_id] = 0
1004 1005 1006 1007 1008
      if catalog.filter_dict.has_key(method_id):
        self._is_filtered_archive[method_id] = catalog.filter_dict[method_id]['filtered']
        self._filter_expression_archive[method_id] = catalog.filter_dict[method_id]['expression']
        self._filter_expression_instance_archive[method_id] = catalog.filter_dict[method_id]['expression_instance']
        self._filter_type_archive[method_id] = catalog.filter_dict[method_id]['type']
1009

Aurel's avatar
Aurel committed
1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
  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():
      object=self._objects[key]
      # 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()
      XMLExportImport.exportXML(object._p_jar, object._p_oid, f)
      bta.addObject(object=f.getvalue(), name=id, path=path)
      # add all datas specific to catalog inside one file
      catalog = context.portal_catalog.getSQLCatalog()
      method_id = object.id
1027 1028
      object_path = os.path.join(path, method_id+'.catalog_keys.xml')

Aurel's avatar
Aurel committed
1029
      f = open(object_path, 'wt')
1030 1031 1032 1033 1034 1035 1036
      xml_data = '<catalog_method>'
      for method in catalog_method_list:
        value = getattr(self, method, 0)[method_id]
        xml_data += os.linesep+' <method>'
        xml_data += os.linesep+'  <key>%s</key>' %(method)
        xml_data += os.linesep+'  <value>%s</value>' %(str(int(value)))
        xml_data += os.linesep+' </method>'
Aurel's avatar
Aurel committed
1037
      if catalog.filter_dict.has_key(method_id):
1038 1039 1040 1041 1042 1043 1044 1045 1046
        for method in catalog_method_filter_list:
          value = getattr(self, method, '')[method_id]
          if method == '_filter_expression_instance_archive':
            # convert instance to a xml file
            object = self._filter_expression_instance_archive[method_id]
            object_io = StringIO()
            XMLExportImport.exportXML(object._p_jar, object._p_oid, object_io)
            bta.addObject(object = object_io.getvalue(), name=id+'.filter_instance', path=path)
          else:
1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
            if type(value) in (type(''), type(u'')):
              xml_data += os.linesep+' <method type="">'
              xml_data += os.linesep+'  <key>%s</key>' %(method)
              xml_data += os.linesep+'  <value>%s</value>' %(str(value))
              xml_data += os.linesep+' </method>'
            elif type(value) in (type(()), type([])):
              xml_data += os.linesep+' <method type="tuple">'
              xml_data += os.linesep+'  <key>%s</key>' %(method)
              for item in value:
                xml_data += os.linesep+'  <value>%s</value>' %(str(item))
              xml_data += os.linesep+' </method>'
1058 1059
      xml_data += os.linesep+'</catalog_method>'
      f.write(str(xml_data))
Aurel's avatar
Aurel committed
1060
      f.close()
1061

1062 1063
  def install(self, context, **kw):
    ObjectTemplateItem.install(self, context, **kw)
1064 1065
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1066
    except KeyError:
1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
      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(catalog.sql_catalog_object)
    sql_catalog_object_list = list(catalog.sql_catalog_object_list)
    sql_uncatalog_object = list(catalog.sql_uncatalog_object)
    sql_update_object = list(catalog.sql_update_object)
    sql_clear_catalog = list(catalog.sql_clear_catalog)
1077

1078
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
1079 1080 1081 1082
      values = self._objects.values()
    else:
      values = self._archive.values()
    for object in values:
1083
      method_id = object.id
1084

Aurel's avatar
Aurel committed
1085 1086 1087 1088 1089 1090
      is_catalog_method = int(self._is_catalog_method_archive[method_id])
      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_update_method = int(self._is_update_method_archive[method_id])
      is_clear_method = int(self._is_clear_method_archive[method_id])
      is_filtered = int(self._is_filtered_archive[method_id])
1091 1092 1093 1094 1095 1096

      if is_catalog_method and method_id not in sql_catalog_object:
        sql_catalog_object.append(method_id)
      elif not is_catalog_method and method_id in sql_catalog_object:
        sql_catalog_object.remove(method_id)

1097 1098 1099 1100 1101
      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)

1102
      if is_uncatalog_method and method_id not in sql_uncatalog_object:
1103
        sql_uncatalog_object.append(method_id)
1104
      elif not is_uncatalog_method and method_id in sql_uncatalog_object:
1105 1106
        sql_uncatalog_object.remove(method_id)

1107
      if is_update_method and method_id not in sql_update_object:
1108
        sql_update_object.append(method_id)
1109
      elif not is_update_method and method_id in sql_update_object:
1110 1111 1112 1113 1114 1115 1116 1117
        sql_update_object.remove(method_id)

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

      if is_filtered:
1118 1119 1120
        expression = self._filter_expression_archive[method_id]
        expression_instance = self._filter_expression_instance_archive[method_id]
        type = self._filter_type_archive[method_id]
1121

1122 1123 1124 1125 1126
        catalog.filter_dict[method_id] = PersistentMapping()
        catalog.filter_dict[method_id]['filtered'] = 1
        catalog.filter_dict[method_id]['expression'] = expression
        catalog.filter_dict[method_id]['expression_instance'] = expression_instance
        catalog.filter_dict[method_id]['type'] = type
1127
      elif method_id in catalog.filter_dict.keys():
1128
        catalog.filter_dict[method_id]['filtered'] = 0
1129 1130

    sql_catalog_object.sort()
1131 1132 1133
    catalog.sql_catalog_object = tuple(sql_catalog_object)
    sql_catalog_object_list.sort()
    catalog.sql_catalog_object_list = tuple(sql_catalog_object_list)
1134
    sql_uncatalog_object.sort()
1135
    catalog.sql_uncatalog_object = tuple(sql_uncatalog_object)
1136
    sql_update_object.sort()
1137
    catalog.sql_update_object = tuple(sql_update_object)
1138
    sql_clear_catalog.sort()
1139
    catalog.sql_clear_catalog = tuple(sql_clear_catalog)
1140 1141

  def uninstall(self, context, **kw):
1142

1143 1144
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1145
    except KeyError:
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
      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(catalog.sql_catalog_object)
    sql_catalog_object_list = list(catalog.sql_catalog_object_list)
    sql_uncatalog_object = list(catalog.sql_uncatalog_object)
    sql_update_object = list(catalog.sql_update_object)
    sql_clear_catalog = list(catalog.sql_clear_catalog)
1158 1159 1160 1161 1162 1163 1164

    for object in self._archive.values():
      method_id = object.id

      if method_id in sql_catalog_object:
        sql_catalog_object.remove(method_id)

1165 1166 1167
      if method_id in sql_catalog_object_list:
        sql_catalog_object_list.remove(method_id)

1168 1169 1170 1171 1172 1173 1174 1175 1176
      if method_id in sql_uncatalog_object:
        sql_uncatalog_object.remove(method_id)

      if method_id in sql_update_object:
        sql_update_object.remove(method_id)

      if method_id in sql_clear_catalog:
        sql_clear_catalog.remove(method_id)

Yoshinori Okuji's avatar
Yoshinori Okuji committed
1177
      if catalog.filter_dict.has_key(method_id):
1178
        del catalog.filter_dict[method_id]
1179

1180 1181 1182 1183 1184
    catalog.sql_catalog_object = tuple(sql_catalog_object)
    catalog.sql_catalog_object_list = tuple(sql_catalog_object_list)
    catalog.sql_uncatalog_object = tuple(sql_uncatalog_object)
    catalog.sql_update_object = tuple(sql_update_object)
    catalog.sql_clear_catalog = tuple(sql_clear_catalog)
1185 1186

    ObjectTemplateItem.uninstall(self, context, **kw)
1187

1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
  def _importFile(self, file_name, file):
    if not '.catalog_keys' in file_name and not '.filter_instance' in file_name:
      # 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
    elif not '.filter_instance' in file_name and '.catalog_keys' in file_name:
      # recreate data mapping specific to catalog method
      path, name = os.path.split(file_name)
      id = string.split(name, '.')[0]
      xml = parse(file)
      method_list = xml.getElementsByTagName('method')
1204
      for method in method_list:
1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219
        type = method.getAttribute('type')
        if type == "":
          key = method.getElementsByTagName('key')[0].childNodes[0].data
          value = method.getElementsByTagName('value')[0].childNodes[0].data
          key = str(key)
          if key in catalog_method_list:
            value = int(value)
          else:
            value = str(value)
        elif type == "tuple":
          value = []
          key = method.getElementsByTagName('key')[0].childNodes[0].data
          value_list = method.getElementsByTagName('value')
          for item in value_list:
            value.append(item.childNodes[0].data)
1220
        else:
1221 1222
          LOG('BusinessTemplate import CatalogMethod, type unknown', 0, type)
          continue
1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235
        dict = getattr(self, key)
        dict[id] = value
    elif '.filter_instance' in file_name:
      # get filter expression instance object from xml file
      path, name = os.path.split(file_name)
      id = string.split(name, '.')[0]
      obj = self
      connection = None
      while connection is None:
        obj=obj.aq_parent
        connection=obj._p_jar
      obj = connection.importFile(file, customImporters=customImporters)
      self._filter_expression_instance_archive[id]=obj
1236

1237 1238

class ActionTemplateItem(ObjectTemplateItem):
1239 1240 1241 1242

  def _splitPath(self, path):
    """
      Split path tries to split a complexe path such as:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1243

1244
      "foo/bar[id=zoo]"
1245

1246
      into
1247

1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261
      "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

  def __init__(self, id_list, **kw):
1262
    # XXX It's look like ObjectTemplateItem __init__
1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276
    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

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
      relative_url, key, value = self._splitPath(id)
      object = p.unrestrictedTraverse(relative_url)
      for ai in object.listActions():
        if getattr(ai, key) == value:
1277
          url = os.path.split(relative_url)
Aurel's avatar
Aurel committed
1278 1279
          key = os.path.join(url[-2], url[-1], value)
          object = ai._getCopy(context)
1280 1281 1282 1283 1284
          if hasattr(object, '__ac_local_roles__'):
            # remove local roles
            object.__ac_local_roles__ = None
          if hasattr(object, '_owner'):
            object._owner = None
Aurel's avatar
Aurel committed
1285 1286 1287 1288
          if hasattr(object, 'uid'):
            object.uid = None
          self._objects[key] = object
          self._objects[key].wl_clearLocks()
1289 1290
          break
      else:
1291
        raise NotFound, 'Action %r not found' %(id,)
Aurel's avatar
Aurel committed
1292

1293
  def install(self, context, **kw):
1294
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
1295 1296
      p = context.getPortalObject()
      for id in self._objects.keys():
1297
        path = id.split(os.sep)
1298
        object = p.unrestrictedTraverse(path[:-1])
Aurel's avatar
Aurel committed
1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333
        for ai in object.listActions():
          if getattr(ai, 'id') == path[-1]:
            raise TemplateConflictError, 'the portal type %s already has the action %s' % (object.id, path[-1])
        action = self._objects[id]
        object.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 ''
                    , optional = getattr(action, 'optional', 0)
                    )
    else:
      BaseTemplateItem.install(self, context, **kw)
      p = context.getPortalObject()
      for id,action in self._archive.items():
        relative_url, key, value = self._splitPath(id)
        object = p.unrestrictedTraverse(relative_url)
        for ai in object.listActions():
          if getattr(ai, key) == value:
            raise TemplateConflictError, 'the portal type %s already has the action %s' % (object.id, value)
        object.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 ''
                    , optional = getattr(action, 'optional', 0)
                    )
1334 1335 1336 1337 1338 1339 1340 1341

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
    for id,action in self._archive.items():
      relative_url, key, value = self._splitPath(id)
      object = p.unrestrictedTraverse(relative_url)
      action_list = object.listActions()
      for index in range(len(action_list)):
1342
        if getattr(action_list[index], key) == value:
1343 1344 1345 1346 1347 1348 1349 1350 1351 1352
          object.deleteActions(selections=(index,))
          break
    BaseTemplateItem.uninstall(self, context, **kw)

class SitePropertyTemplateItem(BaseTemplateItem):

  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
1353 1354
      for property in p.propertyMap():
        if property['id'] == id:
Aurel's avatar
Aurel committed
1355 1356
          object = p.getProperty(id)
          type = property['type']
1357 1358
          break
      else:
Aurel's avatar
Aurel committed
1359 1360
        object = None
      if object is None:
1361
        raise NotFound, 'the property %s is not found' % id
Aurel's avatar
Aurel committed
1362 1363
      self._objects[id] = (type, object)

1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378
  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
      type = prop.getElementsByTagName('type')[0].childNodes[0].data
      if type in ('lines', 'tokens'):
        value = []
        values = prop.getElementsByTagName('value')[0]
        items = values.getElementsByTagName('item')
        for item in items:
          i = item.childNodes[0].data
          value.append(str(i))
      else:
1379
        value = str(prop.getElementsByTagName('value')[0].childNodes[0].data)
1380 1381
      self._objects[str(id)] = (str(type), value)

1382 1383
  def install(self, context, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
1384 1385 1386 1387 1388
      p = context.getPortalObject()
      for path in self._objects.keys():
        dir, id = os.path.split(path)
        if p.hasProperty(id):
          continue
1389
        type, property = self._objects[path]
Aurel's avatar
Aurel committed
1390 1391 1392 1393 1394 1395 1396 1397 1398 1399
        p._setProperty(id, property, type=type)
    else:
      BaseTemplateItem.install(self, context, **kw)
      p = context.getPortalObject()
      for id,property in self._archive.items():
        if p.hasProperty(id):
          continue
          # Too much???
          #raise TemplateConflictError, 'the property %s already exists' % id
        p._setProperty(id, property['value'], type=property['type'])
1400 1401 1402 1403 1404 1405 1406 1407

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
    for id in self._archive.keys():
      if p.hasProperty(id):
        p._delProperty(id)
    BaseTemplateItem.uninstall(self, context, **kw)

1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421
  def generate_xml(self, path=None):
    xml_data = ''
    type, object=self._objects[path]
    xml_data += os.linesep+' <property>'
    xml_data += os.linesep+'  <id>%s</id>' %(path,)
    xml_data += os.linesep+'  <type>%s</type>' %(type,)
    if type in ('lines', 'tokens'):
      xml_data += os.linesep+'  <value>'
      for item in object:
        if item != '':
          xml_data += os.linesep+'   <item>%s</item>' %(item,)
      xml_data += os.linesep+'  </value>'
    else:
      xml_data += os.linesep+'  <value>%r</value>' %((os.linesep).join(object),)
1422
    xml_data += os.linesep+' </property>'
1423 1424
    return xml_data

Aurel's avatar
Aurel committed
1425 1426 1427 1428 1429
  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)
1430 1431 1432 1433
    xml_data = '<site_property>'
    keys = self._objects.keys()
    keys.sort()
    for path in keys:
1434
      xml_data += self.generate_xml(path)
1435 1436
    xml_data += os.linesep+'</site_property>'
    bta.addObject(object=xml_data, name='properties', path=root_path)
1437

1438 1439
class ModuleTemplateItem(BaseTemplateItem):

1440 1441
  def diff(self, max_deep=1, **kw):
    return ''
Aurel's avatar
Aurel committed
1442

1443 1444 1445 1446 1447
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
      module = p.unrestrictedTraverse(id)
1448 1449 1450 1451 1452 1453 1454 1455
      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
1456

1457 1458 1459
  def generate_xml(self, path=None):
    dict = self._objects[path]
    xml_data = '<module>'
1460 1461 1462 1463
    # sort key
    keys = dict.keys()
    keys.sort()
    for key in keys:
1464 1465 1466 1467 1468 1469 1470
      if key =='permission_list':
        # separe permission dict into xml
        xml_data += os.linesep+' <%s>' %(key,)
        permission_list = dict[key]
        for perm in permission_list:
          xml_data += os.linesep+'  <permission>'
          xml_data += os.linesep+'   <name>%s</name>' %(perm[0])
Aurel's avatar
Aurel committed
1471
          role_list = list(perm[1])
1472
          role_list.sort()
1473 1474 1475 1476 1477 1478 1479 1480 1481
          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

1482 1483 1484 1485 1486
  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)
1487 1488 1489
    keys = self._objects.keys()
    keys.sort()
    for id in keys:
1490 1491
      # expor module one by one
      xml_data = self.generate_xml(path=id)
1492 1493 1494
      bta.addObject(object=xml_data, name=id, path=path)

  def install(self, context, **kw):
1495
    portal = context.getPortalObject()
1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509
    if (getattr(self, 'template_format_version', 0)) == 1:
      items = self._objects.items()
    else:
      items = self._archive.items()
    for id,mapping in items:
      path, id = os.path.split(id)
      if id in portal.objectIds():
        module = portal._getOb(id)
        module.portal_type = str(mapping['portal_type']) # XXX
      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([]))
1510 1511 1512 1513 1514
        try:
          module.manage_permission(name, roles=role_list, acquire=acquire)
        except ValueError:
          # Ignore a permission not present in this system.
          pass
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 1540 1541 1542 1543 1544 1545

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

1546 1547 1548 1549 1550
  def uninstall(self, context, **kw):
    p = context.getPortalObject()
    id_list = p.objectIds()
    for id in self._archive.keys():
      if id in id_list:
1551 1552
        try:
          p.manage_delObjects([id])
1553
        except NotFound:
1554
          pass
1555 1556
    BaseTemplateItem.uninstall(self, context, **kw)

1557 1558 1559
  def trash(self, context, new_item, **kw):
    # Do not remove any module for safety.
    pass
1560 1561

class DocumentTemplateItem(BaseTemplateItem):
1562 1563 1564 1565
  local_file_reader_name = 'readLocalDocument'
  local_file_writer_name = 'writeLocalDocument'
  local_file_importer_name = 'importLocalDocument'
  local_file_remover_name = 'removeLocalDocument'
1566 1567 1568 1569

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

1572 1573
  def install(self, context, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
1574 1575 1576 1577
      for id in self._objects.keys():
        text = self._objects[id]
        path, name = os.path.split(id)
        # This raises an exception if the file already exists.
1578
        globals()[self.local_file_writer_name](name, text, create=1)
Aurel's avatar
Aurel committed
1579 1580 1581 1582 1583 1584
        if self.local_file_importer_name is not None:
          globals()[self.local_file_importer_name](name)
    else:
      BaseTemplateItem.install(self, context, **kw)
      for id,text in self._archive.items():
        # This raises an exception if the file exists.
1585
        globals()[self.local_file_writer_name](id, text, create=1)
Aurel's avatar
Aurel committed
1586 1587
        if self.local_file_importer_name is not None:
          globals()[self.local_file_importer_name](id)
1588 1589 1590

  def uninstall(self, context, **kw):
    for id in self._archive.keys():
1591
      globals()[self.local_file_remover_name](id)
1592 1593
    BaseTemplateItem.uninstall(self, context, **kw)

Aurel's avatar
Aurel committed
1594 1595 1596 1597 1598 1599 1600 1601 1602
  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():
      object=self._objects[path]
      bta.addObject(object=object, name=path, path=None, ext='.py')

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

1607 1608 1609 1610 1611 1612
class PropertySheetTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalPropertySheet'
  local_file_writer_name = 'writeLocalPropertySheet'
  local_file_importer_name = 'importLocalPropertySheet'
  local_file_remover_name = 'removeLocalPropertySheet'

1613

1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627
class ExtensionTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalExtension'
  local_file_writer_name = 'writeLocalExtension'
  # XXX is this method a error or ?
  local_file_importer_name = 'importLocalPropertySheet'
  local_file_remover_name = 'removeLocalExtension'

class TestTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalTest'
  local_file_writer_name = 'writeLocalTest'
  # XXX is this a error ?
  local_file_importer_name = None
  local_file_remover_name = 'removeLocalTest'

Aurel's avatar
Aurel committed
1628

1629 1630 1631
class ProductTemplateItem(BaseTemplateItem):
  # XXX Not implemented yet
  pass
1632 1633 1634

class RoleTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
1635 1636 1637 1638
  def build(self, context, **kw):
    role_list = []
    for key in self._archive.keys():
      role_list.append(key)
1639 1640
    if len(role_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'role_list'] = role_list
Aurel's avatar
Aurel committed
1641

1642
  def install(self, context, **kw):
1643 1644 1645
    p = context.getPortalObject()
    roles = {}
    for role in p.__ac_roles__:
1646
      roles[role] = 1
1647
    if (getattr(self, 'template_format_version', 0)) == 1:
1648
      role_list = self._objects.keys()
1649
    else:
1650
      role_list = self._archive.keys()
1651
    for role in role_list:
1652 1653 1654 1655 1656 1657 1658 1659 1660 1661
      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
1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673

  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)

1674 1675 1676 1677 1678 1679 1680 1681 1682
  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
1683
      if role in roles and role not in new_roles:
1684 1685 1686
        del roles[role]
    p.__ac_roles__ = tuple(roles.keys())

1687 1688 1689
  def generate_xml(self, path):
    object=self._objects[path]
    xml_data = '<role_list>'
1690
    object.sort()
1691 1692 1693 1694 1695
    for role in object:
      xml_data += os.linesep+' <role>%s</role>' %(role)
    xml_data += os.linesep+'</role_list>'
    return xml_data

Aurel's avatar
Aurel committed
1696 1697 1698 1699 1700 1701
  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():
1702
      xml_data = self.generate_xml(path=path)
1703
      bta.addObject(object=xml_data, name=path, path=None,)
1704

1705 1706
class CatalogResultKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
1707
  def build(self, context, **kw):
1708 1709
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1710
    except KeyError:
1711 1712 1713 1714
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
1715
    sql_search_result_keys = list(catalog.sql_search_result_keys)
Aurel's avatar
Aurel committed
1716
    role_list = []
1717
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
1718 1719 1720 1721
      if key in sql_search_result_keys:
        role_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
1722
    if len(role_list) > 0:
1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734
      self._objects[self.__class__.__name__+os.sep+'key_list'] = role_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

1735
  def install(self, context, **kw):
1736 1737 1738 1739 1740 1741 1742
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
1743

1744
    sql_search_result_keys = list(catalog.sql_search_result_keys)
1745 1746 1747
    if (getattr(self, 'template_format_version', 0)) == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
1748 1749 1750
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
1751
    else:
1752
      keys = self._archive.keys()
1753 1754 1755 1756 1757
    for key in keys:
      if key not in sql_search_result_keys:
        sql_search_result_keys.append(key)
    catalog.sql_search_result_keys = sql_search_result_keys

1758 1759

  def uninstall(self, context, **kw):
1760 1761
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1762
    except KeyError:
1763 1764 1765 1766 1767
      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)
1768 1769 1770
    for key in self._archive.keys():
      if key in sql_search_result_keys:
        sql_search_result_keys.remove(key)
1771
    catalog.sql_search_result_keys = sql_search_result_keys
1772 1773
    BaseTemplateItem.uninstall(self, context, **kw)

1774 1775 1776
  def generate_xml(self, path=None):
    object=self._objects[path]
    xml_data = '<key_list>'
1777
    object.sort()
1778 1779 1780 1781 1782
    for key in object:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

Aurel's avatar
Aurel committed
1783 1784 1785 1786 1787
  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)
1788
    for path in self._objects.keys():
1789
      xml_data = self.generate_xml(path=path)
1790
      bta.addObject(object=xml_data, name=path, path=None)
1791

1792 1793
class CatalogRelatedKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
1794
  def build(self, context, **kw):
1795 1796
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1797
    except KeyError:
1798 1799 1800 1801
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
1802 1803
    sql_search_related_keys = list(catalog.sql_catalog_related_keys)
    role_list = []
1804
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
1805 1806 1807 1808
      if key in sql_search_related_keys:
        role_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
1809
    if len(role_list) > 0:
1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821
      self._objects[self.__class__.__name__+os.sep+'key_list'] = role_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

1822
  def install(self, context, **kw):
1823 1824 1825 1826 1827 1828 1829
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
1830

1831
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
1832 1833
    if (getattr(self, 'template_format_version', 0)) == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
1834
        return
1835 1836 1837
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
1838
    else:
1839
      keys = self._archive.keys()
1840 1841 1842 1843
    for key in keys:
      if key not in sql_catalog_related_keys:
        sql_catalog_related_keys.append(key)
    catalog.sql_catalog_related_keys = sql_catalog_related_keys
1844

1845 1846 1847
  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1848
    except KeyError:
1849 1850 1851 1852
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
1853
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
1854 1855 1856 1857 1858 1859
    for key in self._archive.keys():
      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)

1860 1861 1862
  def generate_xml(self, path=None):
    object=self._objects[path]
    xml_data = '<key_list>'
1863
    object.sort()
1864 1865 1866 1867 1868
    for key in object:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

Aurel's avatar
Aurel committed
1869 1870 1871 1872 1873 1874
  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():
1875
      xml_data = self.generate_xml(path=path)
1876
      bta.addObject(object=xml_data, name=path, path=None)
1877

1878 1879
class CatalogResultTableTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
1880
  def build(self, context, **kw):
1881 1882
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1883
    except KeyError:
1884 1885 1886 1887
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
1888 1889
    sql_search_result_tables = list(catalog.sql_search_tables)
    role_list = []
1890
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
1891 1892 1893 1894
      if key in sql_search_result_tables:
        role_list.append(key)
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
1895
    if len(role_list) > 0:
1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906
      self._objects[self.__class__.__name__+os.sep+'key_list'] = role_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
Aurel's avatar
Aurel committed
1907

1908
  def install(self, context, **kw):
1909 1910 1911 1912 1913 1914 1915
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
1916

1917
    sql_search_tables = list(catalog.sql_search_tables)
1918 1919
    if (getattr(self, 'template_format_version', 0)) == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
1920
        return
1921 1922 1923
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
1924
    else:
1925
      keys = self._archive.keys()
1926 1927 1928 1929
    for key in keys:
      if key not in sql_search_tables:
        sql_search_tables.append(key)
    catalog.sql_search_tables = sql_search_tables
1930 1931

  def uninstall(self, context, **kw):
1932 1933
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1934
    except KeyError:
1935 1936 1937 1938 1939
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_search_tables = list(catalog.sql_search_tables)
1940 1941 1942
    for key in self._archive.keys():
      if key in sql_search_tables:
        sql_search_tables.remove(key)
1943
    catalog.sql_search_tables = sql_search_tables
1944 1945
    BaseTemplateItem.uninstall(self, context, **kw)

1946 1947 1948
  def generate_xml(self, path=None):
    object=self._objects[path]
    xml_data = '<key_list>'
1949
    object.sort()
1950 1951 1952 1953 1954
    for key in object:
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'    
    return xml_data

Aurel's avatar
Aurel committed
1955 1956 1957 1958 1959 1960
  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():
1961
      xml_data = self.generate_xml(path=path)
1962
      bta.addObject(object=xml_data, name=path, path=None)
1963

1964 1965 1966 1967 1968
class MessageTranslationTemplateItem(BaseTemplateItem):

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

1976
  def install(self, context, **kw):
1977
    localizer = context.getPortalObject().Localizer
1978
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
1979 1980 1981 1982 1983 1984
      for path, po in self._objects.items():
        path = string.split(path, '/')
        lang = path[-3]
        catalog = path[-2]
        if lang not in localizer.get_languages():
          localizer.manage_addLanguage(lang)
1985 1986 1987 1988
        mc = localizer._getOb(catalog)
        if lang not in mc.get_languages():
          mc.manage_addLanguage(lang)
        mc.manage_import(lang, po)
Aurel's avatar
Aurel committed
1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
    else:
      BaseTemplateItem.install(self, context, **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():
      object = self._objects[key]
      path = os.path.join(root_path, key)
      bta.addFolder(name=path)
      f = open(path+'/translation.po', 'wt')
      f.write(str(object))
      f.close()
2012

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

2017
class BusinessTemplate(XMLObject):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2018 2019
    """
    A business template allows to construct ERP5 modules
2020 2021 2022
    in part or completely. Each object are separated from its
    subobjects and exported in xml format.
    It may include:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2023

2024 2025
    - catalog definition
      - SQL method objects
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2026 2027 2028 2029
      - SQL methods including:
        - purpose (catalog, uncatalog, etc.)
        - filter definition

2030 2031 2032
    - portal_types definition
      - object without optinal actions
      - list of relation between portal type and worklfow
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2033

2034
    - module definition
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2035
      - id
2036 2037
      - title
      - portal type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2038 2039
      - roles/security

2040 2041 2042 2043
    - site property definition
      - id
      - type
      - value
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2044

2045 2046
    - document/propertysheet/extension/test definition
      - copy of the local file
2047

2048
    - message transalation definition
2049
      - .po file
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2050

2051 2052
    The Business Template properties are exported to the bt folder with
    one property per file
2053

Jean-Paul Smets's avatar
Jean-Paul Smets committed
2054 2055
    Technology:

2056 2057
    - 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
2058

2059
    - install files to the right location (install)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2060 2061 2062 2063

    Use case:

    - install core ERP5 (the minimum)
2064

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

2067 2068
    - go to "BT" menu. Create new BT.
      Define BT elements (workflow, methods, attributes, etc.).
2069
      Build BT and export or save it
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2070 2071 2072 2073 2074
      Done.
    """

    meta_type = 'ERP5 Business Template'
    portal_type = 'Business Template'
2075
    add_permission = Permissions.AddPortalContent
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088
    isPortalContent = 1
    isRADContent = 1

    # Declarative security
    security = ClassSecurityInfo()
    security.declareObjectProtected(Permissions.View)

    # Declarative interfaces
    __implements__ = ( Interface.Variated, )

    # Declarative properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
2089
                      , PropertySheet.SimpleItem
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2090 2091 2092 2093
                      , PropertySheet.CategoryCore
                      , PropertySheet.BusinessTemplate
                      )

2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168
    # 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
         , 'actions'        :
        ( { 'id'            : 'view'
          , 'name'          : 'View'
          , 'category'      : 'object_view'
          , 'action'        : 'BusinessTemplate_view'
          , 'permissions'   : (
              Permissions.View, )
          }
        , { 'id'            : 'diff'
          , 'name'          : 'Diff'
          , 'category'      : 'object_view'
          , 'action'        : 'BusinessTemplate_viewDiff'
          , 'permissions'   : (
              Permissions.View, )
          }
        , { 'id'            : 'history'
          , 'name'          : 'History'
          , 'category'      : 'object_view'
          , 'action'        : 'Base_viewHistory'
          , 'permissions'   : (
              Permissions.View, )
          }
        , { 'id'            : 'metadata'
          , 'name'          : 'Metadata'
          , 'category'      : 'object_view'
          , 'action'        : 'Base_viewMetadata'
          , 'permissions'   : (
              Permissions.ManageProperties, )
          }
        , { 'id'            : 'update'
          , 'name'          : 'Update Business Template'
          , 'category'      : 'object_action'
          , 'action'        : 'BusinessTemplate_update'
          , 'permissions'   : (
              Permissions.ModifyPortalContent, )
          }
        , { 'id'            : 'save'
          , 'name'          : 'Save Business Template'
          , 'category'      : 'object_action'
          , 'action'        : 'BusinessTemplate_save'
          , 'permissions'   : (
              Permissions.ManagePortal, )
          }
        , { 'id'            : 'export'
          , 'name'          : 'Export Business Template'
          , 'category'      : 'object_action'
          , 'action'        : 'BusinessTemplate_export'
          , 'permissions'   : (
              Permissions.ManagePortal, )
          }
        , { 'id'            : 'unittest_run'
          , 'name'          : 'Run Unit Tests'
          , 'category'      : 'object_action'
          , 'action'        : 'BusinessTemplate_viewUnitTestRunDialog'
          , 'permissions'   : (
              Permissions.ManagePortal, )
          }
        )
      }

2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195
    # This is a global variable
    # Order is important for installation
    _item_name_list = [
      '_product_item',
      '_property_sheet_item',
      '_document_item',
      '_extension_item',
      '_test_item',
      '_role_item',
      '_message_translation_item',
      '_workflow_item',
      '_catalog_method_item',
      '_site_property_item',
      '_portal_type_item',
      '_category_item',
      '_module_item',
      '_path_item',
      '_skin_item',
      '_action_item',
      '_catalog_result_key_item',
      '_catalog_related_key_item',
      '_catalog_result_table_item',
    ]

    def __init__(self, *args, **kw):
      XMLObject.__init__(self, *args, **kw)
      # Initialize all item to None
Aurel's avatar
Aurel committed
2196
      self._objects = PersistentMapping()
2197 2198
      for item_name in self._item_name_list:
        setattr(self, item_name, None)
2199

2200
    security.declareProtected(Permissions.ManagePortal, 'manage_afterAdd')
2201 2202 2203 2204 2205 2206 2207
    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".
2208 2209
        if portal_workflow.getStatusOf(
                'business_template_installation_workflow', self) is not None:
2210
          # XXX Not good to access the attribute directly,
2211 2212 2213
          # but there is no API for clearing the history.
          self.workflow_history[
                            'business_template_installation_workflow'] = None
2214

2215
    security.declareProtected(Permissions.ManagePortal, 'build')
2216
    def build(self, no_action=0):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2217 2218 2219
      """
        Copy existing portal objects to self
      """
2220 2221
      if no_action: return # this is use at import of Business Template to get the status built
      
2222 2223
      # Make sure that everything is sane.
      self.clean()
2224

Yoshinori Okuji's avatar
Yoshinori Okuji committed
2225 2226
      # XXX Trim down the history to prevent it from bloating the bt5 file.
      # XXX Is there any better way to shrink the size???
Aurel's avatar
Aurel committed
2227
      # XXX Is it still necessary as it is not saved in new bt format ??
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2228 2229
      portal_workflow = getToolByName(self, 'portal_workflow')
      wf_id_list = portal_workflow.getChainFor(self)
2230
      original_history_dict = {}
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2231 2232 2233 2234 2235 2236
      for wf_id in wf_id_list:
        history = portal_workflow.getHistoryOf(wf_id, self)
        if history is not None and len(history) > 30:
          original_history_dict[wf_id] = history
          LOG('Business Template', 0, 'trim down the history of %s' % (wf_id,))
          self.workflow_history[wf_id] = history[-30:]
2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282
      # Store all datas
      self._portal_type_item = \
          PortalTypeTemplateItem(self.getTemplatePortalTypeIdList())
      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())
      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())
      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())
      # Build each part
      for item_name in self._item_name_list:
        getattr(self, item_name).build(self)
2283

2284
    build = WorkflowMethod(build)
2285 2286

    def publish(self, url, username=None, password=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2287
      """
2288
        Publish in a format or another
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2289
      """
2290
      return self.portal_templates.publish(self, url, username=username,
2291
                                           password=password)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2292

2293
    def update(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2294
      """
2295
        Update template: download new template definition
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2296
      """
2297
      return self.portal_templates.update(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2298

2299
    def _install(self, **kw):
2300 2301
      installed_bt = self.portal_templates.getInstalledBusinessTemplate(
                                                           self.getTitle())
2302
      LOG('Business Template install', 0,
2303
          'self = %r, installed_bt = %r' % (self, installed_bt))
Aurel's avatar
Aurel committed
2304

2305
      if installed_bt is not None:
2306
        installed_bt.trash(self)
2307
        installed_bt.replace(self)
2308
      # Update local dictionary containing all setup parameters
2309 2310 2311
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
2312 2313 2314 2315
      # Install everything
      for item_name in self._item_name_list:
        item = getattr(self, item_name)
        if item is not None:
2316
          item.install(local_configuration)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2317 2318 2319
      # It is better to clear cache because the installation of a template
      # adds many new things into the portal.
      clearCache()
2320

2321
    security.declareProtected(Permissions.ManagePortal, 'install')
2322 2323 2324 2325 2326
    def install(self, **kw):
      """
        For install based on paramaters provided in **kw
      """
      return self._install(**kw)
2327

2328
    install = WorkflowMethod(install)
2329

2330
    security.declareProtected(Permissions.ManagePortal, 'reinstall')
2331
    def reinstall(self, **kw):
2332 2333 2334 2335
      """Reinstall Business Template.
      """
      return self._install(**kw)

2336
    reinstall = WorkflowMethod(reinstall)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2337

2338
    security.declareProtected(Permissions.ManagePortal, 'trash')
2339 2340
    def trash(self, new_bt, **kw):
      """
2341
        Trash unnecessary items before upgrading to a new business
2342
        template.
2343
        This is similar to uninstall, but different in that this does
2344
        not remove all items.
2345 2346 2347 2348 2349
      """
      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
2350 2351 2352 2353 2354 2355 2356
      # Trash everything
      for item_name in self._item_name_list[::-1]:
        item = getattr(self, item_name)
        if item is not None:
          item.trash(
                local_configuration,
                getattr(new_bt, item_name))
2357

2358
    security.declareProtected(Permissions.ManagePortal, 'uninstall')
2359
    def uninstall(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2360
      """
2361
        For uninstall based on paramaters provided in **kw
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2362
      """
2363 2364 2365 2366
      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
2367 2368 2369 2370 2371 2372
      # Uninstall everything
      # Trash everything
      for item_name in self._item_name_list[::-1]:
        item = getattr(self, item_name)
        if item is not None:
          item.uninstall(local_configuration)
2373
      # It is better to clear cache because the uninstallation of a
2374
      # template deletes many things from the portal.
2375
      clearCache()
2376

2377 2378
    uninstall = WorkflowMethod(uninstall)

2379
    security.declareProtected(Permissions.ManagePortal, 'clean')
2380
    def clean(self):
2381
      """
2382
        Clean built information.
2383
      """
2384
      # First, remove obsolete attributes if present.
Aurel's avatar
Aurel committed
2385
      self._objects = None
2386 2387 2388 2389
      for attr in ( '_action_archive',
                    '_document_archive',
                    '_extension_archive',
                    '_test_archive',
2390
                    '_module_archive',
2391 2392 2393
                    '_object_archive',
                    '_portal_type_archive',
                    '_property_archive',
2394
                    '_property_sheet_archive'):
2395 2396 2397
        if hasattr(self, attr):
          delattr(self, attr)
      # Secondly, make attributes empty.
2398 2399
      for item_name in self._item_name_list:
        item = setattr(self, item_name, None)
2400 2401

    clean = WorkflowMethod(clean)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2402

2403
    security.declareProtected(Permissions.AccessContentsInformation,
2404
                              'getBuildingState')
2405
    def getBuildingState(self, id_only=1):
2406
      """
2407
        Returns the current state in building
2408
      """
2409
      portal_workflow = getToolByName(self, 'portal_workflow')
2410 2411
      wf = portal_workflow.getWorkflowById(
                          'business_template_building_workflow')
2412
      return wf._getWorkflowStateOf(self, id_only=id_only )
2413

2414
    security.declareProtected(Permissions.AccessContentsInformation,
2415
                              'getInstallationState')
2416
    def getInstallationState(self, id_only=1):
2417
      """
2418
        Returns the current state in installation
2419
      """
2420
      portal_workflow = getToolByName(self, 'portal_workflow')
2421 2422
      wf = portal_workflow.getWorkflowById(
                           'business_template_installation_workflow')
2423
      return wf._getWorkflowStateOf(self, id_only=id_only )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2424

Yoshinori Okuji's avatar
Yoshinori Okuji committed
2425 2426 2427 2428 2429 2430
    security.declareProtected(Permissions.AccessContentsInformation, 'toxml')
    def toxml(self):
      """
        Return this Business Template in XML
      """
      portal_templates = getToolByName(self, 'portal_templates')
2431
      export_string = portal_templates.manage_exportObject(
2432 2433
                                               id=self.getId(),
                                               toxml=1,
2434
                                               download=1)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2435
      return export_string
2436

2437
    def _getOrderedList(self, id):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2438
      """
2439 2440
        We have to set this method because we want an
        ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2441
      """
2442
      result = getattr(self, id, ())
2443 2444 2445 2446 2447 2448
      if result is None: result = ()
      if result != ():
        result = list(result)
        result.sort()
        result = tuple(result)
      return result
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2449

2450
    def getTemplateCatalogMethodIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2451
      """
2452 2453
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2454
      """
2455
      return self._getOrderedList('template_catalog_method_id')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2456

2457
    def getTemplateBaseCategoryList(self):
2458
      """
2459 2460
      We have to set this method because we want an
      ordered list
2461
      """
2462
      return self._getOrderedList('template_base_category')
2463

2464
    def getTemplateWorkflowIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2465
      """
2466 2467
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2468
      """
2469
      return self._getOrderedList('template_workflow_id')
2470

2471
    def getTemplatePortalTypeIdList(self):
2472
      """
2473 2474
      We have to set this method because we want an
      ordered list
2475
      """
2476
      return self._getOrderedList('template_portal_type_id')
2477

2478
    def getTemplateActionPathList(self):
2479
      """
2480 2481
      We have to set this method because we want an
      ordered list
2482
      """
2483
      return self._getOrderedList('template_action_path')
2484

2485
    def getTemplateSkinIdList(self):
2486
      """
2487 2488
      We have to set this method because we want an
      ordered list
2489
      """
2490
      return self._getOrderedList('template_skin_id')
2491

2492
    def getTemplateModuleIdList(self):
2493
      """
2494 2495
      We have to set this method because we want an
      ordered list
2496
      """
2497
      return self._getOrderedList('template_module_id')
2498 2499 2500 2501 2502 2503 2504

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

2506
    security.declareProtected(Permissions.AccessContentsInformation,
2507
                              'diff')
2508
    def diff(self, verbose=0):
2509
      """
2510
        Return a 'diff' of the business template compared to the
2511 2512
        __btsave__ version.
      """
2513 2514
      diff_message = '%s : %s\n%s\n' % (self.getPath(), DateTime(),
                                        '='*80)
2515
      # Diff everything
2516 2517 2518 2519 2520
      for item_name in self._item_name_list:
        item = getattr(self, item_name)
        if item is not None:
          diff_message += item.diff(verbose=verbose)
      return diff_message
Aurel's avatar
Aurel committed
2521

2522
    security.declareProtected(Permissions.ManagePortal, 'export')
Aurel's avatar
Aurel committed
2523 2524 2525 2526
    def export(self, path=None, local=0, **kw):
      """
        Export this Business Template
      """
2527 2528 2529
      if self.getBuildingState() != 'built':
        raise TemplateConditionError, 'Business Template must be build before export'

2530

Aurel's avatar
Aurel committed
2531 2532 2533 2534 2535 2536 2537
      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)

2538
      # export bt
2539
      bta.addFolder(path+os.sep+'bt')
Aurel's avatar
Aurel committed
2540 2541 2542
      for prop in self.propertyMap():
        type = prop['type']
        id = prop['id']
2543
        if id in ('uid', 'rid', 'sid', 'id_group', 'last_id'):
2544
          continue
Aurel's avatar
Aurel committed
2545 2546
        value = self.getProperty(id)
        if type == 'text' or type == 'string' or type == 'int':
2547
          bta.addObject(object=value, name=id, path=path+os.sep+'bt', ext='')
Aurel's avatar
Aurel committed
2548
        elif type == 'lines' or type == 'tokens':
2549
          bta.addObject(object=str(os.linesep).join(value), name=id, path=path+os.sep+'bt', ext='')
2550

Aurel's avatar
Aurel committed
2551 2552 2553 2554 2555 2556 2557
      # Export each part
      for item_name in self._item_name_list:
        getattr(self, item_name).export(context=self, bta=bta)


      return bta.finishCreation()

2558
    security.declareProtected(Permissions.ManagePortal, 'importFile')
Aurel's avatar
Aurel committed
2559 2560
    def importFile(self, dir = 0, file=None, root_path=None):
      """
2561
        Import all xml files in Business Template
Aurel's avatar
Aurel committed
2562 2563 2564 2565 2566 2567
      """

      if dir:
        bta = BusinessTemplateFolder(importing=1, file=file, path=root_path)
      else:
        bta = BusinessTemplateTarball(importing=1, file=file)
2568

Aurel's avatar
Aurel committed
2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613
      self._portal_type_item = \
          PortalTypeTemplateItem(self.getTemplatePortalTypeIdList())
      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())
      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())
      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())

      for item_name in self._item_name_list:
        getattr(self, item_name).importFile(bta)