ERP5Conduit.py 45.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 29
##############################################################################
#
# 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.
#
##############################################################################

from Products.ERP5SyncML.SyncCode import SyncCode
30
from Products.ERP5SyncML.XMLSyncUtils import XMLSyncUtilsMixin
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31 32 33 34 35 36
from Products.ERP5SyncML.Subscription import Conflict
from Products.CMFCore.utils import getToolByName
from Products.ERP5SyncML.XupdateUtils import XupdateUtils
from Products.ERP5Type.Utils import convertToUpperCase
from Products.ERP5Type.Accessor.TypeDefinition import list_types
from xml.dom.ext.reader.Sax2 import FromXml
Sebastien Robin's avatar
Sebastien Robin committed
37
from xml.dom.minidom import parse, parseString
38
from DateTime.DateTime import DateTime
Jean-Paul Smets's avatar
Jean-Paul Smets committed
39 40
from email.MIMEBase import MIMEBase
from email import Encoders
Sebastien Robin's avatar
Sebastien Robin committed
41
from AccessControl import ClassSecurityInfo
42
from AccessControl.PermissionMapping import setPermissionMapping
Sebastien Robin's avatar
Sebastien Robin committed
43
from Products.ERP5Type import Permissions
44 45
import pickle
import string
46 47
from xml.dom.ext import PrettyPrint
from cStringIO import StringIO
48
from xml.sax.saxutils import escape, unescape
49
import re, copy
Jean-Paul Smets's avatar
Jean-Paul Smets committed
50

51

Jean-Paul Smets's avatar
Jean-Paul Smets committed
52 53
from zLOG import LOG

54
class ERP5Conduit(XMLSyncUtilsMixin):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
  """
    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

76 77 78 79 80 81 82 83 84 85 86 87 88 89
    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
90

91
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
92

Sebastien Robin's avatar
Sebastien Robin committed
93 94
  # Declarative security
  security = ClassSecurityInfo()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
95

Sebastien Robin's avatar
Sebastien Robin committed
96
  security.declareProtected(Permissions.AccessContentsInformation,'getEncoding')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
97 98 99 100
  def getEncoding(self):
    """
    return the string corresponding to the local encoding
    """
101 102
    #return "iso-8859-1"
    return "utf-8"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
103

Sebastien Robin's avatar
Sebastien Robin committed
104
  security.declareProtected(Permissions.ModifyPortalContent, '__init__')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
105 106
  def __init__(self):
    self.args = {}
107

Sebastien Robin's avatar
Sebastien Robin committed
108
  security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
109
  def addNode(self, xml=None, object=None, previous_xml=None,
110
              object_id=None, force=0, simulate=0, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
111 112 113
    """
    A node is added

114 115 116 117 118 119 120 121
    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
122 123
    This fucntion returns conflict_list, wich is of the form,
    [conflict1,conflict2,...] where conclict1 is of the form :
124
    [object.getPath(),keyword,local_and_actual_value,subscriber_value]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
125 126
    """
    conflict_list = []
127
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
128 129 130 131
    LOG('addNode',0,'xml_reconstitued: %s' % str(xml))
    # In the case where this new node is a object to add
    LOG('addNode',0,'object.id: %s' % object.getId())
    LOG('addNode',0,'xml.nodeName: %s' % xml.nodeName)
132
    LOG('addNode',0,'getSubObjectDepth: %i' % self.getSubObjectDepth(xml))
Sebastien Robin's avatar
Sebastien Robin committed
133
    LOG('addNode',0,'isHistoryAdd: %i' % self.isHistoryAdd(xml))
Sebastien Robin's avatar
Sebastien Robin committed
134
    LOG('addNode xml',0,repr(xml.toxml()))
135
    if xml.nodeName in self.XUPDATE_INSERT_OR_ADD and self.getSubObjectDepth(xml)==0:
Sebastien Robin's avatar
Sebastien Robin committed
136 137 138 139
      if self.isHistoryAdd(xml)!=-1: # bad hack XXX to be removed
        for element in self.getXupdateElementList(xml):
          xml = self.getElementFromXupdate(element)
          conflict_list += self.addNode(xml=xml,object=object,
140 141
                          previous_xml=previous_xml, force=force,
                          simulate=simulate, **kw)
142
    elif xml.nodeName == 'object':
143 144
      if object_id is None:
        object_id = self.getAttribute(xml,'id')
145
      docid = self.getObjectDocid(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
146 147 148
      LOG('addNode',0,'object_id: %s' % object_id)
      if object_id is not None:
        try:
149
          subobject = object._getOb(object_id)
150
        except (AttributeError, KeyError, TypeError):
151
          subobject = None
152
        if subobject is None: # If so, it doesn't exist
Jean-Paul Smets's avatar
Jean-Paul Smets committed
153 154 155
          portal_type = ''
          if xml.nodeName == 'object':
            portal_type = self.getObjectType(xml)
156 157
          elif xml.nodeName in self.XUPDATE_INSERT_OR_ADD: # Deprecated ???
            portal_type = self.getXupdateObjectType(xml) # Deprecated ???
158
          subobject = self.constructContent(object, object_id, docid, portal_type)
159
        self.newObject(object=subobject,xml=xml,simulate=simulate)
160
    elif xml.nodeName in self.XUPDATE_INSERT_OR_ADD \
161
         and self.getSubObjectDepth(xml)>=1:
162 163 164 165
      sub_object_id = self.getSubObjectId(xml)
      LOG('addNode',0,'getSubObjectModification number: %s' % sub_object_id)
      if previous_xml is not None and sub_object_id is not None:
        LOG('addNode',0,'previous xml is not none and also sub_object_id')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
166
        # Find the previous xml corresponding to this subobject
167
        sub_previous_xml = self.getSubObjectXml(sub_object_id,previous_xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
168 169 170 171
        LOG('addNode',0,'isSubObjectModification sub_p_xml: %s' % str(sub_previous_xml))
        if sub_previous_xml is not None:
          sub_object = None
          try:
172
            sub_object = object._getOb(sub_object_id)
173
          except (AttributeError, KeyError, TypeError):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
174 175 176 177 178 179 180 181 182
            pass
          if sub_object is not None:
            LOG('addNode',0,'subobject.id: %s' % sub_object.id)
            # Change the xml in order to directly apply
            # modifications to the subobject
            sub_xml = self.getSubObjectXupdate(xml)
            LOG('addNode',0,'sub_xml: %s' % str(sub_xml))
            # Then do the udpate
            conflict_list += self.addNode(xml=sub_xml,object=sub_object,
183 184
                            previous_xml=sub_previous_xml, force=force,
                            simulate=simulate, **kw)
185
    elif xml.nodeName == self.history_tag or self.isHistoryAdd(xml)>0:
186
      conflict_list += self.addWorkflowNode(object, xml, simulate)
Sebastien Robin's avatar
Sebastien Robin committed
187 188
    #elif xml.nodeName in self.local_role_list or self.isLocalRole(xml)>0 and not simulate:
    elif xml.nodeName in self.local_role_list:
189
      conflict_list += self.addLocalRoleNode(object, xml)
190
    elif xml.nodeName in self.local_permission_list:
191
      conflict_list += self.addLocalPermissionNode(object, xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
192
    else:
193 194
      conflict_list += self.updateNode(xml=xml,object=object, force=force,
                                       simulate=simulate,  **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
195 196
    return conflict_list

Sebastien Robin's avatar
Sebastien Robin committed
197
  security.declareProtected(Permissions.ModifyPortalContent, 'deleteNode')
198 199
  def deleteNode(self, xml=None, object=None, object_id=None, force=None,
                 simulate=0, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
200 201 202
    """
    A node is deleted
    """
203
    # In the case where we have to delete an object
Jean-Paul Smets's avatar
Jean-Paul Smets committed
204
    LOG('ERP5Conduit',0,'deleteNode')
205
    LOG('ERP5Conduit',0,'deleteNode, object.id: %s' % object.getId())
206
    LOG('ERP5Conduit',0,'deleteNode, object path: %s' % repr(object.getPhysicalPath()))
207
    conflict_list = []
208 209
    if xml is not None:
      xml = self.convertToXml(xml)
210 211 212
    if object_id is None:
      LOG('ERP5Conduit',0,'deleteNode, SubObjectDepth: %i' % self.getSubObjectDepth(xml))
      if xml.nodeName == self.xml_object_tag:
213
        object_id = self.getAttribute(xml,'id')
214 215
      elif self.getSubObjectDepth(xml)==1:
        object_id = self.getSubObjectId(xml)
216
        #LOG('ERP5Conduit',0,'deleteNode, SubObjectDepth: %i' % self.getSubObjectDepth(xml))
217 218 219
      elif self.getSubObjectDepth(xml)==2:
        # we have to call delete node on a subsubobject
        sub_object_id = self.getSubObjectId(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
220
        try:
221 222 223
          sub_object = object._getOb(sub_object_id)
          sub_xml = self.getSubObjectXupdate(xml)
          conflict_list += self.deleteNode(xml=sub_xml,object=sub_object,
224
                                           force=force, simulate=simulate, **kw)
225
        except (KeyError, AttributeError, TypeError):
226
          LOG('ERP5Conduit',0,'deleteNode, Unable to delete SubObject: %s' % str(sub_object_id))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
227
          pass
228
    if object_id is not None: # We do have an object_id
229 230 231
      try:
        object._delObject(object_id)
      except (AttributeError, KeyError):
232
        LOG('ERP5Conduit',0,'deleteNode, Unable to delete: %s' % str(object_id))
233
        pass
234 235 236 237 238 239 240 241
    # 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')
        LOG('local_role: ',0,'user: %s' % repr(user))
242 243 244 245
        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])
246 247
      if xml.nodeName in self.local_permission_list and not simulate:
        permission = self.getAttribute(xml,'id')
248
        object.manage_setLocalPermissions(permission)
249
    return conflict_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
250

Sebastien Robin's avatar
Sebastien Robin committed
251
  security.declareProtected(Permissions.ModifyPortalContent, 'updateNode')
252 253
  def updateNode(self, xml=None, object=None, previous_xml=None, force=0,
                 simulate=0,  **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
254 255 256 257 258 259 260 261
    """
    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 = []
262
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
263
    LOG('updateNode',0,'xml.nodeName: %s' % xml.nodeName)
Sebastien Robin's avatar
Sebastien Robin committed
264
    LOG('updateNode, force: ',0,force)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
265 266
    # we have an xupdate xml
    if xml.nodeName == 'xupdate:modifications':
Sebastien Robin's avatar
Sebastien Robin committed
267
      conflict_list += self.applyXupdate(object=object,xupdate=xml,conduit=self,
268 269
                                 previous_xml=previous_xml, force=force, simulate=simulate,
                                 **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
270 271 272
    # we may have only the part of an xupdate
    else:
      args = {}
273
      LOG('isSubObjectModification',0,'result: %s' % str(self.isSubObjectModification(xml)))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
274
      if self.isProperty(xml) and not(self.isSubObjectModification(xml)):
275
        keyword = None
276 277
        for subnode in self.getAttributeNodeList(xml):
          if subnode.nodeName=='select':
278 279 280
            LOG('updateNode',0,'selection: %s' % str(subnode.nodeValue))
            select_list = subnode.nodeValue.split('/') # Something like:
                                                       #('','object[1]','sid[1]')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
281 282
            new_select_list = ()
            for select_item in select_list:
283 284 285
              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
286 287
            select_list = new_select_list # Something like : ('','object','sid')
            keyword = select_list[len(select_list)-1] # this will be 'sid'
288 289
        data_xml = xml
        data = None
290 291
        LOG('updateNode',0,'keyword: %s' % str(keyword))
        if not (xml.nodeName in self.XUPDATE_INSERT_OR_ADD):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
292 293 294 295 296
          for subnode in self.getElementNodeList(xml):
            if subnode.nodeName=='xupdate:element':
              for subnode1 in subnode.attributes:
                if subnode1.nodeName=='name':
                  keyword = subnode1.nodeValue
297
              data_xml = subnode
298 299
        if keyword is None: # This is not a selection, directly the property
          keyword = xml.nodeName
300 301 302 303 304
        if len(self.getElementNodeList(data_xml))==0:
          try:
            data = data_xml.childNodes[0].data
          except IndexError: # There is no data
            data = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
305 306
        if not (keyword in self.NOT_EDITABLE_PROPERTY):
          # We will look for the data to enter
307 308 309
          data_type = object.getPropertyType(keyword)
          LOG('updateNode',0,'data_type: %s' % str(data_type))
          data = self.convertXmlValue(data,data_type=data_type)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
310 311 312 313 314 315 316 317
          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
          #   - old_data : the data from this box but at the time of the last synchronization
          #   - current_data : the data actually on this box
          isConflict = 0
Sebastien Robin's avatar
Sebastien Robin committed
318
          if (previous_xml is not None) and (not force): # if no previous_xml, no conflict
319
            old_data = self.getObjectProperty(keyword,previous_xml,data_type=data_type)
320 321
            #current_data = object.getProperty(keyword)
            current_data = self.getProperty(object, keyword)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
322 323 324
            LOG('updateNode',0,'Conflict data: %s' % str(data))
            LOG('updateNode',0,'Conflict old_data: %s' % str(old_data))
            LOG('updateNode',0,'Conflict current_data: %s' % str(current_data))
325 326
            if (old_data != current_data) and (data != current_data) \
                and keyword not in self.force_conflict_list:
327 328 329
              LOG('updateNode',0,'Conflict on : %s' % keyword)
              # Hack in order to get the synchronization working for demo
              # XXX this have to be removed after
330 331
              #if not (data_type in self.binary_type_list):
              if 1:
332 333
                # This is a conflict
                isConflict = 1
334 335
                string_io = StringIO()
                PrettyPrint(xml,stream=string_io)
Sebastien Robin's avatar
Sebastien Robin committed
336 337
                conflict = Conflict(object_path=object.getPhysicalPath(),
                                    keyword=keyword)
338
                conflict.setXupdate(string_io.getvalue())
Sebastien Robin's avatar
Sebastien Robin committed
339 340 341
                if not (data_type in self.binary_type_list):
                  conflict.setLocalValue(current_data)
                  conflict.setRemoteValue(data)
342 343 344 345
                conflict_list += [conflict]
                #conflict_list += [Conflict(object_path=object.getPhysicalPath(),
                #                           keyword=keyword,
                #                           xupdate=string_io)]
346 347
                                           #publisher_value=current_data, # not needed any more
                                           #subscriber_value=data)] # not needed any more
Jean-Paul Smets's avatar
Jean-Paul Smets committed
348
          # We will now apply the argument with the method edit
349
          if args != {} and (isConflict==0 or force) and (not simulate):
350
            LOG('updateNode',0,'object._edit, args: %s' % str(args))
351 352
            #object._edit(**args)
            self.editDocument(object=object,**args)
353 354 355
            # It is sometimes required to do something after an edit
            if hasattr(object,'manage_afterEdit'):
              object.manage_afterEdit()
356

Jean-Paul Smets's avatar
Jean-Paul Smets committed
357 358 359
        if keyword == 'object':
          # This is the case where we have to call addNode
          LOG('updateNode',0,'we will add sub-object')
360 361 362
          conflict_list += self.addNode(xml=subnode,object=object,force=force,
                                        simulate=simulate, **kw)
        elif keyword == self.history_tag and not simulate:
363
          # This is the case where we have to call addNode
364
          LOG('updateNode',0,'we will add history')
365 366
          conflict_list += self.addNode(xml=subnode,object=object,force=force,
                                        simulate=simulate,**kw)
Sebastien Robin's avatar
Sebastien Robin committed
367
        elif keyword in (self.local_role_tag,self.local_permission_tag) and not simulate:
368
          # This is the case where we have to update Roles or update permission
369
          LOG('updateNode',0,'we will add a local role')
370 371 372 373 374 375
          #user = self.getSubObjectId(xml)
          #roles = self.convertXmlValue(data,data_type='tokens')
          #object.manage_setLocalRoles(user,roles)
          xml = self.getElementFromXupdate(xml)
          conflict_list += self.addNode(xml=xml,object=object,force=force,
                                        simulate=simulate,**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
376 377 378
      elif self.isSubObjectModification(xml):
        # We should find the object corresponding to
        # this update, so we have to look in the previous_xml
379 380 381 382 383
        sub_object_id = self.getSubObjectId(xml)
        LOG('updateNode',0,'getSubObjectModification number: %s' % sub_object_id)
        if previous_xml is not None and sub_object_id is not None:
          LOG('updateNode',0,'previous xml is not none and also sub_object_id')
          sub_previous_xml = self.getSubObjectXml(sub_object_id,previous_xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
384 385 386 387
          LOG('updateNode',0,'isSubObjectModification sub_p_xml: %s' % str(sub_previous_xml))
          if sub_previous_xml is not None:
            sub_object = None
            try:
388
              sub_object = object[sub_object_id]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
389 390 391 392 393 394 395 396 397 398
            except KeyError:
              pass
            if sub_object is not None:
              LOG('updateNode',0,'subobject.id: %s' % sub_object.id)
              # Change the xml in order to directly apply
              # modifications to the subobject
              sub_xml = self.getSubObjectXupdate(xml)
              LOG('updateNode',0,'sub_xml: %s' % str(sub_xml))
              # Then do the udpate
              conflict_list += self.updateNode(xml=sub_xml,object=sub_object, force=force,
399
                              previous_xml=sub_previous_xml,simulate=simulate, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
400 401
    return conflict_list

Sebastien Robin's avatar
Sebastien Robin committed
402
  security.declareProtected(Permissions.AccessContentsInformation,'getFormatedArgs')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
  def getFormatedArgs(self, args=None):
    """
    This lookd inside the args dictionnary and then
    convert any unicode string to string
    """
    LOG('ERP5Conduit.getFormatedArgs',0,'args: %s' % str(args))
    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())
419
            item = item.replace('@@@','\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
420 421 422 423
          new_data += [item]
        data = new_data
      if type(data) is type(u"a"):
        data = data.encode(self.getEncoding())
424
        data = data.replace('@@@','\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
425 426 427 428 429 430 431 432 433
      if keyword == 'binary_data':
        LOG('ERP5Conduit.getFormatedArgs',0,'binary_data keyword: %s' % str(keyword))
        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
434
  security.declareProtected(Permissions.AccessContentsInformation,'isProperty')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
435 436 437 438
  def isProperty(self, xml):
    """
    Check if it is a simple property
    """
439
    bad_list = (self.sub_object_exp,self.history_exp)
440 441
    for subnode in self.getAttributeNodeList(xml):
      if subnode.nodeName=='select':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
442 443
        value = subnode.nodeValue
        for bad_string in bad_list:
444
          if re.search(bad_string,value) is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
445 446 447
            return 0
    return 1

Sebastien Robin's avatar
Sebastien Robin committed
448
  security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectXupdate')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
449 450 451 452 453
  def getSubObjectXupdate(self, xml):
    """
    This will change the xml in order to change the update
    from the object to the subobject
    """
454 455 456 457
    xml = copy.deepcopy(xml)
    for subnode in self.getAttributeNodeList(xml):
      if subnode.nodeName=='select':
        subnode.nodeValue = self.getSubObjectSelect(subnode.nodeValue)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
458 459
    return xml

Sebastien Robin's avatar
Sebastien Robin committed
460
  security.declareProtected(Permissions.AccessContentsInformation,'isHistoryAdd')
461
  def isHistoryAdd(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
462
    bad_list = (self.history_exp,)
463 464
    for subnode in self.getAttributeNodeList(xml):
      if subnode.nodeName=='select':
465 466 467
        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
468 469 470 471
            if re.search(self.bad_history_exp,value) is None:
              return 1
            else:
              return -1
472 473
    return 0

Sebastien Robin's avatar
Sebastien Robin committed
474
  security.declareProtected(Permissions.AccessContentsInformation,'isSubObjectModification')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
475 476 477 478
  def isSubObjectModification(self, xml):
    """
    Check if it is a modification from an subobject
    """
479
    good_list = (self.sub_object_exp,)
480 481
    for subnode in self.getAttributeNodeList(xml) :
      if subnode.nodeName=='select':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
482 483 484
        value = subnode.nodeValue
        LOG('isSubObjectModification',0,'value: %s' % value)
        for good_string in good_list:
485
          if re.search(good_string,value) is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
486 487 488
            return 1
    return 0

Sebastien Robin's avatar
Sebastien Robin committed
489
  security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectDepth')
490 491 492 493 494 495 496 497 498
  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
    """
    LOG('getSubObjectDepth',0,'xml.nodeName: %s' % xml.nodeName)
    if xml.nodeName in self.XUPDATE_TAG:
499 500 501
      i = 0
      if xml.nodeName in self.XUPDATE_INSERT:
        i = 1
502 503 504 505 506 507
      LOG('getSubObjectDepth',0,'xml2.nodeName: %s' % xml.nodeName)
      for subnode in self.getAttributeNodeList(xml):
        LOG('getSubObjectDepth',0,'subnode.nodeName: %s' % subnode.nodeName)
        if subnode.nodeName == 'select':
          value = subnode.nodeValue
          LOG('getSubObjectDepth',0,'subnode.nodeValue: %s' % subnode.nodeValue)
508 509 510 511 512 513 514 515 516 517
          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
518 519
    return 0

Sebastien Robin's avatar
Sebastien Robin committed
520
  security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectSelect')
521
  def getSubObjectSelect(self, select):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
522
    """
523 524
    Return a string wich is the selection for the subobject
    ex: for "/object[@id='161']/object[@id='default_address']/street_address"
525
    it returns "/object[@id='default_address']/street_address"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
526
    """
527 528 529 530 531 532
    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 = '/'
533 534 535
      select = new_value
    return select

Sebastien Robin's avatar
Sebastien Robin committed
536
  security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectId')
537 538 539 540 541 542 543
  def getSubObjectId(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
    for subnode in self.getAttributeNodeList(xml):
      if subnode.nodeName=='select':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
544
        value = subnode.nodeValue
545 546 547 548
        if re.search(self.object_exp,value) is not None:
          s = "'"
          first = value.find(s)+1
          object_id = value[first:value.find(s,first)]
549 550 551
          return object_id
    return object_id

Sebastien Robin's avatar
Sebastien Robin committed
552
  security.declareProtected(Permissions.AccessContentsInformation,'getHistoryIdFromSelect')
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
  def getHistoryIdFromSelect(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
    for subnode in self.getAttributeNodeList(xml):
      if subnode.nodeName=='select':
        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

Sebastien Robin's avatar
Sebastien Robin committed
569
  security.declareProtected(Permissions.AccessContentsInformation,'getSubObjectXml')
570 571 572 573 574 575 576 577
  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):
      if subnode.nodeName==self.xml_object_tag:
        LOG('getSub0bjectXml: object_id:',0,object_id)
578
        if object_id == self.getAttribute(subnode,'id'):
579 580
          return subnode
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
581

Sebastien Robin's avatar
Sebastien Robin committed
582
  security.declareProtected(Permissions.AccessContentsInformation,'getAttribute')
583
  def getAttribute(self, xml, param):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
584
    """
585
    Retrieve the given parameter from the xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
586
    """
587
    for attribute in self.getAttributeNodeList(xml):
588
      if attribute.nodeName == param:
589
        data = attribute.childNodes[0].data
590
        return self.convertXmlValue(data,data_type='string')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
591 592
    return None

Sebastien Robin's avatar
Sebastien Robin committed
593
  security.declareProtected(Permissions.AccessContentsInformation,'getObjectDocid')
594 595 596 597 598 599 600 601 602
  def getObjectDocid(self, xml):
    """
    Retrieve the docid
    """
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName == 'docid':
        data = subnode.childNodes[0].data
        return self.convertXmlValue(data)
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
603

Sebastien Robin's avatar
Sebastien Robin committed
604
  security.declareProtected(Permissions.AccessContentsInformation,'getObjectProperty')
605
  def getObjectProperty(self, property, xml, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
606 607 608
    """
    Retrieve the given property
    """
609
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
610 611 612
    # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName == property:
613 614
        if data_type is None:
          data_type = self.getPropertyType(subnode)
615 616 617 618
        try:
          data = subnode.childNodes[0].data
        except IndexError: # There is no data
          data = None
619
        data =  self.convertXmlValue(data, data_type=data_type)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
620 621 622
        return data
    return None

Sebastien Robin's avatar
Sebastien Robin committed
623
  security.declareProtected(Permissions.AccessContentsInformation,'convertToXml')
624
  def convertToXml(self,xml):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
625 626 627 628
    """
    if xml is a string, convert it to a node
    """
    if type(xml) in (type('a'),type(u'a')):
Sebastien Robin's avatar
Sebastien Robin committed
629
      LOG('Conduit.convertToXml xml',0,repr(xml))
630
      if type(xml) is type(u'a'):
Sebastien Robin's avatar
Sebastien Robin committed
631 632 633 634
        xml = xml.encode('utf-8')
      xml = parseString(xml)
      LOG('Conduit.convertToXml not failed',0,'ok')
      xml = xml.childNodes[0] # Because we just created a new xml
635 636 637
    # If we have the xml from the node erp5, we just take the subnode
    if xml.nodeName=='erp5':
      xml = self.getElementNodeList(xml)[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
638 639
    return xml

Sebastien Robin's avatar
Sebastien Robin committed
640
  security.declareProtected(Permissions.AccessContentsInformation,'getObjectType')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
641 642 643 644 645
  def getObjectType(self, xml):
    """
    Retrieve the portal type from an xml
    """
    portal_type = None
646 647 648 649 650
    for subnode in self.getAttributeNodeList(xml):
      if subnode.nodeName=='portal_type':
        portal_type = subnode.nodeValue
        portal_type = self.convertXmlValue(portal_type)
        return portal_type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
651 652
    return portal_type

Sebastien Robin's avatar
Sebastien Robin committed
653
  security.declareProtected(Permissions.AccessContentsInformation,'getPropertyType')
654 655 656 657
  def getPropertyType(self, xml):
    """
    Retrieve the portal type from an xml
    """
Sebastien Robin's avatar
Sebastien Robin committed
658
    p_type = None # use getElementsByTagName !!!! XXX
659 660 661 662 663 664 665
    for subnode in self.getAttributeNodeList(xml):
      if subnode.nodeName=='type':
        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
666
  security.declareProtected(Permissions.AccessContentsInformation,'getXupdateObjectType')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
667 668 669
  def getXupdateObjectType(self, xml):
    """
    Retrieve the portal type from an xupdate
670
    XXXX  This should not be used any more !!! XXXXXXXXXXX
Jean-Paul Smets's avatar
Jean-Paul Smets committed
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
    """
    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
686
  security.declareProtected(Permissions.ModifyPortalContent, 'newObject')
687
  def newObject(self, object=None, xml=None, simulate=0):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
688 689 690 691 692
    """
      modify the object with datas from
      the xml (action section)
    """
    args = {}
693 694
    if simulate:
      return
695 696 697
    # Retrieve the list of users with a role and delete default roles
    user_role_list = map(lambda x:x[0],object.get_local_roles())
    object.manage_delLocalRoles(user_role_list)
Sebastien Robin's avatar
Sebastien Robin committed
698 699
    if hasattr(object,'workflow_history'):
      object.workflow_history = {}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
700 701 702 703
    if xml.nodeName.find('xupdate')>= 0:
      xml = self.getElementNodeList(xml)[0]
    for subnode in self.getElementNodeList(xml):
      if not(subnode.nodeName in self.NOT_EDITABLE_PROPERTY):
704
        keyword_type = self.getPropertyType(subnode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
705 706 707 708 709 710 711 712
        LOG('newObject',0,str(subnode.childNodes))
        # 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
          args[keyword]=data
        LOG('newObject',0,'keyword: %s' % str(keyword))
        LOG('newObject',0,'keywordtype: %s' % str(keyword_type))
713 714
        #if args.has_key(keyword):
        #  LOG('newObject',0,'data: %s' % str(args[keyword]))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
715
        if args.has_key(keyword):
716
          args[keyword] = self.convertXmlValue(args[keyword],keyword_type)
717
      elif subnode.nodeName in self.ADDABLE_PROPERTY:
Sebastien Robin's avatar
Sebastien Robin committed
718
        self.addNode(object=object,xml=subnode, force=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
719 720
    # We should first edit the object
    args = self.getFormatedArgs(args=args)
721
    LOG('newObject',0,"object.getphyspath: %s" % str(object.getPhysicalPath()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
722 723 724
    LOG('newObject',0,"args: %s" % str(args))
    # edit the object with a dictionnary of arguments,
    # like {"telephone_number":"02-5648"}
725 726
    #object._edit(**args)
    self.editDocument(object=object,**args)
727 728 729
    if hasattr(object,'manage_afterEdit'):
      object.manage_afterEdit()

Jean-Paul Smets's avatar
Jean-Paul Smets committed
730 731

    # Then we may create subobject
732
    for subnode in self.getElementNodeList(xml):
Sebastien Robin's avatar
Sebastien Robin committed
733
      if subnode.nodeName in (self.xml_object_tag,): #,self.history_tag):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
734 735
        self.addNode(object=object,xml=subnode)

Sebastien Robin's avatar
Sebastien Robin committed
736
  security.declareProtected(Permissions.AccessContentsInformation,'getStatusFromXml')
737 738 739 740 741 742 743 744 745 746 747
  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
748
  security.declareProtected(Permissions.AccessContentsInformation,'getXupdateElementList')
749 750 751 752 753 754 755 756
  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]
757
    LOG('getXupdateElementList, e_list:',0,e_list)
758 759
    return e_list

Sebastien Robin's avatar
Sebastien Robin committed
760
  security.declareProtected(Permissions.AccessContentsInformation,'getElementFromXupdate')
761 762 763 764 765
  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
766 767
      result = unicode('<',encoding='utf-8')
      result += xml.attributes.values()[0].nodeValue
768 769
      for subnode in self.getElementNodeList(xml):  #getElementNodeList
        if subnode.nodeName == 'xupdate:attribute':
Sebastien Robin's avatar
Sebastien Robin committed
770
          result += ' ' + subnode.attributes.values()[0].nodeValue + '='
771 772
          result += '"' + subnode.childNodes[0].nodeValue + '"'
      result += '>'
773
      # Then dumps the xml and remove what we does'nt want
Sebastien Robin's avatar
Sebastien Robin committed
774 775 776 777 778
      #xml_string = StringIO()
      #PrettyPrint(xml,xml_string)
      #xml_string = xml_string.getvalue()
      #xml_string = unicode(xml_string,encoding='utf-8')
      xml_string = xml.toxml(encoding='utf-8')
779
      xml_string = unicode(xml_string,encoding='utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
780 781
      #if type(xml_string) is type (u'a'):
      #  xml_string = xml_string.encode('utf-8')
782 783 784
      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
785 786
      result += '</' + xml.attributes.values()[0].nodeValue + '>'
      return self.convertToXml(result.encode('utf-8'))
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
    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]
      if property.find('/')==0:
        property = property[1:]
      result += property
      if select_id is not None:
        result += ' id=%s' % select_id
      result +=  '>'
      # Then dumps the xml and remove what we does'nt want
Sebastien Robin's avatar
Sebastien Robin committed
808 809 810 811 812
      #xml_string = StringIO()
      #PrettyPrint(xml,xml_string)
      #xml_string = xml_string.getvalue()
      #xml_string = unicode(xml_string,encoding='utf-8')
      xml_string = xml.toxml(encoding='utf-8')
813 814 815 816 817 818
      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 + '>'
      LOG('getElementFromXupdate, result:',0,repr(result))
      return self.convertToXml(result)
819 820
    return xml

Sebastien Robin's avatar
Sebastien Robin committed
821
  security.declareProtected(Permissions.AccessContentsInformation,'getWorkflowActionFromXml')
822 823 824 825 826 827 828 829 830 831 832 833 834
  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
835
  security.declareProtected(Permissions.AccessContentsInformation,'convertXmlValue')
836
  def convertXmlValue(self, data, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
837 838
    """
    It is possible that the xml change the value, for example
839 840 841 842 843 844
    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 = ()
845 846
      if data_type in self.text_type_list:
        data = ''
847
      return data
Jean-Paul Smets's avatar
Jean-Paul Smets committed
848 849 850
    data = data.replace('\n','')
    if type(data) is type(u"a"):
      data = data.encode(self.getEncoding())
851 852
    if data=='None':
      return None
853 854 855 856 857 858
    # 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')
859 860 861 862 863 864
#     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)
865
      data = unescape(data)
866
    elif data_type in self.pickle_type_list:
867 868 869 870 871
      data = data.replace('@@@','\n')
      msg = MIMEBase('application','octet-stream')
      Encoders.encode_base64(msg)
      msg.set_payload(data)
      data = msg.get_payload(decode=1)
872
      data = pickle.loads(data)
873 874
    elif data_type in self.date_type_list:
      data = DateTime(data)
Sebastien Robin's avatar
Sebastien Robin committed
875 876
    elif data_type in self.int_type_list:
      data = int(data)
Sebastien Robin's avatar
Sebastien Robin committed
877 878 879 880 881 882 883
    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)
884
    LOG('convertXmlValue',0,'data: %s' % str(data))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
885 886
    return data

887 888
  # 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
889
  security.declareProtected(Permissions.ModifyPortalContent, 'applyXupdate')
890 891
  def applyXupdate(self, object=None, xupdate=None, conduit=None, force=0,
                   simulate=0, **kw):
892 893 894 895 896
    """
    Parse the xupdate and then it will call the conduit
    """
    conflict_list = []
    if type(xupdate) in (type('a'),type(u'a')):
Sebastien Robin's avatar
Sebastien Robin committed
897
      xupdate = parseString(xupdate)
898 899 900 901 902 903

    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, \
904
                                         force=force, simulate=simulate, **kw)
905 906
      elif subnode.nodeName in self.XUPDATE_DEL:
        conflict_list += conduit.deleteNode(xml=sub_xupdate, object=object, \
907
                                         force=force, simulate=simulate, **kw)
908 909
      elif subnode.nodeName in self.XUPDATE_UPDATE:
        conflict_list += conduit.updateNode(xml=sub_xupdate, object=object, \
910
                                         force=force, simulate=simulate, **kw)
911 912 913 914 915
      #elif subnode.nodeName in self.XUPDATE_INSERT:
      #  conflict_list += conduit.addNode(xml=subnode, object=object, force=force, **kw)

    return conflict_list

916 917
  def isWorkflowActionAddable(self, object=None,status=None,wf_tool=None,
                              wf_id=None,xml=None):
Sebastien Robin's avatar
Sebastien Robin committed
918 919
    """
    Some checking in order to check if we should add the workfow or not
920 921 922 923
    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
924 925
    return 1
    # XXX Disable for now
926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941
    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

942 943 944 945 946 947 948 949 950 951
  security.declareProtected(Permissions.ModifyPortalContent, 'constructContent')
  def constructContent(self, object, object_id, docid, portal_type):
    """
    This allows to specify how to construct a new content.
    This is really usefull if you want to write your
    own Conduit.
    """
    portal_types = getToolByName(object,'portal_types')
    LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
    if docid==None: # ERP5 content
952
      object.newContent(portal_type=portal_type,id=object_id)
953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969
    else: # CPS content
      # This is specific to CPS, we will call the proxy tool
      px_tool= getToolByName(object,'portal_proxies')
      trees_tool= getToolByName(object,'portal_trees')
      proxy_type = 'document'
      if portal_type in ('Workspace','Section'):
        proxy_type = 'folder'
      proxy = px_tool.createEmptyProxy(proxy_type,
                                object,portal_type,object_id,docid=docid)
      proxy.isIndexable = 0 # So it will not be reindexed, this prevent errors
      # Calculate rpath
      utool = getToolByName(object, 'portal_url')
      rpath = utool.getRelativeUrl(proxy)
      px_tool._modifyProxy(proxy,rpath)
      trees_tool.notify_tree('sys_modify_object',proxy,rpath)
    subobject = object._getOb(object_id)
    return subobject
970

971 972 973 974 975
  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.
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
    conflict_list = []
    LOG('addNode, workflow_history isHistoryAdd:',0,self.isHistoryAdd(xml))
    # 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)
      LOG('addNode, workflow_history id:',0,wf_id)
      LOG('addNode, workflow_history xml:',0,xml.toxml())
      LOG('addNode, workflow_history xml.getElmentNodeList:',0,self.getElementNodeList(xml))
      xml = self.getElementNodeList(xml)[0]
    LOG('addNode, workflow_history id:',0,wf_id)
    LOG('addNode, workflow_history xml:',0,xml)
    #for action in self.getWorkflowActionFromXml(xml):
    status = self.getStatusFromXml(xml)
    LOG('addNode, status:',0,status)
    add_action = self.isWorkflowActionAddable(object=object,
                                           status=status,wf_tool=wf_tool,
                                           wf_id=wf_id,xml=xml)
    #LOG('addNode, workflow_history wf_conflict_list:',0,wf_conflict_list)
    LOG('addNode, workflow_history add_action:',0,add_action)
    if add_action and not simulate:
      LOG('addNode, setting status:',0,'ok')
      wf_tool.setStatusOf(wf_id,object,status)
1001 1002

    # Specific CPS, try to remove duplicate lines in portal_repository._histories
Sebastien Robin's avatar
Sebastien Robin committed
1003
    tool = getToolByName(self,'portal_repository',None)
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
    if tool is not None:
      if hasattr(self,'getDocid'):
        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)

1014
    return conflict_list
1015

1016 1017 1018 1019 1020
  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.
1021 1022
    """
    conflict_list = []
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034
    # 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
    LOG('local_role: ',0,'user: %s roles: %s' % (repr(user),repr(roles)))
    #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
1035

1036 1037 1038 1039 1040
  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.
1041 1042
    """
    conflict_list = []
1043
    # We want to add a local role
1044 1045 1046
    LOG('addLocalPermissionNode, xml',0,xml)
    if len(xml.childNodes)>0:
      roles = self.convertXmlValue(xml.childNodes[0].data,data_type='tokens')
1047
      roles = list(roles) # Needed for CPS, or we have a CPS error
1048
    else:
1049
      roles = ()
1050 1051 1052 1053 1054
    permission = self.getAttribute(xml,'id')
    LOG('local_role: ',0,'permission: %s roles: %s' % (repr(permission),repr(roles)))
    #user = roles[0]
    #roles = roles[1:]
    if xml.nodeName.find(self.local_permission_tag)>=0:
1055
      object.manage_setLocalPermissions(permission,roles)
1056 1057
    return conflict_list

1058
  security.declareProtected(Permissions.ModifyPortalContent, 'editDocument')
1059 1060 1061 1062 1063 1064 1065
  def editDocument(self, object=None, **kw):
    """
    This is the default editDocument method. This method
    can easily be overwritten.
    """
    object._edit(**kw)

1066 1067 1068 1069 1070 1071 1072 1073
  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)

1074

1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
    # This doesn't works fine because all workflows variables
    # are not set the same way
#    if status.has_key('action'):
#      action_name = status['action']
#      authorized = 0
#      authorized_actions = wf_tool.getActionsFor(object)
#      LOG('isWorkflowActionAddable, status:',0,status)
#      LOG('isWorkflowActionAddable, authorized_actions:',0,authorized_actions)
#      for action in authorized_actions:
#        if action['id']==action_name:
#          authorized = 1
#      if not authorized:
#        string_io = StringIO()
#        PrettyPrint(xml,stream=string_io)
#        conflict = Conflict(object_path=object.getPhysicalPath(),
#                            keyword=self.history_tag)
#        conflict.setXupdate(string_io.getvalue())
#        conflict.setRemoteValue(status)
#        conflict_list += [conflict]
1094
#    return conflict_list