namespace eval ::xowf {}
::nsf::object::alloc ::xotcl::Class ::xowf::Context {set :__default_metaclass ::xotcl::Class
set :__default_superclass ::xotcl::Object}
::xowf::Context proc require {{-new:switch false} obj} {
set ctx $obj-wfctx
if {$new && [nsf::is object $ctx]} {
$ctx destroy
}
if {![nsf::is object $ctx]} {
set wfContextClass [$obj wf_property workflow_context_class [self]]
regsub -all \r\n [$obj wf_property workflow_definition] \n workflow_definition
$wfContextClass create $ctx -object $obj -workflow_definition $workflow_definition -destroy_on_cleanup
$ctx initialize_context $obj
}
return $ctx
}
::xowf::Context instproc get_actions {} {
set actions [list]
foreach action [${:current_state} get_actions] {
lappend actions ${:wf_container}::$action
}
return $actions
}
::xowf::Context instproc wf-specific args {
if {[llength $args] > 0} {
ns_log warning "wf-specific NOT SUPPORTED for non shared workflow " "${:object} [${:object} name]: $args"
}
}
::xowf::Context instproc dotcode {{-current_state ""} {-visited ""} {-dpi 96}} {
set obj_id [namespace tail ${:object}]
set dotcode [subst {digraph workflow_$obj_id \{
dpi = $dpi;
node \[shape=doublecircle, margin=0.001, fontsize=8, fixedsize=1, width=0.4, style=filled\]; start;
node \[shape=ellipse, fontname="Courier", color=lightblue2, style=filled,
fixedsize=0, fontsize=10, margin=0.06\];
edge \[fontname="Courier", fontsize=9\];
}]
foreach s [:defined State] {
if {[$s name] eq $current_state} {
set color ",color=orange"
} elseif {[$s name] in $visited} {
set color ",color=yellow"
} else {
set color ""
}
append dotcode " state_[$s name] \[label=\"[$s label]\"$color\];\n"
}
set initializeObj [:wf_definition_object initialize]
if {[nsf::is object $initializeObj]} {
append dotcode "start->state_initial \[label=\"[$initializeObj label]\"\];\n"
} else {
append dotcode "start->state_initial;\n"
}
set :condition_count 0
foreach s [:defined State] {
foreach a [$s get_actions -set true] {
set actionObj [:wf_definition_object $a]
append dotcode [:draw_transition $s $actionObj ""]
set drawn($actionObj) 1
}
foreach role [$s set handled_roles] {
set role_ctx [self]-$role
if {[nsf::is object ${role_ctx}::[$s name]]} {
foreach a [${role_ctx}::[$s name] get_actions] {
append dotcode [:draw_transition $s ${role_ctx}::$a "$role:"]
}
}
}
}
foreach action [:defined Action] {
if {[info exists drawn($action)]} {continue}
if {[$action state_safe]} {
foreach s [:defined State] {
append dotcode [:draw_transition $s $action ""]
}
}
}
append dotcode "\}\n"
return $dotcode
}
::xowf::Context instproc get_current_state {} {
namespace tail ${:current_state}
}
::xowf::Context instproc object-specific code {
:uplevel [list ${:object} eval $code]
}
::xowf::Context instproc draw_arc {from_state next_state action label style} {
if {$next_state eq ""} {set next_state $from_state}
set key transition($from_state,$next_state,$action)
if {[info exists :$key]} {
return ""
}
set :$key 1
return " state_$from_state -> state_$next_state \[label=\"$label\"$style\];\n"
}
::xowf::Context instproc default_load_form_id form_name -returns integer {
set form_id 0
if {$form_name ne ""} {
set resolved [:resolve_form_name -object ${:object} $form_name]
set form_id [dict get $resolved form_id]
if {$form_id == 0} {
ns_log warning "could not resolve '$form_name' for ${:object}: $resolved"
}
}
return $form_id
}
::xowf::Context instproc resolve_form_name {-object:required name} {
set package_id [$object package_id]
set parent_id [$object parent_id]
set item_info [::$package_id item_ref -normalize_name false -use_package_path 1 -use_site_wide_pages true -default_lang [$object lang] -parent_id $parent_id $name]
set item_id [dict get $item_info item_id]
set form_name [dict get $item_info prefix]:[dict get $item_info stripped_name]
return [list form_id $item_id name $form_name]
}
::xowf::Context instproc auto_form_constraints {} {
if {${:wf_container} ne [self]} {
return [${:wf_container} auto_form_constraints]
} elseif {[info exists :auto_form_constraints]} {
return ${:auto_form_constraints}
}
return ""
}
::xowf::Context instproc init {} {
array set :handled_roles {}
${:object} wf_context [self]
:require_workflow_definition ${:workflow_definition}
}
::xowf::Context instproc as_graph {{-current_state ""} {-visited ""} {-dpi 72} {-style "width:20%"}} {
set dot ""
set dot [::util::which dot]
if {$dot eq ""} {
return "<font color='red'>Program 'dot' is not available! No graph displayed.</font>"
}
set dotcode [:dotcode -current_state $current_state -visited $visited -dpi $dpi]
set svg [util::inline_svg_from_dot -css [subst {
svg g a:link {text-decoration: none;}
div.inner svg {height:100%; overflow: visible; $style; margin: 0 auto;}
}] $dotcode]
return $svg
}
::xowf::Context instproc force_named_form form_name {
set form_id [:default_load_form_id $form_name]
if {$form_id == 0} {
ns_log warning "use_named_form: could not locate form $form_name"
} else {
if {![nsf::is object ::${form_id}]} {
::xo::db::CrClass get_instance_from_db -item_id ${form_id}
}
set :form_id $form_id
}
}
::xowf::Context instproc check {} {
ns_log notice "--- check context"
set o [:wf_definition_object initial]
if {![nsf::is object $o] || ![$o istype State]} {
return [list rc 1 errorMsg "No State 'initial' defined"]
}
foreach s [:defined State] {set state([$s name]) $s}
foreach a [:defined Action] {set action([$a name]) $a}
foreach a [:defined Condition] {set condition([$a name]) $a}
array set condition {else 1 true 1 default 1}
foreach a [:defined Action] {
foreach {cond value} [$a get_cond_values [$a next_state]] {
if {$cond ne "" && ![info exists condition($cond)]} {
return [list rc 1 errorMsg "Error in action [$a name]: no such condition '$cond' defined (valid: [lsort [array names condition]])"]
}
if {$value ne "" && ![info exists state($value)]} {
return [list rc 1 errorMsg "Error in action [$a name]: no such state '$value' defined (valid: [lsort [array names state]])"]
}
}
}
foreach s [:defined State] {
foreach {cond actions} [$s get_cond_values [$s actions]] {
foreach a $actions {
if {![info exists action($a)]} {
return [list rc 1 errorMsg "Error in state [$s name]: no such action '$a' defined (valid: [lsort [array names action]])"]
}
}
}
if {[$s form_loader] eq "" && [$s form] ne ""} {
set :forms([$s form]) 1
}
}
foreach p [:defined ::xowiki::formfield::FormField] {
if {[$p exists parampage]} {set :parampages([$p set parampage]) 1}
}
if {![info exists :in_role]} {
foreach role [array names :handled_roles] {
set role_ctx [self]-$role
if {[nsf::is object $role_ctx]} {
set info [$role_ctx check]
if {[dict get $info rc] == 1} {
return $info
}
array set :forms [$role_ctx array get forms]
array set :parampage [$role_ctx array get parampage]
}
}
set page ${:object}
$page references clear
$page set __unresolved_object_type ::xowiki::Form
foreach {type pages} [list wf_form [array names :forms] wf_parampage [array names :parampages]] {
foreach p $pages {
set form_info [:resolve_form_name -object $page $p]
set l [::xowiki::Link new -volatile -lang en -page $page -type $type -name [dict get $form_info name] -item_id [dict get $form_info form_id]]
set link_text [$l render]
}
}
set references [$page references get resolved]
if {[llength $references] > 0} {
$page references_update [lsort -unique $references]
$page set __extra_references $references
$page references clear
}
if {[llength [$page references get unresolved]] > 0} {
:msg -html t "Missing forms: [join [$page references get unresolved] {, }]"
}
}
return [list rc 0]
}
::xowf::Context instproc debug {} {
if {${:wf_container} ne [self]} {
return [${:wf_container} debug]
} elseif {[info exists :debug]} {
return ${:debug}
}
return 0
}
::xowf::Context instproc defined what {
set result [list]
foreach c [${:wf_container} info children] {
if {[$c istype $what]} {lappend result $c}
}
return $result
}
::xowf::Context instproc create_auto_form object {
set vars [dict keys [$object set instance_attributes]]
set template [:auto_form_template]
if {$template ne ""} {
:log "USE autoform template"
} elseif {[llength $vars] == 0} {
set template "@_text@"
} else {
set template "@[join $vars @,@]@<br>@_text@"
}
set package_id [$object package_id]
return [::xowiki::Form new -package_id $package_id -parent_id [::$package_id folder_id] -name "Auto-Form" -anon_instances [:autoname] -form {} -text [list $template text/html] -form_constraints [:auto_form_constraints] -destroy_on_cleanup ]
}
::xowf::Context instproc require_workflow_definition workflow_definition {
if {$::xowf::sharedWorkflowDefinition} {
if {[${:object} is_wf]} {
set source_obj ${:object}
} else {
set source_obj [${:object} page_template]
}
set revision_id [$source_obj revision_id]
if {$revision_id == 0} {
set revision_id [::acs::dc call content_item get_live_revision -item_id [$source_obj item_id]]
ns_log warning "xowf: tried to create a wf_container with revision_id 0 -> fixed to $revision_id"
}
set :wf_container ::xowf::$revision_id
if {[nsf::is object ${:wf_container}]} {
set ok 1
if {[${:wf_container} exists __xowf_depends]} {
set depends [${:wf_container} set __xowf_depends]
foreach {fn mtime} $depends {
if {[ad_file mtime $fn] ne $mtime} {
set ok 0
break
}
}
}
if {!$ok} {
${:wf_container} destroy
ns_log notice "xowf: invalidate container ${:wf_container}"
}
}
if {![nsf::is object ${:wf_container}]} {
WorkflowContainer create ${:wf_container}
:create_workflow_definition $workflow_definition
}
set use_shared_definition [${:wf_container} shared_definition]
} else {
set use_shared_definition 0
}
if {$use_shared_definition == 0} {
set :wf_container [self]
:create_workflow_definition $workflow_definition
} else {
if {[${:object} is_wf_instance]} {
set os_code [${:wf_container} object-specific]
if {$os_code ne ""} {
${:object} eval $os_code
}
}
}
}
::xowf::Context instproc draw_transition {from action role} {
if {[$action state_safe]} {
set arc_style {,style="dashed",penwidth=1,color=gray}
} else {
set arc_style ""
}
set cond_values [$action get_cond_values [$action next_state]]
set result ""
if {[llength $cond_values]>2} {
set c cond_[$from name]_[incr :condition_count]
append arc_style {,style="setlinewidth(1)",penwidth=1,color=gray}
append result " state_$c \[shape=diamond, fixedsize=1, width=0.2, height=0.2, fixedsize=1,style=solid,color=gray,label=\"\"\];\n"
append result [:draw_arc [$from name] $c [$action name]-1 $role[$action label] ""]
foreach {cond value} $cond_values {
if {$cond ne ""} {set prefix "$cond"} {set prefix "else"}
append result [:draw_arc $c $value [$action name] \[$prefix\] $arc_style]
}
} else {
set prefix ""
append result [:draw_arc [$from name] [lindex $cond_values 1] [$action name] $role$prefix[$action label] $arc_style]
}
return $result
}
::xowf::Context instproc autoname {} {
if {${:wf_container} ne [self]} {
if {[${:wf_container} exists autoname]} {
return [${:wf_container} autoname]
}
} elseif {[info exists :autoname]} {
return ${:autoname}
}
return "f"
}
::xowf::Context instproc form_object object -returns object {
set parent_id [$object parent_id]
if {[info exists :form_obj]} {
return ${:form_obj}
}
set package_id [$object package_id]
set loader [:form_loader]
if {$loader eq "" || [:info methods $loader] eq ""} {
set form_id [:default_load_form_id [${:current_state} form]]
if {$form_id == 0} {
:log "=== NO default_load_form_id state ${:current_state} form <[${:current_state} form]>"
set form_object [:create_auto_form $object]
:log "=== autoform $form_object"
}
} else {
set form_object [:$loader [:form]]
}
if {![info exists form_object]
&& [string is integer -strict $form_id]
&& $form_id > 0
} {
if {![nsf::is object ::$form_id]} {
::xo::db::CrClass get_instance_from_db -item_id $form_id
}
set form_object ::$form_id
}
if {[$form_object istype "::xowiki::Form"]} {
} elseif {[$form_object istype "::xowiki::FormPage"]} {
if {[$form_object property form] eq ""} {
set text [$form_object render_content]
$form_object set_property -new 1 form "<form>$text</form>"
}
} elseif {[$form_object info class] eq "::xowiki::Page"} {
set form_object [::xowiki::Form new -package_id $package_id -parent_id [::$package_id folder_id] -name "Auto-Form" -anon_instances [:autoname] -form "<form>[$form_object get_html_from_content [::$form_id text]]</form>" -text "" -form_constraints "" -destroy_on_cleanup ]
}
set :form_obj $form_object
return $form_object
}
::xowf::Context instproc show_debug_info obj {
set form [${:current_state} form]
set view_method [${:current_state} view_method]
set form_loader [${:current_state} form_loader]
if {$form eq ""} {set form NONE}
if {$view_method eq ""} {set view_method NONE}
if {$form_loader eq ""} {set form_loader NONE}
$obj debug_msg "State: [${:current_state} name], Form: $form, View method: $view_method, Form loader: $form_loader, Context class: [:info class]"
$obj debug_msg "Instance attributes: [list [$obj instance_attributes]]"
foreach kind {State Action Condition} {
$obj debug_msg "...${kind}s: [lsort [:defined $kind]]"
}
}
::xowf::Context instproc wf_definition_object name {
return ${:wf_container}::$name
}
::xowf::Context instproc flush_form_object {} {
unset -nocomplain :form_obj
}
::xowf::Context instproc set_current_state value {
set :current_state ${:wf_container}::$value
}
::xowf::Context instproc create_workflow_definition workflow_definition {
if {[catch {${:wf_container} contains "
Class create Action -superclass ::xowf::Action
Class create State -superclass ::xowf::State
Class create Condition -superclass ::xowf::Condition
Class create Property -superclass ::xowf::Property -set abstract 1
[:default_definition]
$workflow_definition"} errorMsg]} {
ns_log error "Error in workflow definition ([${:object} name]): $errorMsg\n$::errorInfo\n ===== default_definition: [:default_definition] \n ===== workflow_definition: $workflow_definition"
:msg -html t "Error in workflow definition of [${:object} name]: [ns_quotehtml $errorMsg]"
}
if {[info exists ::__xowf_depends]} {
${:wf_container} set __xowf_depends [set ::__xowf_depends]
}
if {${:all_roles}} {
foreach role [array names :handled_roles] {
Context create ${:wf_container}-$role -workflow_definition $workflow_definition -in_role $role -object ${:object}
}
}
}
::xowf::Context instproc initialize_context obj {
set :object $obj
set state [$obj state]
if {$state eq ""} {
set state "initial"
}
:set_current_state $state
if {![nsf::is object ${:current_state}]} {
if {$state eq "initial"} {
ns_log warning "no state object ${:current_state}"
} else {
:log "===== Workflow instance [$obj name] is in an undefined state '$state', reset to initial"
$obj msg "Workflow instance [$obj name] is in an undefined state '$state', reset to initial"
:set_current_state initial
}
}
set package_id [$obj package_id]
if {[info commands ::$package_id] eq ""} {
:log "... OBJECT $obj HAS $package_id, which is not initialized yet"
xo::Package require $package_id
}
[::$package_id context] set embedded_context [self]
set stateObj ${:current_state}
catch {$stateObj eval [$stateObj eval_when_active]}
if {[$obj istype ::xowiki::FormPage] && [$obj is_wf_instance]} {
if {[${:wf_container} exists debug] && [${:wf_container} set debug] > 0} {
:show_debug_info $obj
}
if {[${:wf_container} exists policy]} {
set policy [${:wf_container} set policy]
if {![nsf::is object $policy]} {
:msg "ignore non-existent policy '$policy'"
} else {
[$obj package_id] set policy $policy
}
}
}
}
::xowf::Context instproc auto_form_template {} {
if {${:wf_container} ne [self]} {
return [${:wf_container} auto_form_template]
} elseif {[info exists :auto_form_template]} {
return ${:auto_form_template}
}
return ""
}
::xowf::Context instforward get_form_constraints {%set :current_state} form_constraints
::xowf::Context instforward form {%set :current_state} form
::xowf::Context instforward property {%set :object} %proc
::xowf::Context instforward set_property {%set :object} %proc
::xowf::Context instforward set_new_property {%set :object} set_property -new 1
::xowf::Context instforward get_view_method {%set :current_state} view_method
::xowf::Context instforward form_loader {%set :current_state} form_loader
::xowf::Context instforward get_property {%set :object} %proc
::xowf::Context instparametercmd current_state
::xowf::Context instparametercmd workflow_definition
::xowf::Context instparametercmd all_roles
::xowf::Context instparametercmd wf_container
::xowf::Context instparametercmd object
::xowf::Context instparametercmd in_role
::xowf::Context instparametercmd default_definition
::nx::slotObj -container slot ::xowf::Context
::xowf::Context::slot eval {set :__parameter {
{current_state "[self]::initial"}
workflow_definition
object
{all_roles false}
{default_definition ""}
in_role
wf_container
}}
::nsf::object::alloc ::xotcl::Attribute ::xowf::Context::slot::default_definition {set :accessor public
set :configurable true
set :convert false
set :default {}
set :defaultmethods {}
set :disposition alias
set :domain ::xowf::Context
set :incremental 0
set :manager ::xowf::Context::slot::default_definition
set :methodname default_definition
set :multiplicity 1..1
set :name default_definition
set :parameterSpec {-default_definition:substdefault {}}
set :per-object false
set :position 0
set :required false
set :substdefault 0b111
set :trace none
: init}
::nsf::object::alloc ::xotcl::Attribute ::xowf::Context::slot::all_roles {set :accessor public
set :configurable true
set :convert false
set :default false
set :defaultmethods {}
set :disposition alias
set :domain ::xowf::Context
set :incremental 0
set :manager ::xowf::Context::slot::all_roles
set :methodname all_roles
set :multiplicity 1..1
set :name all_roles
set :parameterSpec {-all_roles:substdefault false}
set :per-object false
set :position 0
set :required false
set :substdefault 0b111
set :trace none
: init}
::nsf::object::alloc ::xotcl::Attribute ::xowf::Context::slot::object {set :accessor public
set :configurable true
set :convert false
set :defaultmethods {}
set :disposition alias
set :domain ::xowf::Context
set :incremental 0
set :manager ::xowf::Context::slot::object
set :methodname object
set :multiplicity 1..1
set :name object
set :parameterSpec -object
set :per-object false
set :position 0
set :required false
set :trace none
: init}
::nsf::object::alloc ::xotcl::Attribute ::xowf::Context::slot::workflow_definition {set :accessor public
set :configurable true
set :convert false
set :defaultmethods {}
set :disposition alias
set :domain ::xowf::Context
set :incremental 0
set :manager ::xowf::Context::slot::workflow_definition
set :methodname workflow_definition
set :multiplicity 1..1
set :name workflow_definition
set :parameterSpec -workflow_definition
set :per-object false
set :position 0
set :required false
set :trace none
: init}
::nsf::object::alloc ::xotcl::Attribute ::xowf::Context::slot::wf_container {set :accessor public
set :configurable true
set :convert false
set :defaultmethods {}
set :disposition alias
set :domain ::xowf::Context
set :incremental 0
set :manager ::xowf::Context::slot::wf_container
set :methodname wf_container
set :multiplicity 1..1
set :name wf_container
set :parameterSpec -wf_container
set :per-object false
set :position 0
set :required false
set :trace none
: init}
::nsf::object::alloc ::xotcl::Attribute ::xowf::Context::slot::current_state {set :accessor public
set :configurable true
set :convert false
set :default {[self]::initial}
set :defaultmethods {}
set :disposition alias
set :domain ::xowf::Context
set :incremental 0
set :manager ::xowf::Context::slot::current_state
set :methodname current_state
set :multiplicity 1..1
set :name current_state
set :parameterSpec {-current_state:substdefault {[self]::initial}}
set :per-object false
set :position 0
set :required false
set :substdefault 0b111
set :trace none
: init}
::nsf::object::alloc ::xotcl::Attribute ::xowf::Context::slot::in_role {set :accessor public
set :configurable true
set :convert false
set :defaultmethods {}
set :disposition alias
set :domain ::xowf::Context
set :incremental 0
set :manager ::xowf::Context::slot::in_role
set :methodname in_role
set :multiplicity 1..1
set :name in_role
set :parameterSpec -in_role
set :per-object false
set :position 0
set :required false
set :trace none
: init}