ERP5Conduit.py 46.5 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#          Sebastien Robin <seb@nexedi.com>
#
# 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 Products.ERP5SyncML.XMLSyncUtils import XMLSyncUtilsMixin
Jean-Paul Smets's avatar
Jean-Paul Smets committed
30 31
from Products.ERP5SyncML.Subscription import Conflict
from Products.CMFCore.utils import getToolByName
32
from Products.ERP5SyncML.XMLSyncUtils import Parse
33
from DateTime.DateTime import DateTime
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34 35
from email.MIMEBase import MIMEBase
from email import Encoders
Sebastien Robin's avatar
Sebastien Robin committed
36
from AccessControl import ClassSecurityInfo
Fabien Morin's avatar
Fabien Morin committed
37
from Products.ERP5Type import Permissions, Interface
Sebastien Robin's avatar
Sebastien Robin committed
38
from Globals import PersistentMapping
39
import pickle
40
from cStringIO import StringIO
41
from xml.sax.saxutils import escape, unescape
Fabien Morin's avatar
Fabien Morin committed
42
import re
43
import cStringIO
44
from zLOG import LOG, INFO, DEBUG
45 46 47 48 49 50 51 52 53 54 55
try:
  from Ft.Xml.Domlette import Print, PrettyPrint
except ImportError:
  LOG('ERP5Conduit',0,"Can't import Print and PrettyPrint")
  class Print:
    def __init__(self, *args, **kw):
      raise ImportError, "Sorry, it was not possible to import Ft library"

  class PrettyPrint:
    def __init__(self, *args, **kw):
      raise ImportError, "Sorry, it was not possible to import Ft library"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
56

Fabien Morin's avatar
Fabien Morin committed
57

58
class ERP5Conduit(XMLSyncUtilsMixin):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
  """
    A conduit is a piece of code in charge of

    - updating an object attributes from an XUpdate XML stream

    (Conduits are not in charge of creating new objects which
    are eventually missing in a synchronisation process)

    If an object has be created during a synchronisation process,
    the way to proceed consists in:

    1- creating an empty instance of the appropriate class
      in the appropriate directory

    2- updating that empty instance with the conduit

    The first implementation of ERP5 synchronisation
    will define a default location to create new objects and
    a default class. This will be defined at the level of the synchronisation
    tool

80 81 82 83 84 85 86 87 88 89 90 91 92 93
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
    Look carefully when we are adding elements,
    for example, when we do 'insert-after', with 2 xupdate:element,
    so adding 2 differents objects, actually it adds only XXXX one XXX object
    In this case the getSubObjectDepth(), doesn't have
    too much sence
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    There is also one problem, when we synchronize a conflict, we are not waiting
    the response of the client, so that we are not sure if it take into account,
    we may have CONFLICT_NOT_SYNCHRONIZED AND CONFLICT_SYNCHRONIZED
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
94

Fabien Morin's avatar
Fabien Morin committed
95 96 97
  # Declarative interfaces
  __implements__ = ( Interface.IConduit, )

Sebastien Robin's avatar
Sebastien Robin committed
98 99
  # Declarative security
  security = ClassSecurityInfo()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
100

Sebastien Robin's avatar
Sebastien Robin committed
101
  security.declareProtected(Permissions.AccessContentsInformation,'getEncoding')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
102 103 104 105
  def getEncoding(self):
    """
    return the string corresponding to the local encoding
    """
106 107
    #return "iso-8859-1"
    return "utf-8"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
108

Sebastien Robin's avatar
Sebastien Robin committed
109
  security.declareProtected(Permissions.ModifyPortalContent, '__init__')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
110 111
  def __init__(self):
    self.args = {}
112

Sebastien Robin's avatar
Sebastien Robin committed
113
  security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
114
  def addNode(self, xml=None, object=None, previous_xml=None,
115
              object_id=None, sub_object=None, force=0, simulate=0, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
116 117 118
    """
    A node is added

119 120 121 122 123 124 125 126
    xml : the xml wich contains what we want to add

    object : from where we want to add something

    previous_xml : the previous xml of the object, if any

    force : apply updates even if there's a conflict

Jean-Paul Smets's avatar
Jean-Paul Smets committed
127 128
    This fucntion returns conflict_list, wich is of the form,
    [conflict1,conflict2,...] where conclict1 is of the form :
129
    [object.getPath(),keyword,local_and_actual_value,subscriber_value]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
130
    """
131 132
    reset_local_roles = 0
    reset_workflow = 0
Jean-Paul Smets's avatar
Jean-Paul Smets committed
133
    conflict_list = []
134
    xml = self.convertToXml(xml)
135 136
    if xml is None:
      return {'conflict_list':conflict_list, 'object':sub_object}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
137
    # In the case where this new node is a object to add
138
    if xml.nodeName in self.XUPDATE_INSERT_OR_ADD and \
Nicolas Delaby's avatar
Nicolas Delaby committed
139
        self.getSubObjectDepth(xml) == 0:
Sebastien Robin's avatar
Sebastien Robin committed
140 141 142
      if self.isHistoryAdd(xml)!=-1: # bad hack XXX to be removed
        for element in self.getXupdateElementList(xml):
          xml = self.getElementFromXupdate(element)
143 144 145 146 147 148 149
          conflict_list += self.addNode(
                                  xml=xml,
                                  object=object,
                                  previous_xml=previous_xml,
                                  force=force,
                                  simulate=simulate,
                                  **kw)['conflict_list']
150
    elif xml.nodeName == 'object':
151
      if object_id is None:
152
        object_id = self.getAttribute(xml, 'id')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
153
      if object_id is not None:
154 155 156 157 158
        if sub_object is None:
          try:
            sub_object = object._getOb(object_id)
          except (AttributeError, KeyError, TypeError):
            sub_object = None
159
        if sub_object is None: # If so, it doesn't exist
Jean-Paul Smets's avatar
Jean-Paul Smets committed
160 161 162
          portal_type = ''
          if xml.nodeName == 'object':
            portal_type = self.getObjectType(xml)
163 164
          elif xml.nodeName in self.XUPDATE_INSERT_OR_ADD: # Deprecated ???
            portal_type = self.getXupdateObjectType(xml) # Deprecated ???
165 166 167 168
          sub_object, reset_local_roles, reset_workflow = self.constructContent(
                                                                  object,
                                                                  object_id,
                                                                  portal_type)
169 170 171 172 173 174
        self.newObject(
                  object=sub_object,
                  xml=xml,
                  simulate=simulate,
                  reset_local_roles=reset_local_roles,
                  reset_workflow=reset_workflow)
175
    elif xml.nodeName in self.XUPDATE_INSERT_OR_ADD \
176
         and self.getSubObjectDepth(xml)>=1:
177 178
      sub_object_id = self.getSubObjectId(xml)
      if previous_xml is not None and sub_object_id is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
179
        # Find the previous xml corresponding to this subobject
180
        sub_previous_xml = self.getSubObjectXml(sub_object_id, previous_xml)
181
        #LOG('addNode', DEBUG,'isSubObjectModification sub_previous_xml: %s' % str(sub_previous_xml))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
182 183 184
        if sub_previous_xml is not None:
          sub_object = None
          try:
185
            sub_object = object._getOb(sub_object_id)
186
          except (AttributeError, KeyError, TypeError):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
187 188
            pass
          if sub_object is not None:
189
            #LOG('addNode', DEBUG, 'subobject.id: %s' % sub_object.id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
190 191 192
            # Change the xml in order to directly apply
            # modifications to the subobject
            sub_xml = self.getSubObjectXupdate(xml)
193
            #LOG('addNode', DEBUG, 'sub_xml: %s' % str(sub_xml))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
194 195
            # Then do the udpate
            conflict_list += self.addNode(xml=sub_xml,object=sub_object,
196
                            previous_xml=sub_previous_xml, force=force,
197
                            simulate=simulate, **kw)['conflict_list']
198
    elif xml.nodeName == self.history_tag or self.isHistoryAdd(xml)>0:
199
      conflict_list += self.addWorkflowNode(object, xml, simulate)
Sebastien Robin's avatar
Sebastien Robin committed
200 201
    #elif xml.nodeName in self.local_role_list or self.isLocalRole(xml)>0 and not simulate:
    elif xml.nodeName in self.local_role_list:
202
      conflict_list += self.addLocalRoleNode(object, xml)
203
    elif xml.nodeName in self.local_permission_list:
204
      conflict_list += self.addLocalPermissionNode(object, xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
205
    else:
206 207
      conflict_list += self.updateNode(xml=xml,object=object, force=force,
                                       simulate=simulate,  **kw)
208 209
    # We must returns the object created
    return {'conflict_list':conflict_list, 'object': sub_object}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
210

Sebastien Robin's avatar
Sebastien Robin committed
211
  security.declareProtected(Permissions.ModifyPortalContent, 'deleteNode')
212 213
  def deleteNode(self, xml=None, object=None, object_id=None, force=None,
                 simulate=0, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
214 215 216
    """
    A node is deleted
    """
217
    # In the case where we have to delete an object
218
    #LOG('ERP5Conduit.deleteNode', DEBUG, 'deleteNode, object path: %s' % repr(object.getPhysicalPath()))
219
    conflict_list = []
220 221
    if xml is not None:
      xml = self.convertToXml(xml)
222
    if object_id is None:
223
      #LOG('ERP5Conduit.deleteNode', DEBUG, 'deleteNode, SubObjectDepth: %i' % self.getSubObjectDepth(xml))
224
      if xml.nodeName == self.xml_object_tag:
225
        object_id = self.getAttribute(xml,'id')
Nicolas Delaby's avatar
Nicolas Delaby committed
226
      elif self.getSubObjectDepth(xml) == 1:
227
        object_id = self.getSubObjectId(xml)
Nicolas Delaby's avatar
Nicolas Delaby committed
228
      elif self.getSubObjectDepth(xml) == 2:
229 230
        # we have to call delete node on a subsubobject
        sub_object_id = self.getSubObjectId(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
231
        try:
232 233 234
          sub_object = object._getOb(sub_object_id)
          sub_xml = self.getSubObjectXupdate(xml)
          conflict_list += self.deleteNode(xml=sub_xml,object=sub_object,
235
                                       force=force, simulate=simulate, **kw)
236
        except (KeyError, AttributeError, TypeError):
237
          #LOG('ERP5Conduit.deleteNode', DEBUG, 'deleteNode, Unable to delete SubObject: %s' % str(sub_object_id))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
238
          pass
239
    if object_id is not None: # We do have an object_id
240
      self.deleteObject(object, object_id)
241 242 243 244 245 246 247
    # In the case where we have to delete an user role
    # If we are still there, this means the delete is for this node
    elif xml.nodeName in self.XUPDATE_DEL:
      xml = self.getElementFromXupdate(xml)
      if xml.nodeName in self.local_role_list and not simulate:
        # We want to del a local role
        user = self.getAttribute(xml,'id')
248
        #LOG('ERP5Conduit.deleteNode local_role: ', DEBUG, 'user: %s' % repr(user))
249 250 251 252
        if xml.nodeName.find(self.local_role_tag)>=0:
          object.manage_delLocalRoles([user])
        elif xml.nodeName.find(self.local_group_tag)>=0:
          object.manage_delLocalGroupRoles([user])
253 254
      if xml.nodeName in self.local_permission_list and not simulate:
        permission = self.getAttribute(xml,'id')
255
        object.manage_setLocalPermissions(permission)
256
    return conflict_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
257

258 259 260 261 262
  security.declareProtected(Permissions.ModifyPortalContent, 'deleteObject')
  def deleteObject(self, object, object_id):
    try:
      object._delObject(object_id)
    except (AttributeError, KeyError):
263
      #LOG('ERP5Conduit.deleteObject', DEBUG, 'Unable to delete: %s' % str(object_id))
264 265
      pass

Sebastien Robin's avatar
Sebastien Robin committed
266
  security.declareProtected(Permissions.ModifyPortalContent, 'updateNode')
267 268
  def updateNode(self, xml=None, object=None, previous_xml=None, force=0,
                 simulate=0,  **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
269 270 271 272 273 274 275 276
    """
    A node is updated with some xupdate
      - xml : the xml corresponding to the update, it should be xupdate
      - object : the object on wich we want to apply the xupdate
      - [previous_xml] : the previous xml of the object, it is mandatory
                         when we have sub objects
    """
    conflict_list = []
277 278
    if xml is None:
      return {'conflict_list':conflict_list, 'object':object}
279
    xml = self.convertToXml(xml)
280 281
    #LOG('ERP5Conduit.updateNode', DEBUG, 'xml.nodeName: %s' % xml.nodeName)
    #LOG('ERP5Conduit.updateNode, force: ', DEBUG, force)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
282 283
    # we have an xupdate xml
    if xml.nodeName == 'xupdate:modifications':
284 285 286 287 288
      conflict_list += self.applyXupdate(object=object,
                                         xupdate=xml,
                                         conduit=self,
                                         previous_xml=previous_xml,
                                         force=force,
Nicolas Delaby's avatar
Nicolas Delaby committed
289
                                         simulate=simulate, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
290 291 292 293
    # we may have only the part of an xupdate
    else:
      args = {}
      if self.isProperty(xml) and not(self.isSubObjectModification(xml)):
294
        keyword = None
295
        for subnode in self.getAttributeNodeList(xml):
Nicolas Delaby's avatar
Nicolas Delaby committed
296
          if subnode.nodeName == 'select':
297 298
            select_list = subnode.nodeValue.split('/') # Something like:
                                                       #('','object[1]','sid[1]')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
299 300
            new_select_list = ()
            for select_item in select_list:
301 302 303
              if select_item.find('[')>=0:
                select_item = select_item[:select_item.find('[')]
              new_select_list += (select_item,)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
304 305
            select_list = new_select_list # Something like : ('','object','sid')
            keyword = select_list[len(select_list)-1] # this will be 'sid'
306 307
        data_xml = xml
        data = None
308
        if xml.nodeName not in self.XUPDATE_INSERT_OR_ADD:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
309
          for subnode in self.getElementNodeList(xml):
Nicolas Delaby's avatar
Nicolas Delaby committed
310
            if subnode.nodeName == 'xupdate:element':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
311
              for subnode1 in subnode.attributes:
Nicolas Delaby's avatar
Nicolas Delaby committed
312
                if subnode1.nodeName == 'name':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
313
                  keyword = subnode1.nodeValue
314
              data_xml = subnode
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
        else:
          #We can call add node
          conflict_list += self.addNode(xml=xml,
                                        object=object,
                                        force=force,
                                        simulate=simulate,
                                        **kw)
          return conflict_list

        if xml.nodeName in self.XUPDATE_DEL:
          conflict_list += self.deleteNode(xml=xml,
                                           object=object,
                                           force=force,
                                           simulate=simulate,
                                           **kw)
          return conflict_list
331 332
        if keyword is None: # This is not a selection, directly the property
          keyword = xml.nodeName
Nicolas Delaby's avatar
Nicolas Delaby committed
333
        if len(self.getElementNodeList(data_xml)) == 0:
334 335 336 337
          try:
            data = data_xml.childNodes[0].data
          except IndexError: # There is no data
            data = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
338 339
        if not (keyword in self.NOT_EDITABLE_PROPERTY):
          # We will look for the data to enter
340
          data_type = object.getPropertyType(keyword)
341
          #LOG('ERP5Conduit.updateNode', DEBUG, 'data_type: %s for keyword: %s' % (str(data_type), keyword))
Nicolas Delaby's avatar
Nicolas Delaby committed
342
          data = self.convertXmlValue(data, data_type=data_type)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
343 344 345 346 347
          args[keyword] = data
          args = self.getFormatedArgs(args=args)
          # This is the place where we should look for conflicts
          # For that we need :
          #   - data : the data from the remote box
Fabien Morin's avatar
Fabien Morin committed
348 349
          #   - old_data : the data from this box but at the time of the i
          #last synchronization
Jean-Paul Smets's avatar
Jean-Paul Smets committed
350 351
          #   - current_data : the data actually on this box
          isConflict = 0
352
          if (previous_xml is not None) and (not force):
Fabien Morin's avatar
Fabien Morin committed
353
          # if no previous_xml, no conflict
354
            old_data = self.getObjectProperty(keyword, previous_xml,
355
                data_type=data_type)
356 357
            #current_data = object.getProperty(keyword)
            current_data = self.getProperty(object, keyword)
358 359 360
            #LOG('ERP5Conduit.updateNode', DEBUG, 'Conflict data: %s' % str(data))
            #LOG('ERP5Conduit.updateNode', DEBUG, 'Conflict old_data: %s' % str(old_data))
            #LOG('ERP5Conduit.updateNode', DEBUG, 'Conflict current_data: %s' % str(current_data))
361 362
            if (old_data != current_data) and (data != current_data) \
                and keyword not in self.force_conflict_list:
363
              #LOG('ERP5Conduit.updateNode', DEBUG, 'Conflict on : %s' % keyword)
364 365
              # Hack in order to get the synchronization working for demo
              # XXX this have to be removed after
366 367
              #if not (data_type in self.binary_type_list):
              if 1:
368 369
                # This is a conflict
                isConflict = 1
370
                string_io = StringIO()
Nicolas Delaby's avatar
Nicolas Delaby committed
371
                PrettyPrint(xml, stream=string_io)
Sebastien Robin's avatar
Sebastien Robin committed
372 373
                conflict = Conflict(object_path=object.getPhysicalPath(),
                                    keyword=keyword)
374
                conflict.setXupdate(string_io.getvalue())
Sebastien Robin's avatar
Sebastien Robin committed
375 376 377
                if not (data_type in self.binary_type_list):
                  conflict.setLocalValue(current_data)
                  conflict.setRemoteValue(data)
378
                conflict_list += [conflict]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
379
          # We will now apply the argument with the method edit
Nicolas Delaby's avatar
Nicolas Delaby committed
380 381
          if args != {} and (isConflict == 0 or force) and (not simulate):
            self.editDocument(object=object, **args)
382
            # It is sometimes required to do something after an edit
Nicolas Delaby's avatar
Nicolas Delaby committed
383
            if getattr(object, 'manage_afterEdit', None) is not None:
384
              object.manage_afterEdit()
385

Jean-Paul Smets's avatar
Jean-Paul Smets committed
386 387
        if keyword == 'object':
          # This is the case where we have to call addNode
388 389 390 391 392
          conflict_list += self.addNode(xml=xml,
                                        object=object,
                                        force=force,
                                        simulate=simulate,
                                        **kw)['conflict_list']
393
        elif keyword == self.history_tag and not simulate:
394
          # This is the case where we have to call addNode
395
          conflict_list += self.addNode(xml=subnode,object=object,force=force,
396
              simulate=simulate,**kw)['conflict_list']
397
        elif keyword in (self.local_role_tag, self.local_permission_tag) and not simulate:
398
          # This is the case where we have to update Roles or update permission
399
          #LOG('ERP5Conduit.updateNode', DEBUG, 'we will add a local role')
400 401 402 403
          #user = self.getSubObjectId(xml)
          #roles = self.convertXmlValue(data,data_type='tokens')
          #object.manage_setLocalRoles(user,roles)
          xml = self.getElementFromXupdate(xml)
404 405
          conflict_list += self.addNode(xml=xml, object=object, force=force, 
              simulate=simulate,**kw)['conflict_list']
Jean-Paul Smets's avatar
Jean-Paul Smets committed
406 407 408
      elif self.isSubObjectModification(xml):
        # We should find the object corresponding to
        # this update, so we have to look in the previous_xml
409
        sub_object_id = self.getSubObjectId(xml)
410
        #LOG('ERP5Conduit.updateNode', DEBUG,'isSubObjectModification sub_object_id: %s' % sub_object_id)
411 412
        if previous_xml is not None and sub_object_id is not None:
          sub_previous_xml = self.getSubObjectXml(sub_object_id,previous_xml)
413
          #LOG('ERP5Conduit.updateNode', DEBUG, 'isSubObjectModification sub_previous_xml: %s' % str(sub_previous_xml))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
414 415 416
          if sub_previous_xml is not None:
            sub_object = None
            try:
417
              sub_object = object[sub_object_id]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
418 419 420
            except KeyError:
              pass
            if sub_object is not None:
421
              #LOG('ERP5Conduit.updateNode', DEBUG, 'subobject.id: %s' % sub_object.id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
422 423 424
              # Change the xml in order to directly apply
              # modifications to the subobject
              sub_xml = self.getSubObjectXupdate(xml)
425
              #LOG('ERP5Conduit.updateNode', DEBUG, 'sub_xml: %s' % str(sub_xml))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
426
              # Then do the udpate
427 428 429
              conflict_list += self.updateNode(xml=sub_xml, object=sub_object, 
                  force=force, previous_xml=sub_previous_xml, 
                  simulate=simulate, **kw)
430 431 432 433 434 435 436 437 438 439 440 441 442
        elif previous_xml is None and xml is not None and sub_object_id is not None:
          sub_object = None
          try:
            sub_object = object[sub_object_id]
          except KeyError:
            pass
          if sub_object is not None:
            sub_xml = self.getSubObjectXupdate(xml)
            conflict_list += self.updateNode(xml=sub_xml,
                                             object=sub_object,
                                             force=force,
                                             simulate=simulate,
                                             **kw)
443 444 445 446 447 448 449 450 451 452 453 454 455
        elif previous_xml is None and xml is not None and sub_object_id is not None:
          sub_object = None
          try:
            sub_object = object[sub_object_id]
          except KeyError:
            pass
          if sub_object is not None:
            sub_xml = self.getSubObjectXupdate(xml)
            conflict_list += self.updateNode(xml=sub_xml,
                                             object=sub_object,
                                             force=force,
                                             simulate=simulate,
                                             **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
456 457
    return conflict_list

458 459
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getFormatedArgs')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
  def getFormatedArgs(self, args=None):
    """
    This lookd inside the args dictionnary and then
    convert any unicode string to string
    """
    new_args = {}
    for keyword in args.keys():
      data = args[keyword]
      if type(keyword) is type(u"a"):
        keyword = keyword.encode(self.getEncoding())
      if type(data) is type([]) or type(data) is type(()):
        new_data = []
        for item in data:
          if type(item) is type(u"a"):
            item = item.encode(self.getEncoding())
475
            item = item.replace('@@@','\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
476 477 478 479
          new_data += [item]
        data = new_data
      if type(data) is type(u"a"):
        data = data.encode(self.getEncoding())
480
        data = data.replace('@@@','\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
481
      if keyword == 'binary_data':
482
        #LOG('ERP5Conduit.getFormatedArgs', DEBUG, 'binary_data keyword: %s' % str(keyword))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
483 484 485 486 487 488 489
        msg = MIMEBase('application','octet-stream')
        Encoders.encode_base64(msg)
        msg.set_payload(data)
        data = msg.get_payload(decode=1)
      new_args[keyword] = data
    return new_args

Sebastien Robin's avatar
Sebastien Robin committed
490
  security.declareProtected(Permissions.AccessContentsInformation,'isProperty')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
491 492 493 494
  def isProperty(self, xml):
    """
    Check if it is a simple property
    """
495
    bad_list = (self.sub_object_exp,self.history_exp)
496
    for subnode in self.getAttributeNodeList(xml):
Nicolas Delaby's avatar
Nicolas Delaby committed
497
      if subnode.nodeName == 'select':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
498 499
        value = subnode.nodeValue
        for bad_string in bad_list:
500
          if re.search(bad_string,value) is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
501 502 503
            return 0
    return 1

504 505
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubObjectXupdate')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
506 507 508 509 510
  def getSubObjectXupdate(self, xml):
    """
    This will change the xml in order to change the update
    from the object to the subobject
    """
511 512
    xml_copy = xml.cloneNode(True) #make a deepcopy of the node xml
    for subnode in self.getAttributeNodeList(xml_copy):
Nicolas Delaby's avatar
Nicolas Delaby committed
513
      if subnode.nodeName == 'select':
514
        subnode.nodeValue = self.getSubObjectSelect(subnode.nodeValue)
515
    return xml_copy
Jean-Paul Smets's avatar
Jean-Paul Smets committed
516

517 518
  security.declareProtected(Permissions.AccessContentsInformation, 
      'isHistoryAdd')
519
  def isHistoryAdd(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
520
    bad_list = (self.history_exp,)
521
    for subnode in self.getAttributeNodeList(xml):
Nicolas Delaby's avatar
Nicolas Delaby committed
522
      if subnode.nodeName == 'select':
523 524 525
        value = subnode.nodeValue
        for bad_string in bad_list:
          if re.search(bad_string,value) is not None:
Sebastien Robin's avatar
Sebastien Robin committed
526 527 528 529
            if re.search(self.bad_history_exp,value) is None:
              return 1
            else:
              return -1
530 531
    return 0

532 533
  security.declareProtected(Permissions.AccessContentsInformation, 
      'isSubObjectModification')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
534 535 536 537
  def isSubObjectModification(self, xml):
    """
    Check if it is a modification from an subobject
    """
538
    good_list = (self.sub_object_exp,)
539
    for subnode in self.getAttributeNodeList(xml) :
Nicolas Delaby's avatar
Nicolas Delaby committed
540
      if subnode.nodeName == 'select':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
541 542
        value = subnode.nodeValue
        for good_string in good_list:
543
          if re.search(good_string,value) is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
544 545 546
            return 1
    return 0

547 548
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubObjectDepth')
549 550 551 552 553 554 555
  def getSubObjectDepth(self, xml):
    """
    Give the Depth of a subobject modification
    0 means, no depth
    1 means it is a subobject
    2 means it is more depth than subobject
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
556
    #LOG('getSubObjectDepth',0,'xml.nodeName: %s' % xml.nodeName)
557
    if xml.nodeName in self.XUPDATE_TAG:
558 559 560
      i = 0
      if xml.nodeName in self.XUPDATE_INSERT:
        i = 1
Nicolas Delaby's avatar
Nicolas Delaby committed
561
      #LOG('getSubObjectDepth',0,'xml2.nodeName: %s' % xml.nodeName)
562
      for subnode in self.getAttributeNodeList(xml):
Nicolas Delaby's avatar
Nicolas Delaby committed
563
        #LOG('getSubObjectDepth',0,'subnode.nodeName: %s' % subnode.nodeName)
564 565
        if subnode.nodeName == 'select':
          value = subnode.nodeValue
Nicolas Delaby's avatar
Nicolas Delaby committed
566
          #LOG('getSubObjectDepth',0,'subnode.nodeValue: %s' % subnode.nodeValue)
567 568 569 570 571 572 573 574 575 576
          if re.search(self.sub_sub_object_exp,value) is not None:
            return 2 # This is sure in all cases
          elif re.search(self.sub_object_exp,value) is not None:
            #new_select = self.getSubObjectSelect(value) # Still needed ???
            #if self.getSubObjectSelect(new_select) != new_select:
            #  return (2 - i)
            #return (1 - i)
            return (2 - i)
          elif re.search(self.object_exp,value) is not None:
            return (1 - i)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
577 578
    return 0

579 580
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubObjectSelect')
581
  def getSubObjectSelect(self, select):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
582
    """
583 584
    Return a string wich is the selection for the subobject
    ex: for "/object[@id='161']/object[@id='default_address']/street_address"
585
    it returns "/object[@id='default_address']/street_address"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
586
    """
587 588 589 590 591 592
    if re.search(self.object_exp,select) is not None:
      s = '/'
      if re.search('/.*/',select) is not None: # This means we have more than just object
        new_value = select[select.find(s,select.find(s)+1):]
      else:
        new_value = '/'
593 594 595
      select = new_value
    return select

596 597
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubObjectId')
598 599 600 601 602 603
  def getSubObjectId(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
    for subnode in self.getAttributeNodeList(xml):
Nicolas Delaby's avatar
Nicolas Delaby committed
604
      if subnode.nodeName == 'select':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
605
        value = subnode.nodeValue
606 607 608 609
        if re.search(self.object_exp,value) is not None:
          s = "'"
          first = value.find(s)+1
          object_id = value[first:value.find(s,first)]
610 611 612
          return object_id
    return object_id

613 614
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getHistoryIdFromSelect')
615 616 617 618 619 620
  def getHistoryIdFromSelect(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
    for subnode in self.getAttributeNodeList(xml):
Nicolas Delaby's avatar
Nicolas Delaby committed
621
      if subnode.nodeName == 'select':
622 623 624 625 626 627 628 629 630
        value = subnode.nodeValue
        if re.search(self.history_exp,value) is not None:
          s = self.history_tag
          object_id = value[value.find(s):]
          object_id = object_id[object_id.find("'")+1:]
          object_id = object_id[:object_id.find("'")]
          return object_id
    return object_id

631 632
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubObjectXml')
633 634 635 636 637 638
  def getSubObjectXml(self, object_id, xml):
    """
    Return the xml of the subobject which as the id object_id
    """
    xml = self.convertToXml(xml)
    for subnode in self.getElementNodeList(xml):
639
      if subnode.nodeName == self.xml_object_tag:
640
        if object_id == self.getAttribute(subnode,'id'):
641 642
          return subnode
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
643

Sebastien Robin's avatar
Sebastien Robin committed
644
  security.declareProtected(Permissions.AccessContentsInformation,'getAttribute')
645
  def getAttribute(self, xml, param):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
646
    """
647
    Retrieve the given parameter from the xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
648
    """
649
    for attribute in self.getAttributeNodeList(xml):
650
      if attribute.nodeName == param:
651
        data = attribute.value
Nicolas Delaby's avatar
Nicolas Delaby committed
652
        return self.convertXmlValue(data, data_type='string')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
653 654
    return None

Sebastien Robin's avatar
Sebastien Robin committed
655
  security.declareProtected(Permissions.AccessContentsInformation,'getObjectProperty')
656
  def getObjectProperty(self, property, xml, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
657 658 659
    """
    Retrieve the given property
    """
660
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
661 662 663
    # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName == property:
664 665
        if data_type is None:
          data_type = self.getPropertyType(subnode)
666 667 668 669
        try:
          data = subnode.childNodes[0].data
        except IndexError: # There is no data
          data = None
670
        data =  self.convertXmlValue(data, data_type=data_type)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
671 672 673
        return data
    return None

Sebastien Robin's avatar
Sebastien Robin committed
674
  security.declareProtected(Permissions.AccessContentsInformation,'convertToXml')
675
  def convertToXml(self,xml):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
676 677 678
    """
    if xml is a string, convert it to a node
    """
679 680
    if xml is None:
      return
Jean-Paul Smets's avatar
Jean-Paul Smets committed
681
    if type(xml) in (type('a'),type(u'a')):
682
      if type(xml) is type(u'a'):
Sebastien Robin's avatar
Sebastien Robin committed
683
        xml = xml.encode('utf-8')
684
      xml = Parse(xml)
Nicolas Delaby's avatar
Nicolas Delaby committed
685
      #LOG('Conduit.convertToXml not failed',0,'ok')
Sebastien Robin's avatar
Sebastien Robin committed
686
      xml = xml.childNodes[0] # Because we just created a new xml
687
    # If we have the xml from the node erp5, we just take the subnode
Nicolas Delaby's avatar
Nicolas Delaby committed
688
    if xml.nodeName == 'erp5':
689
      xml = self.getElementNodeList(xml)[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
690 691
    return xml

Sebastien Robin's avatar
Sebastien Robin committed
692
  security.declareProtected(Permissions.AccessContentsInformation,'getObjectType')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
693 694 695 696 697
  def getObjectType(self, xml):
    """
    Retrieve the portal type from an xml
    """
    portal_type = None
698
    for subnode in self.getAttributeNodeList(xml):
Nicolas Delaby's avatar
Nicolas Delaby committed
699
      if subnode.nodeName == 'portal_type':
700 701 702
        portal_type = subnode.nodeValue
        portal_type = self.convertXmlValue(portal_type)
        return portal_type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
703 704
    return portal_type

Sebastien Robin's avatar
Sebastien Robin committed
705
  security.declareProtected(Permissions.AccessContentsInformation,'getPropertyType')
706 707 708 709
  def getPropertyType(self, xml):
    """
    Retrieve the portal type from an xml
    """
Sebastien Robin's avatar
Sebastien Robin committed
710
    p_type = None # use getElementsByTagName !!!! XXX
711
    for subnode in self.getAttributeNodeList(xml):
Nicolas Delaby's avatar
Nicolas Delaby committed
712
      if subnode.nodeName == 'type':
713 714 715 716 717
        p_type = subnode.nodeValue
        p_type = self.convertXmlValue(p_type,data_type='string')
        return p_type
    return p_type

Sebastien Robin's avatar
Sebastien Robin committed
718
  security.declareProtected(Permissions.AccessContentsInformation,'getXupdateObjectType')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
719 720 721
  def getXupdateObjectType(self, xml):
    """
    Retrieve the portal type from an xupdate
722
    XXXX  This should not be used any more !!! XXXXXXXXXXX
Jean-Paul Smets's avatar
Jean-Paul Smets committed
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
    """
    if xml.nodeName.find('xupdate')>=0:
      for subnode in self.getElementNodeList(xml):
        if subnode.nodeName == 'xupdate:element':
          for subnode1 in self.getElementNodeList(subnode):
            if subnode1.nodeName == 'xupdate:attribute':
              for attribute in subnode1.attributes:
                if attribute.nodeName == 'name':
                  if attribute.nodeValue == 'portal_type':
                    data = subnode1.childNodes[0].data
                    data = self.convertXmlValue(data)
                    return data
    return None


Sebastien Robin's avatar
Sebastien Robin committed
738
  security.declareProtected(Permissions.ModifyPortalContent, 'newObject')
739
  def newObject(self, object=None, xml=None, simulate=0, reset_local_roles=1, reset_workflow=1):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
740 741 742 743 744
    """
      modify the object with datas from
      the xml (action section)
    """
    args = {}
745 746
    if simulate:
      return
747
    # Retrieve the list of users with a role and delete default roles
748 749 750
    if reset_local_roles:
      user_role_list = map(lambda x:x[0],object.get_local_roles())
      object.manage_delLocalRoles(user_role_list)
Nicolas Delaby's avatar
Nicolas Delaby committed
751
    if getattr(object, 'workflow_history', None) is not None and reset_workflow:
752
      object.workflow_history = PersistentMapping()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
753 754 755
    if xml.nodeName.find('xupdate')>= 0:
      xml = self.getElementNodeList(xml)[0]
    for subnode in self.getElementNodeList(xml):
756
      if subnode.nodeName not in self.NOT_EDITABLE_PROPERTY:
757
        keyword_type = self.getPropertyType(subnode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
758 759 760 761
        # This is the case where the property is a list
        keyword=str(subnode.nodeName)
        if len(subnode.childNodes) > 0: # We check that this tag is not empty
          data = subnode.childNodes[0].data
762 763 764
        else:
          data=None
        args[keyword]=data
765 766
        #if args.has_key(keyword):
        #  LOG('newObject',0,'data: %s' % str(args[keyword]))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
767
        if args.has_key(keyword):
768
          args[keyword] = self.convertXmlValue(args[keyword],keyword_type)
769
      elif subnode.nodeName in self.ADDABLE_PROPERTY:
Sebastien Robin's avatar
Sebastien Robin committed
770
        self.addNode(object=object,xml=subnode, force=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
771 772 773 774
    # We should first edit the object
    args = self.getFormatedArgs(args=args)
    # edit the object with a dictionnary of arguments,
    # like {"telephone_number":"02-5648"}
775
    self.editDocument(object=object,**args)
Nicolas Delaby's avatar
Nicolas Delaby committed
776
    if getattr(object, 'manage_afterEdit', None) is not None:
777
      object.manage_afterEdit()
Nicolas Delaby's avatar
Nicolas Delaby committed
778
    self.afterNewObject(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
779 780

    # Then we may create subobject
781
    for subnode in self.getElementNodeList(xml):
Sebastien Robin's avatar
Sebastien Robin committed
782
      if subnode.nodeName in (self.xml_object_tag,): #,self.history_tag):
Nicolas Delaby's avatar
Nicolas Delaby committed
783
        self.addNode(object=object, xml=subnode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
784

785 786 787 788
  security.declareProtected(Permissions.AccessContentsInformation,'afterNewObject')
  def afterNewObject(self, object):
    pass

Sebastien Robin's avatar
Sebastien Robin committed
789
  security.declareProtected(Permissions.AccessContentsInformation,'getStatusFromXml')
790 791 792 793 794 795 796 797 798 799 800
  def getStatusFromXml(self, xml):
    """
    Return a worklow status from xml
    """
    status = {}
    for subnode in self.getElementNodeList(xml):
      keyword = self.convertXmlValue(subnode.nodeName)
      value = self.getObjectProperty(keyword,xml)
      status[keyword] = value
    return status

Sebastien Robin's avatar
Sebastien Robin committed
801
  security.declareProtected(Permissions.AccessContentsInformation,'getXupdateElementList')
802 803 804 805 806 807 808 809
  def getXupdateElementList(self, xml):
    """
    Retrieve the list of xupdate:element subnodes
    """
    e_list = []
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName in self.XUPDATE_EL:
        e_list += [subnode]
Nicolas Delaby's avatar
Nicolas Delaby committed
810
    #LOG('getXupdateElementList, e_list:',0,e_list)
811 812
    return e_list

Sebastien Robin's avatar
Sebastien Robin committed
813
  security.declareProtected(Permissions.AccessContentsInformation,'getElementFromXupdate')
814 815 816 817 818
  def getElementFromXupdate(self, xml):
    """
    from a xupdate:element returns the element as xml
    """
    if xml.nodeName in self.XUPDATE_EL:
Sebastien Robin's avatar
Sebastien Robin committed
819 820
      result = unicode('<',encoding='utf-8')
      result += xml.attributes.values()[0].nodeValue
821 822
      for subnode in self.getElementNodeList(xml):  #getElementNodeList
        if subnode.nodeName == 'xupdate:attribute':
Sebastien Robin's avatar
Sebastien Robin committed
823
          result += ' ' + subnode.attributes.values()[0].nodeValue + '='
824 825
          result += '"' + subnode.childNodes[0].nodeValue + '"'
      result += '>'
826
      # Then dumps the xml and remove what we does'nt want
Sebastien Robin's avatar
Sebastien Robin committed
827 828 829 830
      #xml_string = StringIO()
      #PrettyPrint(xml,xml_string)
      #xml_string = xml_string.getvalue()
      #xml_string = unicode(xml_string,encoding='utf-8')
831
      xml_string = self.nodeToString(xml)
832
      xml_string = unicode(xml_string, encoding='utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
833 834
      #if type(xml_string) is type (u'a'):
      #  xml_string = xml_string.encode('utf-8')
835 836 837
      maxi = max(xml_string.find('>')+1,\
                 xml_string.rfind('</xupdate:attribute>')+len('</xupdate:attribute>'))
      result += xml_string[maxi:xml_string.find('</xupdate:element>')]
Sebastien Robin's avatar
Sebastien Robin committed
838 839
      result += '</' + xml.attributes.values()[0].nodeValue + '>'
      return self.convertToXml(result.encode('utf-8'))
840 841 842 843 844 845 846 847 848 849 850 851 852 853
    if xml.nodeName in (self.XUPDATE_UPDATE+self.XUPDATE_DEL):
      result = u'<'
      for subnode in self.getAttributeNodeList(xml):
        if subnode.nodeName == 'select':
          attribute = subnode.nodeValue
      s = '[@id='
      s_place = attribute.find(s)
      select_id = None
      if (s_place > 0):
        select_id = attribute[s_place+len(s):]
        select_id = select_id[:select_id.find("'",1)+1]
      else:
        s_place = len(attribute)
      property = attribute[:s_place]
Nicolas Delaby's avatar
Nicolas Delaby committed
854
      if property.find('/') == 0:
855 856 857 858 859
        property = property[1:]
      result += property
      if select_id is not None:
        result += ' id=%s' % select_id
      result +=  '>'
860
      xml_string = self.nodeToString(xml)
861 862 863 864
      xml_string = unicode(xml_string,encoding='utf-8')
      maxi = xml_string.find('>')+1
      result += xml_string[maxi:xml_string.find('</%s>' % xml.nodeName)]
      result += '</' + property + '>'
Nicolas Delaby's avatar
Nicolas Delaby committed
865
      #LOG('getElementFromXupdate, result:',0,repr(result))
866
      return self.convertToXml(result)
867 868
    return xml

Sebastien Robin's avatar
Sebastien Robin committed
869
  security.declareProtected(Permissions.AccessContentsInformation,'getWorkflowActionFromXml')
870 871 872 873 874 875 876 877 878 879 880 881 882
  def getWorkflowActionFromXml(self, xml):
    """
    Return the list of workflow actions
    """
    action_list = []
    if xml.nodeName in self.XUPDATE_EL:
      action_list += [xml]
      return action_list
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName == self.action_tag:
        action_list += [subnode]
    return action_list

Sebastien Robin's avatar
Sebastien Robin committed
883
  security.declareProtected(Permissions.AccessContentsInformation,'convertXmlValue')
884
  def convertXmlValue(self, data, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
885 886
    """
    It is possible that the xml change the value, for example
887 888 889 890 891 892
    there is some too much '\n' and some spaces. We have to do some extra
    things so that we convert correctly the vlalue
    """
    if data is None:
      if data_type in self.list_type_list:
        data = ()
893 894
      if data_type in self.text_type_list:
        data = ''
895
      return data
Jean-Paul Smets's avatar
Jean-Paul Smets committed
896 897 898
    data = data.replace('\n','')
    if type(data) is type(u"a"):
      data = data.encode(self.getEncoding())
Nicolas Delaby's avatar
Nicolas Delaby committed
899
    if data == 'None':
900
      return None
901 902 903 904 905 906
    # We can now convert string in tuple, dict, binary...
    if data_type in self.list_type_list:
      if type(data) is type('a'):
        data = tuple(data.split('@@@'))
    elif data_type in self.text_type_list:
      data = data.replace('@@@','\n')
907 908 909 910 911 912
#     elif data_type in self.binary_type_list:
#       data = data.replace('@@@','\n')
#       msg = MIMEBase('application','octet-stream')
#       Encoders.encode_base64(msg)
#       msg.set_payload(data)
#       data = msg.get_payload(decode=1)
913
      data = unescape(data)
914
    elif data_type in self.pickle_type_list:
915 916 917 918 919
      data = data.replace('@@@','\n')
      msg = MIMEBase('application','octet-stream')
      Encoders.encode_base64(msg)
      msg.set_payload(data)
      data = msg.get_payload(decode=1)
920
      data = pickle.loads(data)
921 922
    elif data_type in self.date_type_list:
      data = DateTime(data)
Sebastien Robin's avatar
Sebastien Robin committed
923 924
    elif data_type in self.int_type_list:
      data = int(data)
Sebastien Robin's avatar
Sebastien Robin committed
925 926 927 928 929 930 931
    elif data_type in self.dict_type_list: # only usefull for CPS, with data = '{fr:1}'
      if data == '{}':
        data = {}
      else:
        dict_list = map(lambda x:x.split(':'),data[1:-1].split(','))
        data = map(lambda (x,y):(x.replace(' ','').replace("'",''),int(y)),dict_list)
        data = dict(data)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
932 933
    return data

934 935
  # XXX is it the right place ? It should be in XupdateUtils, but here we
  # have some specific things to do
Sebastien Robin's avatar
Sebastien Robin committed
936
  security.declareProtected(Permissions.ModifyPortalContent, 'applyXupdate')
937 938
  def applyXupdate(self, object=None, xupdate=None, conduit=None, force=0,
                   simulate=0, **kw):
939 940 941 942 943
    """
    Parse the xupdate and then it will call the conduit
    """
    conflict_list = []
    if type(xupdate) in (type('a'),type(u'a')):
944
      xupdate = Parse(xupdate)
945 946 947 948 949 950 951 952 953 954 955 956 957 958
    #When xupdate mix different object, (like object and his subobject) we need to treat them separatly
    if self.isMixedXupdate(xupdate):
      #return to updateNode with only one line
      #del all sub_element
      sub_node_list = self.getElementNodeList(xupdate)
      #clean the node
      for subnode in sub_node_list:
        #Create one xupdate:modification per update node
        conflict_list += self.updateNode(xml=subnode,
                                         object=object,
                                         force=force,
                                         simulate=simulate,
                                         **kw)
      return conflict_list
959 960 961 962 963
    for subnode in self.getElementNodeList(xupdate):
      sub_xupdate = self.getSubObjectXupdate(subnode)
      selection_name = ''
      if subnode.nodeName in self.XUPDATE_INSERT_OR_ADD:
        conflict_list += conduit.addNode(xml=sub_xupdate,object=object, \
964
                                         force=force, simulate=simulate, **kw)['conflict_list']
965 966
      elif subnode.nodeName in self.XUPDATE_DEL:
        conflict_list += conduit.deleteNode(xml=sub_xupdate, object=object, \
967
                                      force=force, simulate=simulate, **kw)
968 969
      elif subnode.nodeName in self.XUPDATE_UPDATE:
        conflict_list += conduit.updateNode(xml=sub_xupdate, object=object, \
970
                                         force=force, simulate=simulate, **kw)
971 972 973 974 975
      #elif subnode.nodeName in self.XUPDATE_INSERT:
      #  conflict_list += conduit.addNode(xml=subnode, object=object, force=force, **kw)

    return conflict_list

976 977 978 979 980 981
  def isMixedXupdate(self, xml):
    #If an xupdate:modifications contains modification which concerns different objects
    subnode_list = self.getElementNodeList(xml)
    nb_sub = len(subnode_list)
    comp = 0
    for subnode in subnode_list:
982
      value = self.getAttribute(subnode, 'select')
983 984 985 986 987 988
      if re.search(self.object_exp, value):
        comp += 1
    if nb_sub == comp:
      return 0
    return 1

989 990
  def isWorkflowActionAddable(self, object=None,status=None,wf_tool=None,
                              wf_id=None,xml=None):
Sebastien Robin's avatar
Sebastien Robin committed
991 992
    """
    Some checking in order to check if we should add the workfow or not
993 994 995 996
    We should not returns a conflict list as we wanted before, we should
    instead go to a conflict state.
    """
    # We first test if the status in not already inside the workflow_history
997 998
    return 1
    # XXX Disable for now
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
    wf_history = object.workflow_history
    if wf_history.has_key(wf_id):
      action_list = wf_history[wf_id]
    else: action_list = []
    addable = 1
    for action in action_list:
      this_one = 1
      for key in action.keys():
        if status[key] != action[key]:
          this_one = 0
          break
      if this_one:
        addable = 0
        break
    return addable

1015
  security.declareProtected(Permissions.ModifyPortalContent, 'constructContent')
1016
  def constructContent(self, object, object_id, portal_type):
1017 1018 1019 1020 1021
    """
    This allows to specify how to construct a new content.
    This is really usefull if you want to write your
    own Conduit.
    """
1022
    portal_types = getToolByName(object, 'portal_types')
Nicolas Delaby's avatar
Nicolas Delaby committed
1023
    #LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
1024 1025

    object.newContent(portal_type=portal_type, id=object_id)
1026
    subobject = object._getOb(object_id)
1027
    return subobject, 1, 1
1028

1029 1030 1031 1032 1033
  security.declareProtected(Permissions.ModifyPortalContent, 'addWorkflowNode')
  def addWorkflowNode(self, object, xml, simulate):
    """
    This allows to specify how to handle the workflow informations.
    This is really usefull if you want to write your own Conduit.
1034
    """
1035 1036 1037 1038 1039 1040 1041 1042 1043
    conflict_list = []
    # We want to add a workflow action
    wf_tool = getToolByName(object,'portal_workflow')
    wf_id = self.getAttribute(xml,'id')
    if wf_id is None: # History added by xupdate
      wf_id = self.getHistoryIdFromSelect(xml)
      xml = self.getElementNodeList(xml)[0]
    #for action in self.getWorkflowActionFromXml(xml):
    status = self.getStatusFromXml(xml)
Nicolas Delaby's avatar
Nicolas Delaby committed
1044
    #LOG('addNode, status:',0,status)
1045 1046 1047 1048 1049
    add_action = self.isWorkflowActionAddable(object=object,
                                           status=status,wf_tool=wf_tool,
                                           wf_id=wf_id,xml=xml)
    if add_action and not simulate:
      wf_tool.setStatusOf(wf_id,object,status)
1050 1051

    # Specific CPS, try to remove duplicate lines in portal_repository._histories
Sebastien Robin's avatar
Sebastien Robin committed
1052
    tool = getToolByName(self,'portal_repository',None)
1053
    if tool is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
1054
      if getattr(self, 'getDocid', None) is not None:
1055 1056 1057 1058 1059 1060 1061 1062
        docid = self.getDocid()
        history = tool.getHistory(docid)
        new_history = ()
        for history_line in history:
          if history_line not in new_history:
            new_history += (history_line,)
        tool.setHistory(docid,new_history)

1063
    return conflict_list
1064

1065 1066 1067 1068 1069
  security.declareProtected(Permissions.ModifyPortalContent, 'addLocalRoleNode')
  def addLocalRoleNode(self, object, xml):
    """
    This allows to specify how to handle the local role informations.
    This is really usefull if you want to write your own Conduit.
1070 1071
    """
    conflict_list = []
1072 1073 1074 1075
    # We want to add a local role
    roles = self.convertXmlValue(xml.childNodes[0].data,data_type='tokens')
    user = self.getAttribute(xml,'id')
    roles = list(roles) # Needed for CPS, or we have a CPS error
1076
    #LOG('local_role: ',0,'user: %s roles: %s' % (repr(user),repr(roles)))
1077 1078 1079 1080 1081 1082 1083
    #user = roles[0]
    #roles = roles[1:]
    if xml.nodeName.find(self.local_role_tag)>=0:
      object.manage_setLocalRoles(user,roles)
    elif xml.nodeName.find(self.local_group_tag)>=0:
      object.manage_setLocalGroupRoles(user,roles)
    return conflict_list
1084

1085 1086 1087 1088 1089
  security.declareProtected(Permissions.ModifyPortalContent, 'addLocalPermissionNode')
  def addLocalPermissionNode(self, object, xml):
    """
    This allows to specify how to handle the local permision informations.
    This is really usefull if you want to write your own Conduit.
1090 1091
    """
    conflict_list = []
1092
    # We want to add a local role
1093
    #LOG('addLocalPermissionNode, xml',0,xml)
1094 1095
    if len(xml.childNodes)>0:
      roles = self.convertXmlValue(xml.childNodes[0].data,data_type='tokens')
1096
      roles = list(roles) # Needed for CPS, or we have a CPS error
1097
    else:
1098
      roles = ()
1099
    permission = self.getAttribute(xml,'id')
1100
    #LOG('local_role: ',0,'permission: %s roles: %s' % (repr(permission),repr(roles)))
1101 1102 1103
    #user = roles[0]
    #roles = roles[1:]
    if xml.nodeName.find(self.local_permission_tag)>=0:
1104
      object.manage_setLocalPermissions(permission,roles)
1105 1106
    return conflict_list

1107
  security.declareProtected(Permissions.ModifyPortalContent, 'editDocument')
1108 1109 1110 1111 1112
  def editDocument(self, object=None, **kw):
    """
    This is the default editDocument method. This method
    can easily be overwritten.
    """
1113
    object._edit(**kw)
1114

1115 1116 1117 1118 1119 1120 1121 1122
  security.declareProtected(Permissions.ModifyPortalContent, 'getProperty')
  def getProperty(self, object, kw):
    """
    This is the default getProperty method. This method
    can easily be overwritten.
    """
    return object.getProperty(kw)

1123 1124 1125 1126 1127 1128 1129
  def nodeToString(self, node):
    """
    return an xml string corresponding to the node
    """
    buf = cStringIO.StringIO()
    Print(node, stream=buf, encoding='utf-8')
    xml_string = buf.getvalue()
1130
    buf.close()
1131
    return xml_string
1132 1133 1134 1135 1136 1137 1138

  def getGidFromObject(self, object):
    """
    return the Gid composed with the object informations
    """
    return object.getId()

1139 1140 1141 1142 1143
  #def getGidFromXML(self, xml, gid_from_xml_list):
    #"""
    #return the Gid composed with xml informations
    #"""
    #return None
1144