Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.56% covered (success)
89.56%
163 / 182
n/a
0 / 0
CRAP
n/a
0 / 0
1<?php
2
3/**
4 * @file
5 * Place action: product list for date range.
6 */
7
8declare(strict_types=1);
9
10require_once __DIR__.'/../../Legacy/common/session.php';
11
12/**
13 * Action callable: get product list for place and date range.
14 *
15 * @param string $body Unused.
16 * @param array<string, mixed> $query Query params: device, session, place, dateini, dateend, timestamp.
17 * @param \ConxHelper $conx Connection helper.
18 * @param \Psr\Log\LoggerInterface $logger Logger.
19 * @param \UppServices\SessionService $sessionService Session service for check.
20 * @return array{output: string, contentType: string}
21 */
22return function (string $body, array $query, \ConxHelper $conx, \Psr\Log\LoggerInterface $logger, \UppServices\SessionService $sessionService): array {
23    $retval = \ApplicationError::Success;
24    $place = null;
25    $now = null;
26
27    $device = $query['device'] ?? null;
28    if ($device === null || $device === '') {
29        $logger->error("No device provided on URL. Operation cancelled.");
30        $retval = \ApplicationError::Parameters;
31    }
32
33    if (\success($retval)) {
34        $session = $query['session'] ?? null;
35        if ($session === null || $session === '') {
36            $logger->error("No session provided on URL. Operation cancelled.");
37            $retval = \ApplicationError::Parameters;
38        } else {
39            $unused = null;
40            $sessionObjid = null;
41            $retval = $sessionService->checkSession($conx, (string) $session, (string) $device, $unused, $sessionObjid);
42        }
43    }
44
45    if (\success($retval)) {
46        $place = $query['place'] ?? null;
47        if ($place === null || $place === '') {
48            $logger->error("Mandatory argument 'place' not provided in url parameters.");
49            $retval = \ApplicationError::Parameters;
50        }
51    }
52
53    if (\success($retval)) {
54        $conx->tenant = $place;
55    }
56
57    if (\success($retval) && $conx->tenant === null) {
58        $logger->error("Connection to place database failed.");
59        $retval = \ApplicationError::Database;
60    }
61
62    if (\success($retval)) {
63        $now = $conx->tenant->now(-60);
64    }
65
66    $ini = null;
67    $end = null;
68    $timestamp = null;
69    if (\success($retval)) {
70        $ini = $query['dateini'] ?? null;
71        if ($ini === null) {
72            $logger->error("Mandatory argument 'dateini' not provided in url parameters.");
73            $retval = \ApplicationError::Parameters;
74        } else {
75            $ini = date("Y-m-d H:i:s", (int) $ini);
76        }
77    }
78    if (\success($retval)) {
79        $end = $query['dateend'] ?? null;
80        if ($end === null) {
81            $logger->error("Mandatory argument 'dateend' not provided in url parameters.");
82            $retval = \ApplicationError::Parameters;
83        } else {
84            $end = date("Y-m-d H:i:s", (int) $end);
85        }
86    }
87    if (\success($retval)) {
88        $timestamp = $query['timestamp'] ?? null;
89        if ($timestamp === null) {
90            $logger->error("Mandatory argument 'timestamp' not provided in url parameters.");
91            $retval = \ApplicationError::Parameters;
92        }
93    }
94
95    $ticketproducts = [];
96    if (\success($retval)) {
97        $tsDatetime = is_numeric($timestamp) ? date('Y-m-d H:i:s', (int) $timestamp) : (string) $timestamp;
98        if ($tsDatetime === '' || $tsDatetime === '0' || (string) $timestamp === '0') {
99            $tsDatetime = '1970-01-01 00:00:00';
100        }
101        $startRange = ($tsDatetime > $ini) ? $tsDatetime : $ini;
102
103        $sql = "select TICKETPRODUCT.objid, TICKETPRODUCT.product, TICKETPRODUCT.offer, TICKETPRODUCT.amount, TICKETPRODUCT.charge ";
104        $sql .= "from TICKETPRODUCT inner join TICKET on TICKETPRODUCT.ticket = TICKET.objid ";
105        $sql .= "where product in (select PRODUCT.objid from PRODUCT) ";
106        $sql .= "and TICKET.status in ('AC', 'RD', 'PP', 'PD') ";
107        $sql .= "and TICKETPRODUCT.status = 'AC' ";
108        $sql .= "and TICKETPRODUCT.updated >= '".$startRange."' ";
109        $sql .= "and TICKETPRODUCT.created >= '".$ini."' ";
110        $sql .= "and TICKETPRODUCT.created <= '".$end."' ";
111        $res = $conx->tenant->runsql('TICKETPRODUCT', $sql, $ticketproducts);
112        if (!$res) {
113            $logger->error("Product list query failed for place ".$place);
114            $retval = \ApplicationError::Database;
115        }
116    }
117
118    $indexed = [];
119    if (\success($retval)) {
120        foreach ($ticketproducts as &$ticketproduct) {
121            $product = $ticketproduct['product'];
122            if (!array_key_exists($product, $indexed)) {
123                $indexed[$product] = ['product' => null, 'ticketproducts' => []];
124            }
125            $indexed[$product]['ticketproducts'][] = [
126                "objid" => (int) $ticketproduct['objid'],
127                "inoffer" => $ticketproduct['offer'] ? true : false,
128                "amount" => (float) $ticketproduct['amount'],
129                "charge" => (float) $ticketproduct['charge'],
130            ];
131        }
132        unset($ticketproduct);
133    }
134
135    $productRows = [];
136    if (\success($retval) && count($indexed) > 0) {
137        $products = [];
138        $res = $conx->tenant->query('PRODUCT', ['objid' => array_keys($indexed)], $products);
139        if (!$res) {
140            $logger->error("Product query failed for place ".$place);
141            $retval = \ApplicationError::Database;
142        } else {
143            foreach ($products as &$product) {
144                $indexed[$product['objid']]['product'] = &$product;
145                $productRows[(string) $product['objid']] = $product;
146            }
147            unset($product);
148        }
149    }
150
151    if (\success($retval) && count($productRows) > 0) {
152        $pendingNext = [];
153        foreach ($productRows as $row) {
154            if (!empty($row['next'])) {
155                $pendingNext[(string) $row['next']] = true;
156            }
157        }
158
159        while (count($pendingNext) > 0) {
160            $toFetch = [];
161            foreach (array_keys($pendingNext) as $nextId) {
162                if (!array_key_exists($nextId, $productRows)) {
163                    $toFetch[] = $nextId;
164                }
165            }
166            $pendingNext = [];
167            if (count($toFetch) === 0) {
168                break;
169            }
170
171            $extra = [];
172            if (!$conx->tenant->query('PRODUCT', ['objid' => $toFetch], $extra)) {
173                $logger->error("Product chain query failed for place ".$place);
174                $retval = \ApplicationError::Database;
175                break;
176            }
177
178            foreach ($extra as $row) {
179                $productRows[(string) $row['objid']] = $row;
180                if (!empty($row['next'])) {
181                    $pendingNext[(string) $row['next']] = true;
182                }
183            }
184        }
185    }
186
187    $chainHeadByObjid = [];
188    $resolveChainHead = static function (string $objid) use (&$productRows, &$chainHeadByObjid): string {
189        if (array_key_exists($objid, $chainHeadByObjid)) {
190            return $chainHeadByObjid[$objid];
191        }
192
193        $visited = [];
194        $current = $objid;
195        while (true) {
196            $row = $productRows[$current] ?? null;
197            if ($row === null || empty($row['next'])) {
198                $chainHeadByObjid[$objid] = $current;
199
200                return $current;
201            }
202
203            $next = (string) $row['next'];
204            if (array_key_exists($next, $visited)) {
205                $chainHeadByObjid[$objid] = $next;
206
207                return $next;
208            }
209
210            $visited[$current] = true;
211            $current = $next;
212        }
213    };
214
215    $productlist = [];
216    if (\success($retval)) {
217        $nameIds = [];
218        foreach ($productRows as $row) {
219            if (!empty($row['name'])) {
220                $nameIds[] = $row['name'];
221            }
222        }
223
224        $i18nMaps = [];
225        $nameIds = array_values(array_filter($nameIds, static fn ($id) => $id !== null && $id !== '' && $id !== 0));
226        if (count($nameIds) > 0) {
227            $i18nRows = [];
228            if ($conx->tenant->query('I18NVALUE', ['objid' => $nameIds], $i18nRows)) {
229                foreach ($i18nRows as $row) {
230                    $map = [];
231                    foreach (['ES', 'EN', 'EU', 'ZH'] as $lang) {
232                        if (!array_key_exists($lang, $row)) {
233                            continue;
234                        }
235                        $text = $row[$lang];
236                        if ($text !== null && $text !== '') {
237                            $map[$lang] = (string) $text;
238                        }
239                    }
240                    if (count($map) > 0) {
241                        $i18nMaps[$row['objid']] = $map;
242                    }
243                }
244            }
245        }
246
247        foreach ($indexed as $objid => &$info) {
248            if (!$info['product']) {
249                continue;
250            }
251
252            $productObjid = (string) $info['product']['objid'];
253            $chainHead = $resolveChainHead($productObjid);
254            $headRow = $productRows[$chainHead] ?? $info['product'];
255            $nameFk = $info['product']['name'] ?? null;
256            $headNameFk = $headRow['name'] ?? null;
257            $nextFk = $info['product']['next'] ?? null;
258            $status = $info['product']['status'] ?? null;
259            $headStatus = $headRow['status'] ?? null;
260
261            $productlist[] = [
262                'objid' => $info['product']['objid'],
263                'name' => ($nameFk && isset($i18nMaps[$nameFk])) ? $i18nMaps[$nameFk] : null,
264                'headName' => ($headNameFk && isset($i18nMaps[$headNameFk])) ? $i18nMaps[$headNameFk] : null,
265                'price' => round((float) $info['product']['price'], 2),
266                'items' => $info['ticketproducts'],
267                'next' => ($nextFk !== null && $nextFk !== '' && $nextFk !== 0) ? $nextFk : null,
268                'status' => ($status !== null && $status !== '') ? (string) $status : null,
269                'headStatus' => ($headStatus !== null && $headStatus !== '') ? (string) $headStatus : null,
270                'chainHead' => $chainHead,
271            ];
272        }
273        unset($info);
274    }
275
276    $result = [
277        "errorcode" => $retval,
278        "products" => \success($retval) ? $productlist : null,
279        "timestamp" => \success($retval) ? $now : null,
280    ];
281
282    return ['output' => json_encode($result), 'contentType' => 'application/json; charset=utf-8'];
283};