1 | <?php |
||
2 | /** |
||
3 | * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). |
||
4 | * |
||
5 | * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) |
||
6 | * |
||
7 | * This program is free software: you can redistribute it and/or modify |
||
8 | * it under the terms of the GNU Affero General Public License as published |
||
9 | * by the Free Software Foundation, either version 3 of the License, or |
||
10 | * (at your option) any later version. |
||
11 | * |
||
12 | * This program is distributed in the hope that it will be useful, |
||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
15 | * GNU Affero General Public License for more details. |
||
16 | * |
||
17 | * You should have received a copy of the GNU Affero General Public License |
||
18 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
||
19 | */ |
||
20 | |||
21 | declare(strict_types=1); |
||
22 | |||
23 | namespace App\Entity\LogSystem; |
||
24 | |||
25 | use App\Entity\Attachments\Attachment; |
||
26 | use App\Entity\Attachments\AttachmentType; |
||
27 | use App\Entity\Base\AbstractDBElement; |
||
28 | use App\Entity\ProjectSystem\Project; |
||
29 | use App\Entity\ProjectSystem\ProjectBOMEntry; |
||
30 | use App\Entity\LabelSystem\LabelProfile; |
||
31 | use App\Entity\Parameters\AbstractParameter; |
||
32 | use App\Entity\Parts\Category; |
||
33 | use App\Entity\Parts\Footprint; |
||
34 | use App\Entity\Parts\Manufacturer; |
||
35 | use App\Entity\Parts\MeasurementUnit; |
||
36 | use App\Entity\Parts\Part; |
||
37 | use App\Entity\Parts\PartLot; |
||
38 | use App\Entity\Parts\Storelocation; |
||
39 | use App\Entity\Parts\Supplier; |
||
40 | use App\Entity\PriceInformations\Currency; |
||
41 | use App\Entity\PriceInformations\Orderdetail; |
||
42 | use App\Entity\PriceInformations\Pricedetail; |
||
43 | use App\Entity\UserSystem\Group; |
||
44 | use App\Entity\UserSystem\User; |
||
45 | use DateTime; |
||
46 | use Doctrine\ORM\Mapping as ORM; |
||
47 | use InvalidArgumentException; |
||
48 | use Psr\Log\LogLevel; |
||
49 | |||
50 | /** |
||
51 | * This entity describes a entry in the event log. |
||
52 | * |
||
53 | * @ORM\Entity(repositoryClass="App\Repository\LogEntryRepository") |
||
54 | * @ORM\Table("log", indexes={ |
||
55 | * @ORM\Index(name="log_idx_type", columns={"type"}), |
||
56 | * @ORM\Index(name="log_idx_type_target", columns={"type", "target_type", "target_id"}), |
||
57 | * @ORM\Index(name="log_idx_datetime", columns={"datetime"}), |
||
58 | * }) |
||
59 | * @ORM\InheritanceType("SINGLE_TABLE") |
||
60 | * @ORM\DiscriminatorColumn(name="type", type="smallint") |
||
61 | * @ORM\DiscriminatorMap({ |
||
62 | * 1 = "UserLoginLogEntry", |
||
63 | * 2 = "UserLogoutLogEntry", |
||
64 | * 3 = "UserNotAllowedLogEntry", |
||
65 | * 4 = "ExceptionLogEntry", |
||
66 | * 5 = "ElementDeletedLogEntry", |
||
67 | * 6 = "ElementCreatedLogEntry", |
||
68 | * 7 = "ElementEditedLogEntry", |
||
69 | * 8 = "ConfigChangedLogEntry", |
||
70 | * 9 = "LegacyInstockChangedLogEntry", |
||
71 | * 10 = "DatabaseUpdatedLogEntry", |
||
72 | * 11 = "CollectionElementDeleted", |
||
73 | * 12 = "SecurityEventLogEntry", |
||
74 | * 13 = "PartStockChangedLogEntry", |
||
75 | * }) |
||
76 | */ |
||
77 | abstract class AbstractLogEntry extends AbstractDBElement |
||
78 | { |
||
79 | public const LEVEL_EMERGENCY = 0; |
||
80 | public const LEVEL_ALERT = 1; |
||
81 | public const LEVEL_CRITICAL = 2; |
||
82 | public const LEVEL_ERROR = 3; |
||
83 | public const LEVEL_WARNING = 4; |
||
84 | public const LEVEL_NOTICE = 5; |
||
85 | public const LEVEL_INFO = 6; |
||
86 | public const LEVEL_DEBUG = 7; |
||
87 | |||
88 | protected const TARGET_TYPE_NONE = 0; |
||
89 | protected const TARGET_TYPE_USER = 1; |
||
90 | protected const TARGET_TYPE_ATTACHEMENT = 2; |
||
91 | protected const TARGET_TYPE_ATTACHEMENTTYPE = 3; |
||
92 | protected const TARGET_TYPE_CATEGORY = 4; |
||
93 | protected const TARGET_TYPE_DEVICE = 5; |
||
94 | protected const TARGET_TYPE_DEVICEPART = 6; |
||
95 | protected const TARGET_TYPE_FOOTPRINT = 7; |
||
96 | protected const TARGET_TYPE_GROUP = 8; |
||
97 | protected const TARGET_TYPE_MANUFACTURER = 9; |
||
98 | protected const TARGET_TYPE_PART = 10; |
||
99 | protected const TARGET_TYPE_STORELOCATION = 11; |
||
100 | protected const TARGET_TYPE_SUPPLIER = 12; |
||
101 | protected const TARGET_TYPE_PARTLOT = 13; |
||
102 | protected const TARGET_TYPE_CURRENCY = 14; |
||
103 | protected const TARGET_TYPE_ORDERDETAIL = 15; |
||
104 | protected const TARGET_TYPE_PRICEDETAIL = 16; |
||
105 | protected const TARGET_TYPE_MEASUREMENTUNIT = 17; |
||
106 | protected const TARGET_TYPE_PARAMETER = 18; |
||
107 | protected const TARGET_TYPE_LABEL_PROFILE = 19; |
||
108 | |||
109 | /** |
||
110 | * @var array This const is used to convert the numeric level to a PSR-3 compatible log level |
||
111 | */ |
||
112 | protected const LEVEL_ID_TO_STRING = [ |
||
113 | self::LEVEL_EMERGENCY => LogLevel::EMERGENCY, |
||
114 | self::LEVEL_ALERT => LogLevel::ALERT, |
||
115 | self::LEVEL_CRITICAL => LogLevel::CRITICAL, |
||
116 | self::LEVEL_ERROR => LogLevel::ERROR, |
||
117 | self::LEVEL_WARNING => LogLevel::WARNING, |
||
118 | self::LEVEL_NOTICE => LogLevel::NOTICE, |
||
119 | self::LEVEL_INFO => LogLevel::INFO, |
||
120 | self::LEVEL_DEBUG => LogLevel::DEBUG, |
||
121 | ]; |
||
122 | |||
123 | protected const TARGET_CLASS_MAPPING = [ |
||
124 | self::TARGET_TYPE_USER => User::class, |
||
125 | self::TARGET_TYPE_ATTACHEMENT => Attachment::class, |
||
126 | self::TARGET_TYPE_ATTACHEMENTTYPE => AttachmentType::class, |
||
127 | self::TARGET_TYPE_CATEGORY => Category::class, |
||
128 | self::TARGET_TYPE_DEVICE => Project::class, |
||
129 | self::TARGET_TYPE_DEVICEPART => ProjectBOMEntry::class, |
||
130 | self::TARGET_TYPE_FOOTPRINT => Footprint::class, |
||
131 | self::TARGET_TYPE_GROUP => Group::class, |
||
132 | self::TARGET_TYPE_MANUFACTURER => Manufacturer::class, |
||
133 | self::TARGET_TYPE_PART => Part::class, |
||
134 | self::TARGET_TYPE_STORELOCATION => Storelocation::class, |
||
135 | self::TARGET_TYPE_SUPPLIER => Supplier::class, |
||
136 | self::TARGET_TYPE_PARTLOT => PartLot::class, |
||
137 | self::TARGET_TYPE_CURRENCY => Currency::class, |
||
138 | self::TARGET_TYPE_ORDERDETAIL => Orderdetail::class, |
||
139 | self::TARGET_TYPE_PRICEDETAIL => Pricedetail::class, |
||
140 | self::TARGET_TYPE_MEASUREMENTUNIT => MeasurementUnit::class, |
||
141 | self::TARGET_TYPE_PARAMETER => AbstractParameter::class, |
||
142 | self::TARGET_TYPE_LABEL_PROFILE => LabelProfile::class, |
||
143 | ]; |
||
144 | |||
145 | /** @var User The user which has caused this log entry |
||
146 | * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", fetch="EAGER") |
||
147 | * @ORM\JoinColumn(name="id_user", nullable=true, onDelete="SET NULL") |
||
148 | */ |
||
149 | protected ?User $user = null; |
||
150 | |||
151 | /** |
||
152 | * @var string The username of the user which has caused this log entry (shown if the user is deleted) |
||
153 | * @ORM\Column(type="string", nullable=false) |
||
154 | */ |
||
155 | protected string $username = ''; |
||
156 | |||
157 | /** @var DateTime The datetime the event associated with this log entry has occured |
||
158 | * @ORM\Column(type="datetime", name="datetime") |
||
159 | */ |
||
160 | protected ?DateTime $timestamp = null; |
||
161 | |||
162 | /** @var int The priority level of the associated level. 0 is highest, 7 lowest |
||
163 | * @ORM\Column(type="integer", name="level", columnDefinition="TINYINT(4) NOT NULL") |
||
164 | */ |
||
165 | protected int $level; |
||
166 | |||
167 | /** @var int The ID of the element targeted by this event |
||
168 | * @ORM\Column(name="target_id", type="integer", nullable=false) |
||
169 | */ |
||
170 | protected int $target_id = 0; |
||
171 | |||
172 | /** @var int The Type of the targeted element |
||
173 | * @ORM\Column(name="target_type", type="smallint", nullable=false) |
||
174 | */ |
||
175 | protected int $target_type = 0; |
||
176 | |||
177 | /** @var string The type of this log entry, aka the description what has happened. |
||
178 | * The mapping between the log entry class and the discriminator column is done by doctrine. |
||
179 | * Each subclass should override this string to specify a better string. |
||
180 | */ |
||
181 | protected string $typeString = 'unknown'; |
||
182 | |||
183 | /** @var array The extra data in raw (short form) saved in the DB |
||
184 | * @ORM\Column(name="extra", type="json") |
||
185 | */ |
||
186 | protected $extra = []; |
||
187 | |||
188 | public function __construct() |
||
189 | { |
||
190 | $this->timestamp = new DateTime(); |
||
191 | $this->level = self::LEVEL_WARNING; |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Get the user that caused the event associated with this log entry. |
||
196 | * |
||
197 | * @return User |
||
198 | */ |
||
199 | public function getUser(): ?User |
||
200 | { |
||
201 | return $this->user; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Sets the user that caused the event. |
||
206 | * |
||
207 | * @return $this |
||
208 | */ |
||
209 | public function setUser(User $user): self |
||
210 | { |
||
211 | $this->user = $user; |
||
212 | |||
213 | //Save the username for later use |
||
214 | $this->username = $user->getUsername(); |
||
215 | |||
216 | return $this; |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * Retuns the username of the user that caused the event (useful if the user was deleted). |
||
221 | * |
||
222 | * @return string |
||
223 | */ |
||
224 | public function getUsername(): string |
||
225 | { |
||
226 | return $this->username; |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Returns the timestamp when the event that caused this log entry happened. |
||
231 | */ |
||
232 | public function getTimestamp(): DateTime |
||
233 | { |
||
234 | return $this->timestamp; |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Sets the timestamp when the event happened. |
||
239 | * |
||
240 | * @return $this |
||
241 | */ |
||
242 | public function setTimestamp(DateTime $timestamp): self |
||
243 | { |
||
244 | $this->timestamp = $timestamp; |
||
245 | |||
246 | return $this; |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * Get the priority level of this log entry. 0 is highest and 7 lowest level. |
||
251 | * See LEVEL_* consts in this class for more info. |
||
252 | */ |
||
253 | public function getLevel(): int |
||
254 | { |
||
255 | //It is always alerting when a wrong int is saved in DB... |
||
256 | if ($this->level < 0 || $this->level > 7) { |
||
257 | return self::LEVEL_ALERT; |
||
258 | } |
||
259 | |||
260 | return $this->level; |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Sets the new level of this log entry. |
||
265 | * |
||
266 | * @return $this |
||
267 | */ |
||
268 | public function setLevel(int $level): self |
||
269 | { |
||
270 | if ($level < 0 || $this->level > 7) { |
||
271 | throw new InvalidArgumentException(sprintf('$level must be between 0 and 7! %d given!', $level)); |
||
272 | } |
||
273 | $this->level = $level; |
||
274 | |||
275 | return $this; |
||
276 | } |
||
277 | |||
278 | /** |
||
279 | * Get the priority level of this log entry as PSR3 compatible string. |
||
280 | */ |
||
281 | public function getLevelString(): string |
||
282 | { |
||
283 | return self::levelIntToString($this->getLevel()); |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Sets the priority level of this log entry as PSR3 compatible string. |
||
288 | * |
||
289 | * @return $this |
||
290 | */ |
||
291 | public function setLevelString(string $level): self |
||
292 | { |
||
293 | $this->setLevel(self::levelStringToInt($level)); |
||
294 | |||
295 | return $this; |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * Returns the type of the event this log entry is associated with. |
||
300 | */ |
||
301 | public function getType(): string |
||
302 | { |
||
303 | return $this->typeString; |
||
304 | } |
||
305 | |||
306 | /** |
||
307 | * Returns the class name of the target element associated with this log entry. |
||
308 | * Returns null, if this log entry is not associated with an log entry. |
||
309 | * |
||
310 | * @return string|null the class name of the target class |
||
311 | */ |
||
312 | public function getTargetClass(): ?string |
||
313 | { |
||
314 | if (self::TARGET_TYPE_NONE === $this->target_type) { |
||
315 | return null; |
||
316 | } |
||
317 | |||
318 | return self::targetTypeIdToClass($this->target_type); |
||
319 | } |
||
320 | |||
321 | /** |
||
322 | * Returns the ID of the target element associated with this log entry. |
||
323 | * Returns null, if this log entry is not associated with an log entry. |
||
324 | * |
||
325 | * @return int|null the ID of the associated element |
||
326 | */ |
||
327 | public function getTargetID(): ?int |
||
328 | { |
||
329 | if (0 === $this->target_id) { |
||
330 | return null; |
||
331 | } |
||
332 | |||
333 | return $this->target_id; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Checks if this log entry is associated with an element. |
||
338 | * |
||
339 | * @return bool true if this log entry is associated with an element, false otherwise |
||
340 | */ |
||
341 | public function hasTarget(): bool |
||
342 | { |
||
343 | return null !== $this->getTargetID() && null !== $this->getTargetClass(); |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * Sets the target element associated with this element. |
||
348 | * |
||
349 | * @param AbstractDBElement|null $element the element that should be associated with this element |
||
350 | * |
||
351 | * @return $this |
||
352 | */ |
||
353 | public function setTargetElement(?AbstractDBElement $element): self |
||
354 | { |
||
355 | if (null === $element) { |
||
356 | $this->target_id = 0; |
||
357 | $this->target_type = self::TARGET_TYPE_NONE; |
||
358 | |||
359 | return $this; |
||
360 | } |
||
361 | |||
362 | $this->target_type = static::targetTypeClassToID(get_class($element)); |
||
363 | $this->target_id = $element->getID(); |
||
364 | |||
365 | return $this; |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * Sets the target ID of the element associated with this element. |
||
370 | * |
||
371 | * @return $this |
||
372 | */ |
||
373 | public function setTargetElementID(int $target_id): self |
||
374 | { |
||
375 | $this->target_id = $target_id; |
||
376 | |||
377 | return $this; |
||
378 | } |
||
379 | |||
380 | public function getExtraData(): array |
||
381 | { |
||
382 | return $this->extra; |
||
383 | } |
||
384 | |||
385 | /** |
||
386 | * This function converts the internal numeric log level into an PSR3 compatible level string. |
||
387 | * |
||
388 | * @param int $level The numerical log level |
||
389 | * |
||
390 | * @return string The PSR3 compatible level string |
||
391 | */ |
||
392 | final public static function levelIntToString(int $level): string |
||
393 | { |
||
394 | if (!isset(self::LEVEL_ID_TO_STRING[$level])) { |
||
395 | throw new InvalidArgumentException('No level with this int is existing!'); |
||
396 | } |
||
397 | |||
398 | return self::LEVEL_ID_TO_STRING[$level]; |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * This function converts a PSR3 compatible string to the internal numeric level string. |
||
403 | * |
||
404 | * @param string $level the PSR3 compatible string that should be converted |
||
405 | * |
||
406 | * @return int the internal int representation |
||
407 | */ |
||
408 | final public static function levelStringToInt(string $level): int |
||
409 | { |
||
410 | $tmp = array_flip(self::LEVEL_ID_TO_STRING); |
||
411 | if (!isset($tmp[$level])) { |
||
412 | throw new InvalidArgumentException('No level with this string is existing!'); |
||
413 | } |
||
414 | |||
415 | return $tmp[$level]; |
||
416 | } |
||
417 | |||
418 | /** |
||
419 | * Converts an target type id to an full qualified class name. |
||
420 | * |
||
421 | * @param int $type_id The target type ID |
||
422 | */ |
||
423 | final public static function targetTypeIdToClass(int $type_id): string |
||
424 | { |
||
425 | if (!isset(self::TARGET_CLASS_MAPPING[$type_id])) { |
||
426 | throw new InvalidArgumentException('No target type with this ID is existing!'); |
||
427 | } |
||
428 | |||
429 | return self::TARGET_CLASS_MAPPING[$type_id]; |
||
430 | } |
||
431 | |||
432 | /** |
||
433 | * Convert a class name to a target type ID. |
||
434 | * |
||
435 | * @param string $class The name of the class (FQN) that should be converted to id |
||
436 | * |
||
437 | * @return int the ID of the associated target type ID |
||
438 | */ |
||
439 | final public static function targetTypeClassToID(string $class): int |
||
440 | { |
||
441 | $tmp = array_flip(self::TARGET_CLASS_MAPPING); |
||
442 | //Check if we can use a key directly |
||
443 | if (isset($tmp[$class])) { |
||
444 | return $tmp[$class]; |
||
445 | } |
||
446 | |||
447 | //Otherwise we have to iterate over everything and check for inheritance |
||
448 | foreach ($tmp as $compare_class => $class_id) { |
||
449 | if (is_a($class, $compare_class, true)) { |
||
450 | return $class_id; |
||
451 | } |
||
452 | } |
||
453 | |||
454 | throw new InvalidArgumentException('No target ID for this class is existing! (Class: '.$class.')'); |
||
455 | } |
||
456 | } |
||
457 |