BusinessTemplate.py 118 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
66
from Products.CMFCore.Expression import Expression
Aurel's avatar
Aurel committed
67
import tarfile
68
from urllib import pathname2url, url2pathname
69
from difflib import unified_diff
Aurel's avatar
Aurel committed
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 98 99 100
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
101 102 103
class BusinessTemplateArchive:
  """
    This is the base class for all Business Template archives
104
  """
Aurel's avatar
Aurel committed
105 106 107 108 109 110 111 112 113 114 115 116

  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
117

Aurel's avatar
Aurel committed
118 119 120 121 122 123
  def finishCreation(self, **kw):
    pass

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

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

141
  def addObject(self, obj, name, path=None, ext='.xml'):
142
    name = pathname2url(name)
Aurel's avatar
Aurel committed
143 144 145
    if path is None:
      object_path = os.path.join(self.path, name)
    else:
146 147
      if '%' not in path:
        path = pathname2url(path)
Aurel's avatar
Aurel committed
148 149
      object_path = os.path.join(path, name)
    f = open(object_path+ext, 'wt')
150
    f.write(str(obj))
Aurel's avatar
Aurel committed
151 152 153 154
    f.close()

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

158
  def importFiles(self, klass, **kw):
Aurel's avatar
Aurel committed
159 160 161 162
    """
      Import file from a local folder
    """
    class_name = klass.__class__.__name__
163 164 165 166
    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
167
          # get object id
168 169
          folders = file_path.split(os.sep)
          file_name = string.join(folders[self.root_path_len:], os.sep)
170 171
          if '%' in file_name:
            file_name = url2pathname(file_name)
172
          klass._importFile(file_name, file)
Aurel's avatar
Aurel committed
173
          # close file
174
          file.close()
175

Aurel's avatar
Aurel committed
176 177 178 179 180 181
class BusinessTemplateTarball(BusinessTemplateArchive):
  """
    Class archiving businnes template into a tarball file
  """

  def _initCreation(self, path):
182
    # make tmp dir, must use stringIO instead
Aurel's avatar
Aurel committed
183 184 185 186 187
    self.path = path
    try:
      os.makedirs(self.path)
    except OSError:
      # folder already exists, remove it
188
      removeAll(self.path)
Aurel's avatar
Aurel committed
189 190 191 192 193 194
      os.makedirs(self.path)
    # init tarfile obj
    self.fobj = StringIO()
    self.tar = tarfile.open('', 'w:gz', self.fobj)

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

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

  def finishCreation(self):
    self.tar.add(self.path)
    self.tar.close()
213
    removeAll(self.path)
Aurel's avatar
Aurel committed
214 215 216 217 218
    return self.fobj

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

219
  def importFiles(self, klass, **kw):
Aurel's avatar
Aurel committed
220 221
    """
      Import all file from the archive to the site
222
    """
Aurel's avatar
Aurel committed
223 224 225 226 227 228
    class_name = klass.__class__.__name__
    self.f.seek(0)
    data = GzipFile(fileobj=self.f).read()
    io = StringIO(data)
    tar = tarfile.TarFile(fileobj=io)
    for info in tar.getmembers():
Yoshinori Okuji's avatar
Yoshinori Okuji committed
229 230
      if 'CVS' in info.name.split('/'):
        continue
Aurel's avatar
Aurel committed
231 232 233
      if class_name in info.name:
        if info.isreg():
          file = tar.extractfile(info)
234
          folders = string.split(info.name, os.sep)
235 236 237 238
          file_name = (os.sep).join(folders[2:])
          if '%' in file_name:
            file_name = url2pathname(file_name)
          klass._importFile(file_name, file)
Aurel's avatar
Aurel committed
239 240 241
          file.close()
    tar.close()
    io.close()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
242

243 244
class TemplateConditionError(Exception): pass

245 246
class TemplateConflictError(Exception): pass

247
class BaseTemplateItem(Implicit, Persistent):
248
  """
249
    This class is the base class for all template items.
250
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
251

252
  def __init__(self, id_list, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
253
    self.__dict__.update(kw)
254
    self._archive = PersistentMapping()
Aurel's avatar
Aurel committed
255
    self._objects = PersistentMapping()
256 257 258 259 260 261 262
    for id in id_list:
      if not id: continue
      self._archive[id] = None

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

263 264 265 266 267 268 269
  def preinstall(self, context, installed_bt, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
      modified_object_list = {}
      new_keys = self._objects.keys()
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see it there is changes
270 271
          new_obj_xml = self.generateXml(path=path)
          old_obj_xml = installed_bt.generateXml(path=path)
272 273 274 275 276 277 278 279 280 281 282 283 284
          if new_obj_xml != old_obj_xml:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list


  def install(self, context, trashbin, **kw):
285
    pass
286 287 288

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

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
  def remove(self, context, **kw):
    remove_dict = kw.get('remove_object_dict', {})
    keys = self._objects.keys()
    keys.sort()
    # if you choose remove, the object and all its subobjects will be removed
    # even if you choose backup or keep for subobjects
    # it is same behaviour for backup_and_remove, all we be save
    for path in keys:
      if remove_dict.has_key(path):
        action = remove_dict[path]
        if action == 'save_and_remove':
          # like trash
          self.uninstall(context, trash=1, object_path=path, **kw)
        elif action == 'remove':
          self.uninstall(context, trash=0, object_path=path, **kw)
        

307 308 309 310
  def trash(self, context, new_item, **kw):
    # trash is quite similar to uninstall.
    return self.uninstall(context, new_item=new_item, trash=1, **kw)

311 312
  def diff(self, **kw):
    return ''
313

Aurel's avatar
Aurel committed
314
  def export(self, context, bta, **kw):
315
    pass
Aurel's avatar
Aurel committed
316 317

  def importFile(self, bta, **kw):
318
    bta.importFiles(klass=self)
319

320 321 322
class ObjectTemplateItem(BaseTemplateItem):
  """
    This class is used for generic objects and as a subclass.
323
  """
324

325 326 327 328
  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()
329
      self._archive.clear()
330 331 332
      for id in id_list:
        self._archive["%s/%s" % (tool_id, id)] = None

333 334 335 336 337
  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():
338
      obj = self._objects[key]
339 340 341 342 343 344
      # 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()
345 346
      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
      bta.addObject(obj=f.getvalue(), name=id, path=path)
347

Aurel's avatar
Aurel committed
348 349 350 351
  def build_sub_objects(self, context, id_list, url, **kw):
    p = context.getPortalObject()
    sub_list = {}
    for id in id_list:
352
      relative_url = '/'.join([url,id])
353 354 355 356
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
      id_list = obj.objectIds()
      if hasattr(obj, '__ac_local_roles__'):
357
        # remove local roles
358 359 360
        obj.__ac_local_roles__ = None
      if hasattr(obj, '_owner'):
        obj._owner = None
361
      if hasattr(aq_base(obj), 'groups'):
Aurel's avatar
Aurel committed
362
        # we must keep groups because it's ereased when we delete subobjects
363
        groups = deepcopy(obj.groups)
Aurel's avatar
Aurel committed
364 365
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
366 367 368
        obj.manage_delObjects(list(id_list))
      if hasattr(aq_base(obj), 'uid'):
        obj.uid = None
369
      if hasattr(aq_base(obj), 'groups'):
370 371 372
        obj.groups = groups
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
Aurel's avatar
Aurel committed
373 374
    return sub_list

375 376 377 378
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
379 380 381 382
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
      id_list = obj.objectIds()
      if hasattr(obj, '__ac_local_roles__'):
383
        # remove local roles
384 385 386
        obj.__ac_local_roles__ = None
      if hasattr(obj, '_owner'):
        obj._owner = None
387
      if hasattr(aq_base(obj), 'groups'):
Aurel's avatar
Aurel committed
388
        # we must keep groups because it's ereased when we delete subobjects
389
        groups = deepcopy(obj.groups)
Aurel's avatar
Aurel committed
390 391
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
392 393 394
        obj.manage_delObjects(list(id_list))
      if hasattr(aq_base(obj), 'uid'):
        obj.uid = None
395
      if hasattr(aq_base(obj), 'groups'):
396 397 398
        obj.groups = groups
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
399

400 401 402 403 404 405 406 407 408 409
  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

410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
  def preinstall(self, context, installed_bt, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
      modified_object_list = {}
      portal = context.getPortalObject()
      new_keys = self._objects.keys()
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see it there is changes
          new_object = self._objects[path]
          old_object = installed_bt._objects[path]
          new_io = StringIO()
          old_io = StringIO()
          OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, new_io)
          OFS.XMLExportImport.exportXML(old_object._p_jar, old_object._p_oid, old_io)
          new_obj_xml = new_io.getvalue()
          old_obj_xml = old_io.getvalue()
          new_io.close()
          old_io.close()
          if new_obj_xml != old_obj_xml:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def _backupObject(self, action, trashbin, container_path, object_id):
    """
      Backup the object in portal trash if necessery and return its subobjects
    """
    if action == 'btsave':
      subobjects_dict = self.portal_trash.backupObject(trashbin, container_path, object_id, save=1)
    elif action == 'install':
      subobjects_dict = self.portal_trash.backupObject(trashbin, container_path, object_id, save=0)
    return subobjects_dict
    
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
452 453 454 455 456 457 458
    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:
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
        if update_dict.has_key(path) or force:
          # get action for the oject
          if not force:
            action = update_dict[path]
            if action == 'nothing':
              continue
          else:
            action = 'btsave'
          # get subobjects in path
          container_path = path.split('/')[:-1]
          object_id = path.split('/')[-1]
          try:
            container = portal.unrestrictedTraverse(container_path)
          except KeyError:
            # parent object can be set to nothing, in this case just go on
            container_url = '/'.join(container_path)            
            if update_dict.has_key(container_url):
              if update_dict[container_url] == 'nothing':
                continue
            raise
          container_ids = container.objectIds()
          subobjects_dict = {}
          # Object already exists
          if object_id in container_ids:
            subobjects_dict = self._backupObject(action, trashbin, container_path, object_id)
            container.manage_delObjects([object_id])
          # install object
486
          obj = self._objects[path]
487
          if hasattr(aq_base(obj), 'groups'):
488
            # we must keep original order groups because they change when we add subobjects
489
            groups[path] = deepcopy(obj.groups)
490
          # copy the object
491 492 493 494 495
          obj = obj._getCopy(container)
          container._setObject(object_id, obj)
          obj = container._getOb(object_id)
          obj.manage_afterClone(obj)
          obj.wl_clearLocks()
496 497 498
          # import sub objects if there is
          if len(subobjects_dict) > 0:
            # get a jar
499 500
            connection = obj._p_jar
            o = obj
501
            while connection is None:
502 503
              o = o.aq_parent
              connection = o._p_jar
504 505 506 507 508
            # import subobjects
            for subobject_id in subobjects_dict.keys():
              subobject_data = subobjects_dict[subobject_id]
              subobject_data.seek(0)
              subobject = connection.importFile(subobject_data)
509 510
              if subobject_id not in obj.objectIds():
                obj._setObject(subobject_id, subobject)
511
              
512
          if obj.meta_type in ('Z SQL Method',):
513 514 515
            # It is necessary to make sure that the sql connection
            # in this method is valid.
            sql_connection_list = portal.objectIds(spec=('Z MySQL Database Connection',))
516 517
            if obj.connection_id not in sql_connection_list:
              obj.connection_id = sql_connection_list[0]
518 519
      # now put original order group
      for path in groups.keys():
520 521
        obj = portal.unrestrictedTraverse(path)
        obj.groups = groups[path]
Aurel's avatar
Aurel committed
522
    else:
523 524
      # for old business template format
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
525
      portal = context.getPortalObject()
526
      for relative_url in self._archive.keys():
527
        obj = self._archive[relative_url]
Aurel's avatar
Aurel committed
528 529 530 531
        container_path = relative_url.split('/')[0:-1]
        object_id = relative_url.split('/')[-1]
        container = portal.unrestrictedTraverse(container_path)
        container_ids = container.objectIds()
532 533 534
        if object_id in container_ids:    # Object already exists          
          self._backupObject('btsave', trashbin, container_path, object_id)
          container.manage_delObjects([object_id])
Aurel's avatar
Aurel committed
535
        # Set a hard link
536 537 538 539 540 541
        obj = obj._getCopy(container)
        container._setObject(object_id, obj)
        obj = container._getOb(object_id)
        obj.manage_afterClone(obj)
        obj.wl_clearLocks()
        if obj.meta_type in ('Z SQL Method',):
542
          # It is necessary to make sure that the sql connection
Aurel's avatar
Aurel committed
543 544 545
          # in this method is valid.
          sql_connection_list = portal.objectIds(
                                   spec=('Z MySQL Database Connection',))
546 547
          if obj.connection_id not in sql_connection_list:
            obj.connection_id = sql_connection_list[0]
548 549 550

  def uninstall(self, context, **kw):
    portal = context.getPortalObject()
551
    trash = kw.get('trash', 0)
552 553 554 555 556 557 558
    trashbin = kw.get('trashbin', None)
    object_path = kw.get('object_path', None)
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for relative_url in object_keys:
559 560
      container_path = relative_url.split('/')[0:-1]
      object_id = relative_url.split('/')[-1]
561
      try:        
562 563
        container = portal.unrestrictedTraverse(container_path)
        if trash:
564 565 566 567
          self.portal_trash.backupObject(trashbin, container_path, object_id, save=1, keep_subobjects=1)
        container.manage_delObjects([object_id])
      except (NotFound, KeyError):
        # object is already backup and/or removed
568
        pass
569 570
    BaseTemplateItem.uninstall(self, context, **kw)

571 572
  def _compareObjects(self, object1, object2, btsave_object_included=0):
    """
573
      Execute a diff between 2 objects,
574 575 576 577 578 579 580
      and return a string diff.
    """
    xml_dict = {
      str(object1): None,
      str(object2): None,
    }
    # Generate XML
581
    for obj in (object1, object2):
582
      string_io = StringIO()
583
      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, string_io)
584 585 586
      object_xml = string_io.getvalue()
      string_io.close()
      object_xml_lines = object_xml.splitlines(1)
587
      xml_dict[str(obj)] = object_xml_lines
588 589 590 591 592
    # 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] != ' ']
593 594 595 596 597
#     # 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 = []
598
    # Return string
599 600 601 602 603 604 605 606 607 608
    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()
609 610 611
    if (getattr(self, 'template_format_version', 0)) == 0:
      object_list = getattr(self, archive_variable).items()
    else:
612 613 614 615 616 617 618 619 620 621 622
      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)
        obj = self._objects[path]
        object_list.append(('/'.join(path.split('/')[2:]), obj))

    for relative_url, obj in object_list:
623
      # Browse all items stored
624
      obj = portal.unrestrictedTraverse(relative_url)
625 626 627 628 629 630 631 632 633 634 635 636 637
      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(
638 639 640 641
              (portal.unrestrictedTraverse(container_path+[object_id]),
               portal.unrestrictedTraverse(container_path+[new_object_id])))
#               (object, portal.unrestrictedTraverse(
#                                   container_path+[new_object_id])))
642 643
        n += 1
        new_object_id = '%s_btsave_%s' % (object_id, n)
644 645 646 647
#       if n == 1:
#         result += "$$$ Added: %s $$$\n" % \
#               ('/'.join(container_path+[object_id]))
#         result += '%s\n' % ('-'*80)
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
      # 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:
682
            result += tmp_diff
683 684
          result += '%s\n' % ('-'*80)
    return result
685

686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
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.
701

702 703 704 705 706 707 708 709 710
      The folder is the current object which contains sub-objects.
      The list of ids are path components. If the list is empty,
      the current folder is valid.
    """
    if len(id_list) == 0:
      return ['/'.join(relative_url_list)]
    id = id_list[0]
    if re.search('[\*\?\[\]]', id) is None:
      # If the id has no meta character, do not have to check all objects.
711 712
      obj = folder._getOb(id)
      return self._resolvePath(obj, relative_url_list + [id], id_list[1:])
713 714 715 716
    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
717

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

752 753
  def __init__(self, id_list, tool_id='portal_categories', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
754

755 756 757 758
  def build_sub_objects(self, context, id_list, url, **kw):
    p = context.getPortalObject()
    for id in id_list:
      relative_url = '/'.join([url,id])
759 760 761
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
      if hasattr(obj, '__ac_local_roles__'):
762
        # remove local roles
763 764 765 766
        obj.__ac_local_roles__ = None
      if hasattr(obj, '_owner'):
        obj._owner = None
      id_list = obj.objectIds()
767 768
      if len(id_list) > 0:
        self.build_sub_objects(context, id_list, relative_url)
769 770 771 772 773
        obj.manage_delObjects(list(id_list))
      if hasattr(aq_base(obj), 'uid'):
        obj.uid = None
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
774 775 776 777 778

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

880 881
class SkinTemplateItem(ObjectTemplateItem):

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

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

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

946 947
    ObjectTemplateItem.uninstall(self, context, **kw)

948 949
  def diff(self, max_deep=1, **kw):
    return ObjectTemplateItem.diff(self, max_deep=max_deep, **kw)
950

951
class WorkflowTemplateItem(ObjectTemplateItem):
952

953 954
  def __init__(self, id_list, tool_id='portal_workflow', **kw):
    return ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
955

956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
  def preinstall(self, context, installed_bt, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
      modified_object_list = {}
      portal = context.getPortalObject()
      new_keys = self._objects.keys()
      for path in new_keys:
        if len(path.split('/')) == 2:
          if installed_bt._objects.has_key(path):          
            # compare object to see it there is changes
            new_object = self._objects[path]
            old_object = installed_bt._objects[path]
            new_io = StringIO()
            old_io = StringIO()
            OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, new_io)
            OFS.XMLExportImport.exportXML(old_object._p_jar, old_object._p_oid, old_io)
            new_obj_xml = new_io.getvalue()
            old_obj_xml = old_io.getvalue()
            new_io.close()
            old_io.close()
            if new_obj_xml != old_obj_xml:
              modified_object_list.update({path : ['Modified', 'Workflow']})
          else: # new object
            modified_object_list.update({path : ['New', 'Workflow']})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
      portal = context.getPortalObject()
      # sort to add objects before their subobjects
      keys = self._objects.keys()
      keys.sort()
      update_dict = kw.get('object_to_update')
      force = kw.get('force')
      for path in keys:
        wf_path = '/'.join(path.split('/')[:2])
        if wf_path in update_dict or force:
          if not force:
            action = update_dict[wf_path]
            if action == 'nothing':
              continue
          else:
            action = 'btsave'
          container_path = path.split('/')[:-1]
          object_id = path.split('/')[-1]
          try:
            container = portal.unrestrictedTraverse(container_path)
          except KeyError:
            # parent object can be set to nothing, in this case just go on
            container_url = '/'.join(container_path)            
            if update_dict.has_key(container_url):
              if update_dict[container_url] == 'nothing':
                continue
            raise
          container_ids = container.objectIds()
          if object_id in container_ids:    # Object already exists
            self._backupObject(action, trashbin, container_path, object_id)
            container.manage_delObjects([object_id])
1018 1019 1020 1021 1022 1023
          obj = self._objects[path]
          obj = obj._getCopy(container)
          container._setObject(object_id, obj)
          obj = container._getOb(object_id)
          obj.manage_afterClone(obj)
          obj.wl_clearLocks()
1024 1025 1026 1027 1028
    else:
      ObjectTemplateItem.install(self, context, trashbin, **kw)

  

1029 1030 1031 1032 1033 1034 1035 1036
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
1037
    This is only useful in order to use
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061
    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)

1062 1063
  def __init__(self, id_list, tool_id='portal_types', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1064 1065 1066
    self._workflow_chain_archive = PersistentMapping()

  def build(self, context, **kw):
1067 1068
    p = context.getPortalObject()
    for relative_url in self._archive.keys():
1069 1070 1071
      obj = p.unrestrictedTraverse(relative_url)
      obj = obj._getCopy(context)
      id_list = obj.objectIds()
Aurel's avatar
Aurel committed
1072
      # remove optional actions
1073
      optional_action_list = []
1074
      for index,ai in enumerate(obj.listActions()):
1075 1076 1077
        if ai.getOption():
          optional_action_list.append(index)
      if len(optional_action_list) > 0:
1078 1079
        obj.deleteActions(selections=optional_action_list)
      if hasattr(obj, '__ac_local_roles__'):
1080
        # remove local roles
1081 1082 1083
        obj.__ac_local_roles__ = None
      if hasattr(obj, '_owner'):
        obj._owner = None
1084
      if hasattr(aq_base(obj), 'uid'):
1085 1086 1087
        obj.uid = None
      self._objects[relative_url] = obj
      obj.wl_clearLocks()
Aurel's avatar
Aurel committed
1088
    # also export workflow chain
1089
    (default_chain, chain_dict) = self._getChainByType(context)
1090 1091
    for obj in self._objects.values():
      portal_type = obj.id
1092 1093
      self._workflow_chain_archive[portal_type] = chain_dict['chain_%s' % portal_type]

Aurel's avatar
Aurel committed
1094 1095 1096 1097
  def export(self, context, bta, **kw):
    if len(self._objects.keys()) == 0:
      return
    root_path = os.path.join(bta.path, self.__class__.__name__)
1098
    # export portal type object
1099
    ObjectTemplateItem.export(self, context, bta, **kw)
1100 1101
    # export workflow chain
    xml_data = '<workflow_chain>'
1102 1103 1104
    keys = self._workflow_chain_archive.keys()
    keys.sort()
    for key in keys:
1105 1106 1107
      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],)
1108
      xml_data += os.linesep+' </chain>'
1109
    xml_data += os.linesep+'</workflow_chain>'
1110
    bta.addObject(obj=xml_data, name='workflow_chain_type',  path=root_path)
1111

1112 1113 1114 1115
  def install(self, context, trashbin, **kw):
    ObjectTemplateItem.install(self, context, trashbin, **kw)
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1116 1117 1118 1119 1120 1121
    # 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
1122 1123
    default_chain = ''
    if (getattr(self, 'template_format_version', 0)) == 1:
1124
      object_list = self._objects
1125
    else:
1126 1127
      object_list = self._archive
    for path in object_list.keys():
1128 1129 1130 1131 1132
      if update_dict.has_key(path) or force:
        if not force:
          action = update_dict[path]
          if action == 'nothing':
            continue          
1133 1134
        obj = object_list[path]
        portal_type = obj.id
1135 1136 1137 1138 1139 1140
        chain_dict['chain_%s' % portal_type] = \
                              self._workflow_chain_archive[portal_type]
        context.portal_workflow.manage_changeWorkflows(default_chain,
                                                       props=chain_dict)

  def _backupObject(self, action, trashbin, container_path, object_id, **kw):
1141 1142 1143
    """
      Backup portal type and keep the workflow chain.
    """
1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
    subobjects_dict = {}
    if action == 'btsave':
      # Get the chain value
#       (default_chain, chain_dict) = self._getChainByType(self)
#       chain = chain_dict['chain_%s' % object_id]
      # Backup the portal type    
      subobjects_dict = ObjectTemplateItem._backupObject(self, action, trashbin, container_path,
                                                        object_id, **kw)
    return subobjects_dict
      # 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)
1158 1159 1160

  def diff(self, verbose=0, **kw):
    """
1161
      Make a diff between portal type.
1162 1163 1164 1165 1166 1167
      Also compare the workflow chain.
    """
    # Compare XML portal type
    result = ObjectTemplateItem.diff(self, verbose=verbose, **kw)
    # Compare chains
    container_ids = self.portal_types.objectIds()
1168
    if (getattr(self, 'template_format_version', 0)) == 0:
1169
      for obj in self._archive.values():
1170
        try:
1171
          object_id = obj.id
1172 1173 1174 1175 1176
        except:
          import pdb
          pdb.set_trace()
        object_chain = self.portal_workflow.getChainFor(object_id)
        n = 1
1177
        new_object_id = '%s_btsave_%s' % (object_id, n)
1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
        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)
1189
    return result
1190

1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209
  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)


1210 1211
class CatalogMethodTemplateItem(ObjectTemplateItem):

1212 1213
  def __init__(self, id_list, tool_id='portal_catalog', **kw):
    ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
1214
    self._is_catalog_method_archive = PersistentMapping()
1215
    self._is_catalog_list_method_archive = PersistentMapping()
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225
    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)
1226 1227
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1228 1229
    except KeyError:
      catalog = None
1230
    if catalog is None:
1231
      LOG('BusinessTemplate build', 0, 'catalog not found')
1232
      return
1233 1234
    for obj in self._objects.values():
      method_id = obj.id
1235 1236 1237 1238 1239
      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
1240
      self._is_filtered_archive[method_id] = 0
1241 1242 1243 1244 1245
      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']
1246

Aurel's avatar
Aurel committed
1247 1248 1249 1250 1251
  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():
1252
      obj = self._objects[key]
Aurel's avatar
Aurel committed
1253 1254 1255 1256 1257 1258
      # 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()
1259 1260
      XMLExportImport.exportXML(obj._p_jar, obj._p_oid, f)
      bta.addObject(obj=f.getvalue(), name=id, path=path)
Aurel's avatar
Aurel committed
1261 1262
      # add all datas specific to catalog inside one file
      catalog = context.portal_catalog.getSQLCatalog()
1263
      method_id = obj.id
1264 1265
      object_path = os.path.join(path, method_id+'.catalog_keys.xml')

Aurel's avatar
Aurel committed
1266
      f = open(object_path, 'wt')
1267 1268 1269
      xml_data = '<catalog_method>'
      for method in catalog_method_list:
        value = getattr(self, method, 0)[method_id]
1270
        xml_data += os.linesep+' <item key="%s" type="int">' %(method,)
1271
        xml_data += os.linesep+'  <value>%s</value>' %(str(int(value)))
1272
        xml_data += os.linesep+' </item>'
Aurel's avatar
Aurel committed
1273
      if catalog.filter_dict.has_key(method_id):
1274 1275 1276
        for method in catalog_method_filter_list:
          value = getattr(self, method, '')[method_id]
          if method == '_filter_expression_instance_archive':
1277
            pass
1278
          else:
1279
            if type(value) in (type(''), type(u'')):
1280
              xml_data += os.linesep+' <item key="%s" type="str">' %(method,)
1281
              xml_data += os.linesep+'  <value>%s</value>' %(str(value))
1282
              xml_data += os.linesep+' </item>'
1283
            elif type(value) in (type(()), type([])):
1284
              xml_data += os.linesep+' <item key="%s" type="tuple">'%(method)
1285 1286
              for item in value:
                xml_data += os.linesep+'  <value>%s</value>' %(str(item))
1287
              xml_data += os.linesep+' </item>'
1288 1289
      xml_data += os.linesep+'</catalog_method>'
      f.write(str(xml_data))
Aurel's avatar
Aurel committed
1290
      f.close()
1291

1292 1293
  def install(self, context, trashbin, **kw):
    ObjectTemplateItem.install(self, context, trashbin, **kw)
1294 1295
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1296
    except KeyError:
1297 1298 1299 1300 1301 1302 1303 1304 1305 1306
      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)
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
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
    values = []
    new_bt_format = getattr(self, 'template_format_version', 0)

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

Aurel's avatar
Aurel committed
1337 1338 1339 1340 1341 1342
      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])
1343 1344 1345 1346 1347 1348

      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)

1349 1350 1351 1352 1353
      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)

1354
      if is_uncatalog_method and method_id not in sql_uncatalog_object:
1355
        sql_uncatalog_object.append(method_id)
1356
      elif not is_uncatalog_method and method_id in sql_uncatalog_object:
1357 1358
        sql_uncatalog_object.remove(method_id)

1359
      if is_update_method and method_id not in sql_update_object:
1360
        sql_update_object.append(method_id)
1361
      elif not is_update_method and method_id in sql_update_object:
1362 1363 1364 1365 1366 1367 1368 1369
        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:
1370
        expression = self._filter_expression_archive[method_id]
1371 1372 1373 1374
        if (getattr(self, 'template_format_version', 0)) == 1:
          expr_instance = Expression(expression)
        else:
          expr_instance = self._filter_expression_instance_archive[method_id]
1375
        type = self._filter_type_archive[method_id]
1376 1377 1378
        catalog.filter_dict[method_id] = PersistentMapping()
        catalog.filter_dict[method_id]['filtered'] = 1
        catalog.filter_dict[method_id]['expression'] = expression
1379
        catalog.filter_dict[method_id]['expression_instance'] = expr_instance
1380
        catalog.filter_dict[method_id]['type'] = type
1381
      elif method_id in catalog.filter_dict.keys():
1382
        catalog.filter_dict[method_id]['filtered'] = 0
1383 1384

    sql_catalog_object.sort()
1385 1386 1387
    catalog.sql_catalog_object = tuple(sql_catalog_object)
    sql_catalog_object_list.sort()
    catalog.sql_catalog_object_list = tuple(sql_catalog_object_list)
1388
    sql_uncatalog_object.sort()
1389
    catalog.sql_uncatalog_object = tuple(sql_uncatalog_object)
1390
    sql_update_object.sort()
1391
    catalog.sql_update_object = tuple(sql_update_object)
1392
    sql_clear_catalog.sort()
1393
    catalog.sql_clear_catalog = tuple(sql_clear_catalog)
1394 1395

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

1397
    ObjectTemplateItem.uninstall(self, context, **kw)
1398 1399
    try:
      catalog = context.portal_catalog.getSQLCatalog()
1400
    except KeyError:
1401 1402 1403 1404 1405 1406
      catalog = None

    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return

1407 1408 1409 1410 1411 1412 1413 1414
    values = []
    object_path = kw.get('object_path', None)
    # get required values
    if object_path is None:
      values = self._archive.values()
    else:      
      values.append(self._archive[object_path])

1415 1416 1417 1418 1419 1420
    # 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)
1421

1422 1423
    for obj in values:
      method_id = obj.id
1424 1425 1426 1427

      if method_id in sql_catalog_object:
        sql_catalog_object.remove(method_id)

1428 1429 1430
      if method_id in sql_catalog_object_list:
        sql_catalog_object_list.remove(method_id)

1431 1432 1433 1434 1435 1436 1437 1438 1439
      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
1440
      if catalog.filter_dict.has_key(method_id):
1441
        del catalog.filter_dict[method_id]
1442

1443 1444 1445 1446 1447
    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)
1448 1449

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

1451
  def _importFile(self, file_name, file):
1452
    if not '.catalog_keys' in file_name:
1453 1454 1455 1456 1457 1458 1459 1460
      # 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
1461
    elif '.catalog_keys' in file_name:
1462 1463 1464 1465
      # recreate data mapping specific to catalog method
      path, name = os.path.split(file_name)
      id = string.split(name, '.')[0]
      xml = parse(file)
1466
      method_list = xml.getElementsByTagName('item')
1467
      for method in method_list:
1468 1469 1470 1471 1472 1473 1474
        key = method.getAttribute('key')
        type = str(method.getAttribute('type'))
        if type == "str":
          value = str(method.getElementsByTagName('value')[0].childNodes[0].data)
          key = str(key)
        elif type == "int":
          value = int(method.getElementsByTagName('value')[0].childNodes[0].data)
1475 1476 1477 1478 1479 1480
          key = str(key)
        elif type == "tuple":
          value = []
          value_list = method.getElementsByTagName('value')
          for item in value_list:
            value.append(item.childNodes[0].data)
1481
        else:
1482 1483
          LOG('BusinessTemplate import CatalogMethod, type unknown', 0, type)
          continue
1484 1485 1486 1487
        dict = getattr(self, key)
        dict[id] = value

class ActionTemplateItem(ObjectTemplateItem):
1488 1489 1490 1491

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

1493
      "foo/bar[id=zoo]"
1494

1495
      into
1496

1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510
      "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):
1511
    # XXX It's look like ObjectTemplateItem __init__
1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522
    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)
1523 1524
      obj = p.unrestrictedTraverse(relative_url)
      for ai in obj.listActions():
1525
        if getattr(ai, key) == value:
1526
          url = os.path.split(relative_url)
Aurel's avatar
Aurel committed
1527
          key = os.path.join(url[-2], url[-1], value)
1528 1529
          action = ai._getCopy(context)
          if hasattr(action, '__ac_local_roles__'):
1530
            # remove local roles
1531 1532 1533
            action.__ac_local_roles__ = None
          if hasattr(action, '_owner'):
            action._owner = None
1534
          if hasattr(aq_base(action), 'uid'):
1535 1536
            action.uid = None
          self._objects[key] = action
Aurel's avatar
Aurel committed
1537
          self._objects[key].wl_clearLocks()
1538 1539
          break
      else:
1540
        raise NotFound, 'Action %r not found' %(id,)
Aurel's avatar
Aurel committed
1541

1542 1543 1544
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1545
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
1546 1547
      p = context.getPortalObject()
      for id in self._objects.keys():
1548 1549 1550 1551 1552 1553
        if update_dict.has_key(id) or force:
          if not force:
            action = update_dict[id]
            if action == 'nothing':
              continue
          path = id.split(os.sep)
1554 1555
          obj = p.unrestrictedTraverse(path[:-1])
          action_list = obj.listActions()
1556 1557 1558
          for index in range(len(action_list)):
            if getattr(action_list[index], 'id') == path[-1]:          
              # remove previous action
1559
              obj.deleteActions(selections=(index,))
1560
          action = self._objects[id]
1561
          obj.addAction(
1562 1563 1564 1565 1566 1567 1568 1569 1570
                        id = action.id
                      , name = action.title
                      , action = action.action.text
                      , condition = action.getCondition()
                      , permission = action.permissions
                      , category = action.category
                      , visible = action.visible
                      , icon = getattr(action, 'icon', None) and action.icon.text or ''
                      , optional = getattr(action, 'optional', 0)
Aurel's avatar
Aurel committed
1571 1572
                    )
    else:
1573
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
1574
      p = context.getPortalObject()
1575 1576
      for id in self._archive.keys():
        action = self._archive[id]
Aurel's avatar
Aurel committed
1577
        relative_url, key, value = self._splitPath(id)
1578 1579
        obj = p.unrestrictedTraverse(relative_url)
        for ai in obj.listActions():
Aurel's avatar
Aurel committed
1580
          if getattr(ai, key) == value:
1581 1582
            raise TemplateConflictError, 'the portal type %s already has the action %s' % (obj.id, value)
        obj.addAction(
Aurel's avatar
Aurel committed
1583 1584 1585 1586 1587 1588 1589 1590 1591 1592
                      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)
                    )
1593 1594 1595

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
1596 1597 1598 1599 1600 1601 1602 1603
    object_path = kw.get("object_path", None)
    if object_path is not None:
      keys = [object_path]
    else:
      keys = self._archive.keys()
    
    for id in keys:
      action = self._archive[id]
1604
      relative_url, key, value = self._splitPath(id)
1605 1606
      obj = p.unrestrictedTraverse(relative_url)
      action_list = obj.listActions()
1607
      for index in range(len(action_list)):
1608
        if getattr(action_list[index], key) == value:
1609
          obj.deleteActions(selections=(index,))
1610 1611 1612 1613 1614 1615 1616 1617 1618
          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():
1619 1620
      for property in p.propertyMap():
        if property['id'] == id:
1621
          obj = p.getProperty(id)
Aurel's avatar
Aurel committed
1622
          type = property['type']
1623 1624
          break
      else:
1625 1626
        obj = None
      if obj is None:
1627
        raise NotFound, 'the property %s is not found' % id
1628
      self._objects[id] = (type, obj)
Aurel's avatar
Aurel committed
1629

1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644
  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:
1645
        value = str(prop.getElementsByTagName('value')[0].childNodes[0].data)
1646 1647
      self._objects[str(id)] = (str(type), value)

1648 1649 1650
  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1651
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
1652 1653
      p = context.getPortalObject()
      for path in self._objects.keys():
1654 1655 1656 1657 1658 1659 1660 1661 1662 1663
        if update_dict.has_key(path) or force:
          if not force:
            action = update_dict[path]
            if action == 'nothing':
              continue          
          dir, id = os.path.split(path)
          if p.hasProperty(id):
            continue
          type, property = self._objects[path]
          p._setProperty(id, property, type=type)
Aurel's avatar
Aurel committed
1664
    else:
1665
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
1666
      p = context.getPortalObject()
1667 1668
      for id,property in self._archive.keys():
        property = self._archive[id]
Aurel's avatar
Aurel committed
1669 1670 1671 1672 1673
        if p.hasProperty(id):
          continue
          # Too much???
          #raise TemplateConflictError, 'the property %s already exists' % id
        p._setProperty(id, property['value'], type=property['type'])
1674 1675 1676

  def uninstall(self, context, **kw):
    p = context.getPortalObject()
1677 1678 1679 1680 1681 1682
    object_path = kw.get('object_path', None)
    if object_path is not None:
      keys = [object_path]
    else:
      keys = self._archive.keys()
    for id in keys:
1683 1684 1685 1686
      if p.hasProperty(id):
        p._delProperty(id)
    BaseTemplateItem.uninstall(self, context, **kw)

1687
  def generateXml(self, path=None):
1688
    xml_data = ''
1689
    type, obj = self._objects[path]
1690 1691 1692 1693 1694
    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>'
1695
      for item in obj:
1696 1697 1698 1699
        if item != '':
          xml_data += os.linesep+'   <item>%s</item>' %(item,)
      xml_data += os.linesep+'  </value>'
    else:
1700
      xml_data += os.linesep+'  <value>%r</value>' %((os.linesep).join(obj),)
1701
    xml_data += os.linesep+' </property>'
1702 1703
    return xml_data

Aurel's avatar
Aurel committed
1704 1705 1706 1707 1708
  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)
1709 1710 1711 1712
    xml_data = '<site_property>'
    keys = self._objects.keys()
    keys.sort()
    for path in keys:
1713
      xml_data += self.generateXml(path)
1714
    xml_data += os.linesep+'</site_property>'
1715
    bta.addObject(obj=xml_data, name='properties', path=root_path)
1716

1717 1718
class ModuleTemplateItem(BaseTemplateItem):

1719 1720
  def diff(self, max_deep=1, **kw):
    return ''
Aurel's avatar
Aurel committed
1721

1722 1723 1724 1725 1726
  def build(self, context, **kw):
    BaseTemplateItem.build(self, context, **kw)
    p = context.getPortalObject()
    for id in self._archive.keys():
      module = p.unrestrictedTraverse(id)
1727 1728 1729 1730 1731 1732 1733 1734
      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
1735

1736
  def generateXml(self, path=None):
1737 1738
    dict = self._objects[path]
    xml_data = '<module>'
1739 1740 1741 1742
    # sort key
    keys = dict.keys()
    keys.sort()
    for key in keys:
1743 1744 1745 1746 1747 1748 1749
      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
1750
          role_list = list(perm[1])
1751
          role_list.sort()
1752 1753 1754 1755 1756 1757 1758 1759 1760
          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

1761 1762 1763 1764 1765
  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)
1766 1767 1768
    keys = self._objects.keys()
    keys.sort()
    for id in keys:
1769
      # expor module one by one
1770
      xml_data = self.generateXml(path=id)
1771
      bta.addObject(obj=xml_data, name=id, path=path)
1772

1773
  def install(self, context, trashbin, **kw):
1774
    portal = context.getPortalObject()
1775 1776
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1777
    if (getattr(self, 'template_format_version', 0)) == 1:
1778
      items = self._objects
1779
    else:
1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802
      items = self._archive

    for id in items.keys():
      if update_dict.has_key(id) or force:
        if not force:
          action = update_dict[id]
          if action == 'nothing':
            continue
        mapping = items[id]
        path, id = os.path.split(id)
        if id in portal.objectIds():
          module = portal._getOb(id)
          module.portal_type = str(mapping['portal_type']) 
        else:
          module = portal.newContent(id=id, portal_type=str(mapping['portal_type']))
        module.setTitle(str(mapping['title']))
        for name,role_list in list(mapping['permission_list']):
          acquire = (type(role_list) == type([]))
          try:
            module.manage_permission(name, roles=role_list, acquire=acquire)
          except ValueError:
            # Ignore a permission not present in this system.
            pass
1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833

  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

1834
  def uninstall(self, context, **kw):
1835 1836 1837 1838 1839 1840 1841
    trash = kw.get('trash', 0)      
    object_path = kw.get('object_path', None)
    trashbin = kw.get('trashbin', None)
    if object_path is None:
      keys = self._archive.keys()
    else:
      keys = [object_path]
1842 1843
    p = context.getPortalObject()
    id_list = p.objectIds()
1844
    for id in keys:
1845
      if id in id_list:
1846
        try:
1847 1848 1849
          if trash:
            container_path = id.split('/')
            self.portal_trash.backupObject(trashbin, container_path, id, save=1, keep_subobjects=1)
1850
          p.manage_delObjects([id])
1851
        except NotFound:
1852
          pass
1853 1854
    BaseTemplateItem.uninstall(self, context, **kw)

1855 1856 1857
  def trash(self, context, new_item, **kw):
    # Do not remove any module for safety.
    pass
1858 1859

class DocumentTemplateItem(BaseTemplateItem):
1860 1861 1862 1863
  local_file_reader_name = 'readLocalDocument'
  local_file_writer_name = 'writeLocalDocument'
  local_file_importer_name = 'importLocalDocument'
  local_file_remover_name = 'removeLocalDocument'
1864 1865 1866 1867

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

1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892
  def preinstall(self, context, installed_bt, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
      modified_object_list = {}
      new_keys = self._objects.keys()
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see if there is changes
          new_obj_code = self._objects[path]
          old_obj_code = installed_bt._objects[path]
          if new_obj_code != old_obj_code:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
          # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
1893
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
1894
      for id in self._objects.keys():
1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908
        if update_dict.has_key(id) or force:
          if not force:
            action = update_dict[id]
            if action == 'nothing':
              continue
          text = self._objects[id]
          path, name = os.path.split(id)
          # This raises an exception if the file already exists.
          try:
            globals()[self.local_file_writer_name](name, text, create=1)
          except IOError:
            continue
          if self.local_file_importer_name is not None:
            globals()[self.local_file_importer_name](name)
Aurel's avatar
Aurel committed
1909
    else:
1910 1911 1912
      BaseTemplateItem.install(self, context, trashbin, **kw)
      for id in self._archive.keys():
        text = self._archive[id]
Aurel's avatar
Aurel committed
1913
        # This raises an exception if the file exists.
1914
        globals()[self.local_file_writer_name](id, text, create=1)
Aurel's avatar
Aurel committed
1915 1916
        if self.local_file_importer_name is not None:
          globals()[self.local_file_importer_name](id)
1917 1918

  def uninstall(self, context, **kw):
1919 1920 1921 1922 1923 1924
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for id in object_keys:
1925
      globals()[self.local_file_remover_name](id)
1926 1927
    BaseTemplateItem.uninstall(self, context, **kw)

Aurel's avatar
Aurel committed
1928 1929 1930 1931 1932 1933
  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():
1934 1935
      obj = self._objects[path]
      bta.addObject(obj=obj, name=path, path=None, ext='.py')
Aurel's avatar
Aurel committed
1936

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

1941 1942 1943 1944 1945 1946
class PropertySheetTemplateItem(DocumentTemplateItem):
  local_file_reader_name = 'readLocalPropertySheet'
  local_file_writer_name = 'writeLocalPropertySheet'
  local_file_importer_name = 'importLocalPropertySheet'
  local_file_remover_name = 'removeLocalPropertySheet'

1947

1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961
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
1962

1963 1964 1965
class ProductTemplateItem(BaseTemplateItem):
  # XXX Not implemented yet
  pass
1966 1967 1968

class RoleTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
1969 1970 1971 1972
  def build(self, context, **kw):
    role_list = []
    for key in self._archive.keys():
      role_list.append(key)
1973 1974
    if len(role_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'role_list'] = role_list
Aurel's avatar
Aurel committed
1975

1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992
  def preinstall(self, context, installed_bt, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
      modified_object_list = {}
      new_roles = self._objects.keys()
      for role in new_roles:
        if installed_bt._objects.has_key(role):
          continue
        else: # only show new roles
          modified_object_list.update({role : ['New', 'Role']})
      # get removed roles
      old_roles = installed_bt._objects.keys()
      for role in old_roles:
        if role not in new_roles:
          modified_object_list.update({role : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
1993
    p = context.getPortalObject()
1994
    # get roles
1995
    if (getattr(self, 'template_format_version', 0)) == 1:
1996
      role_list = self._objects.keys()
1997
    else:
1998
      role_list = self._archive.keys()
1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010
    # set roles in PAS
    if p.acl_users.meta_type == 'Pluggable Auth Service':
      role_manager_list = p.acl_users.objectValues('ZODB Role Manager')
      for role_manager in role_manager_list:
        existing_role_list = role_manager.listRoleIds()
        for role in role_list:
          if role not in existing_role_list:
            role_manager.addRole(role)
    # set roles on portal
    roles = {}
    for role in p.__ac_roles__:
      roles[role] = 1
2011
    for role in role_list:
2012 2013 2014 2015 2016 2017 2018 2019 2020 2021
      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
2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033

  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)

2034 2035 2036 2037 2038 2039 2040 2041 2042
  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
2043
      if role in roles and role not in new_roles:
2044 2045 2046
        del roles[role]
    p.__ac_roles__ = tuple(roles.keys())

2047
  def generateXml(self, path):
2048
    obj = self._objects[path]
2049
    xml_data = '<role_list>'
2050 2051
    obj.sort()
    for role in obj:
2052 2053 2054 2055
      xml_data += os.linesep+' <role>%s</role>' %(role)
    xml_data += os.linesep+'</role_list>'
    return xml_data

Aurel's avatar
Aurel committed
2056 2057 2058 2059 2060 2061
  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():
2062
      xml_data = self.generateXml(path=path)
2063
      bta.addObject(obj=xml_data, name=path, path=None,)
2064

2065 2066
class CatalogResultKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2067
  def build(self, context, **kw):
2068 2069
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2070
    except KeyError:
2071 2072 2073 2074
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2075
    sql_search_result_keys = list(catalog.sql_search_result_keys)
2076
    key_list = []
2077
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
2078
      if key in sql_search_result_keys:
2079
        key_list.append(key)
Aurel's avatar
Aurel committed
2080 2081
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
2082 2083
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'key_list'] = key_list
2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094

  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

2095
  def install(self, context, trashbin, **kw):
2096 2097 2098 2099 2100 2101 2102
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2103

2104
    sql_search_result_keys = list(catalog.sql_search_result_keys)
2105 2106 2107
    if (getattr(self, 'template_format_version', 0)) == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
        return
2108 2109 2110
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
2111
    else:
2112
      keys = self._archive.keys()
2113 2114
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2115
    for key in keys:
2116 2117 2118 2119 2120 2121 2122
      if update_dict.has_key(key) or force:
        if not force:
          action = update_dict[key]
          if action == 'nothing':
            continue          
        if key not in sql_search_result_keys:
          sql_search_result_keys.append(key)
2123 2124
    catalog.sql_search_result_keys = sql_search_result_keys

2125
  def uninstall(self, context, **kw):
2126 2127
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2128
    except KeyError:
2129 2130 2131 2132 2133
      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)
2134 2135 2136 2137 2138 2139
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
2140 2141
      if key in sql_search_result_keys:
        sql_search_result_keys.remove(key)
2142
    catalog.sql_search_result_keys = sql_search_result_keys
2143 2144
    BaseTemplateItem.uninstall(self, context, **kw)

2145
  def generateXml(self, path=None):
2146
    obj = self._objects[path]
2147
    xml_data = '<key_list>'
2148 2149
    obj.sort()
    for key in obj:
2150 2151 2152 2153
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

Aurel's avatar
Aurel committed
2154 2155 2156 2157 2158
  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)
2159
    for path in self._objects.keys():
2160
      xml_data = self.generateXml(path=path)
2161
      bta.addObject(obj=xml_data, name=path, path=None)
2162

2163 2164
class CatalogRelatedKeyTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2165
  def build(self, context, **kw):
2166 2167
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2168
    except KeyError:
2169 2170 2171 2172
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
2173
    sql_search_related_keys = list(catalog.sql_catalog_related_keys)
2174
    key_list = []
2175
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
2176
      if key in sql_search_related_keys:
2177
        key_list.append(key)
Aurel's avatar
Aurel committed
2178 2179
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
2180 2181
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'key_list'] = key_list
2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192

  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

2193
  def install(self, context, trashbin, **kw):
2194 2195 2196 2197 2198 2199 2200
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2201

2202
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
2203 2204
    if (getattr(self, 'template_format_version', 0)) == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
2205
        return
2206 2207 2208
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
2209
    else:
2210
      keys = self._archive.keys()
2211 2212
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2213
    for key in keys:
2214 2215 2216 2217 2218 2219 2220
      if update_dict.has_key(key) or force:
        if not force:
          action = update_dict[key]
          if action == 'nothing':
            continue          
        if key not in sql_catalog_related_keys:
          sql_catalog_related_keys.append(key)
2221
    catalog.sql_catalog_related_keys = sql_catalog_related_keys
2222

2223 2224 2225
  def uninstall(self, context, **kw):
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2226
    except KeyError:
2227 2228 2229 2230
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2231
    sql_catalog_related_keys = list(catalog.sql_catalog_related_keys)
2232 2233 2234 2235 2236 2237
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
2238 2239 2240 2241 2242
      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)

2243
  def generateXml(self, path=None):
2244
    obj = self._objects[path]
2245
    xml_data = '<key_list>'
2246 2247
    obj.sort()
    for key in obj:
2248 2249 2250 2251
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'
    return xml_data

Aurel's avatar
Aurel committed
2252 2253 2254 2255 2256 2257
  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():
2258
      xml_data = self.generateXml(path=path)
2259
      bta.addObject(obj=xml_data, name=path, path=None)
2260

2261 2262
class CatalogResultTableTemplateItem(BaseTemplateItem):

Aurel's avatar
Aurel committed
2263
  def build(self, context, **kw):
2264 2265
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2266
    except KeyError:
2267 2268 2269 2270
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
Aurel's avatar
Aurel committed
2271
    sql_search_result_tables = list(catalog.sql_search_tables)
2272
    key_list = []
2273
    for key in self._archive.keys():
Aurel's avatar
Aurel committed
2274
      if key in sql_search_result_tables:
2275
        key_list.append(key)
Aurel's avatar
Aurel committed
2276 2277
      else:
        raise NotFound, 'key %r not found in catalog' %(key,)
2278 2279
    if len(key_list) > 0:
      self._objects[self.__class__.__name__+os.sep+'key_list'] = key_list
2280 2281 2282 2283 2284 2285 2286 2287 2288 2289

  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
2290

2291
  def install(self, context, trashbin, **kw):
2292 2293 2294 2295 2296 2297 2298
    try:
      catalog = context.portal_catalog.getSQLCatalog()
    except KeyError:
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
2299

2300
    sql_search_tables = list(catalog.sql_search_tables)
2301 2302
    if (getattr(self, 'template_format_version', 0)) == 1:
      if len(self._objects.keys()) == 0: # needed because of pop()
Aurel's avatar
Aurel committed
2303
        return
2304 2305 2306
      keys = []
      for k in self._objects.values().pop(): # because of list of list
        keys.append(k)
Aurel's avatar
Aurel committed
2307
    else:
2308
      keys = self._archive.keys()
2309 2310
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2311
    for key in keys:
2312 2313 2314 2315 2316 2317 2318
      if update_dict.has_key(key) or force:
        if not force:
          action = update_dict[key]
          if action == 'nothing':
            continue          
        if key not in sql_search_tables:
          sql_search_tables.append(key)
2319
    catalog.sql_search_tables = sql_search_tables
2320 2321

  def uninstall(self, context, **kw):
2322 2323
    try:
      catalog = context.portal_catalog.getSQLCatalog()
2324
    except KeyError:
2325 2326 2327 2328 2329
      catalog = None
    if catalog is None:
      LOG('BusinessTemplate', 0, 'no SQL catalog was available')
      return
    sql_search_tables = list(catalog.sql_search_tables)
2330 2331 2332 2333 2334 2335
    object_path = kw.get('object_path', None)    
    if object_path is not None:
      object_keys = [object_path]
    else:
      object_keys = self._archive.keys()
    for key in object_keys:
2336 2337
      if key in sql_search_tables:
        sql_search_tables.remove(key)
2338
    catalog.sql_search_tables = sql_search_tables
2339 2340
    BaseTemplateItem.uninstall(self, context, **kw)

2341
  def generateXml(self, path=None):
2342
    obj = self._objects[path]
2343
    xml_data = '<key_list>'
2344 2345
    obj.sort()
    for key in obj:
2346 2347 2348 2349
      xml_data += os.linesep+' <key>%s</key>' %(key)
    xml_data += os.linesep+'</key_list>'    
    return xml_data

Aurel's avatar
Aurel committed
2350 2351 2352 2353 2354 2355
  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():
2356
      xml_data = self.generateXml(path=path)
2357
      bta.addObject(obj=xml_data, name=path, path=None)
2358

2359 2360 2361 2362 2363
class MessageTranslationTemplateItem(BaseTemplateItem):

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

2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391
  def preinstall(self, context, installed_bt, **kw):
    if (getattr(self, 'template_format_version', 0)) == 1:
      modified_object_list = {}
      new_keys = self._objects.keys()
      for path in new_keys:
        if installed_bt._objects.has_key(path):
          # compare object to see if there is changes
          new_obj_code = self._objects[path]
          old_obj_code = installed_bt._objects[path]
          if new_obj_code != old_obj_code:
            modified_object_list.update({path : ['Modified', self.__class__.__name__[:-12]]})
        else: # new object
          modified_object_list.update({path : ['New', self.__class__.__name__[:-12]]})
      # get removed object
      old_keys = installed_bt._objects.keys()
      for path in old_keys:
        if path not in new_keys:
          modified_object_list.update({path : ['Removed', self.__class__.__name__[:-12]]})
    return modified_object_list

  def install(self, context, trashbin, **kw):
2392
    localizer = context.getPortalObject().Localizer
2393 2394
    update_dict = kw.get('object_to_update')
    force = kw.get('force')
2395
    if (getattr(self, 'template_format_version', 0)) == 1:
Aurel's avatar
Aurel committed
2396
      for path, po in self._objects.items():
2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410
        if update_dict.has_key(path) or force:
          if not force:
            action = update_dict[path]
            if action == 'nothing':
              continue          
          path = string.split(path, '/')
          lang = path[-3]
          catalog = path[-2]
          if lang not in localizer.get_languages():
            localizer.manage_addLanguage(lang)
          mc = localizer._getOb(catalog)
          if lang not in mc.get_languages():
            mc.manage_addLanguage(lang)
          mc.manage_import(lang, po)
Aurel's avatar
Aurel committed
2411
    else:
2412
      BaseTemplateItem.install(self, context, trashbin, **kw)
Aurel's avatar
Aurel committed
2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427
      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():
2428
      obj = self._objects[key]
Aurel's avatar
Aurel committed
2429 2430 2431
      path = os.path.join(root_path, key)
      bta.addFolder(name=path)
      f = open(path+'/translation.po', 'wt')
2432
      f.write(str(obj))
Aurel's avatar
Aurel committed
2433
      f.close()
2434

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

2439
class BusinessTemplate(XMLObject):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2440 2441
    """
    A business template allows to construct ERP5 modules
2442 2443 2444
    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
2445

2446 2447
    - catalog definition
      - SQL method objects
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2448 2449 2450 2451
      - SQL methods including:
        - purpose (catalog, uncatalog, etc.)
        - filter definition

2452 2453 2454
    - portal_types definition
      - object without optinal actions
      - list of relation between portal type and worklfow
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2455

2456
    - module definition
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2457
      - id
2458 2459
      - title
      - portal type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2460 2461
      - roles/security

2462 2463 2464 2465
    - site property definition
      - id
      - type
      - value
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2466

2467 2468
    - document/propertysheet/extension/test definition
      - copy of the local file
2469

2470
    - message transalation definition
2471
      - .po file
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2472

2473 2474
    The Business Template properties are exported to the bt folder with
    one property per file
2475

Jean-Paul Smets's avatar
Jean-Paul Smets committed
2476 2477
    Technology:

2478 2479
    - 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
2480

2481
    - install files to the right location (install)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2482 2483 2484 2485

    Use case:

    - install core ERP5 (the minimum)
2486

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

2489 2490
    - go to "BT" menu. Create new BT.
      Define BT elements (workflow, methods, attributes, etc.).
2491
      Build BT and export or save it
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2492 2493 2494 2495 2496
      Done.
    """

    meta_type = 'ERP5 Business Template'
    portal_type = 'Business Template'
2497
    add_permission = Permissions.AddPortalContent
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510
    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
2511
                      , PropertySheet.SimpleItem
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2512
                      , PropertySheet.CategoryCore
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2513
                      , PropertySheet.Version
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2514 2515 2516
                      , PropertySheet.BusinessTemplate
                      )

2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591
    # 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, )
          }
        )
      }

2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608
    # 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',
      '_skin_item',
2609
      '_path_item',
2610 2611 2612 2613 2614 2615 2616 2617 2618
      '_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
2619
      self._objects = PersistentMapping()
2620 2621
      for item_name in self._item_name_list:
        setattr(self, item_name, None)
2622

2623
    security.declareProtected(Permissions.ManagePortal, 'manage_afterAdd')
2624 2625 2626 2627 2628 2629 2630
    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".
2631 2632
        if portal_workflow.getStatusOf(
                'business_template_installation_workflow', self) is not None:
2633
          # XXX Not good to access the attribute directly,
2634 2635 2636
          # but there is no API for clearing the history.
          self.workflow_history[
                            'business_template_installation_workflow'] = None
2637

2638
    security.declareProtected(Permissions.ManagePortal, 'build')
2639
    def build(self, no_action=0):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2640 2641 2642
      """
        Copy existing portal objects to self
      """
2643 2644
      if no_action: return # this is use at import of Business Template to get the status built
      
2645 2646
      # Make sure that everything is sane.
      self.clean()
2647

Yoshinori Okuji's avatar
Yoshinori Okuji committed
2648 2649
      # 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
2650
      # XXX Is it still necessary as it is not saved in new bt format ??
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2651 2652
      portal_workflow = getToolByName(self, 'portal_workflow')
      wf_id_list = portal_workflow.getChainFor(self)
2653
      original_history_dict = {}
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2654 2655 2656 2657 2658 2659
      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:]
2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705
      # 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)
2706

2707
    build = WorkflowMethod(build)
2708 2709

    def publish(self, url, username=None, password=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2710
      """
2711
        Publish in a format or another
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2712
      """
2713
      return self.portal_templates.publish(self, url, username=username,
2714
                                           password=password)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2715

2716
    def update(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2717
      """
2718
        Update template: download new template definition
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2719
      """
2720
      return self.portal_templates.update(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2721

2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764
    def preinstall(self, **kw):
      """
        Return the list of modified/new/removed object between a Business Template
        and the one installed if exists
      """      
      modified_object_list = {}
      bt_title = self.getTitle()
      installed_bt = self.portal_templates.getInstalledBusinessTemplate(title=bt_title)
      if installed_bt is None:
        installed_bt_format = 0 # that will not check for modification
      else:
        installed_bt_format = getattr(installed_bt, 'template_format_version', 0)

      new_bt_format = getattr(self, 'template_format_version', 0)      
      if installed_bt_format == 0 and new_bt_format == 0:
        # still use old format, so install everything, no choice
        return modified_object_list
      elif installed_bt_format == 0 and new_bt_format == 1:
        # return list of all object in bt
        for item_name in self._item_name_list:
          item = getattr(self, item_name)
          if item is not None:
            for path in item._objects.keys():
              modified_object_list.update({path : ['New', item.__class__.__name__[:-12]]})
        return modified_object_list
        
      # get the list of modified and new object
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
      for item_name in self._item_name_list:
        new_item = getattr(self, item_name)
        old_item = getattr(installed_bt, item_name)
        if new_item is not None:
          modified_object = new_item.preinstall(context=local_configuration, installed_bt=old_item)
          if len(modified_object) > 0:
            modified_object_list.update(modified_object)
      return modified_object_list

    def _install(self, force=1, object_to_update={}, **kw):
      """
        Install a new Business Template, if force, all we be upgrade or installed
        otherwise depends of dict object_to_update
      """
2765 2766
      installed_bt = self.portal_templates.getInstalledBusinessTemplate(
                                                           self.getTitle())
2767 2768 2769 2770 2771
      if installed_bt is not None:        
        if getattr(installed_bt, 'template_format_version', 0) == 0:
          # maybe another to uninstall old format bt, maybe not needed
          installed_bt.trash(self)
          force = 1
2772
        installed_bt.replace(self)
2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785
        
      if not force:
        if len(object_to_update) == 0:
          # nothing to be done
          return
        
      # always created a trash bin because we may to save object already present
      # but not in a previous business templates apart at creation of a new site
      if len(object_to_update) > 0 or len(self.portal_templates.objectIds()) > 1:
        trashbin = self.portal_trash.newTrashBin(self.getTitle(), self)
      else:
        trashbin = None
        
2786
      # Update local dictionary containing all setup parameters
2787 2788 2789
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803
      
      # get objects to remove
      remove_object_dict = {}
      for path in object_to_update.keys():
        action = object_to_update[path]
        if action == 'remove' or action == 'save_and_remove':
          remove_object_dict[path] = action
          object_to_update.pop(path)
      # remove object from old business template
      if len(remove_object_dict) > 0:
        for item_name in installed_bt._item_name_list:
          item = getattr(installed_bt, item_name)
          if item is not None:
            item.remove(local_configuration, remove_object_dict=remove_object_dict, trashbin=trashbin)
2804
      # Install everything
2805 2806 2807 2808 2809
      if len(object_to_update) > 0 or force:
        for item_name in self._item_name_list:
          item = getattr(self, item_name)
          if item is not None:
            item.install(local_configuration, force=force, object_to_update=object_to_update, trashbin=trashbin)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2810 2811 2812
      # It is better to clear cache because the installation of a template
      # adds many new things into the portal.
      clearCache()
2813

2814
    security.declareProtected(Permissions.ManagePortal, 'install')
2815 2816 2817 2818 2819
    def install(self, **kw):
      """
        For install based on paramaters provided in **kw
      """
      return self._install(**kw)
2820

2821
    install = WorkflowMethod(install)
2822

2823
    security.declareProtected(Permissions.ManagePortal, 'reinstall')
2824
    def reinstall(self, **kw):
2825 2826 2827 2828
      """Reinstall Business Template.
      """
      return self._install(**kw)

2829
    reinstall = WorkflowMethod(reinstall)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2830

2831
    security.declareProtected(Permissions.ManagePortal, 'trash')
2832 2833
    def trash(self, new_bt, **kw):
      """
2834
        Trash unnecessary items before upgrading to a new business
2835
        template.
2836
        This is similar to uninstall, but different in that this does
2837
        not remove all items.
2838 2839 2840 2841 2842
      """
      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
2843 2844 2845 2846 2847 2848 2849
      # 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))
2850

2851
    security.declareProtected(Permissions.ManagePortal, 'uninstall')
2852
    def uninstall(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2853
      """
2854
        For uninstall based on paramaters provided in **kw
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2855
      """
2856 2857 2858 2859
      # Update local dictionary containing all setup parameters
      # This may include mappings
      self.portal_templates.updateLocalConfiguration(self, **kw)
      local_configuration = self.portal_templates.getLocalConfiguration(self)
2860 2861 2862 2863 2864 2865
      # 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)
2866
      # It is better to clear cache because the uninstallation of a
2867
      # template deletes many things from the portal.
2868
      clearCache()
2869

2870 2871
    uninstall = WorkflowMethod(uninstall)

2872
    security.declareProtected(Permissions.ManagePortal, 'clean')
2873
    def clean(self):
2874
      """
2875
        Clean built information.
2876
      """
2877
      # First, remove obsolete attributes if present.
Aurel's avatar
Aurel committed
2878
      self._objects = None
2879 2880 2881 2882
      for attr in ( '_action_archive',
                    '_document_archive',
                    '_extension_archive',
                    '_test_archive',
2883
                    '_module_archive',
2884 2885 2886
                    '_object_archive',
                    '_portal_type_archive',
                    '_property_archive',
2887
                    '_property_sheet_archive'):
2888 2889 2890
        if hasattr(self, attr):
          delattr(self, attr)
      # Secondly, make attributes empty.
2891 2892
      for item_name in self._item_name_list:
        item = setattr(self, item_name, None)
2893 2894

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

2896
    security.declareProtected(Permissions.AccessContentsInformation,
2897
                              'getBuildingState')
2898
    def getBuildingState(self, id_only=1):
2899
      """
2900
        Returns the current state in building
2901
      """
2902
      portal_workflow = getToolByName(self, 'portal_workflow')
2903 2904
      wf = portal_workflow.getWorkflowById(
                          'business_template_building_workflow')
2905
      return wf._getWorkflowStateOf(self, id_only=id_only )
2906

2907
    security.declareProtected(Permissions.AccessContentsInformation,
2908
                              'getInstallationState')
2909
    def getInstallationState(self, id_only=1):
2910
      """
2911
        Returns the current state in installation
2912
      """
2913
      portal_workflow = getToolByName(self, 'portal_workflow')
2914 2915
      wf = portal_workflow.getWorkflowById(
                           'business_template_installation_workflow')
2916
      return wf._getWorkflowStateOf(self, id_only=id_only )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2917

Yoshinori Okuji's avatar
Yoshinori Okuji committed
2918 2919 2920 2921 2922 2923
    security.declareProtected(Permissions.AccessContentsInformation, 'toxml')
    def toxml(self):
      """
        Return this Business Template in XML
      """
      portal_templates = getToolByName(self, 'portal_templates')
2924
      export_string = portal_templates.manage_exportObject(
2925 2926
                                               id=self.getId(),
                                               toxml=1,
2927
                                               download=1)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
2928
      return export_string
2929

2930
    def _getOrderedList(self, id):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2931
      """
2932 2933
        We have to set this method because we want an
        ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2934
      """
2935
      result = getattr(self, id, ())
2936 2937 2938 2939 2940 2941
      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
2942

2943
    def getTemplateCatalogMethodIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2944
      """
2945 2946
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2947
      """
2948
      return self._getOrderedList('template_catalog_method_id')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2949

2950
    def getTemplateBaseCategoryList(self):
2951
      """
2952 2953
      We have to set this method because we want an
      ordered list
2954
      """
2955
      return self._getOrderedList('template_base_category')
2956

2957
    def getTemplateWorkflowIdList(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2958
      """
2959 2960
      We have to set this method because we want an
      ordered list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2961
      """
2962
      return self._getOrderedList('template_workflow_id')
2963

2964
    def getTemplatePortalTypeIdList(self):
2965
      """
2966 2967
      We have to set this method because we want an
      ordered list
2968
      """
2969
      return self._getOrderedList('template_portal_type_id')
2970

2971
    def getTemplateActionPathList(self):
2972
      """
2973 2974
      We have to set this method because we want an
      ordered list
2975
      """
2976
      return self._getOrderedList('template_action_path')
2977

2978
    def getTemplateSkinIdList(self):
2979
      """
2980 2981
      We have to set this method because we want an
      ordered list
2982
      """
2983
      return self._getOrderedList('template_skin_id')
2984

2985
    def getTemplateModuleIdList(self):
2986
      """
2987 2988
      We have to set this method because we want an
      ordered list
2989
      """
2990
      return self._getOrderedList('template_module_id')
2991 2992 2993 2994 2995 2996 2997

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

2999
    security.declareProtected(Permissions.AccessContentsInformation,
3000
                              'diff')
3001
    def diff(self, verbose=0):
3002
      """
3003
        Return a 'diff' of the business template compared to the
3004 3005
        __btsave__ version.
      """
3006 3007
      diff_message = '%s : %s\n%s\n' % (self.getPath(), DateTime(),
                                        '='*80)
3008
      # Diff everything
3009 3010 3011 3012 3013
      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
3014

3015
    security.declareProtected(Permissions.ManagePortal, 'export')
Aurel's avatar
Aurel committed
3016 3017 3018 3019
    def export(self, path=None, local=0, **kw):
      """
        Export this Business Template
      """
3020 3021
      if self.getBuildingState() != 'built':
        raise TemplateConditionError, 'Business Template must be build before export'
3022
      
Aurel's avatar
Aurel committed
3023 3024 3025 3026 3027 3028 3029
      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)

3030
      # export bt
3031
      bta.addFolder(path+os.sep+'bt')
Aurel's avatar
Aurel committed
3032 3033 3034
      for prop in self.propertyMap():
        type = prop['type']
        id = prop['id']
3035
        if id in ('id', 'uid', 'rid', 'sid', 'id_group', 'last_id', 'install_object_list_list'):        
3036
          continue
Aurel's avatar
Aurel committed
3037 3038
        value = self.getProperty(id)
        if type == 'text' or type == 'string' or type == 'int':
3039
          bta.addObject(obj=value, name=id, path=path+os.sep+'bt', ext='')
Aurel's avatar
Aurel committed
3040
        elif type == 'lines' or type == 'tokens':
3041
          bta.addObject(obj=str(os.linesep).join(value), name=id, path=path+os.sep+'bt', ext='')
3042

Aurel's avatar
Aurel committed
3043 3044 3045
      # Export each part
      for item_name in self._item_name_list:
        getattr(self, item_name).export(context=self, bta=bta)
3046
        
Aurel's avatar
Aurel committed
3047 3048
      return bta.finishCreation()

3049
    security.declareProtected(Permissions.ManagePortal, 'importFile')
Aurel's avatar
Aurel committed
3050 3051
    def importFile(self, dir = 0, file=None, root_path=None):
      """
3052
        Import all xml files in Business Template
Aurel's avatar
Aurel committed
3053 3054 3055 3056 3057 3058
      """

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

Aurel's avatar
Aurel committed
3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104
      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)
3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167


    def diffObject(self, REQUEST):
      """
        Make a diff between an object in the Business Template
        and the same installed in the site
      """

      class_name_dict = {
        'Product' : '_product_item',
        'PropertySheet' : '_property_sheet_item', 
        'Document' : '_document_item',
        'Extension' : '_extension_item',
        'Test' : '_test_item',
        'Role' : '_role_item',
        'MessageTranslation' : '_message_translation_item',
        'Workflow' : '_workflow_item',
        'CatalogMethod' : '_catalog_method_item',
        'SiteProperty' : '_site_property_item',
        'PortalType' : '_portal_type_item',
        'Category' : '_category_item',
        'Module' : '_module_item',
        'Skin' : '_skin_item',
        'Path' : '_path_item',
        'Action' : '_action_item',
        'CatalogResultKey' : '_catalog_result_key_item',
        'CatalogRelatedKey' : '_catalog_related_key_item',
        'CatalogResultTable' : '_catalog_result_table_item',
        }

      object_id = REQUEST.object_id
      object_class = REQUEST.object_class
      # get objects
      item_name = class_name_dict[object_class]
      new_bt =self
      installed_bt = self.getInstalledBusinessTemplate(title=self.getTitle())
      new_item = getattr(new_bt, item_name)
      installed_item = getattr(installed_bt, item_name)
      new_object = new_item._objects[object_id]
      installed_object = installed_item._objects[object_id]
      # make diff
      diff_msg = ''
      item_list_1 = ['_product_item', '_workflow_item', '_portal_type_item', '_category_item', '_path_item', '_skin_item', '_action_item']
      item_list_2 = ['_site_property_item', '_module_item', '_catalog_result_key_item', '_catalog_related_key_item', '_catalog_result_table_item']
      item_list_3 = ['_document_item', '_property_sheet_item', '_extension_item', '_test_item', '_message_translation_item']
      if item_name in item_list_1:
        f1 = StringIO()
        f2 = StringIO()
        OFS.XMLExportImport.exportXML(new_object._p_jar, new_object._p_oid, f1)
        OFS.XMLExportImport.exportXML(installed_object._p_jar, installed_object._p_oid, f2)
        new_obj_xml = f1.getvalue()
        installed_obj_xml = f2.getvalue()
        f1.close()
        f2.close()
        new_ob_xml_lines = new_obj_xml.splitlines()
        installed_ob_xml_lines = installed_obj_xml.splitlines()
        diff_list = list(unified_diff(installed_ob_xml_lines, new_ob_xml_lines, tofile=new_bt.getId(), fromfile=installed_bt.getId(), lineterm=''))
        if len(diff_list) != 0:
          diff_msg += '\n\nObject %s diff :\n' %( object_id)
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'
      elif item_name in item_list_2:
3168 3169
        new_obj_xml = new_item.generateXml(path= object_id)
        installed_obj_xml = installed_item.generateXml(path= object_id)
3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189
        new_obj_xml_lines = new_obj_xml.splitlines()
        installed_obj_xml_lines = installed_obj_xml.splitlines()
        diff_list = list(unified_diff(installed_obj_xml_lines, new_obj_xml_lines, tofile=new_bt.getId(), fromfile=installed_bt.getId(), lineterm=''))
        if len(diff_list) != 0:
          diff_msg += '\n\nObject %s diff :\n' %( object_id)
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'
      elif item_name in item_list_3:
        new_obj_lines = new_object.splitlines()
        installed_obj_lines = installed_object.splitlines()
        diff_list = list(unified_diff(installed_obj_lines, new_obj_lines, tofile=new_bt.getId(), fromfile=installed_bt.getId(), lineterm=''))
        if len(diff_list) != 0:
          diff_msg += '\n\nObject %s diff :\n' %( object_id)
          diff_msg += '\n'.join(diff_list)
        else:
          diff_msg = 'No diff'                
      
      return diff_msg