dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxWorkflowAdmin.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxWorkflow_admin;
3
5
6 private $ddDefinition = 'dbxWorkflow|workflowDefinition';
7 private $ddInstance = 'dbxWorkflow|workflowInstance';
8 private $ddStep = 'dbxWorkflow|workflowStep';
9 private $ddBind = 'dbxWorkflow|workflowModuleBind';
10
11 private function tpl() {
12 return dbx()->get_system_obj('dbxTPL');
13 }
14
15 private function db() {
16 return dbx()->get_system_obj('dbxDB');
17 }
18
19 private function h($value) {
20 return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
21 }
22
23 private function alert($type, $msg) {
24 return $this->tpl()->get_tpl('dbx|alert-' . $type, array('msg' => $msg));
25 }
26
27 private function frame($content) {
28 $help = dbx()->get_include_obj('dbxAdminHelp', 'dbxAdmin');
29 $topic = $help->resolveTopic('dbxWorkflow_admin');
30 $barActions = ''
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>';
34
35 return $this->tpl()->get_tpl('dbxWorkflow_admin|workflow-admin-actions', array_merge(
36 $help->moduleBarTemplateData($topic, $barActions),
37 array(
38 'content' => $content,
39 )
40 ));
41 }
42
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;
51 }
52 }
53
54 $engine = dbx()->get_include_obj('dbxWorkflowEngine', 'dbxWorkflow');
55 $engine->seed_demo_definitions();
56
57 return $this->frame($this->alert('success', 'Workflow-Datenbank installiert. Definitionen, Modul-Bindings und Demo-Workflows wurden angelegt/aktualisiert.'));
58 }
59
60 private function default_definition_text() {
61 return dbx()->get_include_obj('dbxWorkflowEngine', 'dbxWorkflow')->default_definition_text();
62 }
63
64 private function load_row($rid) {
65 if ($rid === 'new' || (int)$rid <= 0) {
66 return array(
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(),
72 'active' => 1
73 );
74 }
75
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();
78 }
79
80 private function normalize_key($value) {
81 $value = strtolower(trim((string)$value));
82 $value = preg_replace('/[^a-z0-9_]+/', '_', $value);
83 return trim($value, '_');
84 }
85
86 private function workflow_action_options($selected) {
87 $items = array(
88 'form' => 'Bearbeiten / bestaetigen',
89 'select' => 'Auswaehlen',
90 'create' => 'Neu anlegen',
91 'module' => 'Modulformular oeffnen',
92 );
93 $html = '';
94 foreach ($items as $value => $label) {
95 $html .= '<option value="' . $this->h($value) . '"' . ((string)$selected === $value ? ' selected' : '') . '>' . $this->h($label) . '</option>';
96 }
97 return $html;
98 }
99
100 private function workflow_mode_options($selected) {
101 $items = array(
102 'single' => 'Ein Wert',
103 'multiple' => 'Mehrere Werte',
104 );
105 $html = '';
106 foreach ($items as $value => $label) {
107 $html .= '<option value="' . $this->h($value) . '"' . ((string)$selected === $value ? ' selected' : '') . '>' . $this->h($label) . '</option>';
108 }
109 return $html;
110 }
111
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>';
118 }
119 return $html;
120 }
121
122 private function workflow_options_text($options) {
123 $lines = array();
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;
130 }
131 } else {
132 $text = trim((string)$option);
133 if ($text !== '') $lines[] = $text;
134 }
135 }
136 return implode("\n", $lines);
137 }
138
139 private function parse_workflow_options($text) {
140 $options = array();
141 foreach (preg_split('/\r\n|\r|\n|,/', (string)$text) as $line) {
142 $line = trim($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);
150 }
151 } else {
152 $options[] = $line;
153 }
154 }
155 return $options;
156 }
157
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);
163 $rows = '';
164
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());
180 $sourceJson = '';
181 if (!empty($need['source']) && is_array($need['source'])) {
182 $sourceJson = json_encode($need['source'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
183 }
184 $bindJson = '';
185 if (!empty($need['bind']) && is_array($need['bind'])) {
186 $bindJson = json_encode($need['bind'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
187 }
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);
191 }
192
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>'
213 . '</div>'
214 . '</div>'
215 . '<label class="dbx-workflow-step-active"><input type="checkbox" name="workflow_step_active[' . $i . ']" value="1"' . (!$isEmpty ? ' checked' : '') . '> aktiv</label>'
216 . '</div>';
217 }
218
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);
222
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>'
227 . '</div>'
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>'
230 . '</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>'
235 . '<script>
236document.addEventListener("click", function(ev) {
237 var btn = ev.target.closest("[data-workflow-add-step]");
238 if (!btn) return;
239 var root = btn.closest(".dbx-workflow-builder");
240 var row = root ? root.querySelector(".dbx-workflow-step-row-empty") : null;
241 if (!row) return;
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();
248});
249</script>'
250 . '</section>';
251 }
252
253 private function workflow_definition_from_post($oForm) {
254 $steps = array();
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;
264
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';
268
269 $need = array(
270 'key' => $key,
271 'label' => $label,
272 'mode' => $mode,
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] ?? '')),
277 );
278
279 $event = trim((string)($_POST['workflow_step_event'][$idx] ?? ''));
280 if ($event !== '') $need['event'] = $event;
281
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;
287 }
288
289 $options = $this->parse_workflow_options($_POST['workflow_step_options'][$idx] ?? '');
290 if ($options) $need['options'] = $options;
291
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;
295
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;
299
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;
303
304 $steps[] = $need;
305 }
306
307 $definition = array(
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', '', '*'),
312 'needs' => $steps,
313 'finish' => array(
314 'label' => trim((string)($_POST['workflow_finish_label'] ?? '')) ?: 'Workflow abschliessen',
315 ),
316 );
317
318 $bindRef = trim((string)($_POST['workflow_bind_ref'] ?? ''));
319 if ($bindRef !== '') $definition['bind_ref'] = $bindRef;
320
321 return $definition;
322 }
323
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);
328
329 if (!$data) {
330 return $this->frame($this->alert('warning', 'Workflow Definition nicht gefunden.'));
331 }
332
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>'
339 );
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.';
348
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));
355
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),
369 ))));
370 return $oForm->run();
371 }
372 $definitionText = json_encode($definition, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
373
374 $values = array(
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')
381 );
382
383 $db = $this->db();
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;
387
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.';
391 } else {
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.';
394 if ($ok) {
395 $oForm->_data = array_merge($oForm->_data, $values);
396 $oForm->add_obj('workflow_builder', 'obj-value', $this->workflow_builder_html($oForm->_data));
397 }
398 }
399 } else {
400 $oForm->_msg_error = 'Bitte Eingaben pruefen.';
401 }
402 }
403
404 return $oForm->run();
405 }
406
407 private function decorate_rows($rows) {
408 $out = array();
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'] ?? ''))
413 ));
414 $out[] = $row;
415 }
416 return $out;
417 }
418
419 private function report_search_where(string $rwhere): array|string {
420 $rwhere = trim($rwhere);
421 if ($rwhere === '') {
422 return '';
423 }
424
425 return array(
426 'search' => array(
427 'value' => $rwhere,
428 'like' => array('workflow_key', 'title', 'result_label', 'description', 'status', 'message', 'current_need'),
429 'mode' => 'contains',
430 ),
431 );
432 }
433
434 private function list_definitions() {
435 $db = $this->db();
436 dbx()->get_include_obj('dbxWorkflowEngine', 'dbxWorkflow')->ensure_shop_definitions();
437
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');
449
450 if ($oReport->submit()) {
451 if (!$oReport->errors()) {
452 $oReport->_msg_info = 'Filter angewendet.';
453 } else {
454 $oReport->_msg_info = 'Bitte Eingaben pruefen.';
455 }
456 }
457
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');
463
464 $where = $this->report_search_where($rwhere);
465 $cols = array('id', 'workflow_key', 'title', 'result_label', 'active', 'update_date');
466
467 $oReport->_rflds = array(
468 'id' => 'ID',
469 'workflow_key' => 'Key',
470 'title' => 'Titel',
471 'result_label' => 'Ziel',
472 'active' => 'Aktiv',
473 'update_date' => 'Update',
474 'action' => 'Aktion'
475 );
476 $oReport->_rpt_format = array(
477 'update_date' => 'php-datetime-usr',
478 'action' => 'html',
479 );
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));
485
486 return $this->frame($oReport->run());
487 }
488
489 private function list_instances() {
490 $db = $this->db();
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');
502
503 if ($oReport->submit()) {
504 if (!$oReport->errors()) {
505 $oReport->_msg_info = 'Filter angewendet.';
506 } else {
507 $oReport->_msg_info = 'Bitte Eingaben pruefen.';
508 }
509 }
510
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');
516
517 $where = $this->report_search_where($rwhere);
518 $cols = array('id', 'create_date', 'workflow_key', 'result_label', 'status', 'current_need', 'percent', 'message');
519
520 $oReport->_rflds = array(
521 'id' => 'ID',
522 'create_date' => 'Start',
523 'workflow_key' => 'Workflow',
524 'result_label' => 'Ziel',
525 'status' => 'Status',
526 'current_need' => 'Aufgabe',
527 'percent' => '%',
528 'message' => 'Meldung',
529 'instance_action' => 'Aktion'
530 );
531 $oReport->_rpt_format = array(
532 'create_date' => 'php-datetime-usr',
533 'status' => 'html',
534 'percent' => 'html',
535 'message' => 'html',
536 'instance_action' => 'html'
537 );
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));
543
544 return $this->frame($oReport->run());
545 }
546
547 private function decorate_instance_rows($rows) {
548 $out = array();
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'] ?? ''));
554 $out[] = $row;
555 }
556 return $out;
557 }
558
559 private function instance_status_badge(string $status): string {
560 $status = strtolower(trim($status));
561 $labels = array(
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'),
567 );
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>';
570 }
571
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>'
577 . '</div>';
578 }
579
580 private function instance_message(string $message): string {
581 $message = trim($message);
582 if ($message === '') {
583 return '<span class="text-muted">-</span>';
584 }
585 return '<span class="dbx-workflow-instance-message">' . $this->h($message) . '</span>';
586 }
587
588 private function instance_action_button(array $row): string {
589 $id = (int)($row['id'] ?? 0);
590 if ($id <= 0) {
591 return '';
592 }
593
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';
599 }
600
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;
605
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>';
609 }
610
611 private function bindRegistry() {
612 return dbx()->get_include_obj('dbxWorkflowBindRegistry', 'dbxWorkflow');
613 }
614
615 private function load_bind_row($rid) {
616 if ($rid === 'new' || (int)$rid <= 0) {
617 return array(
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.',
622 'bind_json' => '',
623 'active' => 1,
624 );
625 }
626
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();
629 }
630
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);
635
636 if (!$data) {
637 return $this->frame($this->alert('warning', 'Modul-Binding nicht gefunden.'));
638 }
639
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);
644 if ($generated) {
645 $data = array_merge($data, $generated);
646 }
647 }
648
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>'
655 );
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';
664
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');
671
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.';
679 } else {
680 $values = array(
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'),
687 );
688
689 $db = $this->db();
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)) . "'";
692 if (!$isNew) {
693 $where .= ' AND id <> ' . (int)$rid;
694 }
695
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.';
699 } else {
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.';
702 if ($ok) {
703 $oForm->_data = array_merge($oForm->_data, $values);
704 }
705 }
706 }
707 } else {
708 $oForm->_msg_error = 'Bitte Eingaben pruefen.';
709 }
710 }
711
712 $ddOptions = '';
713 foreach ($this->bindRegistry()->listModuleDds((string)($data['modul'] ?? '')) as $ddRef) {
714 $ddOptions .= '<option value="' . $this->h($ddRef) . '">' . $this->h($ddRef) . '</option>';
715 }
716
717 $oForm->add_obj('bind_generator', 'dbxWorkflow_admin|workflow-bind-generator', array(
718 'modul' => $this->h($data['modul'] ?? ''),
719 'dd_options' => $ddOptions,
720 ));
721
722 return $this->frame($oForm->run());
723 }
724
725 private function decorate_bind_rows($rows) {
726 $out = array();
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']),
732 ));
733 $out[] = $row;
734 }
735 return $out;
736 }
737
738 private function list_binds() {
739 $db = $this->db();
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.';
750
751 $cols = array('id', 'modul', 'bind_key', 'title', 'active', 'update_date');
752 $oReport->_rflds = array(
753 'id' => 'ID',
754 'modul' => 'Modul',
755 'bind_key' => 'Bind Key',
756 'title' => 'Titel',
757 'active' => 'Aktiv',
758 'update_date' => 'Update',
759 'bind_ref' => 'Referenz',
760 'action' => 'Aktion',
761 );
762 $oReport->_rpt_format = array(
763 'update_date' => 'php-datetime-usr',
764 'action' => 'html',
765 );
766 $oReport->_rrows = 30;
767 $oReport->_rpos = 0;
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));
771
772 return $this->frame($oReport->run());
773 }
774
775 public function run() {
776 $run = dbx()->get_modul_var('dbx_run1', 'list', 'parameter');
777
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();
783
784 return $this->list_definitions();
785 }
786}
787?>
DBX schema administration.