ERP5Conduit.py 41.1 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 DateTime.DateTime import DateTime
Jean-Paul Smets's avatar
Jean-Paul Smets committed
33 34
from email.MIMEBase import MIMEBase
from email import Encoders
Sebastien Robin's avatar
Sebastien Robin committed
35
from AccessControl import ClassSecurityInfo
36
from Products.ERP5Type import Permissions, interfaces
Sebastien Robin's avatar
Sebastien Robin committed
37
from Globals import PersistentMapping
38
import pickle
39
from cStringIO import StringIO
40
from xml.sax.saxutils import escape, unescape
Fabien Morin's avatar
Fabien Morin committed
41
import re
42
import cStringIO
43
from lxml import etree
44
parser = etree.XMLParser(remove_blank_text=True)
45
from xml.marshal.generic import loads as unmarshaler
46
from zLOG import LOG, INFO, DEBUG
Fabien Morin's avatar
Fabien Morin committed
47

48
class ERP5Conduit(XMLSyncUtilsMixin):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
  """
    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

70 71 72 73 74 75 76 77 78 79 80 81 82 83
    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
84

Fabien Morin's avatar
Fabien Morin committed
85
  # Declarative interfaces
86
  __implements__ = ( interfaces.IConduit, )
Fabien Morin's avatar
Fabien Morin committed
87

Sebastien Robin's avatar
Sebastien Robin committed
88 89
  # Declarative security
  security = ClassSecurityInfo()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
90

Sebastien Robin's avatar
Sebastien Robin committed
91
  security.declareProtected(Permissions.AccessContentsInformation,'getEncoding')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
92 93 94 95
  def getEncoding(self):
    """
    return the string corresponding to the local encoding
    """
96 97
    #return "iso-8859-1"
    return "utf-8"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
98

Sebastien Robin's avatar
Sebastien Robin committed
99
  security.declareProtected(Permissions.ModifyPortalContent, '__init__')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
100 101
  def __init__(self):
    self.args = {}
102

Sebastien Robin's avatar
Sebastien Robin committed
103
  security.declareProtected(Permissions.ModifyPortalContent, 'addNode')
104
  def addNode(self, xml=None, object=None, previous_xml=None,
105
              object_id=None, sub_object=None, force=0, simulate=0, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
106 107 108
    """
    A node is added

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

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

248 249 250 251 252
  security.declareProtected(Permissions.ModifyPortalContent, 'deleteObject')
  def deleteObject(self, object, object_id):
    try:
      object._delObject(object_id)
    except (AttributeError, KeyError):
253
      #LOG('ERP5Conduit.deleteObject', DEBUG, 'Unable to delete: %s' % str(object_id))
254 255
      pass

Sebastien Robin's avatar
Sebastien Robin committed
256
  security.declareProtected(Permissions.ModifyPortalContent, 'updateNode')
257 258
  def updateNode(self, xml=None, object=None, previous_xml=None, force=0,
                 simulate=0,  **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
259 260 261 262 263 264 265 266
    """
    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 = []
267 268
    if xml is None:
      return {'conflict_list':conflict_list, 'object':object}
269
    xml = self.convertToXml(xml)
270
    #LOG('ERP5Conduit.updateNode', DEBUG, 'xml.tag: %s' % xml.tag)
271
    #LOG('ERP5Conduit.updateNode, force: ', DEBUG, force)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
272
    # we have an xupdate xml
273
    if xml.xpath('name()') == 'xupdate:modifications':
274 275 276 277 278
      conflict_list += self.applyXupdate(object=object,
                                         xupdate=xml,
                                         conduit=self,
                                         previous_xml=previous_xml,
                                         force=force,
Nicolas Delaby's avatar
Nicolas Delaby committed
279
                                         simulate=simulate, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
280 281 282
    # we may have only the part of an xupdate
    else:
      args = {}
283
      if self.isProperty(xml):
284
        keyword = None
285 286 287 288 289 290 291 292 293 294 295
        value = xml.attrib.get('select', None)
        if value is not None:
          select_list = value.split('/') # Something like:
                                         #('','object[1]','sid[1]')
          new_select_list = ()
          for select_item in select_list:
            if select_item.find('[') >= 0:
              select_item = select_item[:select_item.find('[')]
            new_select_list += (select_item,)
          select_list = new_select_list # Something like : ('','object','sid')
          keyword = select_list[len(select_list)-1] # this will be 'sid'
296
        data = None
297 298 299 300
        if xml.xpath('name()') not in self.XUPDATE_INSERT_OR_ADD:
          for subnode in xml:
            if subnode.xpath('name()') in self.XUPDATE_EL:
              keyword = subnode.attrib.get('name', None)
301
              data_xml = subnode
302 303 304 305 306 307 308 309 310
        else:
          #We can call add node
          conflict_list += self.addNode(xml=xml,
                                        object=object,
                                        force=force,
                                        simulate=simulate,
                                        **kw)
          return conflict_list

311
        if xml.xpath('name()') in self.XUPDATE_DEL:
312 313 314 315 316 317
          conflict_list += self.deleteNode(xml=xml,
                                           object=object,
                                           force=force,
                                           simulate=simulate,
                                           **kw)
          return conflict_list
318
        if keyword is None: # This is not a selection, directly the property
319
          keyword = xml.tag
Jean-Paul Smets's avatar
Jean-Paul Smets committed
320 321
        if not (keyword in self.NOT_EDITABLE_PROPERTY):
          # We will look for the data to enter
322
          data_type = object.getPropertyType(keyword)
323
          #LOG('ERP5Conduit.updateNode', DEBUG, 'data_type: %s for keyword: %s' % (str(data_type), keyword))
324
          data = self.convertXmlValue(xml, data_type=data_type)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
325 326 327 328 329
          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
330 331
          #   - 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
332 333
          #   - current_data : the data actually on this box
          isConflict = 0
334
          if (previous_xml is not None) and (not force):
Fabien Morin's avatar
Fabien Morin committed
335
          # if no previous_xml, no conflict
336
            old_data = self.getObjectProperty(keyword, previous_xml,
337
                                              data_type=data_type)
338 339
            #current_data = object.getProperty(keyword)
            current_data = self.getProperty(object, keyword)
340 341 342
            #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))
343 344
            if (old_data != current_data) and (data != current_data) \
                and keyword not in self.force_conflict_list:
345
              #LOG('ERP5Conduit.updateNode', DEBUG, 'Conflict on : %s' % keyword)
346 347
              # Hack in order to get the synchronization working for demo
              # XXX this have to be removed after
348 349
              #if not (data_type in self.binary_type_list):
              if 1:
350 351
                # This is a conflict
                isConflict = 1
352
                xml_string = etree.tostring(xml, encoding='utf-8')
Sebastien Robin's avatar
Sebastien Robin committed
353 354
                conflict = Conflict(object_path=object.getPhysicalPath(),
                                    keyword=keyword)
355
                conflict.setXupdate(xml_string)
Sebastien Robin's avatar
Sebastien Robin committed
356 357 358
                if not (data_type in self.binary_type_list):
                  conflict.setLocalValue(current_data)
                  conflict.setRemoteValue(data)
359
                conflict_list += [conflict]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
360
          # We will now apply the argument with the method edit
Nicolas Delaby's avatar
Nicolas Delaby committed
361 362
          if args != {} and (isConflict == 0 or force) and (not simulate):
            self.editDocument(object=object, **args)
363
            # It is sometimes required to do something after an edit
Nicolas Delaby's avatar
Nicolas Delaby committed
364
            if getattr(object, 'manage_afterEdit', None) is not None:
365
              object.manage_afterEdit()
366

Jean-Paul Smets's avatar
Jean-Paul Smets committed
367 368
        if keyword == 'object':
          # This is the case where we have to call addNode
369 370 371 372 373
          conflict_list += self.addNode(xml=xml,
                                        object=object,
                                        force=force,
                                        simulate=simulate,
                                        **kw)['conflict_list']
374
        elif keyword == self.history_tag and not simulate:
375
          # This is the case where we have to call addNode
376 377 378
          conflict_list += self.addNode(xml=subnode, object=object,
                                        force=force, simulate=simulate,
                                        **kw)['conflict_list']
379
        elif keyword in (self.local_role_tag, self.local_permission_tag) and not simulate:
380
          # This is the case where we have to update Roles or update permission
381
          #LOG('ERP5Conduit.updateNode', DEBUG, 'we will add a local role')
382 383 384 385
          #user = self.getSubObjectId(xml)
          #roles = self.convertXmlValue(data,data_type='tokens')
          #object.manage_setLocalRoles(user,roles)
          xml = self.getElementFromXupdate(xml)
386 387 388
          conflict_list += self.addNode(xml=xml, object=object,
                                       force=force, simulate=simulate,
                                       **kw)['conflict_list']
Jean-Paul Smets's avatar
Jean-Paul Smets committed
389 390 391
      elif self.isSubObjectModification(xml):
        # We should find the object corresponding to
        # this update, so we have to look in the previous_xml
392
        sub_object_id = self.getSubObjectId(xml)
393
        #LOG('ERP5Conduit.updateNode', DEBUG,'isSubObjectModification sub_object_id: %s' % sub_object_id)
394
        if previous_xml is not None and sub_object_id is not None:
395
          sub_previous_xml = self.getSubObjectXml(sub_object_id, previous_xml)
396
          #LOG('ERP5Conduit.updateNode', DEBUG, 'isSubObjectModification sub_previous_xml: %s' % str(sub_previous_xml))
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
          sub_object = None
          try:
            sub_object = object._getOb(sub_object_id)
          except KeyError:
            pass
          if sub_object is not None:
            #LOG('ERP5Conduit.updateNode', DEBUG, 'subobject.id: %s' % sub_object.id)
            # Change the xml in order to directly apply
            # modifications to the subobject
            sub_xml = self.getSubObjectXupdate(xml)
            #LOG('ERP5Conduit.updateNode', DEBUG, 'sub_xml: %s' % str(sub_xml))
            # Then do the udpate
            conflict_list += self.updateNode(xml=sub_xml, object=sub_object,
                                              force=force,
                                              previous_xml=sub_previous_xml,
                                              simulate=simulate, **kw)
413 414 415 416 417 418 419 420 421 422 423 424 425
        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)
426 427 428 429 430 431 432 433 434 435 436 437 438
        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
439 440
    return conflict_list

441 442
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getFormatedArgs')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
443 444 445 446 447 448 449 450
  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]
451
      if isinstance(keyword, unicode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
452
        keyword = keyword.encode(self.getEncoding())
453
      if isinstance(data, (tuple, list)):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
454 455
        new_data = []
        for item in data:
456
          if isinstance(item, unicode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
457
            item = item.encode(self.getEncoding())
458
          new_data.append(item)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
459
        data = new_data
460
      if isinstance(data, unicode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
461 462
        data = data.encode(self.getEncoding())
      if keyword == 'binary_data':
463
        #LOG('ERP5Conduit.getFormatedArgs', DEBUG, 'binary_data keyword: %s' % str(keyword))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
464 465 466 467 468 469 470
        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

471
  security.declareProtected(Permissions.AccessContentsInformation, 'isProperty')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
472 473 474
  def isProperty(self, xml):
    """
    Check if it is a simple property
475 476 477 478 479 480 481 482
    not an attribute @type it's a metadata
    """
    bad_list = (self.sub_object_exp, self.history_exp, self.attribute_type_exp,)
    value = xml.attrib.get('select', None)
    if value is not None:
      for bad_string in bad_list:
        if bad_string.search(value) is not None:
          return 0
Jean-Paul Smets's avatar
Jean-Paul Smets committed
483 484
    return 1

485
  security.declareProtected(Permissions.AccessContentsInformation,
486
      'getSubObjectXupdate')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
487 488 489 490 491
  def getSubObjectXupdate(self, xml):
    """
    This will change the xml in order to change the update
    from the object to the subobject
    """
492 493 494
    from copy import deepcopy
    xml_copy = deepcopy(xml)
    self.changeSubObjectSelect(xml_copy)
495
    return xml_copy
Jean-Paul Smets's avatar
Jean-Paul Smets committed
496

497
  security.declareProtected(Permissions.AccessContentsInformation,
498
      'isHistoryAdd')
499
  def isHistoryAdd(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
500
    bad_list = (self.history_exp,)
501 502 503 504 505 506 507 508
    value = xml.attrib.get('select')
    if value is not None:
      for bad_string in bad_list:
        if bad_string.search(value) is not None:
          if self.bad_history_exp.search(value) is None:
            return 1
          else:
            return -1
509 510
    return 0

511 512
  security.declareProtected(Permissions.AccessContentsInformation, 
      'isSubObjectModification')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
513 514 515 516
  def isSubObjectModification(self, xml):
    """
    Check if it is a modification from an subobject
    """
517
    good_list = (self.sub_object_exp,)
518 519 520 521 522
    value = xml.attrib.get('select', None)
    if value is not None:
      for good_string in good_list:
        if good_string.search(value) is not None:
          return 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
523 524
    return 0

525 526
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubObjectDepth')
527 528 529 530 531 532 533
  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
    """
534 535
    #LOG('getSubObjectDepth',0,'xml.tag: %s' % xml.tag)
    if xml.xpath('name()') in self.XUPDATE_TAG:
536
      i = 0
537
      if xml.xpath('name()') in self.XUPDATE_INSERT:
538
        i = 1
539 540 541 542 543 544 545 546 547 548 549 550 551 552
      #LOG('getSubObjectDepth',0,'xml2.tag: %s' % xml.tag)
      value = xml.attrib.get('select', None)
      if value is not None:
        #LOG('getSubObjectDepth',0,'subnode.nodeValue: %s' % subnode.nodeValue)
        if self.sub_sub_object_exp.search(value) is not None:
          return 2 # This is sure in all cases
        elif self.sub_object_exp.search(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 self.object_exp.search(value) is not None:
          return (1 - i)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
553 554
    return 0

555 556 557
  security.declareProtected(Permissions.ModifyPortalContent,
      'changeSubObjectSelect')
  def changeSubObjectSelect(self, xml):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
558
    """
559 560
    Return a string wich is the selection for the subobject
    ex: for "/object[@id='161']/object[@id='default_address']/street_address"
561
    it returns "/object[@id='default_address']/street_address"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
562
    """
563 564
    select = xml.attrib.get('select')
    if self.object_exp.search(select) is not None:
565
      s = '/'
566 567
      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):]
568 569
      else:
        new_value = '/'
570
      select = new_value
571
    xml.attrib['select'] = select
572

573 574
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubObjectId')
575 576 577 578 579
  def getSubObjectId(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
580 581 582 583 584 585 586
    value = xml.attrib.get('select', None)
    if value is not None:
      if self.object_exp.search(value) is not None:
        s = "'"
        first = value.find(s) + 1
        object_id = value[first:value.find(s, first)]
        return object_id
587 588
    return object_id

589 590
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getHistoryIdFromSelect')
591 592 593 594 595
  def getHistoryIdFromSelect(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
596 597 598 599 600 601 602 603
    value = xml.attrib.get('select', None)
    if value is not None:
      if self.history_exp.search(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
604 605
    return object_id

606 607
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubObjectXml')
608 609 610 611 612
  def getSubObjectXml(self, object_id, xml):
    """
    Return the xml of the subobject which as the id object_id
    """
    xml = self.convertToXml(xml)
613 614 615
    for subnode in xml:
      if subnode.tag == self.xml_object_tag:
        if object_id == self.getAttribute(subnode, 'id'):
616 617
          return subnode
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
618

Sebastien Robin's avatar
Sebastien Robin committed
619
  security.declareProtected(Permissions.AccessContentsInformation,'getAttribute')
620
  def getAttribute(self, xml, param):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
621
    """
622
    Retrieve the given parameter from the xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
623
    """
624
    return xml.attrib.get(param, None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
625

Sebastien Robin's avatar
Sebastien Robin committed
626
  security.declareProtected(Permissions.AccessContentsInformation,'getObjectProperty')
627
  def getObjectProperty(self, property, xml, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
628 629 630
    """
    Retrieve the given property
    """
631
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
632
    # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
633 634 635
    for subnode in xml:
      if subnode.tag == property:
        return self.convertXmlValue(subnode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
636 637
    return None

Sebastien Robin's avatar
Sebastien Robin committed
638
  security.declareProtected(Permissions.AccessContentsInformation,'convertToXml')
639
  def convertToXml(self, xml):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
640 641 642
    """
    if xml is a string, convert it to a node
    """
643 644 645
    if xml is None: return None
    if isinstance(xml, (str, unicode)):
      if isinstance(xml, unicode):
Sebastien Robin's avatar
Sebastien Robin committed
646
        xml = xml.encode('utf-8')
647
      xml = etree.XML(xml, parser=parser)
648
    # If we have the xml from the node erp5, we just take the subnode
649 650
    if xml.tag == 'erp5':
      xml = xml[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
651 652
    return xml

Sebastien Robin's avatar
Sebastien Robin committed
653
  security.declareProtected(Permissions.AccessContentsInformation,'getObjectType')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
654 655 656 657
  def getObjectType(self, xml):
    """
    Retrieve the portal type from an xml
    """
658
    return '%s' % xml.xpath('string(.//@portal_type)')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
659

Sebastien Robin's avatar
Sebastien Robin committed
660
  security.declareProtected(Permissions.AccessContentsInformation,'getPropertyType')
661 662 663 664
  def getPropertyType(self, xml):
    """
    Retrieve the portal type from an xml
    """
665
    return '%s' % xml.xpath('string(.//@type)')
666

Sebastien Robin's avatar
Sebastien Robin committed
667
  security.declareProtected(Permissions.AccessContentsInformation,'getXupdateObjectType')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
668 669 670
  def getXupdateObjectType(self, xml):
    """
    Retrieve the portal type from an xupdate
671
    XXXX  This should not be used any more !!! XXXXXXXXXXX
Jean-Paul Smets's avatar
Jean-Paul Smets committed
672
    """
673
    return xml.xpath('string(.//*[name() == "xupdate:attribute"][@name = "portal_type"])') or None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
674

Sebastien Robin's avatar
Sebastien Robin committed
675
  security.declareProtected(Permissions.ModifyPortalContent, 'newObject')
676 677
  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
678 679 680 681 682
    """
      modify the object with datas from
      the xml (action section)
    """
    args = {}
683 684
    if simulate:
      return
685
    # Retrieve the list of users with a role and delete default roles
686
    if reset_local_roles:
Nicolas Delaby's avatar
Nicolas Delaby committed
687
      user_role_list = [x[0] for x in object.get_local_roles()]
688
      object.manage_delLocalRoles(user_role_list)
Nicolas Delaby's avatar
Nicolas Delaby committed
689
    if getattr(object, 'workflow_history', None) is not None and reset_workflow:
690
      object.workflow_history = PersistentMapping()
691 692 693 694 695
    if xml.tag.find('xupdate') >= 0:
      xml = xml[0]
    for subnode in xml.xpath('*'):
      #get only Element nodes (not Comments or Processing instructions)
      if subnode.tag not in self.NOT_EDITABLE_PROPERTY:
696
        keyword_type = self.getPropertyType(subnode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
697
        # This is the case where the property is a list
698 699 700
        keyword = subnode.tag
        args[keyword] = self.convertXmlValue(subnode, keyword_type)
      elif subnode.tag in self.ADDABLE_PROPERTY + (self.xml_object_tag,):
Nicolas Delaby's avatar
Nicolas Delaby committed
701
        self.addNode(object=object, xml=subnode, force=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
702 703 704 705
    # We should first edit the object
    args = self.getFormatedArgs(args=args)
    # edit the object with a dictionnary of arguments,
    # like {"telephone_number":"02-5648"}
Nicolas Delaby's avatar
Nicolas Delaby committed
706
    self.editDocument(object=object, **args)
Nicolas Delaby's avatar
Nicolas Delaby committed
707
    if getattr(object, 'manage_afterEdit', None) is not None:
708
      object.manage_afterEdit()
Nicolas Delaby's avatar
Nicolas Delaby committed
709
    self.afterNewObject(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
710

711 712 713 714
    ## Then we may create subobject
    #for subnode in xml:
      #if subnode.tag in (self.xml_object_tag,): #,self.history_tag):
        #self.addNode(object=object, xml=subnode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
715

716 717 718 719
  security.declareProtected(Permissions.AccessContentsInformation,'afterNewObject')
  def afterNewObject(self, object):
    pass

Sebastien Robin's avatar
Sebastien Robin committed
720
  security.declareProtected(Permissions.AccessContentsInformation,'getStatusFromXml')
721 722 723 724 725
  def getStatusFromXml(self, xml):
    """
    Return a worklow status from xml
    """
    status = {}
726 727
    for subnode in xml:
      keyword = subnode.tag
Nicolas Delaby's avatar
Nicolas Delaby committed
728
      value = self.getObjectProperty(keyword, xml)
729 730 731
      status[keyword] = value
    return status

Sebastien Robin's avatar
Sebastien Robin committed
732
  security.declareProtected(Permissions.AccessContentsInformation,'getXupdateElementList')
733 734 735 736
  def getXupdateElementList(self, xml):
    """
    Retrieve the list of xupdate:element subnodes
    """
737
    return xml.xpath('|'.join(['.//*[name() = "%s"]' % name for name in self.XUPDATE_EL]))
738

Sebastien Robin's avatar
Sebastien Robin committed
739
  security.declareProtected(Permissions.AccessContentsInformation,'getElementFromXupdate')
740 741 742 743
  def getElementFromXupdate(self, xml):
    """
    from a xupdate:element returns the element as xml
    """
744 745 746 747 748 749 750 751
    if xml.xpath('name()') in self.XUPDATE_EL:
      result = '<'
      tag_name = xml.attrib.get('name')
      result += tag_name
      for subnode in xml:
        if subnode.xpath('name()') == 'xupdate:attribute':
          result += ' %s=' % subnode.attrib.get('name')
          result += '"%s"' % subnode.text
752
      result += '>'
753
      # Then dumps the xml and remove what we does'nt want
754
      xml_string = self.nodeToString(xml)
755 756 757
      maxi = max(xml_string.find('>')+1,\
                 xml_string.rfind('</xupdate:attribute>')+len('</xupdate:attribute>'))
      result += xml_string[maxi:xml_string.find('</xupdate:element>')]
758 759 760
      result += '</%s>' % tag_name
      return self.convertToXml(result)
    if xml.xpath('name()') in (self.XUPDATE_UPDATE + self.XUPDATE_DEL):
761
      result = u'<'
762
      attribute = xml.attrib.get('select')
763 764 765 766 767 768 769 770
      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)
771
      property = attribute[:s_place].strip('/')
772 773 774 775
      result += property
      if select_id is not None:
        result += ' id=%s' % select_id
      result +=  '>'
776
      xml_string = self.nodeToString(xml)
777
      maxi = xml_string.find('>')+1
778
      result += xml_string[maxi:xml_string.find('</%s>' % xml.xpath('name()'))]
Nicolas Delaby's avatar
Nicolas Delaby committed
779
      result += '</%s>' % (property)
Nicolas Delaby's avatar
Nicolas Delaby committed
780
      #LOG('getElementFromXupdate, result:',0,repr(result))
781
      return self.convertToXml(result)
782 783
    return xml

Sebastien Robin's avatar
Sebastien Robin committed
784
  security.declareProtected(Permissions.AccessContentsInformation,'getWorkflowActionFromXml')
785 786 787 788 789
  def getWorkflowActionFromXml(self, xml):
    """
    Return the list of workflow actions
    """
    action_list = []
790 791
    if xml.xpath('name()') in self.XUPDATE_EL:
      action_list.append(xml)
792
      return action_list
793 794 795
    for subnode in xml:
      if subnode.tag == self.action_tag:
        action_list.append(subnode)
796 797
    return action_list

Sebastien Robin's avatar
Sebastien Robin committed
798
  security.declareProtected(Permissions.AccessContentsInformation,'convertXmlValue')
799
  def convertXmlValue(self, node, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
800 801
    """
    It is possible that the xml change the value, for example
802
    there is some too much '\n' and some spaces. We have to do some extra
803 804
    things so that we convert correctly the value
    XXXNicolas: I'm totally disagree with, so i comment this code
805
    """
806 807 808 809 810 811 812 813
    if node is None: return None
    if data_type is None:
      data_type = self.getPropertyType(node)
    if data_type == self.none_type: return None
    data = node.text
    if data is not None and isinstance(data, unicode):
      data = data.encode('utf-8')
    elif data is None:
814 815
      if data_type in self.list_type_list:
        data = ()
816
      elif data_type in self.text_type_list:
817
        data = ''
818 819 820
      return data
    # We can now convert string in tuple, dict, binary...
    if data_type in self.list_type_list:
821
      data = unmarshaler(node.text)
822
    elif data_type in self.text_type_list:
823
      data = unescape(data)
824
    elif data_type in self.pickle_type_list:
825
      msg = MIMEBase('application', 'octet-stream')
826 827 828
      Encoders.encode_base64(msg)
      msg.set_payload(data)
      data = msg.get_payload(decode=1)
829
      data = pickle.loads(data)
830 831
    elif data_type in self.date_type_list:
      data = DateTime(data)
Sebastien Robin's avatar
Sebastien Robin committed
832 833
    elif data_type in self.int_type_list:
      data = int(data)
Sebastien Robin's avatar
Sebastien Robin committed
834 835 836 837
    elif data_type in self.dict_type_list: # only usefull for CPS, with data = '{fr:1}'
      if data == '{}':
        data = {}
      else:
838
        dict_list = map(lambda x:x.split(':'), data[1:-1].split(','))
Sebastien Robin's avatar
Sebastien Robin committed
839 840
        data = map(lambda (x,y):(x.replace(' ','').replace("'",''),int(y)),dict_list)
        data = dict(data)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
841 842
    return data

843 844
  # 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
845
  security.declareProtected(Permissions.ModifyPortalContent, 'applyXupdate')
846 847
  def applyXupdate(self, object=None, xupdate=None, conduit=None, force=0,
                   simulate=0, **kw):
848 849 850 851
    """
    Parse the xupdate and then it will call the conduit
    """
    conflict_list = []
852
    if isinstance(xupdate, (str, unicode)):
853
      xupdate = etree.XML(xupdate, parser=parser)
854
    for subnode in xupdate:
855 856
      sub_xupdate = self.getSubObjectXupdate(subnode)
      selection_name = ''
857 858 859 860 861 862 863 864 865 866
      if subnode.xpath('name()') in self.XUPDATE_INSERT_OR_ADD:
        conflict_list += conduit.addNode(xml=sub_xupdate,object=object,
                                         force=force, simulate=simulate,
                                         **kw)['conflict_list']
      elif subnode.xpath('name()') in self.XUPDATE_DEL:
        conflict_list += conduit.deleteNode(xml=sub_xupdate, object=object,
                                            force=force, simulate=simulate, **kw)
      elif subnode.xpath('name()') in self.XUPDATE_UPDATE:
        conflict_list += conduit.updateNode(xml=sub_xupdate, object=object,
                                            force=force, simulate=simulate, **kw)
867 868 869

    return conflict_list

870 871
  def isWorkflowActionAddable(self, object=None, status=None, wf_tool=None,
                              wf_id=None, xml=None):
Sebastien Robin's avatar
Sebastien Robin committed
872 873
    """
    Some checking in order to check if we should add the workfow or not
874 875 876 877
    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
878 879
    return 1
    # XXX Disable for now
880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
    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

896
  security.declareProtected(Permissions.ModifyPortalContent, 'constructContent')
897
  def constructContent(self, object, object_id, portal_type):
898 899 900 901 902
    """
    This allows to specify how to construct a new content.
    This is really usefull if you want to write your
    own Conduit.
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
903
    #LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
904 905

    object.newContent(portal_type=portal_type, id=object_id)
906
    subobject = object._getOb(object_id)
907
    return subobject, 1, 1
908

909 910 911 912 913
  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.
914
    """
915 916 917 918 919 920
    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)
921
      xml = xml[0]
922 923
    #for action in self.getWorkflowActionFromXml(xml):
    status = self.getStatusFromXml(xml)
Nicolas Delaby's avatar
Nicolas Delaby committed
924
    #LOG('addNode, status:',0,status)
925 926 927 928 929
    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)
930 931

    # Specific CPS, try to remove duplicate lines in portal_repository._histories
Sebastien Robin's avatar
Sebastien Robin committed
932
    tool = getToolByName(self,'portal_repository',None)
933
    if tool is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
934
      if getattr(self, 'getDocid', None) is not None:
935 936 937 938 939 940 941 942
        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)

943
    return conflict_list
944

945 946 947 948 949
  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.
950
    """
951
    # We want to add a local role
952 953
    roles = self.convertXmlValue(xml, data_type='tokens')
    user = self.getAttribute(xml, 'id')
954
    roles = list(roles) # Needed for CPS, or we have a CPS error
955
    #LOG('local_role: ',0,'user: %s roles: %s' % (repr(user),repr(roles)))
956 957
    #user = roles[0]
    #roles = roles[1:]
958 959 960 961
    if xml.tag.find(self.local_role_tag) >= 0:
      object.manage_setLocalRoles(user, roles)
    elif xml.tag.find(self.local_group_tag) >= 0:
      object.manage_setLocalGroupRoles(user, roles)
962

963 964 965 966 967
  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.
968 969
    """
    conflict_list = []
970
    # We want to add a local role
971
    #LOG('addLocalPermissionNode, xml',0,xml)
972 973
    if len(xml.text):
      roles = self.convertXmlValue(xml, data_type='tokens')
974
      roles = list(roles) # Needed for CPS, or we have a CPS error
975
    else:
976
      roles = ()
977
    permission = self.getAttribute(xml, 'id')
978
    #LOG('local_role: ',0,'permission: %s roles: %s' % (repr(permission),repr(roles)))
979 980
    #user = roles[0]
    #roles = roles[1:]
981 982
    if xml.tag.find(self.local_permission_tag) >= 0:
      object.manage_setLocalPermissions(permission, roles)
983 984
    return conflict_list

985
  security.declareProtected(Permissions.ModifyPortalContent, 'editDocument')
986 987 988 989 990
  def editDocument(self, object=None, **kw):
    """
    This is the default editDocument method. This method
    can easily be overwritten.
    """
991
    object._edit(**kw)
992

993 994 995 996 997 998 999 1000
  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)

1001 1002 1003 1004
  def nodeToString(self, node):
    """
    return an xml string corresponding to the node
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
1005
    return etree.tostring(node, encoding='utf-8', pretty_print=True)
1006 1007 1008 1009 1010 1011 1012

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

1013 1014 1015 1016 1017
  #def getGidFromXML(self, xml, gid_from_xml_list):
    #"""
    #return the Gid composed with xml informations
    #"""
    #return None
1018