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));
27 private function frame($content) {
28 $help =
dbx()->get_include_obj(
'dbxAdminHelp',
'dbxAdmin');
29 $topic = $help->resolveTopic(
'dbxWorkflow_admin');
31 .
'<a class="btn btn-outline-secondary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=binds"><i class="bi bi-plug"></i> Modul-Bindings</a>'
32 .
'<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> Neues Binding</a>'
33 .
'<a class="btn btn-primary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=edit&rid=new"><i class="bi bi-plus-circle"></i> Neuer Workflow</a>';
35 return $this->tpl()->get_tpl(
'dbxWorkflow_admin|workflow-admin-actions', array_merge(
36 $help->moduleBarTemplateData($topic, $barActions),
38 'content' => $content,
43 private function install() {
44 $oDD =
dbx()->get_system_obj(
'dbxDD');
45 foreach (array(
'workflowDefinition',
'workflowInstance',
'workflowStep',
'workflowModuleBind') as $dd) {
46 $oDD->sync_dd_to_db(
'dbxWorkflow', $dd,
'reset');
47 for ($i = 0; $i < 80; $i++) {
48 $state = $oDD->sync_dd_to_db(
'dbxWorkflow', $dd,
'apply');
49 $status = (string)($state[
'status'] ??
'');
50 if ($status ===
'finished' || $status ===
'error')
break;
54 $engine =
dbx()->get_include_obj(
'dbxWorkflowEngine',
'dbxWorkflow');
55 $engine->seed_demo_definitions();
57 return $this->frame($this->alert(
'success',
'Workflow-Datenbank installiert. Definitionen, Modul-Bindings und Demo-Workflows wurden angelegt/aktualisiert.'));
60 private function default_definition_text() {
61 return dbx()->get_include_obj(
'dbxWorkflowEngine',
'dbxWorkflow')->default_definition_text();
64 private function load_row($rid) {
65 if ($rid ===
'new' || (
int)$rid <= 0) {
67 'workflow_key' =>
'invoice_demo',
68 'title' =>
'Rechnung erstellen',
69 'result_label' =>
'Rechnung',
70 'description' =>
'Deklarativer Workflow: Ziel definieren, benoetigte Bausteine beschreiben, dbxapp fuehrt durch die Schritte.',
71 'definition_json' => $this->default_definition_text(),
76 $rows = $this->db()->select($this->ddDefinition, array(
'id' => (
int)$rid),
'*',
'id',
'DESC',
'', 1, 0, 0);
77 return (is_array($rows) && isset($rows[0])) ? $rows[0] : array();
80 private function normalize_key($value) {
81 $value = strtolower(trim((
string)$value));
82 $value = preg_replace(
'/[^a-z0-9_]+/',
'_', $value);
83 return trim($value,
'_');
86 private function workflow_action_options($selected) {
88 'form' =>
'Bearbeiten / bestaetigen',
89 'select' =>
'Auswaehlen',
90 'create' =>
'Neu anlegen',
91 'module' =>
'Modulformular oeffnen',
94 foreach ($items as $value => $label) {
95 $html .=
'<option value="' . $this->h($value) .
'"' . ((string)$selected === $value ?
' selected' :
'') .
'>' . $this->h($label) .
'</option>';
100 private function workflow_mode_options($selected) {
102 'single' =>
'Ein Wert',
103 'multiple' =>
'Mehrere Werte',
106 foreach ($items as $value => $label) {
107 $html .=
'<option value="' . $this->h($value) .
'"' . ((string)$selected === $value ?
' selected' :
'') .
'>' . $this->h($label) .
'</option>';
112 private function workflow_depends_options(array $needs, $selected) {
113 $html =
'<option value="">Immer anzeigen</option>';
114 foreach ($needs as $need) {
115 $key = (string)($need[
'key'] ??
'');
116 if ($key ===
'')
continue;
117 $html .=
'<option value="' . $this->h($key) .
'"' . ((string)$selected === $key ?
' selected' :
'') .
'>' . $this->h($need[
'label'] ?? $key) .
'</option>';
122 private function workflow_options_text($options) {
124 foreach ((array)$options as $option) {
125 if (is_array($option)) {
126 $value = trim((
string)($option[
'value'] ??
''));
127 $label = trim((
string)($option[
'label'] ?? $value));
128 if ($value !==
'' || $label !==
'') {
129 $lines[] = $value !==
'' && $value !== $label ? $value .
'=' . $label : $label;
132 $text = trim((
string)$option);
133 if ($text !==
'') $lines[] = $text;
136 return implode(
"\n", $lines);
139 private function parse_workflow_options($text) {
141 foreach (preg_split(
'/\r\n|\r|\n|,/', (
string)$text) as $line) {
143 if ($line ===
'')
continue;
144 if (strpos($line,
'=') !==
false) {
145 list($value, $label) = explode(
'=', $line, 2);
146 $value = trim($value);
147 $label = trim($label);
148 if ($value !==
'' || $label !==
'') {
149 $options[] = array(
'value' => $value !==
'' ? $value : $label,
'label' => $label !==
'' ? $label : $value);
158 private function workflow_builder_html(array $data) {
159 $engine =
dbx()->get_include_obj(
'dbxWorkflowEngine',
'dbxWorkflow');
160 $definition = $engine->normalize_definition($data[
'definition_json'] ?? $this->default_definition_text(), $data[
'workflow_key'] ??
'');
161 $needs = array_values((array)($definition[
'needs'] ?? array()));
162 $rowCount = max(8, count($needs) + 3);
165 for ($i = 0; $i < $rowCount; $i++) {
166 $need = $needs[$i] ?? array();
167 $isEmpty = empty($need);
168 $key = (string)($need[
'key'] ??
'');
169 $label = (string)($need[
'label'] ??
'');
170 $actions = (array)($need[
'actions'] ?? array(
'form'));
171 $action = (string)($need[
'preferred'] ?? ($actions[0] ??
'form'));
172 if (!in_array($action, array(
'form',
'select',
'create',
'module'),
true)) $action =
'form';
173 $mode = (string)($need[
'mode'] ??
'single');
174 $required = array_key_exists(
'required', $need) ? (int)((
bool)$need[
'required']) : 1;
175 $hint = (string)($need[
'hint'] ??
'');
176 $event = (string)($need[
'event'] ?? ($need[
'result_event'] ??
''));
177 $dependsOn = (string)($need[
'depends_on'] ??
'');
178 $dependsValue = (string)($need[
'depends_value'] ??
'');
179 $optionsText = $this->workflow_options_text($need[
'options'] ?? array());
181 if (!empty($need[
'source']) && is_array($need[
'source'])) {
182 $sourceJson = json_encode($need[
'source'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
185 if (!empty($need[
'bind']) && is_array($need[
'bind'])) {
186 $bindJson = json_encode($need[
'bind'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
188 $moduleLinksJson =
'';
189 if (!empty($need[
'module_links']) && is_array($need[
'module_links'])) {
190 $moduleLinksJson = json_encode($need[
'module_links'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
193 $rowClass = $isEmpty ?
' dbx-workflow-step-row-empty' :
'';
194 $rowStyle = $isEmpty ?
' style="display:none;"' :
'';
195 $rows .=
'<div class="dbx-workflow-step-row' . $rowClass .
'" data-workflow-step-row' . $rowStyle .
'>'
196 .
'<input type="hidden" name="workflow_step_present[' . $i .
']" value="1">'
197 .
'<input type="hidden" name="workflow_step_key[' . $i .
']" value="' . $this->h($key) .
'">'
198 .
'<input type="hidden" name="workflow_step_source[' . $i .
']" value="' . $this->h($sourceJson) .
'">'
199 .
'<input type="hidden" name="workflow_step_bind[' . $i .
']" value="' . $this->h($bindJson) .
'">'
200 .
'<input type="hidden" name="workflow_step_module_links[' . $i .
']" value="' . $this->h($moduleLinksJson) .
'">'
201 .
'<div class="dbx-workflow-step-num">' . ($i + 1) .
'</div>'
202 .
'<div class="dbx-workflow-step-main">'
203 .
'<div class="row g-2 align-items-end">'
204 .
'<div class="col-xl-4 col-md-6"><label class="form-label">Notwendig</label><input class="form-control form-control-sm" name="workflow_step_label[' . $i .
']" value="' . $this->h($label) .
'" placeholder="Artikel auswaehlen"></div>'
205 .
'<div class="col-xl-2 col-md-4"><label class="form-label">Erledigung</label><select class="form-select form-select-sm" name="workflow_step_action[' . $i .
']">' . $this->workflow_action_options($action) .
'</select></div>'
206 .
'<div class="col-xl-2 col-md-4"><label class="form-label">Werte</label><select class="form-select form-select-sm" name="workflow_step_mode[' . $i .
']">' . $this->workflow_mode_options($mode) .
'</select></div>'
207 .
'<div class="col-xl-4 col-md-8"><label class="form-label">Status danach</label><input class="form-control form-control-sm" name="workflow_step_event[' . $i .
']" value="' . $this->h($event) .
'" placeholder="z.B. eBay-Daten bestaetigt"></div>'
208 .
'<div class="col-xl-2 col-md-3"><label class="form-label">Bedeutung</label><select class="form-select form-select-sm" name="workflow_step_required[' . $i .
']"><option value="1"' . ($required ?
' selected' :
'') .
'>Notwendig</option><option value="0"' . (!$required ?
' selected' :
'') .
'>Optional</option></select></div>'
209 .
'<div class="col-xl-3 col-md-4"><label class="form-label">Nur wenn erledigt</label><select class="form-select form-select-sm" name="workflow_step_depends_on[' . $i .
']">' . $this->workflow_depends_options($needs, $dependsOn) .
'</select></div>'
210 .
'<div class="col-xl-3 col-md-5"><label class="form-label">Nur bei Ergebnis</label><input class="form-control form-control-sm" name="workflow_step_depends_value[' . $i .
']" value="' . $this->h($dependsValue) .
'" placeholder="leer = sobald vorhanden"></div>'
211 .
'<div class="col-xl-4 col-md-12"><label class="form-label">Auswahlwerte</label><textarea class="form-control form-control-sm" name="workflow_step_options[' . $i .
']" rows="2" placeholder="wert=Anzeige, je Zeile ein Wert">' . $this->h($optionsText) .
'</textarea></div>'
212 .
'<div class="col-12"><label class="form-label">Hinweis fuer Bearbeiter</label><textarea class="form-control form-control-sm" name="workflow_step_hint[' . $i .
']" rows="2" placeholder="Was muss erledigt, bestaetigt oder geprueft werden?">' . $this->h($hint) .
'</textarea></div>'
215 .
'<label class="dbx-workflow-step-active"><input type="checkbox" name="workflow_step_active[' . $i .
']" value="1"' . (!$isEmpty ?
' checked' :
'') .
'> aktiv</label>'
219 $finishLabel = (string)($definition[
'finish'][
'label'] ?? (($data[
'result_label'] ??
'Workflow') .
' abschliessen'));
220 $bindRef = (string)($definition[
'bind_ref'] ??
'');
221 $technicalPreview = json_encode($definition, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
223 return '<section class="dbx-workflow-builder">'
224 .
'<div class="dbx-workflow-builder-head">'
225 .
'<div><h3>Notwendig fuer das Ziel</h3><p>Lege fest, was erledigt sein muss, damit das Ziel erreicht ist.</p></div>'
226 .
'<button class="btn btn-outline-primary btn-sm" type="button" data-workflow-add-step><i class="bi bi-plus-circle"></i> Aufgabe hinzufuegen</button>'
228 .
'<div class="row g-3 mb-3">'
229 .
'<div class="col-12"><label class="form-label">Wenn alles erledigt ist</label><input class="form-control" name="workflow_finish_label" value="' . $this->h($finishLabel) .
'" placeholder="Ziel abschliessen"></div>'
231 .
'<div class="dbx-workflow-step-list">' . $rows .
'</div>'
232 .
'<details class="dbx-workflow-technical mt-3"><summary>Erweiterte Angaben und technische Vorschau</summary>'
233 .
'<label class="form-label mt-2">Modul-Anbindung</label><input class="form-control mb-2" name="workflow_bind_ref" value="' . $this->h($bindRef) .
'" placeholder="optional, z.B. dbxContact|contact_reply">'
234 .
'<textarea class="form-control font-monospace" rows="10" readonly>' . $this->h($technicalPreview) .
'</textarea></details>'
236document.addEventListener("click", function(ev) {
237 var btn = ev.target.closest("[data-workflow-add-step]");
239 var root = btn.closest(".dbx-workflow-builder");
240 var row = root ? root.querySelector(".dbx-workflow-step-row-empty") : null;
242 row.classList.remove("dbx-workflow-step-row-empty");
243 row.style.display = "";
244 var cb = row.querySelector(".dbx-workflow-step-active input");
245 if (cb) cb.checked = true;
246 var input = row.querySelector("input[name*=\'workflow_step_label\']");
247 if (input) input.focus();
253 private function workflow_definition_from_post($oForm) {
255 $present = $_POST[
'workflow_step_present'] ?? array();
256 foreach ((array)$present as $idx => $unused) {
257 $active = isset($_POST[
'workflow_step_active'][$idx]);
258 $label = trim((
string)($_POST[
'workflow_step_label'][$idx] ??
''));
259 $key = $this->normalize_key($_POST[
'workflow_step_key'][$idx] ?? $label);
260 if (!$active && $label ===
'' && $key ===
'')
continue;
261 if (!$active)
continue;
262 if ($key ===
'') $key =
'schritt_' . ((int)$idx + 1);
263 if ($label ===
'') $label = $key;
265 $action = (string)($_POST[
'workflow_step_action'][$idx] ??
'form');
266 if (!in_array($action, array(
'form',
'select',
'create',
'module'),
true)) $action =
'form';
267 $mode = (string)($_POST[
'workflow_step_mode'][$idx] ??
'single') ===
'multiple' ?
'multiple' :
'single';
273 'required' => ((
string)($_POST[
'workflow_step_required'][$idx] ??
'1') ===
'1'),
274 'actions' => array($action),
275 'preferred' => $action,
276 'hint' => trim((
string)($_POST[
'workflow_step_hint'][$idx] ??
'')),
279 $event = trim((
string)($_POST[
'workflow_step_event'][$idx] ??
''));
280 if ($event !==
'') $need[
'event'] = $event;
282 $dependsOn = $this->normalize_key($_POST[
'workflow_step_depends_on'][$idx] ??
'');
283 if ($dependsOn !==
'') {
284 $need[
'depends_on'] = $dependsOn;
285 $dependsValue = trim((
string)($_POST[
'workflow_step_depends_value'][$idx] ??
''));
286 if ($dependsValue !==
'') $need[
'depends_value'] = $dependsValue;
289 $options = $this->parse_workflow_options($_POST[
'workflow_step_options'][$idx] ??
'');
290 if ($options) $need[
'options'] = $options;
292 $sourceJson = trim((
string)($_POST[
'workflow_step_source'][$idx] ??
''));
293 $source = $sourceJson !==
'' ? json_decode($sourceJson,
true) :
null;
294 if (is_array($source)) $need[
'source'] = $source;
296 $bindJson = trim((
string)($_POST[
'workflow_step_bind'][$idx] ??
''));
297 $bind = $bindJson !==
'' ? json_decode($bindJson,
true) :
null;
298 if (is_array($bind)) $need[
'bind'] = $bind;
300 $moduleLinksJson = trim((
string)($_POST[
'workflow_step_module_links'][$idx] ??
''));
301 $moduleLinks = $moduleLinksJson !==
'' ? json_decode($moduleLinksJson,
true) :
null;
302 if (is_array($moduleLinks)) $need[
'module_links'] = $moduleLinks;
308 'workflow_key' => $oForm->get_post(
'workflow_key',
'',
'parameter|min=2|max=80'),
309 'title' => $oForm->get_post(
'title',
'',
'*|min=2|max=160'),
310 'result' => $oForm->get_post(
'result_label',
'',
'*|min=2|max=160'),
311 'description' => $oForm->get_post_data(
'description',
'',
'*'),
314 'label' => trim((
string)($_POST[
'workflow_finish_label'] ??
'')) ?:
'Workflow abschliessen',
318 $bindRef = trim((
string)($_POST[
'workflow_bind_ref'] ??
''));
319 if ($bindRef !==
'') $definition[
'bind_ref'] = $bindRef;
324 private function edit() {
325 $rid =
dbx()->get_modul_var(
'rid',
'new',
'parameter');
326 $isNew = ($rid ===
'new' || (int)$rid <= 0);
327 $data = $this->load_row($rid);
330 return $this->frame($this->alert(
'warning',
'Workflow Definition nicht gefunden.'));
333 $oForm =
dbx()->get_system_obj(
'dbxForm');
334 $oForm->init(
'workflow-definition',
'workflow-definition-form');
335 $oForm->add_rep(
'bar_title', $isNew ?
'Neues Ziel' :
'Ziel bearbeiten');
336 $oForm->add_obj(
'bar_actions',
'obj-value',
337 '<a class="btn btn-outline-secondary btn-sm" href="?dbx_modul=dbxWorkflow_admin"><i class="bi bi-list-ul"></i> Liste</a>'
338 .
'<button class="btn btn-primary btn-sm" type="submit"><i class="bi bi-save"></i> Speichern</button>'
340 $oForm->_dd = $this->ddDefinition;
341 $oForm->_fd =
'dbxWorkflow|workflow-definition';
342 $oForm->_data = $data;
343 $oForm->_action =
'?dbx_modul=dbxWorkflow_admin&dbx_run1=edit&rid=' . ($isNew ?
'new' : (int)$rid);
344 $oForm->set_activ_id($isNew ? 0 : (
int)$rid);
345 $oForm->_msg_info = $isNew
346 ?
'Neues Ziel anlegen und definieren, was dafuer notwendig ist.'
347 :
'Ziel bearbeiten und festlegen, was dafuer notwendig ist.';
349 $oForm->add_fld(
'workflow_key');
350 $oForm->add_fld(
'title');
351 $oForm->add_fld(
'result_label');
352 $oForm->add_fld(
'description');
353 $oForm->add_fld(
'active');
354 $oForm->add_obj(
'workflow_builder',
'obj-value', $this->workflow_builder_html($data));
356 if ($oForm->submit()) {
357 if (!$oForm->errors()) {
358 $engine =
dbx()->get_include_obj(
'dbxWorkflowEngine',
'dbxWorkflow');
359 $definition = $engine->normalize_definition($this->workflow_definition_from_post($oForm), $oForm->get_post(
'workflow_key',
'',
'parameter|max=80'));
360 if (count((array)($definition[
'needs'] ?? array())) === 0) {
361 $oForm->add_fld_error(
'title',
'Mindestens eine notwendige Aufgabe ist erforderlich.');
362 $oForm->_msg_error =
'Mindestens eine notwendige Aufgabe ist erforderlich.';
363 $oForm->add_obj(
'workflow_builder',
'obj-value', $this->workflow_builder_html(array_merge($data, array(
364 'workflow_key' => $oForm->get_post(
'workflow_key',
'',
'parameter|max=80'),
365 'title' => $oForm->get_post(
'title',
'',
'*'),
366 'result_label' => $oForm->get_post(
'result_label',
'',
'*'),
367 'description' => $oForm->get_post_data(
'description',
'',
'*'),
368 'definition_json' => json_encode($definition, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
370 return $oForm->run();
372 $definitionText = json_encode($definition, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
375 'workflow_key' => $oForm->get_post(
'workflow_key',
'',
'parameter|min=2|max=80'),
376 'title' => $oForm->get_post(
'title',
'',
'*|min=2|max=160'),
377 'result_label' => $oForm->get_post(
'result_label',
'',
'*|min=2|max=160'),
378 'description' => $oForm->get_post_data(
'description',
'',
'*'),
379 'definition_json' => $definitionText,
380 'active' => $oForm->get_post(
'active', 0,
'int')
384 $key =
$db->escape($values[
'workflow_key'],
$db->get_dd_server($this->ddDefinition));
385 $where =
"workflow_key='" . $key .
"'";
386 if (!$isNew) $where .=
' AND id <> ' . (int)$rid;
388 if (
$db->count($this->ddDefinition, $where) > 0) {
389 $oForm->add_fld_error(
'workflow_key',
'Workflow Key existiert bereits.');
390 $oForm->_msg_error =
'Workflow Key existiert bereits.';
392 $ok =
$db->save($this->ddDefinition, $values, $isNew ? 0 : (
int)$rid, 0, 1, 1, 1);
393 $oForm->_msg_success = $ok ?
'Workflow Definition gespeichert.' :
'Workflow Definition konnte nicht gespeichert werden.';
395 $oForm->_data = array_merge($oForm->_data, $values);
396 $oForm->add_obj(
'workflow_builder',
'obj-value', $this->workflow_builder_html($oForm->_data));
400 $oForm->_msg_error =
'Bitte Eingaben pruefen.';
404 return $oForm->run();
407 private function decorate_rows($rows) {
409 foreach ((array)$rows as $row) {
410 $row[
'action'] = $this->tpl()->get_tpl(
'dbxWorkflow_admin|workflow-admin-row-action', array(
411 'id' => (
int)($row[
'id'] ?? 0),
412 'workflow_key' => rawurlencode((
string)($row[
'workflow_key'] ??
''))
419 private function report_search_where(
string $rwhere): array|
string {
420 $rwhere = trim($rwhere);
421 if ($rwhere ===
'') {
428 'like' => array(
'workflow_key',
'title',
'result_label',
'description',
'status',
'message',
'current_need'),
429 'mode' =>
'contains',
434 private function list_definitions() {
436 dbx()->get_include_obj(
'dbxWorkflowEngine',
'dbxWorkflow')->ensure_shop_definitions();
438 $oReport =
dbx()->get_system_obj(
'dbxReport');
439 $oReport->init(
'workflow-definitions');
440 $oReport->_dd = $this->ddDefinition;
441 $oReport->_action =
'?dbx_modul=dbxWorkflow_admin';
442 $oReport->_pages =
true;
443 $oReport->_create_row_select =
false;
444 $oReport->_create_row_edit =
false;
445 $oReport->_create_row_delete =
false;
446 $oReport->_but_pagination = 5;
447 $oReport->_msg_info =
'Workflow Definitionen verwalten, filtern und bearbeiten.';
448 $oReport->create_selection_fields(
'dbxWorkflow_admin|rpt-workflow-definitions-selection');
450 if ($oReport->submit()) {
451 if (!$oReport->errors()) {
452 $oReport->_msg_info =
'Filter angewendet.';
454 $oReport->_msg_info =
'Bitte Eingaben pruefen.';
458 $rwhere = $oReport->get_fld_val(
'dbx_rwhere',
'',
'sqlsearch|max=64');
459 $rrows = $oReport->get_fld_val(
'dbx_rrows', 30,
'int');
460 $rpos = $oReport->get_fld_val(
'dbx_rpos', 0,
'int');
461 $rsort = $oReport->get_fld_val(
'dbx_rsort',
'title',
'parameter');
462 $rdesc = $oReport->get_fld_val(
'dbx_rdesc',
'ASC',
'parameter');
464 $where = $this->report_search_where($rwhere);
465 $cols = array(
'id',
'workflow_key',
'title',
'result_label',
'active',
'update_date');
467 $oReport->_rflds = array(
469 'workflow_key' =>
'Key',
471 'result_label' =>
'Ziel',
473 'update_date' =>
'Update',
476 $oReport->_rpt_format = array(
477 'update_date' =>
'php-datetime-usr',
480 $oReport->_rrows = $rrows;
481 $oReport->_rpos = $rpos;
482 $oReport->_count_all =
$db->count($this->ddDefinition,
'');
483 $oReport->_rcount =
$db->count($this->ddDefinition, $where);
484 $oReport->_rdata = $this->decorate_rows(
$db->select($this->ddDefinition, $where, $cols, $rsort, $rdesc,
'', $rrows, $rpos));
486 return $this->frame($oReport->run());
489 private function list_instances() {
491 $oReport =
dbx()->get_system_obj(
'dbxReport');
492 $oReport->init(
'workflow-instances');
493 $oReport->_dd = $this->ddInstance;
494 $oReport->_action =
'?dbx_modul=dbxWorkflow_admin&dbx_run1=instances';
495 $oReport->_pages =
true;
496 $oReport->_create_row_select =
false;
497 $oReport->_create_row_edit =
false;
498 $oReport->_create_row_delete =
false;
499 $oReport->_but_pagination = 5;
500 $oReport->_msg_info =
'Laufende Workflow-Instanzen anzeigen und filtern.';
501 $oReport->create_selection_fields(
'dbxWorkflow_admin|rpt-workflow-instances-selection');
503 if ($oReport->submit()) {
504 if (!$oReport->errors()) {
505 $oReport->_msg_info =
'Filter angewendet.';
507 $oReport->_msg_info =
'Bitte Eingaben pruefen.';
511 $rwhere = $oReport->get_fld_val(
'dbx_rwhere',
'',
'sqlsearch|max=64');
512 $rrows = $oReport->get_fld_val(
'dbx_rrows', 50,
'int');
513 $rpos = $oReport->get_fld_val(
'dbx_rpos', 0,
'int');
514 $rsort = $oReport->get_fld_val(
'dbx_rsort',
'create_date',
'parameter');
515 $rdesc = $oReport->get_fld_val(
'dbx_rdesc',
'DESC',
'parameter');
517 $where = $this->report_search_where($rwhere);
518 $cols = array(
'id',
'create_date',
'workflow_key',
'result_label',
'status',
'current_need',
'percent',
'message');
520 $oReport->_rflds = array(
522 'create_date' =>
'Start',
523 'workflow_key' =>
'Workflow',
524 'result_label' =>
'Ziel',
525 'status' =>
'Status',
526 'current_need' =>
'Aufgabe',
528 'message' =>
'Meldung',
529 'instance_action' =>
'Aktion'
531 $oReport->_rpt_format = array(
532 'create_date' =>
'php-datetime-usr',
536 'instance_action' =>
'html'
538 $oReport->_rrows = $rrows;
539 $oReport->_rpos = $rpos;
540 $oReport->_count_all =
$db->count($this->ddInstance,
'');
541 $oReport->_rcount =
$db->count($this->ddInstance, $where);
542 $oReport->_rdata = $this->decorate_instance_rows(
$db->select($this->ddInstance, $where, $cols, $rsort, $rdesc,
'', $rrows, $rpos));
544 return $this->frame($oReport->run());
547 private function decorate_instance_rows($rows) {
549 foreach ((array)$rows as $row) {
550 $row[
'instance_action'] = $this->instance_action_button($row);
551 $row[
'status'] = $this->instance_status_badge((
string)($row[
'status'] ??
''));
552 $row[
'percent'] = $this->instance_percent_bar((
int)($row[
'percent'] ?? 0));
553 $row[
'message'] = $this->instance_message((
string)($row[
'message'] ??
''));
559 private function instance_status_badge(
string $status):
string {
560 $status = strtolower(trim($status));
562 'running' => array(
'Laufend',
'primary'),
563 'paused' => array(
'Pausiert',
'warning'),
564 'finished' => array(
'Fertig',
'success'),
565 'canceled' => array(
'Abgebrochen',
'secondary'),
566 'error' => array(
'Fehler',
'danger'),
568 $data = $labels[$status] ?? array(($status !==
'' ? $status :
'Unbekannt'),
'secondary');
569 return '<span class="badge text-bg-' . $this->h($data[1]) .
'">' . $this->h($data[0]) .
'</span>';
572 private function instance_percent_bar(
int $percent):
string {
573 $percent = max(0, min(100, $percent));
574 return '<div class="dbx-workflow-instance-progress" title="' . $percent .
'%">'
575 .
'<div class="dbx-workflow-instance-progress-bar" style="width:' . $percent .
'%"></div>'
576 .
'<span>' . $percent .
'%</span>'
580 private function instance_message(
string $message):
string {
581 $message = trim($message);
582 if ($message ===
'') {
583 return '<span class="text-muted">-</span>';
585 return '<span class="dbx-workflow-instance-message">' . $this->h($message) .
'</span>';
588 private function instance_action_button(array $row):
string {
589 $id = (int)($row[
'id'] ?? 0);
594 $status = strtolower(trim((
string)($row[
'status'] ??
'')));
595 $isFinished = in_array($status, array(
'finished',
'canceled'),
true);
596 $url =
'?dbx_modul=dbxWorkflow&dbx_run1=run&iid=' . $id .
'&dbx_ajax=1';
597 if ($status ===
'paused') {
598 $url .=
'&proc_cmd=resume';
601 $label = $isFinished ?
'Ansehen' :
'Fortsetzen';
602 $icon = $isFinished ?
'bi-search' :
'bi-play-fill';
603 $class = $isFinished ?
'btn btn-outline-primary btn-sm' :
'btn btn-primary btn-sm';
604 $title = $label .
': Workflow #' . $id;
606 return '<a class="' . $class .
' openWin dbx-win dbx-workflow-instance-action" href="' . $this->h($url) .
'"'
607 .
' data-url="' . $this->h($url) .
'" data-title="' . $this->h($title) .
'" data-width="82%" data-height="88%"'
608 .
' title="' . $this->h($title) .
'"><i class="bi ' . $icon .
'"></i> ' . $this->h($label) .
'</a>';
611 private function bindRegistry() {
612 return dbx()->get_include_obj(
'dbxWorkflowBindRegistry',
'dbxWorkflow');
615 private function load_bind_row($rid) {
616 if ($rid ===
'new' || (
int)$rid <= 0) {
618 'modul' =>
'dbxContact',
619 'bind_key' =>
'contact_reply',
620 'title' =>
'Neues Modul-Binding',
621 'description' =>
'Einheitliches Binding: dbxWorkflow nutzt DD/FD/TPL/Config des Moduls.',
627 $rows = $this->db()->select($this->ddBind, array(
'id' => (
int)$rid),
'*',
'id',
'DESC',
'', 1, 0, 0);
628 return (is_array($rows) && isset($rows[0])) ? $rows[0] : array();
631 private function edit_bind() {
632 $rid =
dbx()->get_modul_var(
'rid',
'new',
'parameter');
633 $isNew = ($rid ===
'new' || (int)$rid <= 0);
634 $data = $this->load_bind_row($rid);
637 return $this->frame($this->alert(
'warning',
'Modul-Binding nicht gefunden.'));
640 $generateModul = trim((
string)
dbx()->get_modul_var(
'gen_modul',
'',
'parameter'));
641 $generateDd = trim((
string)
dbx()->get_modul_var(
'gen_dd',
'',
'parameter'));
642 if ($generateModul !==
'' && $generateDd !==
'') {
643 $generated = $this->bindRegistry()->generateBindSkeleton($generateModul, $generateDd);
645 $data = array_merge($data, $generated);
649 $oForm =
dbx()->get_system_obj(
'dbxForm');
650 $oForm->init(
'workflow-module-bind',
'workflow-module-bind-form');
651 $oForm->add_rep(
'bar_title', $isNew ?
'Neues Modul-Binding' :
'Modul-Binding bearbeiten');
652 $oForm->add_obj(
'bar_actions',
'obj-value',
653 '<a class="btn btn-outline-secondary btn-sm" href="?dbx_modul=dbxWorkflow_admin&dbx_run1=binds"><i class="bi bi-list-ul"></i> Liste</a>'
654 .
'<button class="btn btn-primary btn-sm" type="submit"><i class="bi bi-save"></i> Speichern</button>'
656 $oForm->_dd = $this->ddBind;
657 $oForm->_fd =
'dbxWorkflow|workflow-module-bind';
658 $oForm->_data = $data;
659 $oForm->_action =
'?dbx_modul=dbxWorkflow_admin&dbx_run1=edit_bind&rid=' . ($isNew ?
'new' : (int)$rid);
660 $oForm->set_activ_id($isNew ? 0 : (
int)$rid);
661 $oForm->_msg_info = $isNew
662 ?
'Neues Binding anlegen oder per Generator aus Modul-DD erzeugen.'
663 :
'Modul-Binding bearbeiten. Referenz in Workflows: bind_ref = modul|bind_key';
665 $oForm->add_fld(
'modul');
666 $oForm->add_fld(
'bind_key');
667 $oForm->add_fld(
'title');
668 $oForm->add_fld(
'description');
669 $oForm->add_fld(
'bind_json');
670 $oForm->add_fld(
'active');
672 if ($oForm->submit()) {
673 if (!$oForm->errors()) {
674 $bindJson = trim((
string)$oForm->get_post_data(
'bind_json',
'',
'*'));
675 $decoded = json_decode($bindJson,
true);
676 if (!is_array($decoded)) {
677 $oForm->add_fld_error(
'bind_json',
'Binding JSON ist ungueltig.');
678 $oForm->_msg_error =
'Binding JSON ist ungueltig.';
681 'modul' => $oForm->get_post(
'modul',
'',
'parameter|min=2|max=80'),
682 'bind_key' => $oForm->get_post(
'bind_key',
'',
'parameter|min=2|max=80'),
683 'title' => $oForm->get_post(
'title',
'',
'*|min=2|max=160'),
684 'description' => $oForm->get_post_data(
'description',
'',
'*'),
685 'bind_json' => json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
686 'active' => $oForm->get_post(
'active', 0,
'int'),
690 $where =
"modul='" .
$db->escape($values[
'modul'],
$db->get_dd_server($this->ddBind))
691 .
"' AND bind_key='" .
$db->escape($values[
'bind_key'],
$db->get_dd_server($this->ddBind)) .
"'";
693 $where .=
' AND id <> ' . (int)$rid;
696 if (
$db->count($this->ddBind, $where) > 0) {
697 $oForm->add_fld_error(
'bind_key',
'Modul + Bind Key existiert bereits.');
698 $oForm->_msg_error =
'Modul + Bind Key existiert bereits.';
700 $ok =
$db->save($this->ddBind, $values, $isNew ? 0 : (
int)$rid, 0, 1, 1, 1);
701 $oForm->_msg_success = $ok ?
'Modul-Binding gespeichert.' :
'Modul-Binding konnte nicht gespeichert werden.';
703 $oForm->_data = array_merge($oForm->_data, $values);
708 $oForm->_msg_error =
'Bitte Eingaben pruefen.';
713 foreach ($this->bindRegistry()->listModuleDds((
string)($data[
'modul'] ??
'')) as $ddRef) {
714 $ddOptions .=
'<option value="' . $this->h($ddRef) .
'">' . $this->h($ddRef) .
'</option>';
717 $oForm->add_obj(
'bind_generator',
'dbxWorkflow_admin|workflow-bind-generator', array(
718 'modul' => $this->h($data[
'modul'] ??
''),
719 'dd_options' => $ddOptions,
722 return $this->frame($oForm->run());
725 private function decorate_bind_rows($rows) {
727 foreach ((array)$rows as $row) {
728 $row[
'bind_ref'] = (string)($row[
'modul'] ??
'') .
'|' . (string)($row[
'bind_key'] ??
'');
729 $row[
'action'] = $this->tpl()->get_tpl(
'dbxWorkflow_admin|workflow-bind-row-action', array(
730 'id' => (
int)($row[
'id'] ?? 0),
731 'bind_ref' => $this->h($row[
'bind_ref']),
738 private function list_binds() {
740 $oReport =
dbx()->get_system_obj(
'dbxReport');
741 $oReport->init(
'workflow-module-binds',
'dbxWorkflow_admin|workflow-module-binds');
742 $oReport->_dd = $this->ddBind;
743 $oReport->_action =
'?dbx_modul=dbxWorkflow_admin&dbx_run1=binds';
744 $oReport->_pages =
true;
745 $oReport->_create_row_select =
false;
746 $oReport->_create_row_edit =
false;
747 $oReport->_create_row_delete =
false;
748 $oReport->_but_pagination = 5;
749 $oReport->_msg_info =
'Modul-Bindings verwalten. Workflows referenzieren bind_ref (modul|bind_key). Module kennen keinen Workflow.';
751 $cols = array(
'id',
'modul',
'bind_key',
'title',
'active',
'update_date');
752 $oReport->_rflds = array(
755 'bind_key' =>
'Bind Key',
758 'update_date' =>
'Update',
759 'bind_ref' =>
'Referenz',
760 'action' =>
'Aktion',
762 $oReport->_rpt_format = array(
763 'update_date' =>
'php-datetime-usr',
766 $oReport->_rrows = 30;
768 $oReport->_count_all =
$db->count($this->ddBind,
'');
769 $oReport->_rcount = $oReport->_count_all;
770 $oReport->_rdata = $this->decorate_bind_rows(
$db->select($this->ddBind,
'', $cols,
'modul',
'ASC',
'', 30, 0));
772 return $this->frame($oReport->run());
776 $run =
dbx()->get_modul_var(
'dbx_run1',
'list',
'parameter');
778 if ($run ===
'install')
return $this->install();
779 if ($run ===
'edit')
return $this->edit();
780 if ($run ===
'edit_bind')
return $this->edit_bind();
781 if ($run ===
'binds')
return $this->list_binds();
782 if ($run ===
'instances')
return $this->list_instances();
784 return $this->list_definitions();