ERP5Conduit.py 45 KB
Newer Older
Nicolas Delaby's avatar
Nicolas Delaby committed
1
# -*- coding: utf-8 -*-
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#          Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################

30
from Products.ERP5SyncML.XMLSyncUtils import XMLSyncUtilsMixin
31
from Products.ERP5SyncML.Conflict import Conflict
32
from Products.ERP5Type.Utils import deprecated
33
from Products.ERP5Type.XMLExportImport import MARSHALLER_NAMESPACE_URI
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34
from Products.CMFCore.utils import getToolByName
35
from DateTime.DateTime import DateTime
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36 37
from email.MIMEBase import MIMEBase
from email import Encoders
Sebastien Robin's avatar
Sebastien Robin committed
38
from AccessControl import ClassSecurityInfo
39
from Products.ERP5Type import Permissions, interfaces
40
from Products.ERP5Type.Globals import PersistentMapping
41
import pickle
42
from xml.sax.saxutils import unescape
Fabien Morin's avatar
Fabien Morin committed
43
import re
44
from lxml import etree
45
from lxml.etree import Element
46
parser = etree.XMLParser(remove_blank_text=True)
47 48
from xml_marshaller.xml_marshaller import load_tree as unmarshaller
from xupdate_processor import xuproc
49
from zLOG import LOG, INFO, DEBUG
50
from base64 import standard_b64decode
51
from zope.interface import implements
52
from copy import deepcopy
Fabien Morin's avatar
Fabien Morin committed
53

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

    - updating an object attributes from an XUpdate XML stream

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

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

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

    2- updating that empty instance with the conduit

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

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

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

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

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

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

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

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

115 116 117 118 119 120 121 122
    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
123 124
    This fucntion returns conflict_list, wich is of the form,
    [conflict1,conflict2,...] where conclict1 is of the form :
125
    [object.getPath(),keyword,local_and_actual_value,subscriber_value]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
126
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
127 128
    reset_local_roles = False
    reset_workflow = False
Jean-Paul Smets's avatar
Jean-Paul Smets committed
129
    conflict_list = []
130
    xml = self.convertToXml(xml)
131 132
    #LOG('ERP5Conduit.addNode', INFO, 'object path:%s' % object.getPath())
    #LOG('ERP5Conduit.addNode', INFO, '\n%s' % etree.tostring(xml, pretty_print=True))
133
    if xml is None:
134
      return {'conflict_list': conflict_list, 'object': sub_object}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
135
    # In the case where this new node is a object to add
136 137 138 139 140 141
    xpath_expression = xml.get('select')
    if xml.xpath('local-name()') == self.history_tag and not reset:
      conflict_list += self.addWorkflowNode(object, xml, simulate)
    elif xml.xpath('name()') in self.XUPDATE_INSERT_OR_ADD and\
                            MARSHALLER_NAMESPACE_URI not in xml.nsmap.values():
      # change the context according select expression
142 143 144
      get_target_parent = xml.xpath('name()') in self.XUPDATE_INSERT
      context = self.getContextFromXpath(object, xpath_expression,
                                         get_target_parent=get_target_parent)
145 146 147 148
      for element in xml.findall('{%s}element' % xml.nsmap['xupdate']):
        xml = self.getElementFromXupdate(element)
        conflict_list += self.addNode(xml=xml, object=context, **kw)\
                                                              ['conflict_list']
149
    elif xml.xpath('local-name()') == self.xml_object_tag:
150 151 152 153 154
      sub_object = self._createContent(xml=xml,
                                      object=object,
                                      sub_object=sub_object,
                                      reset_local_roles=reset_local_roles,
                                      reset_workflow=reset_workflow,
155
                                      reset=reset,
156 157
                                      simulate=simulate,
                                      **kw)
158
    elif xml.xpath('local-name()') in self.local_role_list:
159
      self.addLocalRoleNode(object, xml)
160
    elif xml.xpath('local-name()') in self.local_permission_list:
161
      conflict_list += self.addLocalPermissionNode(object, xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
162
    else:
163 164
      conflict_list += self.updateNode(xml=xml, object=object, reset=reset,
                                                       simulate=simulate, **kw)
165 166
    # We must returns the object created
    return {'conflict_list':conflict_list, 'object': sub_object}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
167

Sebastien Robin's avatar
Sebastien Robin committed
168
  security.declareProtected(Permissions.ModifyPortalContent, 'deleteNode')
169
  def deleteNode(self, xml=None, object=None, object_id=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
170 171 172
    """
    A node is deleted
    """
173 174 175
    #LOG('ERP5Conduit.deleteNode', INFO, 'object path:%s' % object.getPath())
    #LOG('ERP5Conduit deleteNode', INFO, 'object_id:%r' % object_id)
    if object_id is not None:
176
      self._deleteContent(object=object, object_id=object_id)
177 178 179 180 181 182 183 184 185 186 187 188 189 190
      return []
    xml = self.convertToXml(xml)
    #LOG('ERP5Conduit deleteNode', INFO, etree.tostring(xml, pretty_print=True))
    xpath_expression = xml.get('select')
    context_to_delete = self.getContextFromXpath(object, xpath_expression)
    if context_to_delete != object:
      self._deleteContent(object=context_to_delete.getParentValue(),
                                           object_id=context_to_delete.getId())
    else:
      #same context
      if [role for role in self.local_role_list if role in xpath_expression]:
        user = self.extract_id_from_xpath.findall(xpath_expression)[-1][3]
        #LOG('ERP5Conduit.deleteNode local_role: ', INFO, 'user: %r' % user)
        if self.local_role_tag in xpath_expression:
191
          object.manage_delLocalRoles([user])
192
        elif self.local_group_tag in xpath_expression:
193
          object.manage_delLocalGroupRoles([user])
194 195 196 197 198
      if [permission for permission in self.local_permission_list if\
                                               permission in xpath_expression]:
        permission = self.extract_id_from_xpath.findall(xpath_expression)[-1][3]
        #LOG('ERP5Conduit.deleteNode permission: ', INFO,
                                                 #'permission: %r' % permission)
199
        object.manage_setLocalPermissions(permission)
200
    return []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
201

202 203 204 205 206
  security.declareProtected(Permissions.ModifyPortalContent, 'deleteObject')
  def deleteObject(self, object, object_id):
    try:
      object._delObject(object_id)
    except (AttributeError, KeyError):
207
      #LOG('ERP5Conduit.deleteObject', DEBUG, 'Unable to delete: %s' % str(object_id))
208 209
      pass

Sebastien Robin's avatar
Sebastien Robin committed
210
  security.declareProtected(Permissions.ModifyPortalContent, 'updateNode')
211 212
  def updateNode(self, xml=None, object=None, previous_xml=None, force=False,
                 simulate=False, reset=False, xpath_expression=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
213 214 215 216 217 218 219 220
    """
    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 = []
221 222
    if xml is None:
      return {'conflict_list':conflict_list, 'object':object}
223
    xml = self.convertToXml(xml)
224 225 226 227
    #LOG('ERP5Conduit.updateNode, force: ', INFO, force)
    #LOG('ERP5Conduit updateNode', INFO, object.getPath())
    #LOG('ERP5Conduit updateNode', INFO, '\n%s' % etree.tostring(xml, pretty_print=True))
    if xml.tag == '{%s}modifications' % xml.nsmap.get('xupdate'):
228 229 230 231
      conflict_list += self.applyXupdate(object=object,
                                         xupdate=xml,
                                         previous_xml=previous_xml,
                                         force=force,
232 233 234
                                         simulate=simulate,
                                         reset=reset,
                                         **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
235 236 237
    # we may have only the part of an xupdate
    else:
      args = {}
238
      if self.isProperty(xml):
239
        keyword = None
240
        value = xml.get('select')
241 242 243
        if value is not None:
          select_list = value.split('/') # Something like:
                                         #('','object[1]','sid[1]')
244
          new_select_list = []
245 246 247
          for select_item in select_list:
            if select_item.find('[') >= 0:
              select_item = select_item[:select_item.find('[')]
248
            new_select_list.append(select_item)
249
          select_list = new_select_list # Something like : ('','object','sid')
250
          keyword = select_list[-1] # this will be 'sid'
251
        data = None
252 253
        if xml.xpath('name()') not in self.XUPDATE_INSERT_OR_ADD:
          for subnode in xml:
254
            if subnode.xpath('name()') in self.XUPDATE_ELEMENT:
255
              keyword = subnode.get('name')
256
              data_xml = subnode
257
        else:
258
          #XXX find something better than hardcoded prefix
259
          # We can call add node
260 261 262 263
          conflict_list += self.addNode(xml=xml,
                                        object=object,
                                        force=force,
                                        simulate=simulate,
264
                                        reset=reset,
265 266
                                        **kw)
          return conflict_list
267
        if xml.xpath('name()') in self.XUPDATE_DEL:
268 269 270 271
          conflict_list += self.deleteNode(xml=xml,
                                           object=object,
                                           force=force,
                                           simulate=simulate,
272
                                           reset=reset,
273 274
                                           **kw)
          return conflict_list
275
        if keyword is None: # This is not a selection, directly the property
276
          keyword = xml.xpath('name()')
277
        if keyword not in self.NOT_EDITABLE_PROPERTY:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
278
          # We will look for the data to enter
279
          xpath_expression = xml.get('select', xpath_expression)
280 281 282 283
          get_target_parent = xml.xpath('name()') in self.XUPDATE_INSERT
          context = self.getContextFromXpath(object,
                                           xpath_expression,
                                           get_target_parent=get_target_parent)
284 285
          data_type = context.getPropertyType(keyword)
          #LOG('ERP5Conduit.updateNode', INFO, 'data_type:%r for keyword: %s' % (data_type, keyword))
286
          data = self.convertXmlValue(xml, data_type=data_type)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
287 288 289 290 291
          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
292 293
          #   - 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
294
          #   - current_data : the data actually on this box
Nicolas Delaby's avatar
Nicolas Delaby committed
295
          isConflict = False
296
          if previous_xml is not None and not force:
Fabien Morin's avatar
Fabien Morin committed
297
          # if no previous_xml, no conflict
298 299 300 301 302 303 304 305 306 307 308 309 310 311
            #old_data = self.getObjectProperty(keyword, previous_xml,
                                              #data_type=data_type)
            previous_xml_tree = self.convertToXml(previous_xml)
            old_result = previous_xml_tree.xpath(xpath_expression)
            if old_result:
              old_data = self.convertXmlValue(old_result[0])
            else:
              raise ValueError('Xpath expression does not apply on previous'\
                                                  ' xml:%r' % xpath_expression)
            current_data = self.getProperty(context, keyword)
            #LOG('ERP5Conduit.updateNode', INFO, 'Conflict keyword: %s' % keyword)
            #LOG('ERP5Conduit.updateNode', INFO, 'Conflict data: %s' % str(data))
            #LOG('ERP5Conduit.updateNode', INFO, 'Conflict old_data: %s' % str(old_data))
            #LOG('ERP5Conduit.updateNode', INFO, 'Conflict current_data: %s' % str(current_data))
312 313
            if (old_data != current_data) and (data != current_data) \
                and keyword not in self.force_conflict_list:
314 315 316 317 318 319 320 321 322 323 324
              #LOG('ERP5Conduit.updateNode', INFO, 'Conflict on : %s' % keyword)
              # This is a conflict
              isConflict = True
              xml_string = etree.tostring(xml, encoding='utf-8')
              conflict = Conflict(object_path=context.getPhysicalPath(),
                                  keyword=keyword)
              conflict.setXupdate(xml_string)
              if not (data_type in self.binary_type_list):
                conflict.setLocalValue(current_data)
                conflict.setRemoteValue(data)
              conflict_list += [conflict]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
325
          # We will now apply the argument with the method edit
326
          if args and (not isConflict or force) and \
327
              (not simulate or reset):
328
            self._updateContent(object=context, **args)
329
            # It is sometimes required to do something after an edit
330 331
            if getattr(context, 'manage_afterEdit', None) is not None:
              context.manage_afterEdit()
332

Jean-Paul Smets's avatar
Jean-Paul Smets committed
333 334
        if keyword == 'object':
          # This is the case where we have to call addNode
335 336 337 338
          conflict_list += self.addNode(xml=xml,
                                        object=object,
                                        force=force,
                                        simulate=simulate,
339
                                        reset=reset,
340
                                        **kw)['conflict_list']
341
        elif keyword == self.history_tag and not simulate:
342
          # This is the case where we have to call addNode
343 344
          conflict_list += self.addNode(xml=subnode, object=object,
                                        force=force, simulate=simulate,
345
                                        reset=reset, **kw)['conflict_list']
346
        elif keyword in (self.local_role_tag, self.local_permission_tag) and not simulate:
347
          # This is the case where we have to update Roles or update permission
348
          #LOG('ERP5Conduit.updateNode', DEBUG, 'we will add a local role')
349 350 351
          #user = self.getSubObjectId(xml)
          #roles = self.convertXmlValue(data,data_type='tokens')
          #object.manage_setLocalRoles(user,roles)
352 353
          conflict_list += self.addNode(xml=xml, object=object,
                                       force=force, simulate=simulate,
354
                                       reset=reset, **kw)['conflict_list']
Jean-Paul Smets's avatar
Jean-Paul Smets committed
355 356
    return conflict_list

357
  security.declareProtected(Permissions.AccessContentsInformation,
358
      'getFormatedArgs')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
359 360 361 362 363 364 365 366
  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]
367
      if isinstance(keyword, unicode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
368
        keyword = keyword.encode(self.getEncoding())
369
      if isinstance(data, (tuple, list)):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
370 371
        new_data = []
        for item in data:
372
          if isinstance(item, unicode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
373
            item = item.encode(self.getEncoding())
374
          new_data.append(item)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
375
        data = new_data
376
      if isinstance(data, unicode):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
377 378
        data = data.encode(self.getEncoding())
      if keyword == 'binary_data':
379
        #LOG('ERP5Conduit.getFormatedArgs', DEBUG, 'binary_data keyword: %s' % str(keyword))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
380 381 382
        msg = MIMEBase('application','octet-stream')
        Encoders.encode_base64(msg)
        msg.set_payload(data)
Nicolas Delaby's avatar
Nicolas Delaby committed
383
        data = msg.get_payload(decode=True)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
384 385 386
      new_args[keyword] = data
    return new_args

387
  security.declareProtected(Permissions.AccessContentsInformation, 'isProperty')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
388 389 390
  def isProperty(self, xml):
    """
    Check if it is a simple property
391 392
    not an attribute @type it's a metadata
    """
393
    bad_list = (self.history_exp, self.attribute_type_exp,)
394
    value = xml.get('select')
395 396 397
    if value is not None:
      for bad_string in bad_list:
        if bad_string.search(value) is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
398 399
          return False
    return True
Jean-Paul Smets's avatar
Jean-Paul Smets committed
400

401
  def getContextFromXpath(self, context, xpath, get_target_parent=False):
402 403 404 405 406
    """Return the last object from xpath expression
    /object[@gid='foo']/object[@id='bar']/object[@id='freak']/property
    will return object.getId() == 'freak'
    - We ignore the first object_block /object[@gid='foo'] intentionaly
    because the targeted context is already actual context.
407 408 409
      context: object in acquisition context
      xpath: string which is xpath expression to fetch the object
      get_target_parent: boolean to get the parent of targetted object
410 411 412 413
    """
    if xpath is None:
      return context
    result_list = self.extract_id_from_xpath.findall(xpath)
414 415
    if get_target_parent:
      result_list = result_list[:-1]
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
    first_object = True
    while result_list:
      object_block = result_list[0][0]
      sub_context_id = result_list[0][3]
      sub_context = context._getOb(sub_context_id, None)
      if first_object:
        first_object = False
      elif sub_context is not None:
        context = sub_context
      else:
        # Ignore non existing objects
        LOG('ERP5Conduit', INFO, 'sub document of %s not found with id:%r'%\
                                         (context.getPath(), sub_context_id))
      xpath = xpath.replace(object_block, '', 1)
      result_list = self.extract_id_from_xpath.findall(xpath)
431 432
      if get_target_parent:
        result_list = result_list[:-1]
433 434
    return context

435
  security.declareProtected(Permissions.AccessContentsInformation,
436 437
                                                         'getSubObjectXupdate')
  @deprecated
Jean-Paul Smets's avatar
Jean-Paul Smets committed
438 439 440 441 442
  def getSubObjectXupdate(self, xml):
    """
    This will change the xml in order to change the update
    from the object to the subobject
    """
443 444
    xml_copy = deepcopy(xml)
    self.changeSubObjectSelect(xml_copy)
445
    return xml_copy
Jean-Paul Smets's avatar
Jean-Paul Smets committed
446

447
  security.declareProtected(Permissions.AccessContentsInformation,
448
      'isHistoryAdd')
449
  def isHistoryAdd(self, xml):
Sebastien Robin's avatar
Sebastien Robin committed
450
    bad_list = (self.history_exp,)
451
    value = xml.get('select')
452 453 454 455 456 457 458
    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
459 460
    return 0

461
  security.declareProtected(Permissions.AccessContentsInformation,
462 463
                                                     'isSubObjectModification')
  @deprecated
Jean-Paul Smets's avatar
Jean-Paul Smets committed
464 465 466 467
  def isSubObjectModification(self, xml):
    """
    Check if it is a modification from an subobject
    """
468
    good_list = (self.sub_object_exp,)
469 470 471 472 473
    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
474 475
    return 0

476
  security.declareProtected(Permissions.AccessContentsInformation,
477 478
                                                           'getSubObjectDepth')
  @deprecated
479 480 481 482 483 484 485
  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
    """
486 487
    #LOG('getSubObjectDepth',0,'xml.tag: %s' % xml.tag)
    if xml.xpath('name()') in self.XUPDATE_TAG:
488
      i = 0
489
      if xml.xpath('name()') in self.XUPDATE_INSERT:
490
        i = 1
491 492 493 494 495 496 497 498 499 500 501 502 503 504
      #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
505 506
    return 0

507 508
  security.declareProtected(Permissions.ModifyPortalContent,
      'changeSubObjectSelect')
509
  @deprecated
510
  def changeSubObjectSelect(self, xml):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
511
    """
512 513
    Return a string wich is the selection for the subobject
    ex: for "/object[@id='161']/object[@id='default_address']/street_address"
514
    it returns "/object[@id='default_address']/street_address"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
515
    """
516 517
    select = xml.attrib.get('select')
    if self.object_exp.search(select) is not None:
518
      s = '/'
519 520
      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):]
521 522
      else:
        new_value = '/'
523
      select = new_value
524
    xml.attrib['select'] = select
525

526
  security.declareProtected(Permissions.AccessContentsInformation,
527
      'getSubObjectId')
528
  @deprecated
529 530 531 532 533
  def getSubObjectId(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
534 535 536 537 538 539 540
    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
541 542
    return object_id

543
  security.declareProtected(Permissions.AccessContentsInformation,
544
      'getHistoryIdFromSelect')
545 546 547 548 549
  def getHistoryIdFromSelect(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
550 551 552 553 554 555 556 557
    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
558 559
    return object_id

560 561
  security.declareProtected(Permissions.AccessContentsInformation,
      'getSubObjectXml')
562 563 564 565 566
  def getSubObjectXml(self, object_id, xml):
    """
    Return the xml of the subobject which as the id object_id
    """
    xml = self.convertToXml(xml)
567
    for subnode in xml:
568
      if subnode.xpath('local-name()') == self.xml_object_tag:
569
        if object_id == subnode.get('id'):
570 571
          return subnode
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
572

573 574 575
  security.declareProtected(Permissions.AccessContentsInformation,
                                                                'getAttribute')
  @deprecated
576
  def getAttribute(self, xml, param):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
577
    """
578
    Retrieve the given parameter from the xml
Jean-Paul Smets's avatar
Jean-Paul Smets committed
579
    """
580
    return xml.attrib.get(param, None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
581

582 583 584
  security.declareProtected(Permissions.AccessContentsInformation,
                                                           'getObjectProperty')
  @deprecated
585
  def getObjectProperty(self, property, xml, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
586 587 588
    """
    Retrieve the given property
    """
589
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
590
    # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
591
    for subnode in xml:
592
      if subnode.xpath('local-name()') == property:
593
        return self.convertXmlValue(subnode)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
594
    return None
595

596
  def replaceIdFromXML(self, xml, attribute_name, new_id, as_string=True):
597 598 599
    """
      return a xml with id replace by a new id
    """
600 601 602
    if isinstance(xml, str):
      xml = etree.XML(xml, parser=parser)
    else:
603
      #copy of xml object for modification
604 605 606 607 608 609 610
      xml = deepcopy(xml)
    object_element = xml.find('object')
    del object_element.attrib['id']
    object_element.attrib[attribute_name] = new_id
    if as_string:
      return etree.tostring(xml)
    return xml
611 612 613 614 615 616 617 618 619 620 621 622 623

  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

624
  def getXMLFromObjectWithGid(self, object, gid, xml_mapping, as_string=True):
625 626 627
    """
      return the xml with Gid of Object
    """
628 629 630
    xml_with_id = self.getXMLFromObjectWithId(object, xml_mapping)
    return self.replaceIdFromXML(xml_with_id, 'gid', gid, as_string=as_string)

631

632
  def getXMLFromObjectWithRid(self, object, rid, xml_mapping, as_string=True):
633 634 635 636
    """
      return the xml with Rid of Object
    """
    xml_id = self.getXMLFromObjectWithId(object, xml_mapping)
637
    xml_rid = self.replaceIdFromXML(xml_id, 'rid', rid, as_string=as_string)
638
    return xml_rid
Jean-Paul Smets's avatar
Jean-Paul Smets committed
639

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

Nicolas Delaby's avatar
Nicolas Delaby committed
655 656
  security.declareProtected(Permissions.AccessContentsInformation,
                                                               'getObjectType')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
657 658 659 660
  def getObjectType(self, xml):
    """
    Retrieve the portal type from an xml
    """
661
    return xml.get('portal_type')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
662

Nicolas Delaby's avatar
Nicolas Delaby committed
663 664
  security.declareProtected(Permissions.AccessContentsInformation,
                                                             'getPropertyType')
665 666 667 668
  def getPropertyType(self, xml):
    """
    Retrieve the portal type from an xml
    """
669
    return xml.get('type')
670

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

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

Nicolas Delaby's avatar
Nicolas Delaby committed
717 718
  security.declareProtected(Permissions.AccessContentsInformation,
                                                              'afterNewObject')
719
  def afterNewObject(self, object):
Nicolas Delaby's avatar
Nicolas Delaby committed
720 721
    """Overloadable method
    """
722 723
    pass

Nicolas Delaby's avatar
Nicolas Delaby committed
724 725
  security.declareProtected(Permissions.AccessContentsInformation,
                                                            'getStatusFromXml')
726 727 728 729 730
  def getStatusFromXml(self, xml):
    """
    Return a worklow status from xml
    """
    status = {}
731
    for subnode in xml:
732 733
      keyword = subnode.tag
      value = self.convertXmlValue(xml.find(keyword))
734 735 736
      status[keyword] = value
    return status

737 738 739
  security.declareProtected(Permissions.AccessContentsInformation,
                                                       'getXupdateElementList')
  @deprecated
740 741 742 743
  def getXupdateElementList(self, xml):
    """
    Retrieve the list of xupdate:element subnodes
    """
744
    return xml.xpath('|'.join(['.//*[name() = "%s"]' % name for name in self.XUPDATE_ELEMENT]))
745

Nicolas Delaby's avatar
Nicolas Delaby committed
746 747
  security.declareProtected(Permissions.AccessContentsInformation,
                                                       'getElementFromXupdate')
748 749
  def getElementFromXupdate(self, xml):
    """
750
    return a fragment node with applied xupdate
Nicolas Delaby's avatar
Nicolas Delaby committed
751 752
    This method simulate an xupdate transformation on given XML.
    It transform the xupdate into node handleable by Conduit
753
    """
754
    if xml.xpath('name()') in self.XUPDATE_ELEMENT:
755 756 757 758 759 760 761
      new_node = Element(xml.get('name'), nsmap=xml.nsmap)
      for subnode in xml.findall('{%s}attribute' % xml.nsmap['xupdate']):
        new_node.attrib.update({subnode.get('name'): subnode.text})
      ## Then dumps the xml and remove xupdate:attribute nodes
      new_node.extend(deepcopy(child) for child in\
                                 xml.xpath('*[name() != "xupdate:attribute"]'))
      new_node.text = xml.text
762 763
      new_node.tail = xml.tail
      return new_node
Nicolas Delaby's avatar
Nicolas Delaby committed
764
    if xml.xpath('name()') in (self.XUPDATE_UPDATE):
Nicolas Delaby's avatar
Nicolas Delaby committed
765 766
      # This condition seems not used anymore and not efficient
      # Usage of xupdate_processor is recommanded
767
      result = u'<'
768
      attribute = xml.attrib.get('select')
769 770 771 772 773 774 775 776
      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)
777
      property = attribute[:s_place].strip('/')
778 779 780 781
      result += property
      if select_id is not None:
        result += ' id=%s' % select_id
      result +=  '>'
782
      xml_string = self.nodeToString(xml)
783
      maxi = xml_string.find('>')+1
784
      result += xml_string[maxi:xml_string.find('</%s>' % xml.xpath('name()'))]
Nicolas Delaby's avatar
Nicolas Delaby committed
785
      result += '</%s>' % (property)
Nicolas Delaby's avatar
Nicolas Delaby committed
786
      #LOG('getElementFromXupdate, result:',0,repr(result))
787
      return self.convertToXml(result)
788 789
    return xml

Nicolas Delaby's avatar
Nicolas Delaby committed
790 791
  security.declareProtected(Permissions.AccessContentsInformation,
                                                    'getWorkflowActionFromXml')
792 793 794 795 796
  def getWorkflowActionFromXml(self, xml):
    """
    Return the list of workflow actions
    """
    action_list = []
797
    if xml.xpath('name()') in self.XUPDATE_ELEMENT:
798
      action_list.append(xml)
799
      return action_list
800
    for subnode in xml:
801
      if subnode.xpath('local-name()') == self.action_tag:
802
        action_list.append(subnode)
803 804
    return action_list

805 806
  security.declareProtected(Permissions.AccessContentsInformation,
                                                             'convertXmlValue')
807
  def convertXmlValue(self, node, data_type=None):
808
    """Cast xml information into appropriate python type
Jean-Paul Smets's avatar
Jean-Paul Smets committed
809
    """
810 811
    if node is None:
      return None
812 813
    if data_type is None:
      data_type = self.getPropertyType(node)
814 815
    if data_type == self.none_type:
      return None
816 817 818
    data = node.text
    if data is not None and isinstance(data, unicode):
      data = data.encode('utf-8')
819 820
    elif data is None and data_type in self.text_type_list:
      return ''
821 822
    # We can now convert string in tuple, dict, binary...
    if data_type in self.list_type_list:
823
      data = unmarshaller(node[0])
824
    elif data_type in self.text_type_list:
825
      data = unescape(data)
826 827
    elif data_type in self.data_type_list:
      if data is None:
828 829 830
        # data is splitted inside  block_data nodes
        data = ''.join([standard_b64decode(block.text) for\
                                                 block in node.iterchildren()])
831
    elif data_type in self.pickle_type_list:
832
      data = pickle.loads(standard_b64decode(data))
833 834
    elif data_type in self.date_type_list:
      data = DateTime(data)
Sebastien Robin's avatar
Sebastien Robin committed
835 836
    elif data_type in self.int_type_list:
      data = int(data)
837 838 839 840 841 842 843
    elif data_type == self.boolean_type:
      if data == 'False':
        data = False
      elif data == 'True':
        data = True
      else:
        raise NotImplementedError
Jean-Paul Smets's avatar
Jean-Paul Smets committed
844 845
    return data

846

Sebastien Robin's avatar
Sebastien Robin committed
847
  security.declareProtected(Permissions.ModifyPortalContent, 'applyXupdate')
848
  def applyXupdate(self, object=None, xupdate=None, previous_xml=None, **kw):
849 850 851 852
    """
    Parse the xupdate and then it will call the conduit
    """
    conflict_list = []
853
    if isinstance(xupdate, (str, unicode)):
854
      xupdate = etree.XML(xupdate, parser=parser)
855 856 857
    #LOG("applyXupdate", INFO, etree.tostring(xupdate, pretty_print=True))
    xupdate_builded = False
    xpath_expression_update_dict = {}
858
    for subnode in xupdate:
859
      selection_name = ''
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882
      original_xpath_expression = subnode.get('select', '')
      if not xupdate_builded and\
                            MARSHALLER_NAMESPACE_URI in subnode.nsmap.values()\
                                  or 'block_data' in original_xpath_expression:
        # It means that the xpath expression is targetting
        # marshalled values or data nodes. We need to rebuild the original xml
        # in its own context in order to retrieve original value

        # We are insde a loop build the XUpdated tree only once
        xupdate_builded = True

        # Find the prefix used by marshaller.
        for prefix, namespace_uri in subnode.nsmap.iteritems():
          if namespace_uri == MARSHALLER_NAMESPACE_URI:
            break
        # TODO add support of etree objects for xuproc to avoid
        # serializing tree into string
        if not isinstance(previous_xml, str):
          previous_xml = etree.tostring(previous_xml)
        xupdated_tree = xuproc.applyXUpdate(xml_xu_string=etree.tostring(xupdate),
                                            xml_doc_string=previous_xml)
      if MARSHALLER_NAMESPACE_URI in subnode.nsmap.values():
        xpath_expression = original_xpath_expression
883 884 885
        get_target_parent = subnode.xpath('name()') in self.XUPDATE_INSERT
        context = self.getContextFromXpath(object, xpath_expression,
                                           get_target_parent=get_target_parent)
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
        base_xpath_expression = xpath_expression\
                                            [:xpath_expression.index(prefix)-1]
        xupdated_node_list = xupdated_tree.xpath(base_xpath_expression)
        if xupdated_node_list:
          xupdated_node = xupdated_node_list[0]
        else:
          ValueError('Wrong xpath expression:%r' % base_xpath_expression)
        if base_xpath_expression not in xpath_expression_update_dict:
          xpath_expression_update_dict[base_xpath_expression] = \
                                   dict(xml=xupdated_node,
                                        object=context,
                                        xpath_expression=base_xpath_expression)
      elif 'block_data' in original_xpath_expression:
        """XXX Use Qualified Names for block_data nodes
        to avoid ambiguity
        """
        xpath_expression = original_xpath_expression
Nicolas Delaby's avatar
Typo  
Nicolas Delaby committed
903
        get_target_parent = subnode.xpath('name()') in self.XUPDATE_INSERT
904 905
        context = self.getContextFromXpath(object, xpath_expression,
                                           get_target_parent=get_target_parent)
906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
        base_xpath_expression = xpath_expression\
                                            [:xpath_expression.index('block_data')-1]
        xupdated_node_list = xupdated_tree.xpath(base_xpath_expression)
        if xupdated_node_list:
          xupdated_node = xupdated_node_list[0]
        else:
          ValueError('Wrong xpath expression:%r' % base_xpath_expression)
        if base_xpath_expression not in xpath_expression_update_dict:
          xpath_expression_update_dict[base_xpath_expression] = \
                                   dict(xml=xupdated_node,
                                        object=context,
                                        xpath_expression=base_xpath_expression)
      elif subnode.xpath('name()') in self.XUPDATE_INSERT_OR_ADD:
        conflict_list += self.addNode(xml=subnode, object=object,
                                      previous_xml=previous_xml,
                                      **kw)['conflict_list']
922
      elif subnode.xpath('name()') in self.XUPDATE_DEL:
923 924
        conflict_list += self.deleteNode(xml=subnode, object=object,
                                         previous_xml=previous_xml, **kw)
925
      elif subnode.xpath('name()') in self.XUPDATE_UPDATE:
926 927 928 929 930 931 932 933
        conflict_list += self.updateNode(xml=subnode, object=object,
                                         previous_xml=previous_xml, **kw)

    # Now apply collected xupdated_node
    for update_dict in xpath_expression_update_dict.itervalues():
      update_dict.update(kw)
      conflict_list += self.updateNode(previous_xml=previous_xml,
                                       **update_dict)
934 935
    return conflict_list

936 937
  def isWorkflowActionAddable(self, object=None, status=None, wf_tool=None,
                              wf_id=None, xml=None):
Sebastien Robin's avatar
Sebastien Robin committed
938 939
    """
    Some checking in order to check if we should add the workfow or not
940 941 942 943 944 945 946 947
    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
    wf_history = object.workflow_history
    if wf_history.has_key(wf_id):
      action_list = wf_history[wf_id]
    else: action_list = []
Nicolas Delaby's avatar
Nicolas Delaby committed
948
    addable = True
949
    for action in action_list:
Nicolas Delaby's avatar
Nicolas Delaby committed
950
      this_one = True
951 952
      for key in action.keys():
        if status[key] != action[key]:
Nicolas Delaby's avatar
Nicolas Delaby committed
953
          this_one = False
954 955
          break
      if this_one:
Nicolas Delaby's avatar
Nicolas Delaby committed
956
        addable = False
957 958 959
        break
    return addable

960
  security.declareProtected(Permissions.ModifyPortalContent, 'constructContent')
961
  def constructContent(self, object, object_id, portal_type):
962 963 964 965 966
    """
    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
967
    #LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
968
    object.newContent(portal_type=portal_type, id=object_id)
969
    subobject = object._getOb(object_id)
Nicolas Delaby's avatar
Nicolas Delaby committed
970
    return subobject, True, True
971

972 973 974 975 976
  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.
977
    """
978 979
    conflict_list = []
    # We want to add a workflow action
Nicolas Delaby's avatar
Nicolas Delaby committed
980 981
    wf_tool = getToolByName(object.getPortalObject(), 'portal_workflow')
    wf_id = xml.get('id')
982 983
    if wf_id is None: # History added by xupdate
      wf_id = self.getHistoryIdFromSelect(xml)
984
      xml = xml[0]
985 986
    #for action in self.getWorkflowActionFromXml(xml):
    status = self.getStatusFromXml(xml)
Nicolas Delaby's avatar
Nicolas Delaby committed
987
    #LOG('addNode, status:',0,status)
988
    add_action = self.isWorkflowActionAddable(object=object,
Nicolas Delaby's avatar
Nicolas Delaby committed
989 990
                                              status=status,wf_tool=wf_tool,
                                              wf_id=wf_id,xml=xml)
991
    if add_action and not simulate:
Nicolas Delaby's avatar
Nicolas Delaby committed
992
      wf_tool.setStatusOf(wf_id, object, status)
993

994
    return conflict_list
995

996 997 998 999 1000
  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.
1001
    """
1002
    # We want to add a local role
1003
    roles = self.convertXmlValue(xml, data_type='tokens')
Nicolas Delaby's avatar
Nicolas Delaby committed
1004 1005 1006
    user = xml.get('id')
    #LOG('local_role: %s' % object.getPath(), INFO,
                                          #'user:%r | roles:%r' % (user, roles))
1007 1008
    #user = roles[0]
    #roles = roles[1:]
1009
    if xml.xpath('local-name()') == self.local_role_tag:
1010
      object.manage_setLocalRoles(user, roles)
1011
    elif xml.xpath('local-name()') == self.local_group_tag:
1012
      object.manage_setLocalGroupRoles(user, roles)
1013

1014 1015 1016 1017 1018
  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.
1019 1020
    """
    conflict_list = []
1021
    # We want to add a local role
1022
    #LOG('addLocalPermissionNode, xml',0,xml)
1023 1024 1025
    roles = self.convertXmlValue(xml, data_type='tokens')

    permission = xml.get('id')
1026
    #LOG('local_role: ',0,'permission: %s roles: %s' % (repr(permission),repr(roles)))
1027 1028
    #user = roles[0]
    #roles = roles[1:]
1029
    if xml.xpath('local-name()') == self.local_permission_tag:
1030
      object.manage_setLocalPermissions(permission, roles)
1031 1032
    return conflict_list

1033
  security.declareProtected(Permissions.ModifyPortalContent, 'editDocument')
1034 1035 1036 1037 1038
  def editDocument(self, object=None, **kw):
    """
    This is the default editDocument method. This method
    can easily be overwritten.
    """
1039
    object._edit(**kw)
1040

1041 1042 1043 1044 1045 1046 1047 1048
  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)

1049 1050 1051 1052
  def nodeToString(self, node):
    """
    return an xml string corresponding to the node
    """
Nicolas Delaby's avatar
Nicolas Delaby committed
1053
    return etree.tostring(node, encoding='utf-8', pretty_print=True)
1054 1055 1056 1057 1058 1059 1060

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

1061 1062 1063
  def _createContent(self, xml=None, object=None, object_id=None,
                     sub_object=None, reset_local_roles=False,
                     reset_workflow=False, simulate=False, **kw):
1064 1065 1066 1067
    """
      This is the method calling to create an object
    """
    if object_id is None:
1068
      object_id = xml.get('id')
1069 1070
    if object_id is not None:
      if sub_object is None:
1071
        sub_object = object._getOb(object_id, None)
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
      if sub_object is None: # If so, it doesn't exist
        portal_type = ''
        if xml.xpath('local-name()') == self.xml_object_tag:
          portal_type = self.getObjectType(xml)
        sub_object, reset_local_roles, reset_workflow = self.constructContent(
                                                        object,
                                                        object_id,
                                                        portal_type)
      self.newObject(object=sub_object,
                     xml=xml,
                     simulate=simulate,
                     reset_local_roles=reset_local_roles,
                     reset_workflow=reset_workflow)
    return sub_object

  def _updateContent(self, object=None, **args):
    """
      This is the method for update the object
    """
    return self.editDocument(object=object, **args)

  def _deleteContent(self, object=None, object_id=None):
    """
      This is the method for delete the object
    """
    return self.deleteObject(object, object_id)


1100 1101 1102 1103 1104 1105 1106 1107
#  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