Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
47.01% covered (danger)
47.01%
55 / 117
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Log
47.01% covered (danger)
47.01%
55 / 117
37.50% covered (danger)
37.50%
3 / 8
154.14
0.00% covered (danger)
0.00%
0 / 1
 writeLogEntry
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 writeProcessLog
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
6
 readByProcessId
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 readByProcessData
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 getBySearchParams
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
110
 delete
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 backtraceLogEntry
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
5.05
 clearLogsOlderThan
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BO\Zmsdb;
4
5use BO\Zmsentities\Collection\LogList;
6use BO\Zmsentities\Collection\RequestList;
7use BO\Zmsentities\Log as Entity;
8use DateTime;
9
10/**
11 * Logging for actions
12 *
13 */
14class Log extends Base
15{
16    const PROCESS = 'buerger';
17    const MIGRATION = 'migration';
18    const ERROR = 'error';
19
20    const ACTION_MAIL_SUCCESS = 'E-Mail-Versand erfolgreich';
21    const ACTION_MAIL_FAIL = 'E-Mail-Versand ist fehlgeschlagen';
22    const ACTION_STATUS_CHANGE = 'Terminstatus wurde geändert';
23    const ACTION_SEND_REMINDER = 'Erinnerungsmail wurde gesendet';
24    const ACTION_REMOVED = 'Termin aus der Warteschlange entfernt';
25    const ACTION_CALLED = 'Termin wurde aufgerufen';
26    const ACTION_ARCHIVED = 'Termin wurde archiviert';
27    const ACTION_EDITED = 'Termin wurde geändert';
28    const ACTION_REDIRECTED = 'Termin wurde weitergeleitet';
29    const ACTION_NEW = 'Neuer Termin wurde erstellt';
30    const ACTION_DELETED = 'Termin wurde gelöscht';
31    const ACTION_CANCELED = 'Termin wurde abgesagt';
32
33    public static $operator = 'lib';
34
35    public static function writeLogEntry(
36        $message,
37        $referenceId,
38        $type = self::PROCESS,
39        ?int $scopeId = null,
40        ?string $userId = null,
41        ?string $data = null
42    ) {
43        $message .= " [" . static::$operator . "]";
44        $log = new static();
45        $sql = "INSERT INTO `log` SET 
46`message`=:message, 
47`reference_id`=:referenceId, 
48`type`=:type, 
49`scope_id`=:scopeId,
50`user_id`=:userId,
51`data`=:dataString";
52
53        $parameters = [
54            "message" => $message . static::backtraceLogEntry(),
55            "referenceId" => $referenceId,
56            "type" => $type,
57            "scopeId" => $scopeId,
58            "userId" => $userId,
59            "dataString" => $data
60        ];
61
62        return $log->perform($sql, $parameters);
63    }
64
65    public static function writeProcessLog(
66        string $method,
67        string $action,
68        ?\BO\Zmsentities\Process $process,
69        ?\BO\Zmsentities\Useraccount $userAccount = null
70    ) {
71        if (empty($process) || empty($process->getId()) || empty($userAccount)) {
72            return;
73        }
74
75        $requests = new RequestList();
76        if (! empty($process->getRequestIds())) {
77            $requests = (new Request())->readRequestsByIds($process->getRequestIds());
78        }
79
80        $data = json_encode(array_filter([
81            'Aktion' => $action,
82            "Sachbearbeiter*in" => $userAccount ? $userAccount->getId() : '',
83            "Terminnummer" => $process->getDisplayNumber(),
84            "Wartenummer" => $process->getQueueNumber(),
85            "Terminzeit" => $process->getFirstAppointment()->toDateTime()->format('d.m.Y H:i:s'),
86            "Bürger*in" => $process->getFirstClient()->familyName,
87            "Dienstleistungen" => implode(', ', array_map(function ($request) {
88                return $request->getName();
89            }, $requests->getAsArray())),
90            "Anmerkung" => $process->getAmendment(),
91            "Standort" => $process->scope->getName(),
92            "E-Mail" => $process->getFirstClient()->email,
93            "Telefon" => $process->getFirstClient()->telephone,
94            "Status" => $process->getStatus(),
95            "DB Status" => $process->dbstatus,
96        ]), JSON_UNESCAPED_UNICODE);
97
98        Log::writeLogEntry(
99            $method,
100            $process->getId(),
101            self::PROCESS,
102            $process->getScopeId(),
103            $userAccount->getId(),
104            $data
105        );
106    }
107
108    public function readByProcessId($processId)
109    {
110        $query = new Query\Log(Query\Base::SELECT);
111        $query->addEntityMapping();
112        $query->addConditionProcessId($processId);
113        $logList = new \BO\Zmsentities\Collection\LogList($this->fetchList($query, new Entity()));
114        return $logList;
115    }
116
117    public function readByProcessData(
118        $generalSearch,
119        $service,
120        $provider,
121        $date,
122        $userAction,
123        $page = 1,
124        $perPage = 100
125    ) {
126        $params = [];
127        if ($provider) {
128            $params['Standort'] = $provider;
129        }
130
131        if ($service) {
132            $params['en'] = $service;
133        }
134
135        return $this->getBySearchParams(
136            $params,
137            $generalSearch,
138            $userAction,
139            $date,
140            $perPage,
141            ($page - 1)  * $perPage
142        );
143    }
144
145    public function getBySearchParams(
146        array $fieldValues,
147        ?string $generalSearch,
148        int $userAction,
149        ?DateTime $date,
150        int $perPage,
151        int $offset
152    ) {
153        $sql = "SELECT * FROM log";
154        $conditions = [];
155        $params = [];
156
157        foreach ($fieldValues as $field => $value) {
158            if ($value === null || $value === '') {
159                continue;
160            }
161
162            $escapedField = addslashes($field);
163            $escapedValue = addslashes($value);
164
165            $conditions[] = "(data LIKE '%$escapedField:$escapedValue%' OR data LIKE '%$escapedField\":\"$escapedValue%')";
166        }
167
168        if (!empty($generalSearch)) {
169            $conditions[] = "data LIKE :generalSearch";
170            $params['generalSearch'] = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $generalSearch) . '%';
171        }
172
173        if (!empty($date)) {
174            $start = (clone $date)->setTime(0, 0, 0);
175            $end = (clone $date)->setTime(0, 0, 0)->add(new \DateInterval('P1D'));
176            $conditions[] = "(ts >= :start AND ts < :end)";
177            $params['start'] = $start->format('Y-m-d H:i:s');
178            $params['end'] = $end->format('Y-m-d H:i:s');
179        }
180
181        if ($userAction === 1) {
182            $conditions[] = "data like :ua_yes";
183            $conditions[] = "data not like :ua_system";
184            $params['ua_yes'] = '%Sachbearbeiter*in%';
185            $params['ua_system'] = '%Sachbearbeiter*in\":\"_system_%';
186        }
187
188        if ($userAction === 2) {
189            $conditions[] = "(data like :ua_system OR data not like :ua_yes)";
190            $params['ua_yes'] = '%Sachbearbeiter*in%';
191            $params['ua_system'] = '%Sachbearbeiter*in\":\"_system_%';
192        }
193
194        if (!empty($conditions)) {
195            $sql .= " WHERE " . implode(' AND ', $conditions);
196        }
197
198        $sql .= " ORDER BY ts DESC LIMIT $perPage OFFSET $offset";
199
200        $rows = $this->fetchAll($sql, $params);
201
202        $logs = new LogList();
203        foreach ($rows as $row) {
204            $logs->addEntity(new Entity($row));
205        }
206
207        return $logs;
208    }
209
210    public function delete($processId)
211    {
212        $query = new Query\Log(Query\Base::SELECT);
213        $query->addEntityMapping();
214        $query->addConditionProcessId($processId);
215        $logList = new \BO\Zmsentities\Collection\LogList($this->fetchList($query, new Entity()));
216        return $logList;
217    }
218
219    protected static function backtraceLogEntry()
220    {
221        $trace = debug_backtrace();
222        $short = '';
223        foreach ($trace as $step) {
224            if (
225                isset($step['file'])
226                && isset($step['line'])
227                && !strpos($step['file'], 'Zmsdb')
228            ) {
229                return ' (' . basename($step['file'], '.php') . ')';
230            }
231        }
232        return $short;
233    }
234
235    public function clearLogsOlderThan(int $olderThan): bool
236    {
237        try {
238            $olderThanDate = (new \DateTime())->modify('-' . $olderThan . ' days');
239
240            $query = new Query\Log(Query\Base::DELETE);
241            $query->addConditionOlderThan($olderThanDate);
242
243            $result = $this->writeItem($query);
244            return $result !== false;
245        } catch (\Exception $e) {
246            error_log("Error during log cleanup: " . $e->getMessage());
247            return false;
248        }
249    }
250}