Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.37% covered (warning)
77.37%
106 / 137
n/a
0 / 0
CRAP
n/a
0 / 0
1<?php
2
3/**
4 * @file
5 * Place action: load product details by objids (productnfo).
6 */
7
8declare(strict_types=1);
9
10require_once __DIR__.'/../../Legacy/common/session.php';
11require_once __DIR__.'/../../Legacy/model/synchloader.php';
12
13/**
14 * Action callable: load product details by objids (POST body).
15 *
16 * @param string $body JSON array of product objids.
17 * @param array<string, mixed> $query Query params: device, session, place, dateini, dateend.
18 * @param \ConxHelper $conx Connection helper.
19 * @param \Psr\Log\LoggerInterface $logger Logger.
20 * @param \UppServices\SessionService $sessionService Session service for check.
21 * @return array{output: string, contentType: string}
22 */
23return function (string $body, array $query, \ConxHelper $conx, \Psr\Log\LoggerInterface $logger, \UppServices\SessionService $sessionService): array {
24    $retval = \ApplicationError::Success;
25    $updates = [];
26    $logPrefix = '[place/productnfo]';
27
28    $datamodel = json_decode('{
29    "data": {
30        "PRODUCT": [
31            { "direction": ">", "target": "I18NVALUE", "by": "name", "reason": null },
32            { "direction": ">", "target": "I18NVALUE", "by": "description", "reason": null },
33            { "direction": "<", "target": "TICKETPRODUCT", "by": "product", "reason": "requested", "sql": "and TICKETPRODUCT.status = \'AC\'" }
34        ],
35        "TICKETPRODUCT": [
36            { "direction": ">", "target": "TICKET", "by": "ticket", "reason": null },
37            { "direction": ">", "target": "PRODUCT", "by": "product", "reason": null },
38            { "direction": ">", "target": "TICKETOFFER", "by": "offer", "reason": null },
39            { "direction": "<", "target": "TICKETOPTION", "by": "product", "reason": "options" }
40        ],
41        "TICKETOPTION": [
42            { "direction": ">", "target": "TICKETPRODUCT", "by": "product", "reason": null },
43            { "direction": ">", "target": "PRODUCTOPT", "by": "prodopt", "reason": null }
44        ],
45        "PRODUCTOPT": [
46            { "direction": ">", "target": "I18NVALUE", "by": "name", "reason": null },
47            { "direction": ">", "target": "I18NVALUE", "by": "description", "reason": null },
48            { "direction": ">", "target": "PRODUCT", "by": "product", "reason": null },
49            { "direction": ">", "target": "CATEGORY", "by": "category", "reason": null }
50        ],
51        "CATEGORY": [
52            { "direction": ">", "target": "I18NVALUE", "by": "name", "reason": null },
53            { "direction": ">", "target": "PRODUCT", "by": "product", "reason": null }
54        ],
55        "PRESELECT": [
56            { "direction": ">", "target": "I18NVALUE", "by": "name", "reason": null },
57            { "direction": ">", "target": "I18NVALUE", "by": "description", "reason": null },
58            { "direction": ">", "target": "PRODUCT", "by": "product", "reason": null }
59        ],
60        "TICKETOFFER": [
61            { "direction": ">", "target": "OFFER", "by": "offer", "reason": null }
62        ],
63        "TICKETEXTRA": [
64            { "direction": ">", "target": "EXTRA", "by": "extra", "reason": null }
65        ]
66    }
67}', true);
68
69    $countByTable = static function (array $rows): array {
70        $counts = [];
71        foreach ($rows as $row) {
72            $table = (string) ($row['table'] ?? 'unknown');
73            $counts[$table] = ($counts[$table] ?? 0) + 1;
74        }
75        return $counts;
76    };
77
78    $logger->info($logPrefix.' request query='.json_encode([
79        'place' => $query['place'] ?? null,
80        'dateini' => $query['dateini'] ?? null,
81        'dateend' => $query['dateend'] ?? null,
82        'device' => $query['device'] ?? null,
83        'session' => isset($query['session']) ? '(set)' : null,
84        'lang' => $query['lang'] ?? null,
85    ], JSON_UNESCAPED_UNICODE));
86    $logger->info($logPrefix.' request body='.$body);
87
88    if (!$body) {
89        $logger->error($logPrefix.' No post data provided.');
90        $retval = \ApplicationError::Parameters;
91    }
92
93    if (\success($retval)) {
94        $products = json_decode($body, true);
95        if (!is_array($products)) {
96            $logger->error($logPrefix.' Invalid JSON format in post request data');
97            $retval = \ApplicationError::Parameters;
98        } else {
99            $_objids = implode(', ', array_map('intval', $products));
100            $logger->info($logPrefix.' product objids=['.$_objids.'] count='.count($products));
101        }
102    }
103
104    if (\success($retval)) {
105        $device = $query['device'] ?? null;
106        if (!$device) {
107            $logger->error("No device provided on URL. Operation cancelled.");
108            $retval = \ApplicationError::Parameters;
109        }
110    }
111
112    if (\success($retval)) {
113        $session = $query['session'] ?? null;
114        if (!$session) {
115            $logger->error("No session provided on URL. Operation cancelled.");
116            $retval = \ApplicationError::Parameters;
117        } else {
118            $userid = null;
119            $sessionObjid = null;
120            $retval = $sessionService->checkSession($conx, (string) $session, (string) $device, $userid, $sessionObjid);
121        }
122    }
123
124    if (\success($retval)) {
125        $place = $query['place'] ?? null;
126        if (is_array($place)) {
127            $place = (string) ($place[0] ?? '');
128        }
129        if ($place === null || $place === '') {
130            $logger->error("Mandatory argument 'place' not provided in url parameters.");
131            $retval = \ApplicationError::Parameters;
132        }
133    }
134
135    $ini = null;
136    $end = null;
137    if (\success($retval)) {
138        $ini = $query['dateini'] ?? null;
139        if ($ini === null) {
140            $logger->error("Mandatory argument 'dateini' not provided in url parameters.");
141            $retval = \ApplicationError::Parameters;
142        }
143    }
144    if (\success($retval)) {
145        $end = $query['dateend'] ?? null;
146        if ($end === null) {
147            $logger->error("Mandatory argument 'dateend' not provided in url parameters.");
148            $retval = \ApplicationError::Parameters;
149        }
150    }
151
152    if (\success($retval)) {
153        $conx->tenant = $place;
154    }
155
156    if (\success($retval) && $conx->tenant === null) {
157        $logger->error("Connection to place database failed.");
158        $retval = \ApplicationError::Database;
159    }
160
161    $results = [];
162    if (\success($retval)) {
163        $GLOBALS['session'] = $session;
164        $datamodelData = is_array($datamodel) ? ($datamodel['data'] ?? null) : null;
165        if (!is_array($datamodelData)) {
166            $logger->error($logPrefix.' Invalid productnfo datamodel.');
167            $retval = \ApplicationError::Generic;
168        } else {
169            $logger->info($logPrefix.' load_updates table=PRODUCT timestamp=0 products=['.$_objids.']');
170            $retval = \load_updates($conx, $datamodelData, 'PRODUCT', $products, 0, $results);
171            $logger->info($logPrefix.' load_updates errorcode='.$retval.' rows='.count($results).' by_table='.json_encode($countByTable($results), JSON_UNESCAPED_UNICODE));
172        }
173    }
174
175    $allowedTicketStatuses = ['AC', 'RD', 'PP', 'PD'];
176
177    if (\success($retval)) {
178        // Same period criterion as place/productlst (TICKETPRODUCT.created), not updated.
179        $iniTs = is_numeric($ini) ? (int) $ini : strtotime((string) $ini);
180        $endTs = is_numeric($end) ? (int) $end : strtotime((string) $end);
181        $periodLabel = static function ($ts): string {
182            return ($ts !== false) ? date('Y-m-d H:i:s', (int) $ts) : 'invalid';
183        };
184        $logger->info($logPrefix.' period filter iniTs='.$iniTs.' ('.$periodLabel($iniTs).') endTs='.$endTs.' ('.$periodLabel($endTs).')');
185        $ticketAllowed = [];
186        foreach ($results as $row) {
187            if (($row['table'] ?? '') !== 'TICKET') {
188                continue;
189            }
190            $status = (string) ($row['status'] ?? '');
191            if (in_array($status, $allowedTicketStatuses, true)) {
192                $ticketAllowed[(int) $row['objid']] = true;
193            }
194        }
195        $ticketProductsInPeriod = [];
196        $ticketProductsSkipped = [];
197        foreach ($results as $row) {
198            if (($row['table'] ?? '') !== 'TICKETPRODUCT') {
199                continue;
200            }
201            $objid = (int) $row['objid'];
202            $ticketId = (int) ($row['ticket'] ?? 0);
203            if ($ticketId > 0 && !isset($ticketAllowed[$ticketId])) {
204                $ticketProductsSkipped[$objid] = 'ticket_not_allowed:'.$ticketId;
205                continue;
206            }
207            $createdRaw = $row['created'] ?? null;
208            if ($createdRaw === null || $createdRaw === '') {
209                $ticketProductsSkipped[$objid] = 'no_created';
210                continue;
211            }
212            $cdt = strtotime((string) $createdRaw);
213            if ($cdt === false) {
214                $ticketProductsSkipped[$objid] = 'invalid_created:'.$createdRaw;
215                continue;
216            }
217            if ($cdt >= $iniTs && $cdt <= $endTs) {
218                $ticketProductsInPeriod[$objid] = true;
219            } else {
220                $ticketProductsSkipped[$objid] = 'created='.$createdRaw.' ts='.$cdt;
221            }
222        }
223        $logger->info($logPrefix.' ticketproducts in_period='.count($ticketProductsInPeriod).' skipped='.count($ticketProductsSkipped));
224        if (count($ticketProductsInPeriod) > 0) {
225            $logger->debug($logPrefix.' ticketproducts kept objids='.implode(',', array_keys($ticketProductsInPeriod)));
226        }
227        if (count($ticketProductsSkipped) > 0) {
228            $logger->debug($logPrefix.' ticketproducts skipped detail='.json_encode($ticketProductsSkipped, JSON_UNESCAPED_UNICODE));
229        }
230        foreach ($results as &$result) {
231            $table = $result['table'] ?? '';
232            if ($table === 'TICKETPRODUCT') {
233                if (!isset($ticketProductsInPeriod[(int) $result['objid']])) {
234                    continue;
235                }
236            }
237            $updates[] = &$result;
238        }
239        unset($result);
240        $logger->info($logPrefix.' updates after filter rows='.count($updates).' by_table='.json_encode($countByTable($updates), JSON_UNESCAPED_UNICODE));
241    }
242
243    $result = [
244        "errorcode" => $retval,
245        "updates" => \success($retval) ? $updates : null,
246    ];
247    $logger->info($logPrefix.' response errorcode='.$retval.' updates_count='.(\success($retval) ? count($updates) : 0));
248
249    return ['output' => json_encode($result), 'contentType' => 'application/json; charset=utf-8'];
250};