Workflow.py 43.1 KB
Newer Older
1 2 3 4
##############################################################################
#
# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
#                    Romain Courteaud <romain@nexedi.com>
5
#               2014 Wenjie Zheng <wenjie.zheng@tiolive.com>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# 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.
#
##############################################################################
wenjie.zheng's avatar
wenjie.zheng committed
28 29

import os
30
import sys
31 32

from AccessControl import ClassSecurityInfo
33
from AccessControl.unauthorized import Unauthorized
wenjie.zheng's avatar
wenjie.zheng committed
34
from AccessControl.SecurityManagement import getSecurityManager
35
from Acquisition import aq_base, aq_inner, aq_parent
36
from copy import deepcopy
37
from DateTime import DateTime
wenjie.zheng's avatar
wenjie.zheng committed
38
from DocumentTemplate.DT_Util import TemplateDict
39 40
from lxml import etree
from lxml.etree import Element, SubElement
41 42
from Products.CMFCore.Expression import Expression
from Products.CMFCore.utils import getToolByName
wenjie.zheng's avatar
wenjie.zheng committed
43 44
from Products.CMFCore.WorkflowCore import WorkflowException, ObjectDeleted,\
                                          ObjectMoved
45
from Products.DCWorkflow.DCWorkflow import ValidationFailed
wenjie.zheng's avatar
wenjie.zheng committed
46 47 48 49 50 51 52 53
from Products.DCWorkflow.Expression import StateChangeInfo
from Products.DCWorkflowGraph.config import DOT_EXE
from Products.DCWorkflowGraph.DCWorkflowGraph import bin_search, getGraph
from Products.DCWorkflow.utils import Message as _
from Products.DCWorkflow.utils import modifyRolesForPermission
from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition as DCWorkflow
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.Globals import PersistentMapping
54
from Products.ERP5Type.id_as_reference import IdAsReferenceMixin
55
from Products.ERP5Type.patches.Expression import createExprContext
wenjie.zheng's avatar
wenjie.zheng committed
56 57 58 59 60 61 62 63
from Products.ERP5Type.patches.WorkflowTool import SECURITY_PARAMETER_ID,\
                                                          WORKLIST_METADATA_KEY
from Products.ERP5Type.Utils import UpperCase, convertToMixedCase
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Workflow.Document.Transition import TRIGGER_AUTOMATIC,\
                                    TRIGGER_USER_ACTION, TRIGGER_WORKFLOW_METHOD
from tempfile import mktemp
from types import StringTypes
64
from zLOG import LOG, INFO, WARNING
65

wenjie.zheng's avatar
wenjie.zheng committed
66
class Workflow(IdAsReferenceMixin("", "prefix"), XMLObject):
67 68 69 70 71 72
  """
  A ERP5 Workflow.
  """

  meta_type = 'ERP5 Workflow'
  portal_type = 'Workflow'
73
  _isAWorkflow = True # DCWorkflow Tool compatibility
74 75 76
  add_permission = Permissions.AddPortalContent
  isPortalContent = 1
  isRADContent = 1
77
  default_reference = ''
78 79 80 81 82
  managed_permission_list = ()
  managed_role = ()
  erp5_permission_roles = {} # { permission: [role] or (role,) }
  manager_bypass = 0

83 84 85 86 87 88 89 90 91 92
  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Declarative properties
  property_sheets = (
    PropertySheet.Base,
    PropertySheet.XMLObject,
    PropertySheet.CategoryCore,
    PropertySheet.DublinCore,
93
    PropertySheet.Reference,
94 95 96
    PropertySheet.Workflow,
  )

wenjie.zheng's avatar
wenjie.zheng committed
97
  def notifyCreated(self, document):
98 99 100
    """
    Set initial state on the Document
    """
101
    state_var = self.getStateVariable()
102
    object = self.getStateChangeInformation(document, self.getSourceValue())
103

104
    # Initialize workflow history
105
    state_id = self.getSourceValue().getReference()
106
    status_dict = {state_var: state_id}
107
    variable_list = self.objectValues(portal_type='Variable')
108
    former_status = self._getOb(status_dict[state_var], None)
109
    ec = createExprContext(StateChangeInfo(document, self, former_status))
110

111
    for variable in variable_list:
112 113 114 115 116 117 118
      if variable.for_status == 0:
        continue
      if variable.default_expr is not None:
        expr = Expression(variable.default_expr)
        value = expr(ec)
      else:
        value = variable.getInitialValue(object=object)
119
      if value is None: value = ''
120
      status_dict[variable.getReference()] = value
121

122
    self._updateWorkflowHistory(document, status_dict)
123
    self.updateRoleMappingsFor(document)
124

125 126
  initializeDocument = notifyCreated

127 128 129 130
  def _generateHistoryKey(self):
    """
    Generate a key used in the workflow history.
    """
131 132
    history_key = self.unrestrictedTraverse(self.getRelativeUrl()).getReference()
    return history_key
133 134 135 136 137 138 139 140 141 142

  def _updateWorkflowHistory(self, document, status_dict):
    """
    Change the state of the object.
    """
    # Create history attributes if needed
    if getattr(aq_base(document), 'workflow_history', None) is None:
      document.workflow_history = PersistentMapping()
      # XXX this _p_changed is apparently not necessary
      document._p_changed = 1
143

144 145 146 147
    # Add an entry for the workflow in the history
    workflow_key = self._generateHistoryKey()
    if not document.workflow_history.has_key(workflow_key):
      document.workflow_history[workflow_key] = ()
148

149
    # Update history
150
    document.workflow_history[workflow_key] += (status_dict,)
151 152
    # XXX this _p_changed marks the document modified, but the
    # only the PersistentMapping is modified
wenjie.zheng's avatar
wenjie.zheng committed
153
    # document._p_changed = 1
154
    # XXX this _p_changed is apparently not necessary
155 156
    #document.workflow_history._p_changed = 1

157 158 159 160 161
  def getCurrentStatusDict(self, document):
    """
    Get the current status dict.
    """
    workflow_key = self._generateHistoryKey()
162

163 164 165 166 167 168 169 170 171 172
    # Copy is requested
    result = document.workflow_history[workflow_key][-1].copy()
    return result

  def getDateTime(self):
    """
    Return current date time.
    """
    return DateTime()

173 174 175
  def getManagedPermissionList(self):
    return self.managed_permission_list

176 177 178 179 180 181 182 183 184 185 186 187 188
  def getStateChangeInformation(self, document, state, transition=None):
    """
    Return an object used for variable tales expression.
    """
    if transition is None:
      transition_url = None
    else:
      transition_url = transition.getRelativeUrl()
    return self.asContext(document=document,
                          transition=transition,
                          transition_url=transition_url,
                          state=state)

wenjie.zheng's avatar
wenjie.zheng committed
189
  def isWorkflowMethodSupported(self, document, transition_id):
190
    transition = self._getOb('transition_' + transition_id)
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
    sdef = self._getWorkflowStateOf(document, id_only=0)
    if sdef is None:
      return 0
    if (transition in sdef.getDestinationValueList() and
        self._checkTransitionGuard(transition, document) and
        transition.trigger_type == TRIGGER_WORKFLOW_METHOD
        ):
      return 1
    return 0

  security.declarePrivate('isActionSupported')
  def isActionSupported(self, document, action, **kw):
    '''
    Returns a true value if the given action name
    is possible in the current state.
    '''
    sdef = self._getWorkflowStateOf(document, id_only=0)
    if sdef is None:
      return 0

211 212
    if action in sdef.getDestinationIdList():
      tdef = self._getOb(action, None)
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
      if (tdef is not None and
        tdef.trigger_type == TRIGGER_USER_ACTION and
        self._checkTransitionGuard(tdef, document, **kw)):
        return 1
    return 0

  security.declarePrivate('isInfoSupported')
  def isInfoSupported(self, ob, name):
      '''
      Returns a true value if the given info name is supported.
      '''
      if name == self.getStateVariable():
          return 1
      vdef = self.objectValues(portal_type='Variable').get(name, None)
      if vdef is None:
          return 0
      return 1

  def _checkTransitionGuard(self, tdef, document, **kw):
    guard = tdef.getGuard()
    if guard is None:
      return 1
    if guard.check(getSecurityManager(), self, document, **kw):
      return 1
    return 0

  def _findAutomaticTransition(self, document, sdef):
    tdef = None
241 242
    for tid in sdef.getDestinationIdList():
      t = self._getOb(id=tid)
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
      if t is not None and t.trigger_type == TRIGGER_AUTOMATIC:
        if self._checkTransitionGuard(t, document):
          tdef = t
          break
    return tdef

  security.declarePrivate('updateRoleMappingsFor')
  def updateRoleMappingsFor(self, document):
    """Changes the object permissions according to the current state.
    """
    changed = 0
    sdef = self._getWorkflowStateOf(document, id_only=0)
    managed_permission = self.getManagedPermissionList()
    if sdef is None:
        return 0
wenjie.zheng's avatar
wenjie.zheng committed
258
    # zwj: get all matrix cell objects
259
    permission_role_matrix_cells = sdef.objectValues(portal_type = "PermissionRoles")
wenjie.zheng's avatar
wenjie.zheng committed
260
    # zwj: build a permission roles dict
261 262
    for perm_role in permission_role_matrix_cells:
      permission, role = perm_role.getPermissionRole()
wenjie.zheng's avatar
wenjie.zheng committed
263
      # zwj: double check the right role and permission are obtained
264 265 266 267 268
      if permission != 'None':
        if self.erp5_permission_roles.has_key(permission):
          self.erp5_permission_roles[permission] += (role,)
        else:
          self.erp5_permission_roles.update({permission : (role,)})
wenjie.zheng's avatar
wenjie.zheng committed
269
    # zwj: update role list to permission
270 271 272
    for permission_roles in self.erp5_permission_roles.keys():
      if modifyRolesForPermission(document, permission_roles, self.erp5_permission_roles[permission_roles]):
        changed = 1
wenjie.zheng's avatar
wenjie.zheng committed
273
        # zwj: clean Permission Role list for the next role mapping
274 275 276 277 278 279 280
      del self.erp5_permission_roles[permission_roles]
    return changed

  def getRoleList(self):
    return sorted(self.getPortalObject().getDefaultModule('acl_users').valid_roles())

  security.declarePrivate('doActionFor')
281 282 283 284 285
  def doActionFor(self, document, action, comment='', **kw):
    '''
    Allows the user to request a workflow action.  This method
    must perform its own security checks.
    '''
286
    sdef = self._getWorkflowStateOf(document, id_only=0)
287
    kw['comment'] = comment
288 289 290
    if sdef is None:
      raise WorkflowException(_(u'Object is in an undefined state.'))
    if self.isActionSupported(document, action, **kw):
291
      wf_id = self.getId()
292 293
      if wf_id is None:
        raise WorkflowException(
294 295
            _(u'Requested workflow not found.'))
    tdef = self._getOb(id=action)
296 297 298 299 300 301 302 303 304

    if tdef not in self.objectValues(portal_type='Transition'):
      raise Unauthorized(action)
    if tdef is None or tdef.trigger_type != TRIGGER_USER_ACTION:
      msg = _(u"Transition '${action_id}' is not triggered by a user "
        u"action.", mapping={'action_id': action})
      raise WorkflowException(msg)
    if not self._checkTransitionGuard(tdef, document, **kw):
      raise Unauthorized(action)
305
    self._changeStateOf(document, tdef, kw)
306 307 308 309 310 311 312 313 314 315

  def _changeStateOf(self, document, tdef=None, kwargs=None):
    '''
    Changes state.  Can execute multiple transitions if there are
    automatic transitions.  tdef set to None means the object
    was just created.
    '''
    moved_exc = None
    while 1:
      try:
316
        sdef = self._executeTransition(document, tdef, kwargs)
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
      except ObjectMoved, moved_exc:
        document = moved_exc.getNewObject()
        sdef = self._getWorkflowStateOf(document, id_only=0)
        # Re-raise after all transitions.
      if sdef is None:
        break
      tdef = self._findAutomaticTransition(document, sdef)
      if tdef is None:
        # No more automatic transitions.
        break
      # Else continue.
    if moved_exc is not None:
        # Re-raise.
      raise moved_exc

  def listObjectActions(self, info):
      fmt_data = None
      document = info.object
      sdef = self._getWorkflowStateOf(document, id_only=0)
      if sdef is None:
          return None
      res = []

340 341
      for tid in sdef.getDestinationIdList():
        tdef = self._getOb(id=tid)
342 343 344 345 346 347 348
        if tdef is not None and tdef.trigger_type == TRIGGER_USER_ACTION and \
                tdef.actbox_name and self._checkTransitionGuard(tdef, document):
            if fmt_data is None:
                fmt_data = TemplateDict()
                fmt_data._push(info)
            fmt_data._push({'transition_id': tid})
            res.append((tid, {
349
                'id': tdef.getReference(),
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
                'name': tdef.actbox_name % fmt_data,
                'url': str(tdef.actbox_url) % fmt_data,
                'icon': str(tdef.actbox_icon) % fmt_data,
                'permissions': (),  # Predetermined.
                'category': tdef.actbox_category,
                'transition': tdef}))
            fmt_data._pop()
      res.sort()

      return [ result[1] for result in res ]

  def getWorklistVariableMatchDict(self, info, check_guard=True):
    """
      Return a dict which has an entry per worklist definition
      (worklist id as key) and which value is a dict composed of
      variable matches.
    """
    if not self.objectValues(portal_type='Worklist'):
      return None

    portal = self.getPortalObject()
    def getPortalTypeListForWorkflow(workflow_id):
        workflow_tool = portal.portal_workflow
        result = []
        append = result.append
375 376 377
        if info.object.getTypeInfo() is not None:
          for workflow_id in info.object.getTypeInfo().getTypeWorkflowList():
              append(info.object.getTypeInfo().getId())
378 379
        return result

380
    portal_type_list = getPortalTypeListForWorkflow(self.id)
381 382 383 384
    if not portal_type_list:
      return None
    variable_match_dict = {}
    security_manager = getSecurityManager()
385
    workflow_id = self.id
386 387
    workflow_title = self.getTitle()
    for worklist_definition in self.objectValues(portal_type='Worklist'):
388
      worklist_id = worklist_definition.getReference()
389 390 391 392 393 394 395
      action_box_name = worklist_definition.getActboxName()
      guard = worklist_definition.getGuard()
      if action_box_name:
        variable_match = {}
        for key in worklist_definition.getVarMatchKeys():
          var = worklist_definition.getVarMatch(key)
          if isinstance(var, Expression):
396
            evaluated_value = var(createExprContext(StateChangeInfo(portal,
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
                                  self, kwargs=info.__dict__.copy())))
            if isinstance(evaluated_value, (str, int, long)):
              evaluated_value = [str(evaluated_value)]
          else:
            evaluated_value = [str(x) % info for x in var]
          variable_match[key] = evaluated_value

        if 'portal_type' in variable_match and len(variable_match['portal_type']):
          portal_type_intersection = set(variable_match['portal_type']).intersection(portal_type_list)
          # in case the current workflow is not associated with portal_types
          # defined on the worklist, don't display the worklist for this
          # portal_type.
          variable_match['portal_type'] = list(portal_type_intersection)
        variable_match.setdefault('portal_type', portal_type_list)

        if len(variable_match.get('portal_type', [])) == 0:
          continue

        is_permitted_worklist = 0
        if guard is None:
          is_permitted_worklist = 1
        elif (not check_guard) or \
            Guard_checkWithoutRoles(guard, security_manager, self, portal):
          is_permitted_worklist = 1
          variable_match[SECURITY_PARAMETER_ID] = guard.roles

        if is_permitted_worklist:
          fmt_data = TemplateDict()
          fmt_data._push(info)
          variable_match.setdefault(SECURITY_PARAMETER_ID, ())
          fmt_data._push({(k, ('&%s:list=' % k).join(v)) for\
                                             k, v in variable_match.iteritems()})

          variable_match[WORKLIST_METADATA_KEY] = {
                                                'format_data': fmt_data,
                                                 'worklist_title': action_box_name,
                                                 'worklist_id': worklist_id,
                                                 'workflow_title': workflow_title,
435
                                                 'workflow_id': workflow_id,
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
                                                 'action_box_url': worklist_definition.actbox_url,
                                                 'action_box_category': worklist_definition.actbox_category}

          variable_match_dict[worklist_id] = variable_match

    if len(variable_match_dict) == 0:
      return None
    return variable_match_dict

  security.declarePrivate('getInfoFor')
  def getInfoFor(self, ob, name, default):
      '''
      Allows the user to request information provided by the
      workflow.  This method must perform its own security checks.
      '''
      state_var = self.getStateVariable()
      if name == state_var:
453
          return ob._getDefaultAcquiredValue(state_var).getId()
454

455 456 457 458
      for x in self.objectValues(portal_type='Variable'):
        if x.getReference() == name:
          vdef = x
          break
459 460

      status_dict = self.getCurrentStatusDict(ob)
461
      former_status = self._getOb(status_dict[state_var], None)
462

463 464 465 466 467 468 469 470 471 472 473
      if former_status == None:
        former_status = self.getSourceValue()

      if vdef.info_guard is not None and not vdef.info_guard.check(
          getSecurityManager(), self, ob):
          return default

      if status_dict is not None and name in status_dict:
          value = status_dict[name]
      # Not set yet.  Use a default.
      if vdef.default_expr is not None:
474
          ec = createExprContext(StateChangeInfo(ob, self, former_status))
475 476 477 478 479 480 481 482
          expr = Expression(vdef.default_expr)
          value = expr(ec)
      else:
          value = vdef.default_value
      return value

  def _getWorkflowStateOf(self, ob, id_only=0):
      tool = getToolByName(self, 'portal_workflow')
wenjie.zheng's avatar
wenjie.zheng committed
483
      id_no_suffix = self.getReference()
484
      status = tool.getStatusOf(id_no_suffix, ob)
485
      if status is None:
486
          state = self.getSourceValue()
487
      else:
488 489
          state_id = 'state_' + status.get(self.getStateVariable(), None)
          state = self._getOb(state_id)
490
          if state is None:
491
              state = self.getSourceValue()
492
      if id_only:
493
          return state.getReference()
494
      else:
495
          return state
496

497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
  def getVariableValueList(self):
    variable_dict = {}
    for vdef in self.objectValues(portal_type="Variable"):
      variable_dict[vdef.getReference()] = vdef
    return variable_dict

  def getVariableIdList(self):
    id_list = []
    for ob in self.objectValues(portal_type="Variable"):
      id_list.append(ob.getReference())
    return id_list

  def getStateValueList(self):
    state_dict = {}
    for sdef in self.objectValues(portal_type="State"):
      state_dict[sdef.getReference()] = sdef
    return state_dict

  def getStateIdList(self):
    id_list = []
    for ob in self.objectValues(portal_type="State"):
      id_list.append(ob.getReference())
    return id_list

  def getWorklistValueList(self):
    worklist_dict = {}
    for qdef in self.objectValues(portal_type="Worklist"):
      worklist_dict[qdef.getReference()] = qdef
    return worklist_dict

  def getWorklistIdList():
    id_list = []
    for ob in self.objectValues(portal_type="Worklist"):
      id_list.append(ob.getReference())
    return id_list

  def getTransitionValueList(self):
    transition_dict = {}
    for tdef in self.objectValues(portal_type="Transition"):
      transition_dict[tdef.getReference()] = tdef
    return transition_dict

  def getTransitionIdList(self):
    id_list = []
    for ob in self.objectValues(portal_type="Transition"):
      id_list.append(ob.getReference())
    return id_list

545 546 547
  def getScriptValueList(self):
    scripts = {}
    for script in self.objectValues(portal_type='Workflow Script'):
548
      scripts[script.getReference()] = script
549 550
    return scripts

551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
  def notifyWorkflowMethod(self, ob, transition_list, args=None, kw=None):
    """ Execute workflow methods.
    """
    if type(transition_list) in StringTypes:
      method_id = transition_list
    elif len(transition_list) == 1:
      method_id = transition_list[0]
    else:
      raise ValueError('WorkflowMethod should be attached to exactly 1 transition per DCWorkflow instance.')
    sdef = self._getWorkflowStateOf(ob)
    if sdef is None:
      raise WorkflowException, 'Object is in an undefined state'
    prefix_method_id = 'transition_' + method_id
    if prefix_method_id not in sdef.getDestinationIdList():
      raise Unauthorized(method_id)
    tdef = self._getOb(prefix_method_id)
    if tdef is None or tdef.trigger_type != TRIGGER_WORKFLOW_METHOD:
      raise WorkflowException, (
         'Transition %s is not triggered by a workflow method'
             % method_id)
    if not self._checkTransitionGuard(tdef, ob):
      raise Unauthorized(method_id)
    self._changeStateOf(ob, tdef, kw)
    if getattr(ob, 'reindexObject', None) is not None:
      if kw is not None:
        activate_kw = kw.get('activate_kw', {})
      else:
        activate_kw = {}
      ob.reindexObject(activate_kw=activate_kw)

  def notifyBefore(self, ob, transition_list, args=None, kw=None):
    pass

  def notifySuccess(self, ob, transition_list, result, args=None, kw=None):
    pass

  def _executeTransition(self, document, tdef=None, form_kw=None):
    """
    Execute transition.
    """
    sci = None
    econtext = None
    moved_exc = None
    validation_exc = None
    tool = getToolByName(self, 'portal_workflow')
596

597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
    # Figure out the old and new states.
    state_var = self.getStateVariable()
    status_dict = self.getCurrentStatusDict(document)
    current_state_value = self._getWorkflowStateOf(document, id_only=0)

    if current_state_value == None:
      current_state_value = self.getSourceValue()
    old_state = current_state_value.getReference()
    old_sdef = current_state_value

    if tdef is None:
      new_sdef = self.getSourceValue()
      if not new_state:
        return
      former_status = {}
    else:
      new_sdef = tdef.getDestinationValue()
      if new_sdef == None:
        new_state = old_state
      else:
        new_state = new_sdef.getReference()
      former_status = current_state_value.getReference()

    # Execute the "before" script.
    before_script_success = 1
    if tdef is not None and tdef.getBeforeScriptId():
      script_id = tdef.getBeforeScriptId()
      if script_id:
        script = self._getOb(script_id)
        # Pass lots of info to the script in a single parameter.
        kwargs = form_kw
        sci = StateChangeInfo(
              document, self, former_status, tdef, old_sdef, new_sdef, kwargs)
        try:
          script.execute(sci)  # May throw an exception.
        except ValidationFailed, validation_exc:
          before_script_success = 0
          before_script_error_message = deepcopy(validation_exc.msg)
          validation_exc_traceback = sys.exc_traceback
        except ObjectMoved, moved_exc:
          ob = moved_exc.getNewObject()
          # Re-raise after transition

    # update variables
    state_values = None
642
    object = self.getStateChangeInformation(document, self.getSourceValue())
643 644 645 646 647 648
    if new_sdef is not None:
      state_values = getattr(new_sdef,'var_values', None)
    if state_values is None:
      state_values = {}

    if state_values is None: state_values = {}
649
    tdef_exprs = {}
650
    if tdef is not None:
651 652 653 654 655
      transition_variable_list = tdef.objectValues(portal_type='Transition Variable')
    for transition_variable in transition_variable_list:
      tdef_exprs[transition_variable.getCausalityId()] = transition_variable.getDefaultExpr()
      if tdef_exprs[transition_variable.getCausalityId()] is None:
        tdef_exprs[transition_variable.getCausalityId()] = self._getOb(transition_variable.getCausalityId()).getDefaultExpr()
656

657 658 659 660
    # Update all transition variables
    if form_kw is not None:
      object.REQUEST.other.update(form_kw)
      kwargs = form_kw
661 662 663 664 665 666 667 668 669 670
    for vdef in self.objectValues(portal_type='Variable'):
      id = vdef.getId()
      id_no_suffix = vdef.getReference()
      if vdef.for_status == 0:
        continue
      expr = None
      if id_no_suffix in state_values:
        value = state_values[id_no_suffix]
      elif id in tdef_exprs:
        expr = tdef_exprs[id]
671
      elif not vdef.update_always and id_no_suffix in former_status:
672
        # Preserve former value
673
        value = former_status[id_no_suffix]
674
      else:
675 676
        if vdef.getDefaultExpr() is not None:
          expr = vdef.getDefaultExpr()
677
        else:
678 679
          value = vdef.getInitialValue(object=object)
      if expr is not None and expr != '':
680 681 682 683 684 685 686 687
        # Evaluate an expression.
        if econtext is None:
          # Lazily create the expression context.
          if sci is None:
            kwargs = form_kw
            sci = StateChangeInfo(
                document, self, former_status, tdef,
                old_sdef, new_sdef, kwargs)
688
          econtext = createExprContext(sci)
689 690
        expr = Expression(expr)
        value = expr(econtext)
691
      if value is None: value = ''
692
      status_dict[id_no_suffix] = value
693 694 695 696 697 698 699

    # Do not proceed in case of failure of before script
    if not before_script_success:
      status_dict[state_var] = old_state # Remain in state
      tool.setStatusOf(self.getReference(), document, status_dict)
      sci = StateChangeInfo(
        document, self, former_status, tdef, old_sdef, new_sdef, kwargs)
700
      # put the error message in the workflow history
701 702 703 704 705 706 707 708 709 710 711 712
      sci.setWorkflowVariable(error_message=before_script_error_message)
      if validation_exc :
        # reraise validation failed exception
        raise validation_exc, None, validation_exc_traceback
      return new_sdef

    # update state
    status_dict[state_var] = new_state
    object = self.getStateChangeInformation(document, current_state_value, transition=self)

    tool.setStatusOf(self.getReference(), document, status_dict)
    self.updateRoleMappingsFor(document)
713

714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
    # Execute the "after" script.
    script_id = getattr(tdef, 'getAfterScriptId')()
    if script_id is not None:
      kwargs = form_kw
      # Script can be either script or workflow method
      if script_id in old_sdef.getDestinationIdList() and \
          self._getOb(script_id).trigger_type == TRIGGER_WORKFLOW_METHOD:
        getattr(document, convertToMixedCase(self._getOb(script_id).getReference()))()
      else:
        script = self._getOb(script_id)
        # Pass lots of info to the script in a single parameter.
        if script.getTypeInfo().getId() == 'Workflow Script':
          sci = StateChangeInfo(
              document, self, former_status, tdef, old_sdef, new_sdef, kwargs)
          script.execute(sci)  # May throw an exception.
        else:
          raise NotImplementedError ('Unsupported Script %s for state %s'%(script_id, old_sdef.getReference()))
    # Return the new state object.
    if moved_exc is not None:
        # Propagate the notification that the object has moved.
        raise moved_exc
    else:
        return new_sdef
737

738 739
  def showAsXML(self, root=None):
    if root is None:
740
      root = Element('erp5')
741 742
      return_as_object = False

743 744 745 746
    # Define a list of property to show to users:
    workflow_prop_id_to_show = ['title', 'description', 'state_var',
      'permissions', 'initial_state']

747 748
    # workflow as XML, need to rename DC workflow's portal_type before comparison.
    workflow = SubElement(root, 'workflow',
749
                        attrib=dict(reference=self.getReference(),
750
                        portal_type=self.getPortalType()))
751 752

    for prop_id in sorted(workflow_prop_id_to_show):
753 754
      # In most case, we should not synchronize acquired properties
      if prop_id not in ('uid', 'workflow_history', 'id', 'portal_type',):
755
        if prop_id == 'permissions':
756
          value = tuple(self.getProperty('workflow_managed_permission_list'))
757 758
          prop_type = self.getPropertyType('workflow_managed_permission_list')
        elif prop_id == 'initial_state':
759 760 761 762
          if self.getSourceValue() is not None:
            value = self.getSourceValue().getReference()
          else:
            value = ''
763
          prop_type = 'string'
764 765
        elif prop_id =='state_var':
          value = self.getProperty('state_variable')
766
          prop_type = self.getPropertyType('state_variable')
767
        else:
768
          value = self.getProperty(prop_id)
769 770 771 772 773
          prop_type = self.getPropertyType(prop_id)
        if value is None or value ==() or value == ():
          value = ''
        sub_object = SubElement(workflow, prop_id, attrib=dict(type=prop_type))
        sub_object.text = str(value)
774

775 776 777
    # 1. State as XML
    state_reference_list = []
    state_list = self.objectValues(portal_type='State')
778
    # show reference instead of id
779 780
    state_prop_id_to_show = ['title', 'description',
      'transitions', 'permission_roles']
781 782 783 784 785
    for sdef in state_list:
      state_reference_list.append(sdef.getReference())
    states = SubElement(workflow, 'states', attrib=dict(state_list=str(state_reference_list),
                        number_of_element=str(len(state_reference_list))))
    for sdef in state_list:
786
      state = SubElement(states, 'state', attrib=dict(reference=sdef.getReference(), portal_type=sdef.getPortalType()))
787
      for property_id in sorted(state_prop_id_to_show):
788 789 790 791
        if property_id == 'permission_roles':
          property_value = sdef.getProperty('state_permission_roles')
          property_type = sdef.getPropertyType('state_permission_roles')
        elif property_id == 'transitions':
792 793 794 795
          property_value = sdef.getDestinationIdList()
          destination_list = []
          for tr_id in property_value:
            destination_list.append(self._getOb(tr_id).getReference())
796
          property_value = tuple(destination_list)
797
          property_type = 'multiple selection'
798
        else:
799
          property_value = sdef.getProperty(property_id)
800 801 802 803 804 805
          property_type = sdef.getPropertyType(property_id)

        if property_value is None or property_value ==() or property_value == []:
          property_value = ''
        sub_object = SubElement(state, property_id, attrib=dict(type=property_type))
        sub_object.text = str(property_value)
806

807 808 809
    # 2. Transition as XML
    transition_reference_list = []
    transition_list = self.objectValues(portal_type='Transition')
810 811
    transition_prop_id_to_show = ['title', 'description', 'new_state_id',
      'trigger_type', 'script_name', 'after_script_name', 'actbox_category',
812
      'actbox_icon', 'actbox_name', 'actbox_url', 'roles', 'groups',
813
      'permissions', 'expr', 'transition_variable']
814 815
    for tdef in self.objectValues(portal_type='Transition'):
      transition_reference_list.append(tdef.getReference())
816 817 818
    transitions = SubElement(workflow, 'transitions',
          attrib=dict(transition_list=str(transition_reference_list),
          number_of_element=str(len(transition_reference_list))))
819
    for tdef in transition_list:
820 821 822
      transition = SubElement(transitions, 'transition',
            attrib=dict(reference=tdef.getReference(),
            portal_type=tdef.getPortalType()))
823
      guard = SubElement(transition, 'guard', attrib=dict(type='object'))
824
      transition_variables = SubElement(transition, 'transition_variables', attrib=dict(type='object'))
825
      for property_id in sorted(transition_prop_id_to_show):
826
        if property_id in ('roles', 'groups', 'permissions', 'expr',):
827 828 829 830 831 832 833 834
          if property_id == 'roles':
            property_value = tdef.getRoleList()
          if property_id == 'groups':
            property_value = tdef.getGroupList()
          if property_id == 'permissions':
            property_value = tdef.getPermissionList()
          if property_id == 'expr':
            property_value = tdef.getExpression()
835
          if property_value is None or property_value == [] or property_value == ():
836
            property_value = ''
837
          elif property_id != 'expr':
838
            property_value = tuple(property_value)
839 840
          sub_object = SubElement(guard, property_id, attrib=dict(type='guard configuration'))
        else:
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
          if property_id == 'new_state_id':
            if tdef.getDestinationValue() is not None:
              property_value = tdef.getDestinationValue().getReference()
            else:
              property_value = ''
            sub_object = SubElement(transition, property_id, attrib=dict(type='string'))
          elif property_id == 'script_name':
            property_value = tdef.getBeforeScriptIdList()
            if property_value == [] or property_value is None:
              property_value = ''
            else:
              property_value = self._getOb(tdef.getBeforeScriptIdList()[0]).getReference()
            sub_object = SubElement(transition, property_id, attrib=dict(type='string'))
          elif property_id == 'after_script_name':
            property_value = tdef.getAfterScriptIdList()
            if property_value == [] or property_value is None:
              property_value = ''
            else:
              property_value = self._getOb(tdef.getAfterScriptIdList()[0]).getReference()
            sub_object = SubElement(transition, property_id, attrib=dict(type='string'))
861 862 863 864 865 866
          elif property_id =='transition_variable':
            tr_var_list = tdef.objectValues(portal_type='Transition Variable')
            for tr_var in tr_var_list:
              reference = self._getOb(tr_var.getCausalityId()).getReference()
              transition_variable = SubElement(transition_variables, property_id, attrib=dict(id=reference,type='variable'))
              transition_variable.text = str(tr_var.getDefaultExpr())
867
          else:
868
            property_value = tdef.getProperty(property_id)
869
            property_type = tdef.getPropertyType(property_id)
870 871 872
            sub_object = SubElement(transition, property_id, attrib=dict(type=property_type))
        if property_value is None or property_value ==() or property_value == []:
          property_value = ''
873
        sub_object.text = str(property_value)
874 875 876 877

    # 3. Variable as XML
    variable_reference_list = []
    variable_list = self.objectValues(portal_type='Variable')
878 879
    variable_prop_id_to_show = ['description', 'default_expr',
          'for_catalog', 'for_status', 'update_always']
880 881 882 883 884
    for vdef in variable_list:
      variable_reference_list.append(vdef.getReference())
    variables = SubElement(workflow, 'variables', attrib=dict(variable_list=str(variable_reference_list),
                        number_of_element=str(len(variable_reference_list))))
    for vdef in variable_list:
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
      variable = SubElement(variables, 'variable', attrib=dict(reference=vdef.getReference(),
            portal_type=vdef.getPortalType()))
      for property_id in sorted(variable_prop_id_to_show):
        if property_id == 'update_always':
          property_value = vdef.getAutomaticUpdate()
          sub_object = SubElement(variable, property_id, attrib=dict(type='int'))
        elif property_id == 'default_value':
          property_value = vdef.getInitialValue()
          if vdef.getInitialValue() is not None:
            property_value = vdef.getInitialValue()
          sub_object = SubElement(variable, property_id, attrib=dict(type='string'))
        else:
          property_value = vdef.getProperty(property_id)
          property_type = vdef.getPropertyType(property_id)
          sub_object = SubElement(variable, property_id, attrib=dict(type=property_type))
900 901
        if property_value is None or property_value ==() or property_value == []:
          property_value = ''
902
        sub_object.text = str(property_value)
903 904
        # for a very specific case, action return the reference of transition,
        # but in XML should show the same expression as in DC workflow.
905
        if vdef.getId() == 'variable_action' and property_id == 'default_expr' and property_value != '':
906
          sub_object.text = str('transition/getId|nothing')
907 908 909 910

    # 4. Worklist as XML
    worklist_reference_list = []
    worklist_list = self.objectValues(portal_type='Worklist')
911 912 913 914
    worklist_prop_id_to_show = ['description', 'matched_portal_type_list',
          'matched_validation_state_list', 'matched_simulation_state_list',
          'actbox_category', 'actbox_name', 'actbox_url', 'actbox_icon',
          'roles', 'groups', 'permissions', 'expr']
915 916 917 918 919
    for qdef in worklist_list:
      worklist_reference_list.append(qdef.getReference())
    worklists = SubElement(workflow, 'worklists', attrib=dict(worklist_list=str(worklist_reference_list),
                        number_of_element=str(len(worklist_reference_list))))
    for qdef in worklist_list:
920 921 922 923 924 925 926 927 928 929 930 931 932 933
      worklist = SubElement(worklists, 'worklist', attrib=dict(reference=qdef.getReference(),
      portal_type=qdef.getPortalType()))
      guard = SubElement(worklist, 'guard', attrib=dict(type='object'))
      for property_id in sorted(worklist_prop_id_to_show):
         # show guard configuration:
        if property_id in ('roles', 'groups', 'permissions', 'expr',):
          if property_id == 'roles':
            property_value = qdef.getRoleList()
          if property_id == 'groups':
            property_value = qdef.getGroupList()
          if property_id == 'permissions':
            property_value = qdef.getPermissionList()
          if property_id == 'expr':
            property_value = qdef.getExpression()
934 935
          if property_value is not None:
            property_value = tuple(property_value)
936 937 938 939 940 941 942 943 944 945 946 947 948 949
          sub_object = SubElement(guard, property_id, attrib=dict(type='guard configuration'))
        else:
          property_value = qdef.getProperty(property_id)
          state_ref_list = []
          if property_id in ('matched_validation_state_list',
              'matched_simulation_state_list',) and property_value is not None:
            for sid in property_value:
              state_ref = self._getOb(sid).getReference()
              state_ref_list.append(state_ref)
            property_value = tuple(state_ref_list)
          if property_id == 'matched_portal_type_list':
            property_value = tuple(property_value)
          property_type = qdef.getPropertyType(property_id)
          sub_object = SubElement(worklist, property_id, attrib=dict(type=property_type))
950 951
        if property_value is None or property_value ==() or property_value == []:
          property_value = ''
952
        sub_object.text = str(property_value)
953 954 955 956

    # 5. Script as XML
    script_reference_list = []
    script_list = self.objectValues(portal_type='Workflow Script')
957
    script_prop_id_to_show = sorted(['body', 'parameter_signature','proxy_roles'])
958 959 960 961 962
    for sdef in script_list:
      script_reference_list.append(sdef.getReference())
    scripts = SubElement(workflow, 'scripts', attrib=dict(script_list=str(script_reference_list),
                        number_of_element=str(len(script_reference_list))))
    for sdef in script_list:
963 964 965
      script = SubElement(scripts, 'script', attrib=dict(reference=sdef.getReference(),
        portal_type=sdef.getPortalType()))
      for property_id in script_prop_id_to_show:
966 967 968 969 970 971
        if property_id == 'proxy_roles':
          property_value = tuple(sdef.getProperty('proxy_role_list'))
          property_type = sdef.getPropertyType('proxy_role_list')
        else:
          property_value = sdef.getProperty(property_id)
          property_type = sdef.getPropertyType(property_id)
972 973
        sub_object = SubElement(script, property_id, attrib=dict(type=property_type))
        sub_object.text = str(property_value)
974

975
    # return xml object
976 977 978 979 980
    if return_as_object:
      return root
    return etree.tostring(root, encoding='utf-8',
                          xml_declaration=True, pretty_print=True)

981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
  def _executeMetaTransition(self, ob, new_state_id):
    """
    Allow jumping from state to another without triggering any hooks.
    Must be used only under certain conditions.
    """
    sci = None
    econtext = None
    tdef = None
    kwargs = None
    new_state_id_no_prefix = new_state_id
    new_state_id = 'state_' + new_state_id
    # Figure out the old and new states.
    old_sdef = self._getWorkflowStateOf(ob)
    if old_sdef is None:
      old_state = self._getWorkflowStateOf(ob, id_only=True)
    else:
      old_state = old_sdef.getId()
    if old_state == new_state_id:
      # Object is already in expected state
      return
    former_status = self._getStatusOf(ob)

    new_sdef = self._getOb(new_state_id, None)
    if new_sdef is None:
      raise WorkflowException, ('Destination state undefined: ' + new_state_id)

    # Update variables.
    state_values = self.contentValues(portal_type='Variable')
    if state_values is None:
      state_values = {}

    tdef_exprs = {}
    status = {}
    for id, vdef in self.getVariableValueList():
      if not vdef.for_status:
        continue
      expr = None
      if state_values.has_key(id):
        value = state_values[id]
      elif tdef_exprs.has_key(id):
        expr = tdef_exprs[id]
      elif not vdef.update_always and former_status.has_key(id):
        # Preserve former value
        value = former_status[id]
      else:
        if vdef.default_expr is not None:
          expr = vdef.default_expr
        else:
          value = vdef.default_value
      if expr is not None:
        # Evaluate an expression.
        if econtext is None:
          # Lazily create the expression context.
          if sci is None:
            sci = StateChangeInfo(ob, self, former_status, tdef, old_sdef,
                                  new_sdef, kwargs)
          econtext = createExprContext(sci)
        value = expr(econtext)
      status[id] = value

    status['comment'] = 'Jump from %r to %r' % (old_state, new_state_id_no_prefix,)
    status[self.getStateVariable()] = new_state_id_no_prefix
    tool = self.getParent()
    tool.setStatusOf(self.getId(), ob, status)

    # Update role to permission assignments.
    self.updateRoleMappingsFor(ob)
    return new_sdef

1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
def Guard_checkWithoutRoles(self, sm, wf_def, ob, **kw):
    """Checks conditions in this guard.
       This function is the same as Guard.check, but roles are not taken
       into account here (but taken into account as local roles). This version
       is for worklist guards.

       Note that this patched version is not a monkey patch on the class,
       because we only want this specific behaviour for worklists (Guards are
       also used in transitions).
    """
    u_roles = None
    if wf_def.manager_bypass:
        # Possibly bypass.
        u_roles = sm.getUser().getRolesInContext(ob)
        if 'Manager' in u_roles:
            return 1
    if self.permissions:
        for p in self.permissions:
            if _checkPermission(p, ob):
                break
        else:
            return 0
    if self.groups:
        # Require at least one of the specified groups.
        u = sm.getUser()
        b = aq_base( u )
        if hasattr( b, 'getGroupsInContext' ):
            u_groups = u.getGroupsInContext( ob )
        elif hasattr( b, 'getGroups' ):
            u_groups = u.getGroups()
        else:
            u_groups = ()
        for group in self.groups:
            if group in u_groups:
                break
        else:
            return 0
    expr = self.expr
    if expr is not None:
        econtext = createExprContext(
            StateChangeInfo(ob, wf_def, kwargs=kw))
        res = expr(econtext)
        if not res:
            return 0
    return 1