dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
dbxWorkflowModule.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxWorkflow;
3
5
6 private $ddBind = 'dbxWorkflow|workflowModuleBind';
7
8 private function db() {
9 return dbx()->get_system_obj('dbxDB');
10 }
11
12 private function tpl() {
13 return dbx()->get_system_obj('dbxTPL');
14 }
15
16 private function h($value) {
17 return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
18 }
19
20 private function read_json($value, $default = array()) {
21 $value = trim((string)$value);
22 if ($value === '') {
23 return $default;
24 }
25 $data = json_decode($value, true);
26 return is_array($data) ? $data : $default;
27 }
28
29 public function parseBindRef($bindRef) {
30 $bindRef = trim((string)$bindRef);
31 if ($bindRef === '' || strpos($bindRef, '|') === false) {
32 return array('', '');
33 }
34
35 $parts = explode('|', $bindRef, 2);
36 return array(trim((string)$parts[0]), trim((string)$parts[1]));
37 }
38
39 public function loadBindRecord($bindRef) {
40 list($modul, $bindKey) = $this->parseBindRef($bindRef);
41 if ($modul === '' || $bindKey === '') {
42 return array();
43 }
44
45 $rows = $this->db()->select(
46 $this->ddBind,
47 array('modul' => $modul, 'bind_key' => $bindKey, 'active' => 1, 'trash' => 0),
48 '*',
49 'id',
50 'DESC',
51 '',
52 1,
53 0,
54 0
55 );
56
57 return (is_array($rows) && isset($rows[0])) ? $rows[0] : array();
58 }
59
60 public function applyBindRef(array $definition) {
61 $bindRef = trim((string)($definition['bind_ref'] ?? ''));
62 if ($bindRef === '') {
63 return $definition;
64 }
65
66 $row = $this->loadBindRecord($bindRef);
67 if (!$row) {
68 return $definition;
69 }
70
71 $bind = $this->read_json($row['bind_json'] ?? '', array());
72 if ($bind) {
73 $definition['bind'] = $bind;
74 }
75
76 return $definition;
77 }
78
79 private function bindConfig(array $definition) {
80 return (array)($definition['bind'] ?? array());
81 }
82
83 private function recordConfig(array $definition) {
84 return (array)($this->bindConfig($definition)['record'] ?? array());
85 }
86
87 public function recordIdFromValues(array $definition, array $values) {
88 $record = $this->recordConfig($definition);
89 $idNeed = trim((string)($record['id_need'] ?? ''));
90 if ($idNeed === '' || !array_key_exists($idNeed, $values)) {
91 return 0;
92 }
93
94 return (int)preg_replace('/\D+/', '', (string)$values[$idNeed]);
95 }
96
97 public function loadRecord(array $definition, $rid = 0) {
98 $recordCfg = $this->recordConfig($definition);
99 $dd = trim((string)($recordCfg['dd'] ?? ''));
100 $rid = (int)$rid;
101 if ($dd === '' || $rid <= 0) {
102 return array();
103 }
104
105 $row = $this->db()->select1($dd, $rid);
106 return (is_array($row) && $row) ? $row : array();
107 }
108
109 private function labelFromTemplate($template, array $row) {
110 $label = (string)$template;
111 foreach ($row as $key => $value) {
112 if (is_scalar($value) || $value === null) {
113 $label = str_replace('{' . $key . '}', (string)$value, $label);
114 }
115 }
116
117 return trim($label);
118 }
119
120 private function optionsFromDdSelect(array $bindNeed, array $definition, array $values) {
121 $record = $this->recordConfig($definition);
122 $dd = trim((string)($record['dd'] ?? ''));
123 if ($dd === '') {
124 return array();
125 }
126
127 $source = array_merge(
128 array('dd' => $dd, 'include_from' => (string)($record['id_need'] ?? '')),
129 $bindNeed
130 );
131
132 return $this->optionsFromSource($source, $values, $this->recordIdFromValues($definition, $values));
133 }
134
135 public function optionsFromSource(array $source, array $values = array(), $includeId = 0) {
136 $dd = trim((string)($source['dd'] ?? ''));
137 if ($dd === '') {
138 return array();
139 }
140
141 $where = (array)($source['where'] ?? array());
142 $fields = (array)($source['fields'] ?? array('id'));
143 $valueField = trim((string)($source['value'] ?? 'id'));
144 $labelTpl = trim((string)($source['label'] ?? ('{' . $valueField . '}')));
145
146 if ($valueField !== '' && !in_array($valueField, $fields, true)) {
147 $fields[] = $valueField;
148 }
149
150 $rows = $this->db()->select(
151 $dd,
152 $where,
153 $fields,
154 (string)($source['order_field'] ?? 'id'),
155 (string)($source['order_dir'] ?? 'DESC'),
156 '',
157 (int)($source['limit'] ?? 200),
158 0,
159 0
160 );
161
162 $options = array();
163 $seen = array();
164
165 foreach ((array)$rows as $row) {
166 $id = (string)($row[$valueField] ?? '');
167 if ($id === '') {
168 continue;
169 }
170 $seen[$id] = 1;
171 $options[] = array(
172 'value' => $id,
173 'label' => $this->labelFromTemplate($labelTpl, $row),
174 );
175 }
176
177 $includeId = (string)(int)$includeId;
178 if ((int)$includeId > 0 && empty($seen[$includeId])) {
179 $record = $this->db()->select1($dd, (int)$includeId, $fields);
180 if (is_array($record) && $record) {
181 $options[] = array(
182 'value' => $includeId,
183 'label' => $this->labelFromTemplate($labelTpl, $record),
184 );
185 }
186 }
187
188 return $options;
189 }
190
191 private function optionsFromFdField(array $definition, $fieldName) {
192 $record = $this->recordConfig($definition);
193 $dd = trim((string)($record['dd'] ?? ''));
194 $fieldName = trim((string)$fieldName);
195 if ($dd === '' || $fieldName === '') {
196 return array();
197 }
198
199 $oDD = dbx()->get_system_obj('dbxDD');
200 $model = $oDD->get_dd_model($dd);
201 $optionsRaw = '';
202
203 foreach ((array)($model['fields'] ?? array()) as $field) {
204 if ((string)($field['name'] ?? '') === $fieldName) {
205 $optionsRaw = (string)($field['options'] ?? '');
206 break;
207 }
208 }
209
210 if ($optionsRaw === '') {
211 return array();
212 }
213
214 $options = array();
215 foreach (explode('&', $optionsRaw) as $pair) {
216 $pair = trim($pair);
217 if ($pair === '' || strpos($pair, '=') === false) {
218 continue;
219 }
220 list($value, $label) = explode('=', $pair, 2);
221 $options[] = array(
222 'value' => trim($value),
223 'label' => trim($label),
224 );
225 }
226
227 return $options;
228 }
229
230 private function configHasPart($modul, $key, $part) {
231 if ($part !== 'mail') {
232 return false;
233 }
234
235 if ($modul === 'dbxContact' && !class_exists('\\dbx\\dbxContact\\dbxContactConfig', false)) {
236 $path = dbx()->get_base_dir() . 'dbx/modules/dbxContact/include/dbxContactConfig.class.php';
237 if (is_file($path)) {
238 require_once $path;
239 }
240 }
241
242 if (class_exists('\\dbx\\dbxContact\\dbxContactConfig', false)) {
243 return \dbx\dbxContact\dbxContactConfig::modulMailEnabled((string) $modul, (string) $key);
244 }
245
246 $mode = strtolower(trim((string) dbx()->get_config($modul, $key)));
247 return ($mode === 'both' || $mode === 'mail' || strpos($mode, 'mail') !== false);
248 }
249
250 private function needBind(array $definition, $needKey) {
251 return (array)($this->bindConfig($definition)['needs'][$needKey] ?? array());
252 }
253
254 private function needVisible(array $definition, $needKey, array $bindNeed) {
255 $showIf = (array)($bindNeed['show_if_config'] ?? array());
256 if (!$showIf) {
257 return true;
258 }
259
260 $modul = trim((string)($showIf['modul'] ?? ''));
261 $key = trim((string)($showIf['key'] ?? ''));
262 $has = trim((string)($showIf['has'] ?? ''));
263
264 if ($modul === '' || $key === '' || $has === '') {
265 return true;
266 }
267
268 return $this->configHasPart($modul, $key, $has);
269 }
270
271 public function enrichDefinition(array $definition, array $values = array()) {
272 $definition = $this->applyBindRef($definition);
273 $bindNeeds = (array)($this->bindConfig($definition)['needs'] ?? array());
274 $needs = array();
275
276 foreach ((array)($definition['needs'] ?? array()) as $need) {
277 if (!is_array($need)) {
278 continue;
279 }
280
281 $key = (string)($need['key'] ?? '');
282 $bindNeed = (array)($bindNeeds[$key] ?? array());
283
284 if ($bindNeed && !$this->needVisible($definition, $key, $bindNeed)) {
285 continue;
286 }
287
288 if (!empty($need['source']) && is_array($need['source'])) {
289 $need['options'] = $this->optionsFromSource(
290 $need['source'],
291 $values,
292 $this->recordIdFromValues($definition, $values)
293 );
294 } elseif ($bindNeed) {
295 $type = (string)($bindNeed['type'] ?? '');
296 if ($type === 'dd_select') {
297 $need['options'] = $this->optionsFromDdSelect($bindNeed, $definition, $values);
298 } elseif ($type === 'dd_field_options') {
299 $need['options'] = $this->optionsFromFdField($definition, (string)($bindNeed['field'] ?? ''));
300 } elseif ($type === 'static_select') {
301 $need['options'] = array_values((array)($bindNeed['options'] ?? array()));
302 }
303 }
304
305 $needs[] = $need;
306 }
307
308 $definition['needs'] = $needs;
309 $definition = $this->enrichShopEbayPublishDefinition($definition, $values);
310 return $definition;
311 }
312
313 private function enrichShopEbayPublishDefinition(array $definition, array $values): array {
314 if ((string)($definition['workflow_key'] ?? '') !== 'shop_ebay_publish') {
315 return $definition;
316 }
317
318 $productId = (int)($values['product'] ?? 0);
319 if ($productId <= 0) {
320 return $definition;
321 }
322
323 $rows = $this->db()->select(
324 'dbxShop|shopProductChannel',
325 array('product_id' => $productId, 'channel_key' => 'ebay'),
326 array('external_listing_id', 'external_offer_id', 'export_status', 'export_message'),
327 'id',
328 'DESC',
329 '',
330 1,
331 0,
332 0
333 );
334 $row = (is_array($rows) && isset($rows[0])) ? $rows[0] : array();
335 $listingId = trim((string)($row['external_listing_id'] ?? ''));
336 $offerId = trim((string)($row['external_offer_id'] ?? ''));
337 $status = trim((string)($row['export_status'] ?? ''));
338 $message = trim((string)($row['export_message'] ?? ''));
339
340 foreach ($definition['needs'] as &$need) {
341 if ((string)($need['key'] ?? '') !== 'ebay_view') {
342 continue;
343 }
344
345 if ($listingId !== '') {
346 $need['hint'] = 'Optionaler Kontrollschritt: Das eBay-Angebot wurde mit Listing-ID ' . $listingId . ' gespeichert. Oeffne das Angebot und pruefe Darstellung, Preis, Versand und Bilder.';
347 $need['module_links'] = array(
348 array(
349 'label' => 'Bei eBay ansehen',
350 'icon' => 'bi-box-arrow-up-right',
351 'url' => 'https://www.ebay.de/itm/' . rawurlencode($listingId),
352 'title' => 'eBay-Angebot ansehen',
353 'width' => '92%',
354 'height' => '90%'
355 ),
356 array(
357 'label' => 'eBay-Mapping',
358 'icon' => 'bi-sliders2',
359 'url' => '?dbx_modul=dbxShop_admin&dbx_run1=product_channel_mapping&id={product}&channel=ebay',
360 'title' => 'Channel-Mapping: eBay',
361 'width' => '68%',
362 'height' => '84%'
363 )
364 );
365 } else {
366 $extra = array();
367 if ($offerId !== '') {
368 $extra[] = 'Offer-ID: ' . $offerId;
369 }
370 if ($status !== '') {
371 $extra[] = 'Status: ' . $status;
372 }
373 if ($message !== '') {
374 $extra[] = $message;
375 }
376 $need['hint'] = 'Optionaler Kontrollschritt: Es ist noch keine eBay Listing-ID gespeichert. Pruefe zuerst Exportstatus und Mapping.'
377 . ($extra ? ' ' . implode(' | ', $extra) : '');
378 }
379 }
380 unset($need);
381
382 return $definition;
383 }
384
385 public function prefillStart(array $definition, $rid = 0) {
387 $recordCfg = $this->recordConfig($definition);
388 $rid = (int)$rid;
389 if (empty($recordCfg['prefill_rid']) || $rid <= 0) {
390 return array();
391 }
392
393 $record = $this->loadRecord($definition, $rid);
394 if (!$record) {
395 return array();
396 }
397
398 $values = array();
399 $idNeed = trim((string)($recordCfg['id_need'] ?? ''));
400 if ($idNeed !== '') {
401 $values[$idNeed] = (string)$rid;
402 }
403
404 foreach ((array)($this->bindConfig($definition)['needs'] ?? array()) as $needKey => $bindNeed) {
405 $type = (string)($bindNeed['type'] ?? '');
406 if ($type === 'dd_field_options' && isset($record[(string)($bindNeed['field'] ?? '')])) {
407 $values[$needKey] = (string)$record[(string)$bindNeed['field']];
408 }
409 if ($type === 'dd_field_value' && isset($record[(string)($bindNeed['field'] ?? '')])) {
410 $text = trim((string)$record[(string)$bindNeed['field']]);
411 if ($text !== '') {
412 $values[$needKey] = $text;
413 }
414 }
415 }
416
417 return $values;
418 }
419
420 private function shopEbayReadinessAutomation(array $values): ?array {
421 $productId = (int)($values['product'] ?? 0);
422 if ($productId <= 0) return null;
423
424 $repo = dbx()->get_include_obj('dbxShopRepository', 'dbxShop');
425 if (!is_object($repo) || !method_exists($repo, 'productChannelMapping')) {
426 return null;
427 }
428
429 $data = $repo->productChannelMapping($productId, 'ebay');
430 if (!is_array($data)) {
431 return array('value' => 'needs_work', 'message' => 'Artikel oder eBay-Channel wurde nicht gefunden.');
432 }
433
434 $product = (array)($data['product'] ?? array());
435 $channel = (array)($data['channel'] ?? array());
436 $productChannel = (array)($data['product_channel'] ?? array());
437 $mapping = (array)($data['mapping'] ?? array());
438 $missing = array();
439
440 if ((int)($channel['active'] ?? 0) !== 1) $missing[] = 'eBay-Channel aktiv';
441 if ((int)($channel['export_enabled'] ?? 0) !== 1) $missing[] = 'Channel-Export freigeben';
442 if ((int)($productChannel['active'] ?? 0) !== 1) $missing[] = 'Artikel dem eBay-Channel zuordnen';
443
444 $required = array(
445 'api_client_id' => 'Client-ID/App-ID',
446 'api_client_secret' => 'Client-Secret/Cert-ID',
447 'marketplace_id' => 'Marketplace-ID',
448 'location_key' => 'Location-Key',
449 );
450 foreach ($required as $key => $label) {
451 if (trim((string)($channel[$key] ?? '')) === '') $missing[] = $label;
452 }
453 if (trim((string)($channel['api_refresh_token'] ?? '')) === '' && trim((string)($channel['api_access_token'] ?? '')) === '') {
454 $missing[] = 'Refresh-Token oder Access-Token';
455 }
456
457 $mapped = array(
458 'category_id' => 'Kategorie-ID',
459 'payment_policy_id' => 'Payment-Policy',
460 'fulfillment_policy_id' => 'Fulfillment-Policy',
461 'return_policy_id' => 'Return-Policy',
462 );
463 foreach ($mapped as $key => $label) {
464 $resolved = trim((string)($mapping[$key] ?? $channel[$key] ?? ''));
465 if ($resolved === '') $missing[] = $label;
466 }
467
468 $sku = trim((string)($productChannel['channel_sku'] ?? $product['sku'] ?? ''));
469 if ($sku === '') $missing[] = 'Channel-SKU/Artikelnummer';
470 if (trim((string)($product['title'] ?? '')) === '') $missing[] = 'Artikeltitel';
471
472 $missing = array_values(array_unique($missing));
473 if ($missing) {
474 return array(
475 'value' => 'needs_work',
476 'message' => 'Automatische Bereitschaftsprüfung: Bitte ergänzen: ' . implode(', ', $missing) . '.',
477 );
478 }
479
480 return array(
481 'value' => 'ready',
482 'message' => 'Automatische Bereitschaftsprüfung: eBay-Grunddaten, Zuordnung und Policies sind vollständig.',
483 );
484 }
485
486 private function shopEbayStatusAutomation(array $values): ?array {
487 $productId = (int)($values['product'] ?? 0);
488 if ($productId <= 0) return null;
489
490 $rows = $this->db()->select(
491 'dbxShop|shopProductChannel',
492 array('product_id' => $productId, 'channel_key' => 'ebay'),
493 array('external_listing_id', 'export_status', 'export_message', 'last_export_date'),
494 'id',
495 'DESC',
496 '',
497 1,
498 0,
499 0
500 );
501 $row = (is_array($rows) && isset($rows[0])) ? $rows[0] : array();
502 $lastExport = trim((string)($row['last_export_date'] ?? ''));
503 if ($lastExport === '') return null;
504
505 $status = strtolower(trim((string)($row['export_status'] ?? '')));
506 $listingId = trim((string)($row['external_listing_id'] ?? ''));
507 $connectorMessage = trim((string)($row['export_message'] ?? ''));
508 $value = 'open';
509 if (in_array($status, array('failed', 'error'), true)) {
510 $value = 'error';
511 } elseif ($listingId !== '' && in_array($status, array('published', 'exported', 'ready'), true)) {
512 $value = 'ok';
513 }
514
515 $message = 'Connector-Status automatisch ausgewertet: ' . ($status !== '' ? $status : 'offen') . '.';
516 if ($listingId !== '') $message .= ' Listing-ID: ' . $listingId . '.';
517 if ($connectorMessage !== '') $message .= ' ' . $connectorMessage;
518 return array('value' => $value, 'message' => $message);
519 }
520
521 public function automateNeed(array $definition, array $need, array $values): ?array {
522 if ((string)($need['automation'] ?? 'manual') !== 'observe') return null;
523 if ((string)($definition['workflow_key'] ?? '') !== 'shop_ebay_publish') return null;
524
525 $key = (string)($need['key'] ?? '');
526 if ($key === 'readiness_check') return $this->shopEbayReadinessAutomation($values);
527 if ($key === 'status_check') return $this->shopEbayStatusAutomation($values);
528 return null;
529 }
530
531 private function contextData(array $definition, array $record) {
532 $context = (array)($this->bindConfig($definition)['context'] ?? array());
533 $fields = (array)($context['fields'] ?? array());
534 $data = array();
535
536 foreach ($fields as $tplKey => $recordKey) {
537 $value = $record[(string)$recordKey] ?? '';
538 if ($tplKey === 'phone' && trim((string)$value) === '') {
539 $value = '-';
540 }
541 $data[$tplKey] = $this->h($value);
542 }
543
544 return $data;
545 }
546
547 public function renderStepContext(array $definition, array $need, array $values) {
549 $context = (array)($this->bindConfig($definition)['context'] ?? array());
550 if (!$context) {
551 return '';
552 }
553
554 $hideOn = trim((string)($context['hide_on_need'] ?? ''));
555 $rid = $this->recordIdFromValues($definition, $values);
556
557 if ($rid > 0 && $hideOn !== '' && $need['key'] !== $hideOn) {
558 $record = $this->loadRecord($definition, $rid);
559 if ($record) {
560 $tpl = trim((string)($context['tpl'] ?? ''));
561 if ($tpl !== '') {
562 return $this->tpl()->get_tpl($tpl, $this->contextData($definition, $record));
563 }
564 }
565 }
566
567 if ($hideOn !== '' && $need['key'] === $hideOn && $rid <= 0) {
568 $recordCfg = $this->recordConfig($definition);
569 $bindNeed = $this->needBind($definition, $hideOn);
570 if (($bindNeed['type'] ?? '') === 'dd_select') {
571 $options = $this->optionsFromDdSelect($bindNeed, $definition, $values);
572 if (!$options) {
573 return $this->tpl()->get_tpl('dbx|alert-info', array(
574 'msg' => 'Keine passenden Datensaetze gefunden. Bitte zuerst im Modul erfassen.',
575 ));
576 }
577 }
578 }
579
580 return '';
581 }
582
583 public function renderFormValue(array $definition, array $need, array $values) {
585 $bindNeed = $this->needBind($definition, (string)($need['key'] ?? ''));
586 if ((string)($bindNeed['type'] ?? '') !== 'dd_field_value') {
587 return '';
588 }
589
590 if (array_key_exists($need['key'], $values)) {
591 return $this->h($values[$need['key']]);
592 }
593
594 $rid = $this->recordIdFromValues($definition, $values);
595 if ($rid > 0) {
596 $record = $this->loadRecord($definition, $rid);
597 $field = (string)($bindNeed['field'] ?? '');
598 if ($record && $field !== '') {
599 return $this->h(trim((string)($record[$field] ?? '')));
600 }
601 }
602
603 return '';
604 }
605
606 public function formatValueLabel(array $definition, array $need, $value) {
608 $needKey = (string)($need['key'] ?? '');
609 $bindNeed = $this->needBind($definition, $needKey);
610
611 if (is_array($value) && !empty($value['skipped'])) {
612 return '<em>Uebersprungen</em>';
613 }
614
615 if ((string)($bindNeed['type'] ?? '') === 'dd_select') {
616 $record = $this->loadRecord($definition, (int)$value);
617 if ($record) {
618 return $this->h($this->labelFromTemplate((string)($bindNeed['label'] ?? '#{id}'), $record));
619 }
620 }
621
622 if ((string)($bindNeed['type'] ?? '') === 'dd_field_options') {
623 foreach ($this->optionsFromFdField($definition, (string)($bindNeed['field'] ?? '')) as $opt) {
624 if ((string)($opt['value'] ?? '') === (string)$value) {
625 return $this->h($opt['label']);
626 }
627 }
628 }
629
630 if ((string)($bindNeed['type'] ?? '') === 'static_select') {
631 foreach ((array)($bindNeed['options'] ?? array()) as $opt) {
632 if (is_array($opt) && (string)($opt['value'] ?? '') === (string)$value) {
633 return $this->h($opt['label']);
634 }
635 }
636 }
637
638 return null;
639 }
640
641 private function finalStatusBox(string $type, string $title, string $body, string $extra = ''): string {
642 $icon = 'bi-info-circle';
643 if ($type === 'success') $icon = 'bi-check2-circle';
644 if ($type === 'warning') $icon = 'bi-exclamation-triangle';
645 if ($type === 'danger') $icon = 'bi-x-circle';
646 return '<div class="alert alert-' . $this->h($type) . ' mb-3">'
647 . '<div class="fw-semibold"><i class="bi ' . $icon . '"></i> ' . $this->h($title) . '</div>'
648 . '<div>' . $this->h($body) . '</div>'
649 . $extra
650 . '</div>';
651 }
652
653 private function genericFinalStatus(array $definition, string $instanceStatus, int $completed, int $total, array $missingLabels): string {
654 if ($missingLabels !== array()) {
655 return $this->finalStatusBox(
656 'warning',
657 'Workflow noch nicht vollstaendig',
658 'Es fehlen noch Pflichtschritte: ' . implode(', ', $missingLabels) . '.'
659 );
660 }
661
662 if ($instanceStatus === 'finished') {
663 return $this->finalStatusBox(
664 'success',
665 'Workflow komplett abgeschlossen',
666 'Alle notwendigen Schritte sind erledigt. Ergebnis: ' . (string)($definition['result'] ?? $definition['title'] ?? 'Workflow') . '.'
667 );
668 }
669
670 return $this->finalStatusBox(
671 'info',
672 'Workflow bereit zum Abschluss',
673 'Alle notwendigen Schritte sind erledigt (' . $completed . ' von ' . $total . '). Pruefe die Zusammenfassung und schliesse den Workflow ab.'
674 );
675 }
676
677 private function ebayFinalStatus(array $values, string $instanceStatus, int $completed, int $total, array $missingLabels): string {
678 if ($missingLabels !== array()) {
679 return $this->finalStatusBox(
680 'warning',
681 'eBay-Workflow noch nicht vollstaendig',
682 'Es fehlen noch Pflichtschritte: ' . implode(', ', $missingLabels) . '.'
683 );
684 }
685
686 $productId = (int)($values['product'] ?? 0);
687 if ($productId <= 0) {
688 return $this->finalStatusBox('warning', 'eBay-Status unklar', 'Es ist kein Artikel ausgewaehlt.');
689 }
690
691 $rows = $this->db()->select(
692 'dbxShop|shopProductChannel',
693 array('product_id' => $productId, 'channel_key' => 'ebay'),
694 array('external_listing_id', 'external_offer_id', 'export_status', 'export_message', 'last_export_date'),
695 'id',
696 'DESC',
697 '',
698 1,
699 0,
700 0
701 );
702 $row = (is_array($rows) && isset($rows[0])) ? $rows[0] : array();
703 $listingId = trim((string)($row['external_listing_id'] ?? ''));
704 $offerId = trim((string)($row['external_offer_id'] ?? ''));
705 $status = strtolower(trim((string)($row['export_status'] ?? '')));
706 $message = trim((string)($row['export_message'] ?? ''));
707 $lastExport = trim((string)($row['last_export_date'] ?? ''));
708 $manualCheck = (string)($values['status_check'] ?? '');
709
710 $meta = array();
711 if ($status !== '') $meta[] = 'Status: ' . $status;
712 if ($lastExport !== '') $meta[] = 'Letzter Export: ' . $lastExport;
713 if ($offerId !== '') $meta[] = 'Offer-ID: ' . $offerId;
714 if ($listingId !== '') $meta[] = 'Listing-ID: ' . $listingId;
715 $extra = $meta ? '<div class="small mt-2 text-muted">' . $this->h(implode(' | ', $meta)) . '</div>' : '';
716 if ($message !== '') {
717 $extra .= '<div class="small mt-1">' . $this->h($message) . '</div>';
718 }
719 if ($listingId !== '') {
720 $url = 'https://www.ebay.de/itm/' . rawurlencode($listingId);
721 $extra .= '<div class="mt-2"><a class="btn btn-outline-primary btn-sm dbx-win" href="' . $this->h($url) . '" data-url="' . $this->h($url) . '" data-title="eBay-Angebot ansehen" data-width="92%" data-height="90%"><i class="bi bi-box-arrow-up-right"></i> Bei eBay ansehen</a></div>';
722 }
723
724 if (in_array($status, array('failed', 'error'), true)) {
725 return $this->finalStatusBox('danger', 'eBay-Veroeffentlichung fehlgeschlagen', 'Der Connector hat einen Fehler gemeldet. Bitte Mapping, Zugangsdaten und eBay-Rueckmeldung pruefen.', $extra);
726 }
727
728 if ($listingId !== '' && in_array($status, array('published', 'exported', 'ready'), true)) {
729 return $this->finalStatusBox('success', 'Artikel ist auf eBay veroeffentlicht', 'Der Export hat eine eBay Listing-ID geliefert. Der Workflow ist fachlich erfolgreich.', $extra);
730 }
731
732 if ($lastExport === '') {
733 return $this->finalStatusBox('warning', 'eBay-Export noch nicht ausgefuehrt', 'Der Workflow ist inhaltlich vorbereitet, aber es gibt noch keinen gespeicherten Exportlauf.', $extra);
734 }
735
736 if ($listingId === '') {
737 $text = 'Der Export wurde ausgefuehrt, aber es ist noch keine eBay Listing-ID gespeichert. Das kann bedeuten, dass die Plattform asynchron prueft oder die Rueckmeldung fehlt.';
738 if ($manualCheck === 'error') {
739 $text = 'Der Workflow wurde mit Fehlerstatus geprueft. Bitte die Connector-Meldung und eBay-Daten korrigieren.';
740 }
741 return $this->finalStatusBox('warning', 'eBay-Rueckmeldung fehlt oder ist offen', $text, $extra);
742 }
743
744 return $this->finalStatusBox('info', 'eBay-Status pruefen', 'Alle Workflow-Schritte sind erledigt, der technische Status ist aber nicht eindeutig als veroeffentlicht markiert.', $extra);
745 }
746
747 public function renderFinalStatus(array $definition, array $values, string $instanceStatus, int $completed, int $total, array $missingLabels): string {
749 if ((string)($definition['workflow_key'] ?? '') === 'shop_ebay_publish') {
750 return $this->ebayFinalStatus($values, $instanceStatus, $completed, $total, $missingLabels);
751 }
752 return $this->genericFinalStatus($definition, $instanceStatus, $completed, $total, $missingLabels);
753 }
754
755 private function resolveToken($token, array $definition, array $values, array $record) {
756 $token = trim((string)$token);
757 if ($token === '@now') {
758 return date('Y-m-d H:i:s');
759 }
760 if ($token === '@uid') {
761 return (int)dbx()->user();
762 }
763 if (strpos($token, '@need:') === 0) {
764 $needKey = substr($token, 6);
765 return $values[$needKey] ?? '';
766 }
767 if (array_key_exists($token, $values)) {
768 return $values[$token];
769 }
770 if (array_key_exists($token, $record)) {
771 return $record[$token];
772 }
773
774 return $token;
775 }
776
777 private function resolveMap(array $map, array $definition, array $values, array $record) {
778 $out = array();
779 foreach ($map as $dbField => $source) {
780 $out[$dbField] = $this->resolveToken($source, $definition, $values, $record);
781 }
782 return $out;
783 }
784
785 private function shopWorkflowFinish(array $definition, array $values) {
786 $workflowKey = (string)($definition['workflow_key'] ?? '');
787 if ($workflowKey !== 'shop_article_publish' && $workflowKey !== 'shop_ebay_publish') {
788 return null;
789 }
790
791 $productId = (int)($values['product'] ?? 0);
792 if ($productId <= 0) {
793 return array('ok' => 0, 'message' => 'Kein Artikel ausgewaehlt.');
794 }
795
796 if ($workflowKey === 'shop_article_publish') {
797 $release = (string)($values['final_check'] ?? '');
798 if ($release === 'draft') {
799 return array('ok' => 1, 'message' => 'Artikel bleibt als Entwurf vorbereitet.');
800 }
801
802 $ok = $this->db()->update(
803 'dbxShop|shopProduct',
804 array('active' => 1, 'update_date' => date('Y-m-d H:i:s'), 'update_uid' => (int)dbx()->user()),
805 array('id' => $productId, 'trash' => 0),
806 1,
807 1,
808 1,
809 1
810 );
811 if ($ok !== 1) {
812 return array('ok' => 0, 'message' => 'Artikel konnte nicht freigegeben werden.');
813 }
814 return array('ok' => 1, 'message' => 'Artikel wurde im Shop freigegeben.');
815 }
816
817 $channelRows = $this->db()->select(
818 'dbxShop|shopProductChannel',
819 array('product_id' => $productId, 'channel_key' => 'ebay'),
820 '*',
821 'id',
822 'DESC',
823 '',
824 1,
825 0,
826 1
827 );
828 if (!is_array($channelRows)) {
829 return array('ok' => 0, 'message' => 'Channel-Daten konnten nicht gelesen werden.');
830 }
831 $channelRow = (is_array($channelRows) && isset($channelRows[0])) ? $channelRows[0] : array();
832 if (trim((string)($channelRow['last_export_date'] ?? '')) !== '') {
833 $status = strtolower(trim((string)($channelRow['export_status'] ?? '')));
834 $message = trim((string)($channelRow['export_message'] ?? ''));
835 $listingId = trim((string)($channelRow['external_listing_id'] ?? ''));
836 if (in_array($status, array('failed', 'error'), true)) {
837 return array('ok' => 0, 'message' => 'Der eBay-Export ist fehlgeschlagen' . ($message !== '' ? ': ' . $message : '.') );
838 }
839 if ($listingId === '' || !in_array($status, array('published', 'exported', 'ready'), true)) {
840 return array('ok' => 0, 'message' => 'Die eBay-Rückmeldung ist noch nicht eindeutig erfolgreich. Bitte Status und Listing-ID prüfen' . ($message !== '' ? ': ' . $message : '.') );
841 }
842 return array(
843 'ok' => 1,
844 'message' => 'eBay-Veröffentlichung bestätigt: ' . $status . ', Listing-ID ' . $listingId . ($message !== '' ? ' - ' . $message : '') . '.'
845 );
846 }
847
848 return array(
849 'ok' => 0,
850 'message' => 'Der eBay-Export wurde noch nicht ausgeführt. Bitte im Schritt „Export durchführen“ bewusst veröffentlichen und anschließend die Connector-Rückmeldung prüfen.'
851 );
852 }
853
854 public function applyFinish(array $definition, array $values) {
856 $shopResult = $this->shopWorkflowFinish($definition, $values);
857 if (is_array($shopResult)) {
858 return $shopResult;
859 }
860
861 $finish = (array)($this->bindConfig($definition)['finish'] ?? array());
862 if (!$finish || (string)($finish['type'] ?? '') !== 'dd_update') {
863 return null;
864 }
865
866 $recordCfg = $this->recordConfig($definition);
867 $dd = trim((string)($recordCfg['dd'] ?? ''));
868 $rid = $this->recordIdFromValues($definition, $values);
869 if ($dd === '' || $rid <= 0) {
870 return array('ok' => 0, 'message' => 'Kein Datensatz fuer den Abschluss ausgewaehlt.');
871 }
872
873 $record = $this->loadRecord($definition, $rid);
874 if (!$record) {
875 return array('ok' => 0, 'message' => 'Datensatz #' . $rid . ' wurde nicht gefunden.');
876 }
877
878 $update = $this->resolveMap((array)($finish['map'] ?? array()), $definition, $values, $record);
879
880 if (array_key_exists('reply_text', (array)($finish['map'] ?? array()))) {
881 $replyText = trim((string)($update['reply_text'] ?? ''));
882 if (strlen($replyText) < 2) {
883 return array('ok' => 0, 'message' => 'Bitte eine Rueckmeldung mit mindestens 2 Zeichen erfassen.');
884 }
885 }
886
887 $ok = $this->db()->update($dd, $update, $rid);
888 if ($ok !== 1) {
889 return array('ok' => 0, 'message' => 'Datensatz konnte nicht gespeichert werden.');
890 }
891
892 $message = 'Datensatz #' . $rid . ' wurde gespeichert.';
893 $mail = (array)($finish['mail'] ?? array());
894
895 if ($mail) {
896 $whenNeed = trim((string)($mail['when_need'] ?? ''));
897 $whenValue = (string)($mail['when_value'] ?? '1');
898 $send = ($whenNeed === '') || ((string)($values[$whenNeed] ?? '') === $whenValue);
899
900 $configModul = trim((string)($mail['config_modul'] ?? ($definition['bind']['modul'] ?? '')));
901 $modeKey = trim((string)($mail['mode_key'] ?? 'reply_mode'));
902 if ($configModul !== '' && $modeKey !== '' && !$this->configHasPart($configModul, $modeKey, 'mail')) {
903 $send = false;
904 }
905
906 if ($send) {
907 $toField = trim((string)($mail['to_field'] ?? 'email'));
908 $to = trim((string)($record[$toField] ?? ''));
909 if ($to === '') {
910 $message .= ' E-Mail-Versand nicht moeglich (keine Adresse).';
911 } else {
912 $subjectTpl = (string)($mail['subject_tpl'] ?? 'Antwort');
913 $subject = $this->labelFromTemplate($subjectTpl, array_merge($record, $update));
914 $bodyTpl = trim((string)($mail['body_tpl'] ?? ''));
915 $bodyVars = array();
916 foreach ((array)($mail['body_vars'] ?? array()) as $tplKey => $source) {
917 $bodyVars[$tplKey] = $this->h($this->resolveToken($source, $definition, $values, $record));
918 }
919 $html = $bodyTpl !== '' ? $this->tpl()->get_tpl($bodyTpl, $bodyVars) : nl2br($this->h((string)($update['reply_text'] ?? '')));
920
921 $from = trim((string)dbx()->get_config($configModul, 'mail_from'));
922 $fromName = trim((string)dbx()->get_config($configModul, 'mail_from_name'));
923 $fromParam = ($from !== '') ? array('email' => $from, 'name' => $fromName) : '';
924 $options = array('text' => strip_tags(str_replace('<br />', "\n", $html)));
925 $profile = trim((string)dbx()->get_config($configModul, 'mail_profile'));
926 if ($profile !== '') {
927 $options['mail_profile'] = $profile;
928 }
929
930 $mailOk = dbx()->send_mail($fromParam, $to, $subject, $html, 'html', array(), $options);
931 if ($mailOk) {
932 $track = $this->resolveMap((array)($mail['track_fields'] ?? array()), $definition, $values, $record);
933 if ($track) {
934 $this->db()->update($dd, $track, $rid);
935 }
936 $message .= ' E-Mail wurde versendet.';
937 } else {
938 $message .= ' E-Mail-Versand fehlgeschlagen.';
939 }
940 }
941 }
942 }
943
944 return array('ok' => 1, 'message' => $message);
945 }
946}
947?>
applyFinish(array $definition, array $values)
renderFormValue(array $definition, array $need, array $values)
formatValueLabel(array $definition, array $need, $value)
optionsFromSource(array $source, array $values=array(), $includeId=0)
renderFinalStatus(array $definition, array $values, string $instanceStatus, int $completed, int $total, array $missingLabels)
enrichDefinition(array $definition, array $values=array())
renderStepContext(array $definition, array $need, array $values)
automateNeed(array $definition, array $need, array $values)
recordIdFromValues(array $definition, array $values)
user($key='id')
Liest einen Wert des aktuellen Benutzers.
Definition dbxApi.php:1305
dbx()
Liefert die zentrale dbXapp-API als Singleton.
Definition dbxApi.php:2805
if($stored !=='files/test/') $resolved
if($web->content_permalink_redirect_target() !=='') $tpl
if($out !==$expected) $missing
if(!defined( 'IMG_WEBP')) define( 'IMG_WEBP'
DBX schema administration.
$profile
Definition run.php:6