ERP5Conduit.py 43.9 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
30
from Products.ERP5SyncML.Conflict import Conflict
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31
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
37
from Products.ERP5Type.Globals import PersistentMapping
38
import pickle
39
from xml.sax.saxutils import escape, unescape
40
from cStringIO import StringIO
Fabien Morin's avatar
Fabien Morin committed
41
import re
42
import cStringIO
43
import string
44
from lxml import etree
45
parser = etree.XMLParser(remove_blank_text=True)
46
from xml.marshal.generic import loads as unmarshaler
47
from zLOG import LOG, INFO, DEBUG
48 49
from base64 import standard_b64decode
from OFS.Image import Pdata
50
from zope.interface import implements
Fabien Morin's avatar
Fabien Morin committed
51

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

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

Fabien Morin's avatar
Fabien Morin committed
89
  # Declarative interfaces
90
  implements( interfaces.IConduit, )
Fabien Morin's avatar
Fabien Morin committed
91

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

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

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

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

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

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

Sebastien Robin's avatar
Sebastien Robin committed
268
  security.declareProtected(Permissions.ModifyPortalContent, 'updateNode')
269
  def updateNode(self, xml=None, object=None, previous_xml=None, force=0,
270
                 simulate=0, reset=0, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
271 272 273 274 275 276 277 278
    """
    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 = []
279 280
    if xml is None:
      return {'conflict_list':conflict_list, 'object':object}
281
    xml = self.convertToXml(xml)
282
    #LOG('ERP5Conduit.updateNode', DEBUG, 'xml.tag: %s' % xml.tag)
283
    #LOG('ERP5Conduit.updateNode, force: ', DEBUG, force)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
284
    # we have an xupdate xml
285
    #LOG('ERP5Conduit updateNode', DEBUG, "%s" % etree.tostring(xml,pretty_print=True))
286
    if xml.xpath('name()') == 'xupdate:modifications':
287 288 289 290 291
      conflict_list += self.applyXupdate(object=object,
                                         xupdate=xml,
                                         conduit=self,
                                         previous_xml=previous_xml,
                                         force=force,
292 293 294
                                         simulate=simulate,
                                         reset=reset,
                                         **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
295 296 297
    # we may have only the part of an xupdate
    else:
      args = {}
298
      if self.isProperty(xml):
299
        keyword = None
300 301 302 303 304 305 306 307 308 309 310
        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'
311
        data = None
312 313 314 315
        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)
316
              data_xml = subnode
317 318 319 320 321 322
        else:
          #We can call add node
          conflict_list += self.addNode(xml=xml,
                                        object=object,
                                        force=force,
                                        simulate=simulate,
323
                                        reset=reset,
324 325 326
                                        **kw)
          return conflict_list

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

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

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

478
  security.declareProtected(Permissions.AccessContentsInformation, 'isProperty')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
479 480 481
  def isProperty(self, xml):
    """
    Check if it is a simple property
482 483 484 485 486 487 488 489
    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
490 491
    return 1

492
  security.declareProtected(Permissions.AccessContentsInformation,
493
      'getSubObjectXupdate')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
494 495 496 497 498
  def getSubObjectXupdate(self, xml):
    """
    This will change the xml in order to change the update
    from the object to the subobject
    """
499 500 501
    from copy import deepcopy
    xml_copy = deepcopy(xml)
    self.changeSubObjectSelect(xml_copy)
502
    return xml_copy
Jean-Paul Smets's avatar
Jean-Paul Smets committed
503

504
  security.declareProtected(Permissions.AccessContentsInformation,
505
      'isHistoryAdd')
506
  def isHistoryAdd(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
507
    bad_list = (self.history_exp,)
508 509 510 511 512 513 514 515
    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
516 517
    return 0

518 519
  security.declareProtected(Permissions.AccessContentsInformation, 
      'isSubObjectModification')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
520 521 522 523
  def isSubObjectModification(self, xml):
    """
    Check if it is a modification from an subobject
    """
524
    good_list = (self.sub_object_exp,)
525 526 527 528 529
    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
530 531
    return 0

532 533
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubObjectDepth')
534 535 536 537 538 539 540
  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
    """
541 542
    #LOG('getSubObjectDepth',0,'xml.tag: %s' % xml.tag)
    if xml.xpath('name()') in self.XUPDATE_TAG:
543
      i = 0
544
      if xml.xpath('name()') in self.XUPDATE_INSERT:
545
        i = 1
546 547 548 549 550 551 552 553 554 555 556 557 558 559
      #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
560 561
    return 0

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

580 581
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getSubObjectId')
582 583 584 585 586
  def getSubObjectId(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
587 588 589 590 591 592 593
    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
594 595
    return object_id

596 597
  security.declareProtected(Permissions.AccessContentsInformation, 
      'getHistoryIdFromSelect')
598 599 600 601 602
  def getHistoryIdFromSelect(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
603 604 605 606 607 608 609 610
    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
611 612
    return object_id

613 614
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubObjectXml')
615 616 617 618 619
  def getSubObjectXml(self, object_id, xml):
    """
    Return the xml of the subobject which as the id object_id
    """
    xml = self.convertToXml(xml)
620
    for subnode in xml:
621
      if subnode.xpath('local-name()') == self.xml_object_tag:
622
        if object_id == self.getAttribute(subnode, 'id'):
623 624
          return subnode
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
625

Sebastien Robin's avatar
Sebastien Robin committed
626
  security.declareProtected(Permissions.AccessContentsInformation,'getAttribute')
627
  def getAttribute(self, xml, param):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
628
    """
629
    Retrieve the given parameter from the xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
630
    """
631
    return xml.attrib.get(param, None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
632

Sebastien Robin's avatar
Sebastien Robin committed
633
  security.declareProtected(Permissions.AccessContentsInformation,'getObjectProperty')
634
  def getObjectProperty(self, property, xml, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
635 636 637
    """
    Retrieve the given property
    """
638
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
639
    # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
640
    for subnode in xml:
641
      if subnode.xpath('local-name()') == property:
642
        return self.convertXmlValue(subnode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
643
    return None
644 645 646 647 648
 
  def replaceIdFromXML(self, xml, new_id):
    """
      return a xml with id replace by a new id
    """
649 650 651
    if xml is not None and new_id is not None:
      if isinstance(xml, str):
        xml = etree.XML(xml)
652 653 654
      #copy of xml object for modification
      from copy import deepcopy
      xml_copy = deepcopy(xml)
655
      if xml.nsmap is None or xml.nsmap == {}:
656 657 658
        object_element = xml_copy.find(self.xml_object_tag)
        id_element = object_element.find('id')
      else:
Romain Courteaud's avatar
Romain Courteaud committed
659
        object_element = xml_copy.xpath('//syncml:object',
660
            namespaces={'syncml':xml_copy.nsmap[xml_copy.prefix]})[0]
Romain Courteaud's avatar
Romain Courteaud committed
661
        id_element = object_element.xpath('//syncml:object',
662
            namespaces={'syncml':xml_copy.nsmap[xml_copy.prefix]})[0]
663 664
      object_element.attrib['id'] = new_id
      id_element.text = new_id
665
      return etree.tostring(xml_copy)
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684

  def getXMLFromObjectWithId(self, object, xml_mapping):
    """
      return the xml with Id of Object
    """
    xml = ''
    if xml_mapping is None:
      return xml
    func = getattr(object, xml_mapping, None)
    if func is not None:
      xml = func()
    return xml

  def getXMLFromObjectWithGid(self, object, gid, xml_mapping=None):
    """
      return the xml with Gid of Object
    """
    xml_id = self.getXMLFromObjectWithId(object, xml_mapping)
    xml_gid = self.replaceIdFromXML(xml_id, gid)
685
    return xml_gid
686 687 688 689 690 691 692

  def getXMLFromObjectWithRid(self, object, rid, xml_mapping=None):
    """
      return the xml with Rid of Object
    """
    xml_id = self.getXMLFromObjectWithId(object, xml_mapping)
    xml_rid = self.replaceIdFromXML(xml_id, rid)
693
    return xml_rid
Jean-Paul Smets's avatar
Jean-Paul Smets committed
694

Sebastien Robin's avatar
Sebastien Robin committed
695
  security.declareProtected(Permissions.AccessContentsInformation,'convertToXml')
696
  def convertToXml(self, xml):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
697 698 699
    """
    if xml is a string, convert it to a node
    """
700 701 702
    if xml is None: return None
    if isinstance(xml, (str, unicode)):
      if isinstance(xml, unicode):
Sebastien Robin's avatar
Sebastien Robin committed
703
        xml = xml.encode('utf-8')
704
      xml = etree.XML(xml, parser=parser)
705
    # If we have the xml from the node erp5, we just take the subnode
706
    if xml.xpath('local-name()') == 'erp5':
707
      xml = xml[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
708 709
    return xml

Sebastien Robin's avatar
Sebastien Robin committed
710
  security.declareProtected(Permissions.AccessContentsInformation,'getObjectType')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
711 712 713 714
  def getObjectType(self, xml):
    """
    Retrieve the portal type from an xml
    """
715
    return xml.get('portal_type')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
716

Sebastien Robin's avatar
Sebastien Robin committed
717
  security.declareProtected(Permissions.AccessContentsInformation,'getPropertyType')
718 719 720 721
  def getPropertyType(self, xml):
    """
    Retrieve the portal type from an xml
    """
722
    return xml.get('type')
723

Sebastien Robin's avatar
Sebastien Robin committed
724
  security.declareProtected(Permissions.AccessContentsInformation,'getXupdateObjectType')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
725 726 727
  def getXupdateObjectType(self, xml):
    """
    Retrieve the portal type from an xupdate
728
    XXXX  This should not be used any more !!! XXXXXXXXXXX
Jean-Paul Smets's avatar
Jean-Paul Smets committed
729
    """
730
    return xml.xpath('string(.//*[name() == "xupdate:attribute"][@name = "portal_type"])') or None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
731

Sebastien Robin's avatar
Sebastien Robin committed
732
  security.declareProtected(Permissions.ModifyPortalContent, 'newObject')
733 734
  def newObject(self, object=None, xml=None, simulate=0, reset=0, 
      reset_local_roles=1, reset_workflow=1):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
735 736 737 738 739
    """
      modify the object with datas from
      the xml (action section)
    """
    args = {}
740 741
    if simulate:
      return
742
    # Retrieve the list of users with a role and delete default roles
743
    if reset_local_roles:
Nicolas Delaby's avatar
Nicolas Delaby committed
744
      user_role_list = [x[0] for x in object.get_local_roles()]
745
      object.manage_delLocalRoles(user_role_list)
Nicolas Delaby's avatar
Nicolas Delaby committed
746
    if getattr(object, 'workflow_history', None) is not None and reset_workflow:
747
      object.workflow_history = PersistentMapping()
748
    if xml.xpath('name()').find('xupdate') >= 0:
749 750 751
      xml = xml[0]
    for subnode in xml.xpath('*'):
      #get only Element nodes (not Comments or Processing instructions)
752
      if subnode.xpath('name()') not in self.NOT_EDITABLE_PROPERTY:
753
        keyword_type = self.getPropertyType(subnode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
754
        # This is the case where the property is a list
755
        keyword = subnode.xpath('name()')
756
        args[keyword] = self.convertXmlValue(subnode, keyword_type)
757
      elif subnode.xpath('local-name()') in self.ADDABLE_PROPERTY + (self.xml_object_tag,):
Nicolas Delaby's avatar
Nicolas Delaby committed
758
        self.addNode(object=object, xml=subnode, force=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
759 760 761 762
    # 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
763
    self.editDocument(object=object, **args)
Nicolas Delaby's avatar
Nicolas Delaby committed
764
    if getattr(object, 'manage_afterEdit', None) is not None:
765
      object.manage_afterEdit()
Nicolas Delaby's avatar
Nicolas Delaby committed
766
    self.afterNewObject(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
767

768 769 770 771
    ## 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
772

773 774 775 776
  security.declareProtected(Permissions.AccessContentsInformation,'afterNewObject')
  def afterNewObject(self, object):
    pass

Sebastien Robin's avatar
Sebastien Robin committed
777
  security.declareProtected(Permissions.AccessContentsInformation,'getStatusFromXml')
778 779 780 781 782
  def getStatusFromXml(self, xml):
    """
    Return a worklow status from xml
    """
    status = {}
783
    for subnode in xml:
784
      keyword = subnode.xpath('name()')
Nicolas Delaby's avatar
Nicolas Delaby committed
785
      value = self.getObjectProperty(keyword, xml)
786 787 788
      status[keyword] = value
    return status

Sebastien Robin's avatar
Sebastien Robin committed
789
  security.declareProtected(Permissions.AccessContentsInformation,'getXupdateElementList')
790 791 792 793
  def getXupdateElementList(self, xml):
    """
    Retrieve the list of xupdate:element subnodes
    """
794
    return xml.xpath('|'.join(['.//*[name() = "%s"]' % name for name in self.XUPDATE_EL]))
795

Sebastien Robin's avatar
Sebastien Robin committed
796
  security.declareProtected(Permissions.AccessContentsInformation,'getElementFromXupdate')
797 798 799 800
  def getElementFromXupdate(self, xml):
    """
    from a xupdate:element returns the element as xml
    """
801 802 803 804 805 806 807 808
    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
809
      result += '>'
810
      # Then dumps the xml and remove what we does'nt want
811
      xml_string = self.nodeToString(xml)
812
      maxi = max(xml_string.find('>') + 1, \
813 814
                 xml_string.rfind('</xupdate:attribute>')+len('</xupdate:attribute>'))
      result += xml_string[maxi:xml_string.find('</xupdate:element>')]
815 816 817
      result += '</%s>' % tag_name
      return self.convertToXml(result)
    if xml.xpath('name()') in (self.XUPDATE_UPDATE + self.XUPDATE_DEL):
818
      result = u'<'
819
      attribute = xml.attrib.get('select')
820 821 822 823 824 825 826 827
      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)
828
      property = attribute[:s_place].strip('/')
829 830 831 832
      result += property
      if select_id is not None:
        result += ' id=%s' % select_id
      result +=  '>'
833
      xml_string = self.nodeToString(xml)
834
      maxi = xml_string.find('>')+1
835
      result += xml_string[maxi:xml_string.find('</%s>' % xml.xpath('name()'))]
Nicolas Delaby's avatar
Nicolas Delaby committed
836
      result += '</%s>' % (property)
Nicolas Delaby's avatar
Nicolas Delaby committed
837
      #LOG('getElementFromXupdate, result:',0,repr(result))
838
      return self.convertToXml(result)
839 840
    return xml

Sebastien Robin's avatar
Sebastien Robin committed
841
  security.declareProtected(Permissions.AccessContentsInformation,'getWorkflowActionFromXml')
842 843 844 845 846
  def getWorkflowActionFromXml(self, xml):
    """
    Return the list of workflow actions
    """
    action_list = []
847 848
    if xml.xpath('name()') in self.XUPDATE_EL:
      action_list.append(xml)
849
      return action_list
850
    for subnode in xml:
851
      if subnode.xpath('local-name()') == self.action_tag:
852
        action_list.append(subnode)
853 854
    return action_list

Sebastien Robin's avatar
Sebastien Robin committed
855
  security.declareProtected(Permissions.AccessContentsInformation,'convertXmlValue')
856
  def convertXmlValue(self, node, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
857 858
    """
    It is possible that the xml change the value, for example
859
    there is some too much '\n' and some spaces. We have to do some extra
860 861
    things so that we convert correctly the value
    XXXNicolas: I'm totally disagree with, so i comment this code
862
    """
863 864 865 866 867 868 869 870
    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:
871
      if data_type in self.list_type_list:
872
        return ()
873
      elif data_type in self.text_type_list:
874
        return ''
875 876
    # We can now convert string in tuple, dict, binary...
    if data_type in self.list_type_list:
877
      data = unmarshaler(node.text)
878
    elif data_type in self.text_type_list:
879
      data = unescape(data)
880 881 882
    elif data_type in self.data_type_list:
      if data is None:
        # data is in blocks
883 884
        data = ''.join([standard_b64decode(block.text) \
               for block in node.iterchildren()])
885
    elif data_type in self.pickle_type_list:
886
      data = pickle.loads(standard_b64decode(data))
887 888
    elif data_type in self.date_type_list:
      data = DateTime(data)
Sebastien Robin's avatar
Sebastien Robin committed
889 890
    elif data_type in self.int_type_list:
      data = int(data)
Sebastien Robin's avatar
Sebastien Robin committed
891 892 893 894
    elif data_type in self.dict_type_list: # only usefull for CPS, with data = '{fr:1}'
      if data == '{}':
        data = {}
      else:
895
        dict_list = map(lambda x:x.split(':'), data[1:-1].split(','))
Sebastien Robin's avatar
Sebastien Robin committed
896 897
        data = map(lambda (x,y):(x.replace(' ','').replace("'",''),int(y)),dict_list)
        data = dict(data)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
898 899
    return data

900 901
  # 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
902
  security.declareProtected(Permissions.ModifyPortalContent, 'applyXupdate')
903
  def applyXupdate(self, object=None, xupdate=None, conduit=None, force=0,
904
                   simulate=0, reset=0, **kw):
905 906 907 908
    """
    Parse the xupdate and then it will call the conduit
    """
    conflict_list = []
909
    if isinstance(xupdate, (str, unicode)):
910
      xupdate = etree.XML(xupdate, parser=parser)
911
    #LOG("applyXupdate xupdate",debug, "%s" % etree.tostring(xupdate,pretty_print=True))
912
    for subnode in xupdate:
913 914
      sub_xupdate = self.getSubObjectXupdate(subnode)
      selection_name = ''
915 916 917
      if subnode.xpath('name()') in self.XUPDATE_INSERT_OR_ADD:
        conflict_list += conduit.addNode(xml=sub_xupdate,object=object,
                                         force=force, simulate=simulate,
918
                                         reset=reset, **kw)['conflict_list']
919 920
      elif subnode.xpath('name()') in self.XUPDATE_DEL:
        conflict_list += conduit.deleteNode(xml=sub_xupdate, object=object,
921 922
                                            force=force, simulate=simulate,
                                            reset=reset, **kw)
923 924
      elif subnode.xpath('name()') in self.XUPDATE_UPDATE:
        conflict_list += conduit.updateNode(xml=sub_xupdate, object=object,
925 926
                                            force=force, simulate=simulate,
                                            reset=reset, **kw)
927 928 929

    return conflict_list

930 931
  def isWorkflowActionAddable(self, object=None, status=None, wf_tool=None,
                              wf_id=None, xml=None):
Sebastien Robin's avatar
Sebastien Robin committed
932 933
    """
    Some checking in order to check if we should add the workfow or not
934 935 936 937
    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
938 939
    return 1
    # XXX Disable for now
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955
    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

956
  security.declareProtected(Permissions.ModifyPortalContent, 'constructContent')
957
  def constructContent(self, object, object_id, portal_type):
958 959 960 961 962
    """
    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
963
    #LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
964
    object.newContent(portal_type=portal_type, id=object_id)
965
    subobject = object._getOb(object_id)
966
    return subobject, 1, 1
967

968 969 970 971 972
  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.
973
    """
974 975 976 977 978 979
    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)
980
      xml = xml[0]
981 982
    #for action in self.getWorkflowActionFromXml(xml):
    status = self.getStatusFromXml(xml)
Nicolas Delaby's avatar
Nicolas Delaby committed
983
    #LOG('addNode, status:',0,status)
984 985 986 987 988
    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)
989 990

    # Specific CPS, try to remove duplicate lines in portal_repository._histories
Sebastien Robin's avatar
Sebastien Robin committed
991
    tool = getToolByName(self,'portal_repository',None)
992
    if tool is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
993
      if getattr(self, 'getDocid', None) is not None:
994 995 996 997 998 999 1000 1001
        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)

1002
    return conflict_list
1003

1004 1005 1006 1007 1008
  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.
1009
    """
1010
    # We want to add a local role
1011 1012
    roles = self.convertXmlValue(xml, data_type='tokens')
    user = self.getAttribute(xml, 'id')
1013
    roles = list(roles) # Needed for CPS, or we have a CPS error
1014
    #LOG('local_role: ',0,'user: %s roles: %s' % (repr(user),repr(roles)))
1015 1016
    #user = roles[0]
    #roles = roles[1:]
1017
    if xml.xpath('local-name()') == self.local_role_tag:
1018
      object.manage_setLocalRoles(user, roles)
1019
    elif xml.xpath('local-name()') == self.local_group_tag:
1020
      object.manage_setLocalGroupRoles(user, roles)
1021

1022 1023 1024 1025 1026
  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.
1027 1028
    """
    conflict_list = []
1029
    # We want to add a local role
1030
    #LOG('addLocalPermissionNode, xml',0,xml)
1031 1032
    if len(xml.text):
      roles = self.convertXmlValue(xml, data_type='tokens')
1033
      roles = list(roles) # Needed for CPS, or we have a CPS error
1034
    else:
1035
      roles = ()
1036
    permission = self.getAttribute(xml, 'id')
1037
    #LOG('local_role: ',0,'permission: %s roles: %s' % (repr(permission),repr(roles)))
1038 1039
    #user = roles[0]
    #roles = roles[1:]
1040
    if xml.xpath('local-name()') == self.local_permission_tag:
1041
      object.manage_setLocalPermissions(permission, roles)
1042 1043
    return conflict_list

1044
  security.declareProtected(Permissions.ModifyPortalContent, 'editDocument')
1045 1046 1047 1048 1049
  def editDocument(self, object=None, **kw):
    """
    This is the default editDocument method. This method
    can easily be overwritten.
    """
1050
    object._edit(**kw)
1051

1052 1053 1054 1055 1056 1057 1058 1059
  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)

1060 1061 1062 1063
  def nodeToString(self, node):
    """
    return an xml string corresponding to the node
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
1064
    return etree.tostring(node, encoding='utf-8', pretty_print=True)
1065 1066 1067 1068 1069 1070 1071

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

1072 1073 1074 1075 1076 1077 1078 1079
#  def getGidFromXML(self, xml, namespace, gid_from_xml_list):
#    """
#    return the Gid composed with xml informations
#    """
#    gid = xml.xpath('string(.//syncml:id)')
#    if gid in gid_from_xml_list or gid == ' ':
#      return False
#    return gid
1080