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