6 private $ddDefinition =
'dbxWorkflow|workflowDefinition';
7 private $ddInstance =
'dbxWorkflow|workflowInstance';
8 private $ddStep =
'dbxWorkflow|workflowStep';
9 private $ddBind =
'dbxWorkflow|workflowModuleBind';
11 private function tpl() {
12 return dbx()->get_system_obj(
'dbxTPL');
15 private function db() {
16 return dbx()->get_system_obj(
'dbxDB');
19 private function h(
$value) {
20 return htmlspecialchars((
string)
$value, ENT_QUOTES,
'UTF-8');
23 private function alert($type, $msg) {
24 return $this->tpl()->get_tpl(
'dbx|alert-' . $type, array(
'msg' => $msg));
28 $help =
dbx()->get_include_obj(
'dbxAdminHelp',
'dbxAdmin');
29 $topic =
$help->resolveTopic(
'dbxWorkflow_admin');
31 ?
$texts->get_fd_message(
'bar_title',
'Workflow Definitionen')
32 :
'Workflow Definitionen';
34 ?
$texts->get_fd_message(
'bar_subtitle',
'Workflow-Definitionen')
35 :
'Workflow-Definitionen';
37 ?
$texts->get_fd_message(
'module_bindings',
'Modul-Bindings')
40 ?
$texts->get_fd_message(
'new_binding',
'Neues Binding')
43 ?
$texts->get_fd_message(
'new_workflow',
'Neuer Workflow')
46 .
'<a class="btn btn-outline-secondary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=binds"><i class="bi bi-plug"></i> ' . $this->h($moduleBindings) .
'</a>'
47 .
'<a class="btn btn-primary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=edit_bind&rid=new"><i class="bi bi-plus-circle"></i> ' . $this->h($newBinding) .
'</a>'
48 .
'<a class="btn btn-primary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=edit&rid=new"><i class="bi bi-plus-circle"></i> ' . $this->h($newWorkflow) .
'</a>';
50 return $this->tpl()->get_tpl(
'dbxWorkflow_admin|workflow-admin-actions', array_merge(
51 $help->moduleBarTemplateData($topic, $barActions),
53 'bar_title' => $this->h($title),
54 'bar_subtitle' => $this->h($subtitle),
55 'bar_icon' =>
'bi-diagram-3',
56 'bar_actions' => $barActions,
62 private function install() {
63 $oDD =
dbx()->get_system_obj(
'dbxDD');
64 foreach (array(
'workflowDefinition',
'workflowInstance',
'workflowStep',
'workflowModuleBind') as
$dd) {
65 $oDD->sync_dd_to_db(
'dbxWorkflow',
$dd,
'reset');
66 for ($i = 0; $i < 80; $i++) {
68 $status = (string)(
$state[
'status'] ??
'');
69 if ($status ===
'finished' || $status ===
'error')
break;
73 $engine =
dbx()->get_include_obj(
'dbxWorkflowEngine',
'dbxWorkflow');
74 $engine->seed_demo_definitions();
76 return $this->frame($this->alert(
'success',
'Workflow-Datenbank installiert. Definitionen, Modul-Bindings und Demo-Workflows wurden angelegt/aktualisiert.'));
79 private function default_definition_text() {
80 return dbx()->get_include_obj(
'dbxWorkflowEngine',
'dbxWorkflow')->default_definition_text();
83 private function load_row($rid) {
84 if ($rid ===
'new' || (
int)$rid <= 0) {
86 'workflow_key' =>
'rechnung_erstellen',
87 'title' =>
'Rechnung erstellen',
88 'result_label' =>
'Rechnung',
89 'description' =>
'Alle für eine richtige Rechnung benötigten Angaben werden geprüft und anschließend in klaren Schritten vervollständigt.',
90 'definition_json' => $this->default_definition_text(),
95 $rows = $this->db()->select($this->ddDefinition, array(
'id' => (
int)$rid),
'*',
'id',
'DESC',
'', 1, 0, 0);
99 private function normalize_key(
$value) {
105 private function workflow_action_options($selected,
$texts) {
107 'form' =>
$texts->get_fd_message(
'action_form'),
108 'select' =>
$texts->get_fd_message(
'action_select'),
109 'create' =>
$texts->get_fd_message(
'action_create'),
110 'module' =>
$texts->get_fd_message(
'action_module'),
114 $html .=
'<option value="' . $this->h(
$value) .
'"' . ((string)$selected ===
$value ?
' selected' :
'') .
'>' . $this->h($label) .
'</option>';
119 private function workflow_action_choices(
$index, array $selected,
$texts) {
121 'form' =>
$texts->get_fd_message(
'action_form'),
122 'select' =>
$texts->get_fd_message(
'action_select'),
123 'create' =>
$texts->get_fd_message(
'action_create'),
124 'module' =>
$texts->get_fd_message(
'action_module_short'),
126 $selected = array_fill_keys(array_map(
'strval', $selected),
true);
129 $id =
'workflow-step-action-' . (int)
$index .
'-' .
$value;
130 $html .=
'<label class="dbx-workflow-action-choice" for="' . $id .
'">'
131 .
'<input class="form-check-input" type="checkbox" id="' . $id .
'" name="workflow_step_actions[' . (int)
$index .
'][]" value="' .
$value .
'" data-workflow-action-choice'
132 . (isset($selected[
$value]) ?
' checked' :
'') .
'>'
133 .
'<span>' . $this->h($label) .
'</span></label>';
138 private function workflow_kind_labels(
$texts): array {
140 'input' =>
$texts->get_fd_message(
'kind_input'),
141 'action' =>
$texts->get_fd_message(
'kind_action'),
142 'check' =>
$texts->get_fd_message(
'kind_check'),
143 'decision' =>
$texts->get_fd_message(
'kind_decision'),
147 private function workflow_kind_options($selected,
$texts) {
151 $html .=
'<option value="' . $this->h(
$value) .
'"' . ((string)$selected ===
$value ?
' selected' :
'') .
'>' . $this->h($label) .
'</option>';
156 private function workflow_automation_options($selected,
$texts) {
158 'manual' =>
$texts->get_fd_message(
'automation_manual'),
159 'observe' =>
$texts->get_fd_message(
'automation_observe'),
163 $html .=
'<option value="' . $this->h(
$value) .
'"' . ((string)$selected ===
$value ?
' selected' :
'') .
'>' . $this->h($label) .
'</option>';
168 private function workflow_mode_options($selected,
$texts) {
170 'single' =>
$texts->get_fd_message(
'mode_single'),
171 'multiple' =>
$texts->get_fd_message(
'mode_multiple'),
175 $html .=
'<option value="' . $this->h(
$value) .
'"' . ((string)$selected ===
$value ?
' selected' :
'') .
'>' . $this->h($label) .
'</option>';
180 private function workflow_validation_labels(
$texts): array {
182 'exactly_one' =>
$texts->get_fd_message(
'validation_exactly_one'),
183 'at_least_one' =>
$texts->get_fd_message(
'validation_at_least_one'),
184 'not_empty' =>
$texts->get_fd_message(
'validation_not_empty'),
185 'positive_integer' =>
$texts->get_fd_message(
'validation_positive_integer'),
186 'confirmed' =>
$texts->get_fd_message(
'validation_confirmed'),
190 private function workflow_validation_options($selected,
$texts) {
194 $html .=
'<option value="' . $this->h(
$value) .
'"' . ((string)$selected ===
$value ?
' selected' :
'') .
'>' . $this->h($label) .
'</option>';
199 private function workflow_default_question(
string $label,
$texts):
string {
200 $label = trim($label) !==
'' ? trim($label) :
$texts->get_fd_message(
'default_value_label');
201 return $texts->format_fd_message(
'default_question', array(
'label' => $label));
204 private function workflow_default_missing_message(
string $label,
$texts):
string {
205 $label = trim($label) !==
'' ? trim($label) :
$texts->get_fd_message(
'default_value_label');
206 return $texts->format_fd_message(
'default_missing_message', array(
'label' => $label));
209 private function workflow_resolver_label(
string $action,
$texts):
string {
211 'form' =>
$texts->get_fd_message(
'resolver_form'),
212 'select' =>
$texts->get_fd_message(
'resolver_select'),
213 'create' =>
$texts->get_fd_message(
'resolver_create'),
214 'module' =>
$texts->get_fd_message(
'resolver_module'),
216 return $labels[$action] ?? $labels[
'form'];
219 private function workflow_depends_options(array $needs, $selected,
$texts) {
220 $html =
'<option value="">' . $this->h(
$texts->get_fd_message(
'depends_always')) .
'</option>';
221 foreach ($needs as
$need) {
222 $key = (string)(
$need[
'key'] ??
'');
223 if ($key ===
'')
continue;
224 $html .=
'<option value="' . $this->h($key) .
'"' . ((string)$selected === $key ?
' selected' :
'') .
'>' . $this->h(
$need[
'label'] ?? $key) .
'</option>';
229 private function workflow_options_text(
$options) {
231 foreach ((array)
$options as $option) {
232 if (is_array($option)) {
233 $value = trim((
string)($option[
'value'] ??
''));
234 $label = trim((
string)($option[
'label'] ??
$value));
235 if (
$value !==
'' || $label !==
'') {
239 $text = trim((
string)$option);
240 if ($text !==
'') $lines[] = $text;
243 return implode(
"\n", $lines);
246 private function parse_workflow_options($text) {
248 foreach (preg_split(
'/\r\n|\r|\n|,/', (
string)$text) as $line) {
250 if ($line ===
'')
continue;
251 if (strpos($line,
'=') !==
false) {
252 list(
$value, $label) = explode(
'=', $line, 2);
254 $label = trim($label);
255 if (
$value !==
'' || $label !==
'') {
265 private function workflow_definition_from_data(array $data): array {
266 $engine =
dbx()->get_include_obj(
'dbxWorkflowEngine',
'dbxWorkflow');
267 $definition =
$engine->normalize_definition($data[
'definition_json'] ?? $this->default_definition_text(), $data[
'workflow_key'] ??
'');
268 if (trim((
string)($data[
'workflow_key'] ??
'')) !==
'')
$definition[
'workflow_key'] = trim((
string)$data[
'workflow_key']);
269 if (trim((
string)($data[
'title'] ??
'')) !==
'')
$definition[
'title'] = trim((
string)$data[
'title']);
270 if (trim((
string)($data[
'result_label'] ??
'')) !==
'')
$definition[
'result'] = trim((
string)$data[
'result_label']);
271 if (array_key_exists(
'description', $data))
$definition[
'description'] = (string)$data[
'description'];
275 private function workflow_builder_html(array $data,
$texts) {
276 $definition = $this->workflow_definition_from_data($data);
277 $needs = array_values((array)(
$definition[
'needs'] ?? array()));
278 $rowCount = max(14, count($needs) + 6);
281 for ($i = 0; $i < $rowCount; $i++) {
282 $need = $needs[$i] ?? array();
283 $isEmpty = empty(
$need);
284 $key = (string)(
$need[
'key'] ??
'');
285 $label = (string)(
$need[
'label'] ??
'');
286 $allowedActions = array(
'form',
'select',
'create',
'module');
287 $actions = array_values(array_unique(array_intersect(array_map(
'strval', (array)(
$need[
'actions'] ?? array(
'form'))), $allowedActions)));
289 $action = (string)(
$need[
'preferred'] ??
'');
291 $kind = (string)(
$need[
'kind'] ??
'');
292 if (!in_array($kind, array(
'input',
'action',
'check',
'decision'),
true)) {
293 $kind = $action ===
'module' ?
'action' :
'input';
295 $automation = (string)(
$need[
'automation'] ??
'manual');
296 if (!in_array($automation, array(
'manual',
'observe'),
true)) $automation =
'manual';
299 $rawQuestion = trim((
string)(
$need[
'question'] ??
''));
300 $question = $rawQuestion !==
'' ? $rawQuestion : $this->workflow_default_question($label,
$texts);
301 $validation = (string)(
$need[
'validation'] ?? (
$mode ===
'multiple' ?
'at_least_one' :
'exactly_one'));
302 if (!in_array(
$validation, array(
'exactly_one',
'at_least_one',
'not_empty',
'positive_integer',
'confirmed'),
true)) {
305 $rawMissingMessage = trim((
string)(
$need[
'missing_message'] ??
''));
306 $missingMessage = $rawMissingMessage !==
'' ? $rawMissingMessage : $this->workflow_default_missing_message($label,
$texts);
307 $resolver = is_array(
$need[
'resolver'] ??
null) ?
$need[
'resolver'] : array();
308 $resolverLabel = trim((
string)($resolver[
'label'] ??
''));
309 if ($resolverLabel ===
'') $resolverLabel = $this->workflow_resolver_label($action,
$texts);
310 $hint = (string)(
$need[
'hint'] ??
'');
311 $event = (string)(
$need[
'event'] ?? (
$need[
'result_event'] ??
''));
312 $dependsOn = (string)(
$need[
'depends_on'] ??
'');
313 $dependsValue = (string)(
$need[
'depends_value'] ??
'');
314 $optionsText = $this->workflow_options_text(
$need[
'options'] ?? array());
316 if (!empty(
$need[
'source']) && is_array(
$need[
'source'])) {
317 $sourceJson = json_encode(
$need[
'source'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
320 if (!empty(
$need[
'bind']) && is_array(
$need[
'bind'])) {
321 $bindJson = json_encode(
$need[
'bind'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
323 $moduleLinksJson =
'';
324 if (!empty(
$need[
'module_links']) && is_array(
$need[
'module_links'])) {
325 $moduleLinksJson = json_encode(
$need[
'module_links'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
327 $completeLabel = (string)(
$need[
'complete_label'] ??
'');
328 $originalJson = $isEmpty ?
'' : (json_encode(
$need, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?:
'');
330 $rowClass = $isEmpty ?
' dbx-workflow-step-row-empty' :
'';
331 $rowStyle = $isEmpty ?
' style="display:none;"' :
'';
332 $kindIcon = array(
'input' =>
'bi-input-cursor-text',
'action' =>
'bi-lightning-charge',
'check' =>
'bi-shield-check',
'decision' =>
'bi-signpost-split')[$kind] ??
'bi-input-cursor-text';
333 $kindLabels = $this->workflow_kind_labels(
$texts);
334 $kindLabel = $kindLabels[$kind] ?? $kindLabels[
'input'];
335 $rows .=
'<article class="dbx-workflow-step-row' . $rowClass .
'" data-workflow-step-row data-step-kind="' . $this->h($kind) .
'" data-question-auto="' . ($rawQuestion ===
'' ?
'1' :
'0') .
'" data-missing-auto="' . ($rawMissingMessage ===
'' ?
'1' :
'0') .
'" draggable="true"' . $rowStyle .
'>'
336 .
'<input type="hidden" name="workflow_step_present[' . $i .
']" value="1">'
337 .
'<input type="hidden" name="workflow_step_source[' . $i .
']" value="' . $this->h($sourceJson) .
'">'
338 .
'<input type="hidden" name="workflow_step_bind[' . $i .
']" value="' . $this->h($bindJson) .
'">'
339 .
'<input type="hidden" name="workflow_step_module_links[' . $i .
']" value="' . $this->h($moduleLinksJson) .
'">'
340 .
'<input type="hidden" name="workflow_step_original[' . $i .
']" value="' . $this->h($originalJson) .
'">'
341 .
'<input type="hidden" name="workflow_step_original_index[' . $i .
']" value="' . ($isEmpty ?
'' : $i) .
'">'
342 .
'<input type="hidden" name="workflow_step_action_contract[' . $i .
']" value="1">'
343 .
'<input class="visually-hidden" type="checkbox" name="workflow_step_active[' . $i .
']" value="1"' . (!$isEmpty ?
' checked' :
'') .
'>'
344 .
'<span class="dbx-workflow-node-port is-in" aria-hidden="true"></span>'
345 .
'<details class="dbx-workflow-step-details"' . (!$isEmpty ?
' open' :
'') .
'>'
346 .
'<summary class="dbx-workflow-step-summary">'
347 .
'<button class="dbx-workflow-drag-handle" type="button" draggable="true" data-workflow-drag-handle title="' . $this->h(
$texts->get_fd_message(
'step_move')) .
'" aria-label="' . $this->h(
$texts->get_fd_message(
'step_move')) .
'"><i class="bi bi-grip-vertical"></i></button>'
348 .
'<span class="dbx-workflow-step-num" data-workflow-step-number>' . str_pad((
string)($i + 1), 2,
'0', STR_PAD_LEFT) .
'</span>'
349 .
'<span class="dbx-workflow-step-kind"><i class="bi ' . $this->h($kindIcon) .
'" data-workflow-kind-icon></i><span data-workflow-kind-label>' . $this->h($kindLabel) .
'</span></span>'
350 .
'<span class="dbx-workflow-step-caption"><strong data-workflow-step-title>' . $this->h($label !==
'' ? $label :
$texts->get_fd_message(
'new_task')) .
'</strong><small data-workflow-step-result>' . $this->h($event !==
'' ? $event :
$texts->get_fd_message(
'no_result')) .
'</small></span>'
351 .
'<span class="dbx-workflow-auto-badge" data-workflow-auto-badge' . ($automation ===
'observe' ?
'' :
' hidden') .
'><i class="bi bi-stars"></i> ' . $this->h(
$texts->get_fd_message(
'automation_badge')) .
'</span>'
352 .
'<button class="dbx-workflow-remove-step" type="button" data-workflow-remove-step title="' . $this->h(
$texts->get_fd_message(
'step_remove')) .
'" aria-label="' . $this->h(
$texts->get_fd_message(
'step_remove')) .
'"><i class="bi bi-trash3"></i></button>'
354 .
'<div class="dbx-workflow-step-main">'
355 .
'<div class="row g-2 align-items-end">'
356 .
'<div class="col-xl-2 col-md-4"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_building_block')) .
'</label><select class="form-select form-select-sm" name="workflow_step_kind[' . $i .
']">' . $this->workflow_kind_options($kind,
$texts) .
'</select></div>'
357 .
'<div class="col-xl-4 col-md-8"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_label')) .
'</label><input class="form-control form-control-sm" name="workflow_step_label[' . $i .
']" value="' . $this->h($label) .
'" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_label')) .
'"></div>'
358 .
'<div class="col-xl-3 col-md-6"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_key')) .
'</label><input class="form-control form-control-sm font-monospace" name="workflow_step_key[' . $i .
']" value="' . $this->h($key) .
'" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_key')) .
'"></div>'
359 .
'<div class="col-xl-3 col-md-6"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_result')) .
'</label><input class="form-control form-control-sm" name="workflow_step_event[' . $i .
']" value="' . $this->h($event) .
'" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_result')) .
'"></div>'
360 .
'<div class="col-xl-5 col-md-12"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_allowed_actions')) .
'</label><div class="dbx-workflow-action-options">' . $this->workflow_action_choices($i,
$actions,
$texts) .
'</div></div>'
361 .
'<div class="col-xl-3 col-md-6"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_default_action')) .
'</label><select class="form-select form-select-sm" name="workflow_step_action[' . $i .
']">' . $this->workflow_action_options($action,
$texts) .
'</select></div>'
362 .
'<div class="col-xl-4 col-md-6"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_automation')) .
'</label><select class="form-select form-select-sm" name="workflow_step_automation[' . $i .
']">' . $this->workflow_automation_options($automation,
$texts) .
'</select></div>'
363 .
'<div class="col-xl-3 col-md-4"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_values')) .
'</label><select class="form-select form-select-sm" name="workflow_step_mode[' . $i .
']">' . $this->workflow_mode_options(
$mode,
$texts) .
'</select></div>'
364 .
'<div class="col-xl-3 col-md-4"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_required')) .
'</label><select class="form-select form-select-sm" name="workflow_step_required[' . $i .
']"><option value="1"' . (
$required ?
' selected' :
'') .
'>' . $this->h(
$texts->get_fd_message(
'required_yes')) .
'</option><option value="0"' . (!
$required ?
' selected' :
'') .
'>' . $this->h(
$texts->get_fd_message(
'required_no')) .
'</option></select></div>'
365 .
'<div class="col-xl-6 col-md-8" data-workflow-module-setting' . (!in_array(
'module',
$actions,
true) ?
' hidden' :
'') .
'><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_module_confirmation')) .
'</label><input class="form-control form-control-sm" name="workflow_step_complete_label[' . $i .
']" value="' . $this->h($completeLabel) .
'" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_module_confirmation')) .
'"></div>'
366 .
'<div class="col-12"><span class="dbx-workflow-safety-note"><i class="bi bi-shield-lock"></i> ' . $this->h(
$texts->get_fd_message(
'external_actions_confirmed')) .
'</span></div>'
367 .
'<div class="col-12"><section class="dbx-workflow-step-contract">'
368 .
'<header><i class="bi bi-clipboard2-check"></i><div><strong>' . $this->h(
$texts->get_fd_message(
'automatic_check')) .
'</strong><small>' . $this->h(
$texts->get_fd_message(
'automatic_check_hint')) .
'</small></div></header>'
369 .
'<div class="row g-2">'
370 .
'<div class="col-xl-5 col-md-12"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_question')) .
'</label><input class="form-control form-control-sm" name="workflow_step_question[' . $i .
']" value="' . $this->h($question) .
'" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_question')) .
'"></div>'
371 .
'<div class="col-xl-3 col-md-6"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_validation')) .
'</label><select class="form-select form-select-sm" name="workflow_step_validation[' . $i .
']">' . $this->workflow_validation_options(
$validation,
$texts) .
'</select></div>'
372 .
'<div class="col-xl-4 col-md-6"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_resolver')) .
'</label><input class="form-control form-control-sm" name="workflow_step_resolver_label[' . $i .
']" value="' . $this->h($resolverLabel) .
'" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_resolver')) .
'"></div>'
373 .
'<div class="col-12"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_missing_message')) .
'</label><input class="form-control form-control-sm" name="workflow_step_missing_message[' . $i .
']" value="' . $this->h($missingMessage) .
'" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_missing')) .
'"></div>'
374 .
'</div></section></div>'
375 .
'<div class="col-xl-4 col-md-6"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_depends')) .
'</label><select class="form-select form-select-sm" name="workflow_step_depends_on[' . $i .
']">' . $this->workflow_depends_options($needs, $dependsOn,
$texts) .
'</select></div>'
376 .
'<div class="col-xl-4 col-md-6"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_depends_value')) .
'</label><input class="form-control form-control-sm" name="workflow_step_depends_value[' . $i .
']" value="' . $this->h($dependsValue) .
'" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_depends_value')) .
'"></div>'
377 .
'<div class="col-xl-4 col-md-12"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_options')) .
'</label><textarea class="form-control form-control-sm" name="workflow_step_options[' . $i .
']" rows="3" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_options')) .
'">' . $this->h($optionsText) .
'</textarea></div>'
378 .
'<div class="col-12"><label class="form-label">' . $this->h(
$texts->get_fd_message(
'field_hint')) .
'</label><textarea class="form-control form-control-sm" name="workflow_step_hint[' . $i .
']" rows="2" placeholder="' . $this->h(
$texts->get_fd_message(
'placeholder_hint')) .
'">' . $this->h($hint) .
'</textarea></div>'
379 .
'</div></div></details>'
380 .
'<span class="dbx-workflow-node-port is-out" aria-hidden="true"></span>'
385 foreach ($needs as
$need) {
386 $label = trim((
string)(
$need[
'label'] ??
$need[
'key'] ??
$texts->get_fd_message(
'default_value_label')));
387 $question = trim((
string)(
$need[
'question'] ??
'')) ?: $this->workflow_default_question($label,
$texts);
389 $validation = (string)(
$need[
'validation'] ?? ((
$need[
'mode'] ??
'single') ===
'multiple' ?
'at_least_one' :
'exactly_one'));
390 $action = (string)(
$need[
'preferred'] ?? (((array)(
$need[
'actions'] ?? array(
'form')))[0] ??
'form'));
391 $resolver = is_array(
$need[
'resolver'] ??
null) ?
$need[
'resolver'] : array();
392 $resolverLabel = trim((
string)($resolver[
'label'] ??
'')) ?: $this->workflow_resolver_label($action,
$texts);
393 $validationLabels = $this->workflow_validation_labels(
$texts);
394 $checkRows .= $this->tpl()->get_tpl(
'dbxWorkflow_admin|workflow-check-preview-item', array(
395 'question' => $this->h($question),
396 'label' => $this->h($label),
398 'required' =>
$required ?
$texts->get_fd_message(
'preview_required') :
$texts->get_fd_message(
'preview_optional'),
399 'resolver_label' => $this->h($resolverLabel),
403 $finishLabel = (string)(
$definition[
'finish'][
'label'] ??
$texts->format_fd_message(
405 array(
'result' => ($data[
'result_label'] ??
'Workflow'))
407 $bindRef = (string)(
$definition[
'bind_ref'] ??
'');
408 $technicalPreview = json_encode(
$definition, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
409 $designerMessageKeys = array(
410 'kind_input',
'kind_action',
'kind_check',
'kind_decision',
411 'validation_exactly_one',
'validation_at_least_one',
'validation_not_empty',
412 'validation_positive_integer',
'validation_confirmed',
413 'resolver_form',
'resolver_select',
'resolver_create',
'resolver_module',
414 'default_question',
'default_missing_message',
'new_task',
'no_result',
415 'preview_required',
'preview_optional',
416 'js_new_input',
'js_new_action',
'js_new_check',
'js_new_decision',
417 'js_input_complete',
'js_action_complete',
'js_check_complete',
'js_decision_complete',
418 'js_requirement_singular',
'js_requirement_plural',
'js_direct_after_previous',
419 'js_error_label_missing',
'js_error_key_missing',
'js_error_key_duplicate',
420 'js_error_question_missing',
'js_error_decision_options',
'js_warning_automation',
421 'js_warning_select_source',
'js_warning_module_link',
422 'js_error_dependency_missing',
'js_error_dependency_order',
423 'validation_minimum_step',
'js_status_invalid',
'js_status_warning',
424 'js_status_valid',
'js_status_valid_detail',
'js_no_free_step',
425 'js_no_free_step_detail',
'js_default_event',
'js_yes',
'js_no',
426 'js_payment_label',
'js_payment_event',
'js_payment_question',
427 'js_payment_missing',
'js_payment_resolver',
'js_payment_hint',
430 $designerMessages = array();
431 foreach ($designerMessageKeys as $messageKey) {
432 $designerMessages[$messageKey] =
$texts->get_fd_message($messageKey);
435 return $this->tpl()->get_tpl(
'dbxWorkflow_admin|workflow-builder', array(
436 'need_count' => count($needs),
437 'check_rows' => $checkRows,
438 'step_rows' =>
$rows,
439 'finish_label' => $this->h($finishLabel),
440 'bind_ref' => $this->h($bindRef),
441 'technical_preview' => $this->h($technicalPreview),
442 'designer_messages' => $this->h(json_encode(
444 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
449 private function workflow_json_array(
$value): array {
452 if (
$value ===
'' || strlen(
$value) > 200000)
return array();
453 $decoded = json_decode(
$value,
true);
454 return is_array($decoded) ? $decoded : array();
457 private function workflow_actions_from_post(
$index, array $original,
string $preferred): array {
458 $allowed = array(
'form',
'select',
'create',
'module');
459 $hasActionContract = isset(
$_POST[
'workflow_step_action_contract'])
460 && array_key_exists(
$index, (array)
$_POST[
'workflow_step_action_contract']);
461 $posted =
$_POST[
'workflow_step_actions'][
$index] ?? array();
462 if (!is_array($posted)) $posted = array($posted);
463 $selected = array_values(array_unique(array_intersect(array_map(
'strval', $posted),
$allowed)));
468 if (!$hasActionContract) {
469 $selected = array_values(array_unique(array_intersect(array_map(
'strval', (array)($original[
'actions'] ?? array())),
$allowed)));
471 if (!$selected) $selected = array(in_array($preferred,
$allowed,
true) ? $preferred :
'form');
476 $selectedMap = array_fill_keys($selected,
true);
478 foreach ((array)($original[
'actions'] ?? array()) as $action) {
479 $action = (string)$action;
480 if (isset($selectedMap[$action]) && !in_array($action,
$actions,
true))
$actions[] = $action;
483 if (isset($selectedMap[$action]) && !in_array($action,
$actions,
true))
$actions[] = $action;
488 private function workflow_definition_from_post($oForm, array
$baseDefinition = array()) {
490 $checkKeyRenames = array();
491 $baseNeeds = array_values((array)(
$baseDefinition[
'needs'] ?? array()));
492 $present =
$_POST[
'workflow_step_present'] ?? array();
493 foreach ((array)$present as $idx => $unused) {
494 $active = isset(
$_POST[
'workflow_step_active'][$idx]);
495 $label = trim((
string)(
$_POST[
'workflow_step_label'][$idx] ??
''));
496 $key = $this->normalize_key(
$_POST[
'workflow_step_key'][$idx] ?? $label);
497 if (!$active && $label ===
'' && $key ===
'')
continue;
498 if (!$active)
continue;
499 if ($key ===
'') $key =
'schritt_' . ((int)$idx + 1);
500 if ($label ===
'') $label = $key;
502 $action = (string)(
$_POST[
'workflow_step_action'][$idx] ??
'form');
503 if (!in_array($action, array(
'form',
'select',
'create',
'module'),
true)) $action =
'form';
504 $kind = (string)(
$_POST[
'workflow_step_kind'][$idx] ??
'input');
505 if (!in_array($kind, array(
'input',
'action',
'check',
'decision'),
true)) $kind =
'input';
506 $automation = (string)(
$_POST[
'workflow_step_automation'][$idx] ??
'manual');
507 if (!in_array($automation, array(
'manual',
'observe'),
true)) $automation =
'manual';
508 $mode = (string)(
$_POST[
'workflow_step_mode'][$idx] ??
'single') ===
'multiple' ?
'multiple' :
'single';
510 $need = $this->workflow_json_array(
$_POST[
'workflow_step_original'][$idx] ??
'');
511 $hasActionContract = isset(
$_POST[
'workflow_step_action_contract'])
512 && array_key_exists($idx, (array)
$_POST[
'workflow_step_action_contract']);
513 $originalIndexRaw = trim((
string)(
$_POST[
'workflow_step_original_index'][$idx] ??
''));
514 $originalIndex = ctype_digit($originalIndexRaw) ? (int)$originalIndexRaw :
null;
515 if (!
$need && $originalIndex !==
null && isset($baseNeeds[$originalIndex]) && is_array($baseNeeds[$originalIndex])) {
516 $need = $baseNeeds[$originalIndex];
517 } elseif (!
$need && !$hasActionContract && isset($baseNeeds[(
int)$idx]) && is_array($baseNeeds[(
int)$idx])) {
518 $need = $baseNeeds[(int)$idx];
520 $originalKey = $this->normalize_key(
$need[
'key'] ??
'');
521 if ($originalKey !==
'' && $originalKey !== $key) $checkKeyRenames[$originalKey] = $key;
522 $actions = $this->workflow_actions_from_post($idx,
$need, $action);
524 $resolver = is_array(
$need[
'resolver'] ??
null) ?
$need[
'resolver'] : array();
525 $resolver[
'type'] = $action;
526 $resolver[
'label'] = trim((
string)(
$_POST[
'workflow_step_resolver_label'][$idx] ??
'')) ?: $this->workflow_resolver_label($action, $oForm);
532 $need[
'label'] = $label;
533 $need[
'kind'] = $kind;
534 $need[
'automation'] = $automation;
536 $need[
'required'] = ((string)(
$_POST[
'workflow_step_required'][$idx] ??
'1') ===
'1');
538 $need[
'preferred'] = $action;
539 $need[
'question'] = trim((
string)(
$_POST[
'workflow_step_question'][$idx] ??
'')) ?: $this->workflow_default_question($label, $oForm);
540 $need[
'validation'] = (string)(
$_POST[
'workflow_step_validation'][$idx] ?? (
$mode ===
'multiple' ?
'at_least_one' :
'exactly_one'));
541 $need[
'missing_message'] = trim((
string)(
$_POST[
'workflow_step_missing_message'][$idx] ??
'')) ?: $this->workflow_default_missing_message($label, $oForm);
542 $need[
'resolver'] = $resolver;
543 $need[
'hint'] = trim((
string)(
$_POST[
'workflow_step_hint'][$idx] ??
''));
545 $event = trim((
string)(
$_POST[
'workflow_step_event'][$idx] ??
''));
546 if ($event !==
'')
$need[
'event'] = $event;
547 else unset(
$need[
'event'],
$need[
'result_event']);
549 $dependsOn = $this->normalize_key(
$_POST[
'workflow_step_depends_on'][$idx] ??
'');
550 if ($dependsOn !==
'') {
551 $need[
'depends_on'] = $dependsOn;
552 $dependsValue = trim((
string)(
$_POST[
'workflow_step_depends_value'][$idx] ??
''));
553 if ($dependsValue !==
'')
$need[
'depends_value'] = $dependsValue;
554 else unset(
$need[
'depends_value']);
556 else unset(
$need[
'depends_on'],
$need[
'depends_value']);
558 $options = $this->parse_workflow_options(
$_POST[
'workflow_step_options'][$idx] ??
'');
560 else unset(
$need[
'options']);
562 if (isset(
$_POST[
'workflow_step_complete_label']) && array_key_exists($idx, (array)
$_POST[
'workflow_step_complete_label'])) {
563 $completeLabel = trim((
string)
$_POST[
'workflow_step_complete_label'][$idx]);
564 if ($completeLabel !==
'')
$need[
'complete_label'] = $completeLabel;
565 else unset(
$need[
'complete_label']);
568 $sourceJson = trim((
string)(
$_POST[
'workflow_step_source'][$idx] ??
''));
569 $source = $sourceJson !==
'' ? json_decode($sourceJson,
true) :
null;
571 elseif ($sourceJson ===
'') unset(
$need[
'source']);
573 $bindJson = trim((
string)(
$_POST[
'workflow_step_bind'][$idx] ??
''));
574 $bind = $bindJson !==
'' ? json_decode($bindJson,
true) :
null;
575 if (is_array($bind))
$need[
'bind'] = $bind;
576 elseif ($bindJson ===
'') unset(
$need[
'bind']);
578 $moduleLinksJson = trim((
string)(
$_POST[
'workflow_step_module_links'][$idx] ??
''));
579 $moduleLinks = $moduleLinksJson !==
'' ? json_decode($moduleLinksJson,
true) :
null;
580 if (is_array($moduleLinks))
$need[
'module_links'] = $moduleLinks;
581 elseif ($moduleLinksJson ===
'') unset(
$need[
'module_links']);
587 $definition[
'workflow_key'] = $oForm->get_post(
'workflow_key',
'',
'parameter|min=2|max=80');
588 $definition[
'title'] = $oForm->get_post(
'title',
'',
'*|min=2|max=160');
589 $definition[
'result'] = $oForm->get_post(
'result_label',
'',
'*|min=2|max=160');
590 $definition[
'description'] = $oForm->get_post_data(
'description',
'',
'*');
594 if (!is_array(
$check))
continue;
595 $checkKey = $this->normalize_key(
$check[
'key'] ??
'');
596 if (isset($checkKeyRenames[$checkKey]))
$check[
'key'] = $checkKeyRenames[$checkKey];
601 $finish[
'label'] = trim((
string)(
$_POST[
'workflow_finish_label'] ??
'')) ?: $oForm->format_fd_message(
603 array(
'result' =>
$definition[
'result'] ?:
'Workflow')
607 $bindRef = trim((
string)(
$_POST[
'workflow_bind_ref'] ??
''));
608 if ($bindRef !==
'')
$definition[
'bind_ref'] = $bindRef;
614 private function validate_workflow_definition(array
$definition,
$texts): array {
616 $needs = array_values((array)(
$definition[
'needs'] ?? array()));
617 $positions = array();
620 $key = $this->normalize_key(
$need[
'key'] ??
'');
621 $label = trim((
string)(
$need[
'label'] ?? $key));
624 'validation_step_key_missing',
625 array(
'step' =>
$index + 1)
629 if (array_key_exists($key, $positions)) {
631 'validation_step_key_duplicate',
635 $positions[$key] =
$index;
637 if ((
string)(
$need[
'kind'] ??
'') ===
'decision' && count((array)(
$need[
'options'] ?? array())) < 2) {
639 'validation_decision_options',
640 array(
'label' => $label)
646 $key = $this->normalize_key(
$need[
'key'] ??
'');
647 $dependsOn = $this->normalize_key(
$need[
'depends_on'] ??
'');
648 if ($dependsOn ===
'')
continue;
649 if (!array_key_exists($dependsOn, $positions)) {
651 'validation_dependency_missing',
652 array(
'dependency' => $dependsOn,
'key' => $key)
654 } elseif ($positions[$dependsOn] >=
$index) {
656 'validation_dependency_order',
657 array(
'dependency' => $dependsOn,
'key' => $key)
663 $errors[] =
$texts->get_fd_message(
'validation_minimum_step');
666 return array_values(array_unique(
$errors));
669 private function edit() {
670 $rid =
dbx()->get_modul_var(
'rid',
'new',
'parameter');
671 $isNew = ($rid ===
'new' || (int)$rid <= 0);
673 $oForm =
dbx()->get_system_obj(
'dbxForm');
674 $oForm->init(
'workflow-definition',
'workflow-definition-form');
675 $oForm->_dd = $this->ddDefinition;
676 $oForm->_fd =
'dbxWorkflow|workflow-definition';
677 $oForm->load_fd_messages();
679 $data = $this->load_row($rid);
682 $this->alert(
'warning', $oForm->get_fd_message(
'definition_not_found')),
687 $oForm->add_rep(
'bar_title', $oForm->get_fd_message($isNew ?
'form_new_title' :
'form_edit_title'));
688 $oForm->add_rep(
'bar_subtitle', $oForm->get_fd_message(
'form_subtitle'));
689 $oForm->add_obj(
'bar_actions',
'obj-value',
690 '<a class="btn btn-outline-secondary btn-sm" href="?dbx_modul=dbxWorkflow_admin"><i class="bi bi-list-ul"></i> ' . $this->h($oForm->get_fd_message(
'list_label')) .
'</a>'
691 .
'<button class="btn btn-primary btn-sm" type="submit"><i class="bi bi-save"></i> ' . $this->h($oForm->get_fd_message(
'save_label')) .
'</button>'
693 $oForm->_data = $data;
694 $oForm->_action =
'?dbx_modul=dbxWorkflow_admin&dbx_run1=edit&rid=' . ($isNew ?
'new' : (int)$rid);
695 $oForm->set_activ_id($isNew ? 0 : (
int)$rid);
696 $oForm->_msg_info = $oForm->get_fd_message($isNew ?
'form_new_info' :
'form_edit_info');
698 $oForm->add_fld(
'workflow_key');
699 $oForm->add_fld(
'title');
700 $oForm->add_fld(
'result_label');
701 $oForm->add_fld(
'description');
702 $oForm->add_fld(
'active');
703 $oForm->add_obj(
'workflow_builder',
'obj-value', $this->workflow_builder_html($data, $oForm));
705 if ($oForm->submit()) {
706 if (!$oForm->errors()) {
707 $engine =
dbx()->get_include_obj(
'dbxWorkflowEngine',
'dbxWorkflow');
709 $definition =
$engine->normalize_definition($this->workflow_definition_from_post($oForm,
$baseDefinition), $oForm->get_post(
'workflow_key',
'',
'parameter|max=80'));
710 $definitionErrors = $this->validate_workflow_definition(
$definition, $oForm);
711 if ($definitionErrors) {
712 $errorText = implode(
' ', $definitionErrors);
713 $oForm->add_fld_error(
'title', $errorText);
714 $oForm->_msg_error = $errorText;
715 $oForm->add_obj(
'workflow_builder',
'obj-value', $this->workflow_builder_html(array_merge($data, array(
716 'workflow_key' => $oForm->get_post(
'workflow_key',
'',
'parameter|max=80'),
717 'title' => $oForm->get_post(
'title',
'',
'*'),
718 'result_label' => $oForm->get_post(
'result_label',
'',
'*'),
719 'description' => $oForm->get_post_data(
'description',
'',
'*'),
720 'definition_json' => json_encode(
$definition, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
722 return $oForm->run();
724 $definitionText = json_encode(
$definition, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
727 'workflow_key' => $oForm->get_post(
'workflow_key',
'',
'parameter|min=2|max=80'),
728 'title' => $oForm->get_post(
'title',
'',
'*|min=2|max=160'),
729 'result_label' => $oForm->get_post(
'result_label',
'',
'*|min=2|max=160'),
730 'description' => $oForm->get_post_data(
'description',
'',
'*'),
731 'definition_json' => $definitionText,
732 'active' => $oForm->get_post(
'active', 0,
'int')
736 $key =
$db->escape($values[
'workflow_key'],
$db->get_dd_server($this->ddDefinition));
737 $where =
"workflow_key='" . $key .
"'";
738 if (!$isNew) $where .=
' AND id <> ' . (int)$rid;
740 if (
$db->count($this->ddDefinition, $where) > 0) {
741 $oForm->add_fld_error(
'workflow_key', $oForm->get_fd_message(
'duplicate_workflow_key'));
742 $oForm->_msg_error = $oForm->get_fd_message(
'duplicate_workflow_key');
744 $ok =
$db->save($this->ddDefinition, $values, $isNew ? 0 : (
int)$rid, 0, 1, 1, 1);
745 $oForm->_msg_success = $oForm->get_fd_message(
$ok ?
'save_success' :
'save_error');
747 $oForm->_data = array_merge($oForm->_data, $values);
748 $oForm->add_obj(
'workflow_builder',
'obj-value', $this->workflow_builder_html($oForm->_data, $oForm));
752 $oForm->_msg_error = $oForm->get_fd_message(
'validation_error');
756 return $oForm->run();
759 private function decorate_rows(
$rows) {
761 foreach ((array)
$rows as $row) {
762 $row[
'action'] = $this->tpl()->get_tpl(
'dbxWorkflow_admin|workflow-admin-row-action', array(
763 'id' => (
int)($row[
'id'] ?? 0),
764 'workflow_key' => rawurlencode((
string)($row[
'workflow_key'] ??
'')),
772 private function report_search_where(
string $rwhere): array|
string {
773 $rwhere = trim($rwhere);
774 if ($rwhere ===
'') {
781 'like' => array(
'workflow_key',
'title',
'result_label',
'description',
'status',
'message',
'current_need'),
782 'mode' =>
'contains',
787 private function list_definitions() {
790 $oReport =
dbx()->get_system_obj(
'dbxReport');
791 $oReport->init(
'workflow-definitions');
792 $oReport->_dd = $this->ddDefinition;
793 $oReport->_fd =
'dbxWorkflow_admin|rpt-workflow-definitions-selection';
794 $oReport->load_fd_messages();
795 $oReport->_action =
'?dbx_modul=dbxWorkflow_admin';
796 $oReport->_pages =
true;
797 $oReport->_create_row_select =
false;
798 $oReport->_create_row_edit =
false;
799 $oReport->_create_row_delete =
false;
800 $oReport->_but_pagination = 5;
801 $oReport->set_form_help_enabled(
false);
802 $oReport->_msg_info = $oReport->get_fd_message(
'report_info');
803 $oReport->add_module_bar(
804 $oReport->get_fd_message(
'bar_title'),
806 $oReport->get_fd_message(
'bar_subtitle'),
811 $oReport->get_fd_message(
'bar_subtitle')
815 '<a class="btn btn-outline-secondary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=binds"><i class="bi bi-plug"></i> '
816 . $this->h($oReport->get_fd_message(
'module_bindings'))
818 .
'<a class="btn btn-primary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=edit_bind&rid=new"><i class="bi bi-plus-circle"></i> '
819 . $this->h($oReport->get_fd_message(
'new_binding'))
821 .
'<a class="btn btn-primary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=edit&rid=new"><i class="bi bi-plus-circle"></i> '
822 . $this->h($oReport->get_fd_message(
'new_workflow'))
825 $oReport->create_selection_fields(
'dbxWorkflow_admin|rpt-workflow-definitions-selection');
827 if ($oReport->submit()) {
828 if (!$oReport->errors()) {
829 $oReport->_msg_info = $oReport->get_fd_message(
833 $oReport->_msg_error = $oReport->get_fd_message(
839 $rwhere = $oReport->get_fld_val(
'dbx_rwhere',
'',
'sqlsearch|max=64');
840 $rrows = $oReport->get_fld_val(
'dbx_rrows', 30,
'int');
841 $rpos = $oReport->get_fld_val(
'dbx_rpos', 0,
'int');
842 $rsort = $oReport->get_fld_val(
'dbx_rsort',
'title',
'parameter');
843 $rdesc = $oReport->get_fld_val(
'dbx_rdesc',
'ASC',
'parameter');
845 $where = $this->report_search_where($rwhere);
846 $cols = array(
'id',
'workflow_key',
'title',
'result_label',
'active',
'update_date');
848 $oReport->_rflds = array(
850 'workflow_key' => $oReport->get_fd_message(
'column_key'),
851 'title' => $oReport->get_fd_message(
'column_title'),
852 'result_label' => $oReport->get_fd_message(
'column_goal'),
853 'active' => $oReport->get_fd_message(
'column_active'),
854 'update_date' => $oReport->get_fd_message(
'column_updated'),
855 'action' => $oReport->get_fd_message(
'column_action')
857 $oReport->_rpt_format = array(
858 'update_date' =>
'php-datetime-usr',
861 $oReport->_rrows = $rrows;
862 $oReport->_rpos = $rpos;
863 $oReport->_count_all =
$db->count($this->ddDefinition,
'');
864 $oReport->_rcount =
$db->count($this->ddDefinition, $where);
865 $oReport->_rdata = $this->decorate_rows(
$db->select($this->ddDefinition, $where, $cols, $rsort, $rdesc,
'', $rrows, $rpos));
867 return $this->frame($oReport->run(), $oReport);
870 private function list_instances() {
872 $oReport =
dbx()->get_system_obj(
'dbxReport');
873 $oReport->init(
'workflow-instances');
874 $oReport->_dd = $this->ddInstance;
875 $oReport->_fd =
'dbxWorkflow_admin|rpt-workflow-instances-selection';
876 $oReport->load_fd_messages();
877 $oReport->_action =
'?dbx_modul=dbxWorkflow_admin&dbx_run1=instances';
878 $oReport->_pages =
true;
879 $oReport->_create_row_select =
false;
880 $oReport->_create_row_edit =
false;
881 $oReport->_create_row_delete =
false;
882 $oReport->_but_pagination = 5;
883 $oReport->set_form_help_enabled(
false);
884 $oReport->_msg_info = $oReport->get_fd_message(
'report_info');
885 $oReport->create_selection_fields(
'dbxWorkflow_admin|rpt-workflow-instances-selection');
887 if ($oReport->submit()) {
888 if (!$oReport->errors()) {
889 $oReport->_msg_info = $oReport->get_fd_message(
893 $oReport->_msg_error = $oReport->get_fd_message(
899 $rwhere = $oReport->get_fld_val(
'dbx_rwhere',
'',
'sqlsearch|max=64');
900 $rrows = $oReport->get_fld_val(
'dbx_rrows', 50,
'int');
901 $rpos = $oReport->get_fld_val(
'dbx_rpos', 0,
'int');
902 $rsort = $oReport->get_fld_val(
'dbx_rsort',
'create_date',
'parameter');
903 $rdesc = $oReport->get_fld_val(
'dbx_rdesc',
'DESC',
'parameter');
905 $where = $this->report_search_where($rwhere);
906 $cols = array(
'id',
'create_date',
'workflow_key',
'result_label',
'status',
'current_need',
'percent',
'message');
908 $oReport->_rflds = array(
910 'create_date' => $oReport->get_fd_message(
'column_start'),
911 'workflow_key' => $oReport->get_fd_message(
'column_workflow'),
912 'result_label' => $oReport->get_fd_message(
'column_goal'),
913 'status' => $oReport->get_fd_message(
'column_status'),
914 'current_need' => $oReport->get_fd_message(
'column_task'),
916 'message' => $oReport->get_fd_message(
'column_message'),
917 'instance_action' => $oReport->get_fd_message(
'column_action')
919 $oReport->_rpt_format = array(
920 'create_date' =>
'php-datetime-usr',
924 'instance_action' =>
'html'
926 $oReport->_rrows = $rrows;
927 $oReport->_rpos = $rpos;
928 $oReport->_count_all =
$db->count($this->ddInstance,
'');
929 $oReport->_rcount =
$db->count($this->ddInstance, $where);
930 $oReport->_rdata = $this->decorate_instance_rows(
931 $db->select($this->ddInstance, $where, $cols, $rsort, $rdesc,
'', $rrows, $rpos),
935 return $this->frame($oReport->run(), $oReport);
938 private function decorate_instance_rows(
$rows,
$texts) {
940 foreach ((array)
$rows as $row) {
941 $row[
'instance_action'] = $this->instance_action_button($row,
$texts);
942 $row[
'status'] = $this->instance_status_badge((
string)($row[
'status'] ??
''),
$texts);
943 $row[
'percent'] = $this->instance_percent_bar((
int)($row[
'percent'] ?? 0));
944 $row[
'message'] = $this->instance_message((
string)($row[
'message'] ??
''));
950 private function instance_status_badge(
string $status,
$texts):
string {
951 $status = strtolower(trim($status));
953 'running' => array(
$texts->get_fd_message(
'status_running'),
'primary'),
954 'finishing' => array(
$texts->get_fd_message(
'status_finishing'),
'warning'),
955 'paused' => array(
$texts->get_fd_message(
'status_paused'),
'warning'),
956 'finished' => array(
$texts->get_fd_message(
'status_finished'),
'success'),
957 'canceled' => array(
$texts->get_fd_message(
'status_canceled'),
'secondary'),
958 'error' => array(
$texts->get_fd_message(
'status_error'),
'danger'),
960 $data = $labels[$status] ?? array(($status !==
'' ? $status :
$texts->get_fd_message(
'status_unknown')),
'secondary');
961 return '<span class="badge text-bg-' . $this->h($data[1]) .
'">' . $this->h($data[0]) .
'</span>';
964 private function instance_percent_bar(
int $percent):
string {
965 $percent = max(0, min(100, $percent));
966 return '<div class="dbx-workflow-instance-progress" title="' . $percent .
'%">'
967 .
'<div class="dbx-workflow-instance-progress-bar" style="width:' . $percent .
'%"></div>'
968 .
'<span>' . $percent .
'%</span>'
972 private function instance_message(
string $message):
string {
973 $message = trim($message);
974 if ($message ===
'') {
975 return '<span class="text-muted">-</span>';
977 return '<span class="dbx-workflow-instance-message">' . $this->h($message) .
'</span>';
980 private function instance_action_button(array $row,
$texts):
string {
981 $id = (int)($row[
'id'] ?? 0);
986 $status = strtolower(trim((
string)($row[
'status'] ??
'')));
987 $isFinished = in_array($status, array(
'finished',
'canceled'),
true);
988 $url =
'?dbx_modul=dbxWorkflow&dbx_run1=run&iid=' . $id;
989 if ($status ===
'paused') {
990 $url .=
'&dbx_token=' . rawurlencode(
dbx()->
action_token(
'dbxWorkflow.instance.' . $id)) .
'&proc_cmd=resume';
994 ?
$texts->get_fd_message(
'action_view')
995 :
$texts->get_fd_message(
'action_continue');
996 $icon = $isFinished ?
'bi-search' :
'bi-play-fill';
997 $class = $isFinished ?
'btn btn-outline-primary btn-sm' :
'btn btn-primary btn-sm';
998 $title =
$texts->format_fd_message(
1000 array(
'action' => $label,
'id' => $id)
1003 return '<a class="' .
$class .
' openWin dbx-win dbx-workflow-instance-action" href="' . $this->h(
$url) .
'"'
1004 .
' data-url="' . $this->h(
$url) .
'" data-title="' . $this->h($title) .
'" data-width="82%" data-height="88%"'
1005 .
' title="' . $this->h($title) .
'"><i class="bi ' . $icon .
'"></i> ' . $this->h($label) .
'</a>';
1008 private function bindRegistry() {
1009 return dbx()->get_include_obj(
'dbxWorkflowBindRegistry',
'dbxWorkflow');
1012 private function load_bind_row($rid) {
1013 if ($rid ===
'new' || (
int)$rid <= 0) {
1015 'modul' =>
'dbxContact',
1016 'bind_key' =>
'contact_reply',
1017 'title' =>
'Neues Modul-Binding',
1018 'description' =>
'Einheitliches Binding: dbxWorkflow nutzt DD/FD/TPL/Config des Moduls.',
1024 $rows = $this->db()->select($this->ddBind, array(
'id' => (
int)$rid),
'*',
'id',
'DESC',
'', 1, 0, 0);
1028 private function edit_bind() {
1029 $rid =
dbx()->get_modul_var(
'rid',
'new',
'parameter');
1030 $isNew = ($rid ===
'new' || (int)$rid <= 0);
1031 $data = $this->load_bind_row($rid);
1033 $oForm =
dbx()->get_system_obj(
'dbxForm');
1034 $oForm->init(
'workflow-module-bind',
'workflow-module-bind-form');
1035 $oForm->_dd = $this->ddBind;
1036 $oForm->_fd =
'dbxWorkflow|workflow-module-bind';
1037 $oForm->load_fd_messages();
1039 $data[
'title'] = $oForm->get_fd_message(
'default_binding_title');
1040 $data[
'description'] = $oForm->get_fd_message(
'default_binding_description');
1044 return $this->frame(
1045 $this->alert(
'warning', $oForm->get_fd_message(
'binding_not_found')),
1050 $generatorData = array(
1051 'gen_modul' => trim((
string)
dbx()->get_modul_var(
'gen_modul', (
string)($data[
'modul'] ??
''),
'parameter')),
1052 'gen_dd' => trim((
string)
dbx()->get_modul_var(
'gen_dd',
'',
'parameter')),
1054 $generatorDds = array();
1055 foreach ($this->bindRegistry()->listModuleDds($generatorData[
'gen_modul']) as $ddRef) {
1056 $generatorDds[(string)$ddRef] = (
string)$ddRef;
1062 dbx()->get_system_obj(
'dbxForm',
'use');
1063 $generatorForm = new \dbxForm();
1064 $generatorForm->init(
'workflow-bind-generator',
'workflow-bind-generator');
1065 $generatorForm->_fd =
'dbxWorkflow|workflow-module-bind';
1066 $generatorForm->load_fd_messages();
1067 $generatorForm->set_form_help_enabled(
false);
1068 $generatorForm->_action =
'?dbx_modul=dbxWorkflow_admin&dbx_run1=edit_bind&rid=' . ($isNew ?
'new' : (int)$rid);
1069 $generatorForm->_data = array_merge($generatorForm->_data, $generatorData);
1070 $generatorForm->_msg_info =
'';
1071 $generatorForm->add_fld(
1074 label: $generatorForm->get_fd_message(
'generator_module_label'),
1075 rules:
'parameter|min=2|max=80',
1076 data: array(
'placeholder' =>
'dbxContact')
1078 $generatorForm->add_fld(
1080 'select-single-label',
1081 label: $generatorForm->get_fd_message(
'generator_dd_label'),
1082 rules:
'parameter|min=2|max=160',
1083 options: array(
'' => $generatorForm->get_fd_message(
'generator_dd_select')) + $generatorDds
1086 if ($generatorForm->submit()) {
1087 if (!$generatorForm->errors()) {
1088 $generateModul = trim((
string)$generatorForm->get_post(
'gen_modul',
'',
'parameter|min=2|max=80'));
1089 $generateDd = trim((
string)$generatorForm->get_post(
'gen_dd',
'',
'parameter|min=2|max=160'));
1090 $generated = $this->bindRegistry()->generateBindSkeleton($generateModul, $generateDd);
1092 $data = array_merge($data, $generated);
1093 $generatorForm->_msg_success = $generatorForm->get_fd_message(
'generator_success');
1095 $generatorForm->_msg_error = $generatorForm->get_fd_message(
'generator_error');
1098 $generatorForm->_msg_error = $generatorForm->get_fd_message(
'generator_validation_error');
1102 $oForm->add_rep(
'bar_title', $oForm->get_fd_message($isNew ?
'form_new_title' :
'form_edit_title'));
1103 $oForm->add_rep(
'bar_subtitle', $oForm->get_fd_message(
'form_subtitle'));
1104 $oForm->add_obj(
'bar_actions',
'obj-value',
1105 '<a class="btn btn-outline-secondary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=binds"><i class="bi bi-list-ul"></i> ' . $this->h($oForm->get_fd_message(
'list_label')) .
'</a>'
1106 .
'<button class="btn btn-primary btn-sm" type="submit"><i class="bi bi-save"></i> ' . $this->h($oForm->get_fd_message(
'save_label')) .
'</button>'
1108 $oForm->_data = array_merge($oForm->_data, $data);
1109 $oForm->_action =
'?dbx_modul=dbxWorkflow_admin&dbx_run1=edit_bind&rid=' . ($isNew ?
'new' : (int)$rid);
1110 $oForm->set_activ_id($isNew ? 0 : (
int)$rid);
1111 $oForm->_msg_info = $oForm->get_fd_message($isNew ?
'form_new_info' :
'form_edit_info');
1113 $oForm->add_fld(
'modul');
1114 $oForm->add_fld(
'bind_key');
1115 $oForm->add_fld(
'title');
1116 $oForm->add_fld(
'description');
1117 $oForm->add_fld(
'bind_json');
1118 $oForm->add_fld(
'active');
1120 if ($oForm->submit()) {
1121 if (!$oForm->errors()) {
1122 $bindJson = trim((
string)$oForm->get_post_data(
'bind_json',
'',
'*'));
1123 $decoded = json_decode($bindJson,
true);
1124 if (!is_array($decoded)) {
1125 $oForm->add_fld_error(
'bind_json', $oForm->get_fd_message(
'json_invalid'));
1126 $oForm->_msg_error = $oForm->get_fd_message(
'json_invalid');
1129 'modul' => $oForm->get_post(
'modul',
'',
'parameter|min=2|max=80'),
1130 'bind_key' => $oForm->get_post(
'bind_key',
'',
'parameter|min=2|max=80'),
1131 'title' => $oForm->get_post(
'title',
'',
'*|min=2|max=160'),
1132 'description' => $oForm->get_post_data(
'description',
'',
'*'),
1133 'bind_json' => json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
1134 'active' => $oForm->get_post(
'active', 0,
'int'),
1138 $where =
"modul='" .
$db->escape($values[
'modul'],
$db->get_dd_server($this->ddBind))
1139 .
"' AND bind_key='" .
$db->escape($values[
'bind_key'],
$db->get_dd_server($this->ddBind)) .
"'";
1141 $where .=
' AND id <> ' . (int)$rid;
1144 if (
$db->count($this->ddBind, $where) > 0) {
1145 $oForm->add_fld_error(
'bind_key', $oForm->get_fd_message(
'duplicate_bind_key'));
1146 $oForm->_msg_error = $oForm->get_fd_message(
'duplicate_bind_key');
1148 $ok =
$db->save($this->ddBind, $values, $isNew ? 0 : (
int)$rid, 0, 1, 1, 1);
1149 $oForm->_msg_success = $oForm->get_fd_message(
$ok ?
'save_success' :
'save_error');
1151 $oForm->_data = array_merge($oForm->_data, $values);
1156 $oForm->_msg_error = $oForm->get_fd_message(
'validation_error');
1160 $oForm->add_obj(
'bind_generator',
'obj-value', $generatorForm->run());
1162 return $oForm->run();
1165 private function decorate_bind_rows(
$rows) {
1167 foreach ((array)
$rows as $row) {
1168 $row[
'bind_ref'] = (string)($row[
'modul'] ??
'') .
'|' . (string)($row[
'bind_key'] ??
'');
1169 $row[
'action'] = $this->tpl()->get_tpl(
'dbxWorkflow_admin|workflow-bind-row-action', array(
1170 'id' => (
int)($row[
'id'] ?? 0),
1171 'bind_ref' => $this->h($row[
'bind_ref']),
1178 private function list_binds() {
1180 $oReport =
dbx()->get_system_obj(
'dbxReport');
1181 $oReport->init(
'workflow-module-binds',
'dbxWorkflow_admin|workflow-module-binds');
1182 $oReport->_dd = $this->ddBind;
1183 $oReport->_fd =
'dbxWorkflow|workflow-module-bind';
1184 $oReport->load_fd_messages();
1185 $oReport->_action =
'?dbx_modul=dbxWorkflow_admin&dbx_run1=binds';
1186 $oReport->_pages =
true;
1187 $oReport->_create_row_select =
false;
1188 $oReport->_create_row_edit =
false;
1189 $oReport->_create_row_delete =
false;
1190 $oReport->_but_pagination = 5;
1191 $oReport->set_form_help_enabled(
false);
1192 $oReport->_msg_info = $oReport->get_fd_message(
'list_info');
1194 $cols = array(
'id',
'modul',
'bind_key',
'title',
'active',
'update_date');
1195 $oReport->_rflds = array(
1197 'modul' => $oReport->get_fd_message(
'column_module'),
1198 'bind_key' => $oReport->get_fd_message(
'column_bind_key'),
1199 'title' => $oReport->get_fd_message(
'column_title'),
1200 'active' => $oReport->get_fd_message(
'column_active'),
1201 'update_date' => $oReport->get_fd_message(
'column_update'),
1202 'bind_ref' => $oReport->get_fd_message(
'column_reference'),
1203 'action' => $oReport->get_fd_message(
'column_action'),
1205 $oReport->_rpt_format = array(
1206 'update_date' =>
'php-datetime-usr',
1209 $oReport->_rrows = 30;
1210 $oReport->_rpos = 0;
1211 $oReport->_count_all =
$db->count($this->ddBind,
'');
1212 $oReport->_rcount = $oReport->_count_all;
1213 $oReport->_rdata = $this->decorate_bind_rows(
$db->select($this->ddBind,
'', $cols,
'modul',
'ASC',
'', 30, 0));
1215 return $this->frame($oReport->run(), $oReport);
1219 $run =
dbx()->get_modul_var(
'dbx_run1',
'list',
'parameter');
1221 if (
$run ===
'install')
return $this->install();
1222 if (
$run ===
'edit')
return $this->edit();
1223 if (
$run ===
'edit_bind')
return $this->edit_bind();
1224 if (
$run ===
'binds')
return $this->list_binds();
1225 if (
$run ===
'instances')
return $this->list_instances();
1227 return $this->list_definitions();