diff --git a/src/DataTables/Column/EnumColumn.php b/src/DataTables/Column/EnumColumn.php new file mode 100644 index 00000000..1f591108 --- /dev/null +++ b/src/DataTables/Column/EnumColumn.php @@ -0,0 +1,64 @@ +. + */ + +namespace App\DataTables\Column; + +use Omines\DataTablesBundle\Column\AbstractColumn; +use Symfony\Component\OptionsResolver\OptionsResolver; +use UnitEnum; + +/** + * @template T of UnitEnum + */ +class EnumColumn extends AbstractColumn +{ + + /** + * @phpstan-return T + */ + public function normalize($value): UnitEnum + { + if (is_a($value, $this->getEnumClass())) { + return $value; + } + + //@phpstan-ignore-next-line + return ($this->getEnumClass())::from($value); + } + + protected function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + $resolver->setRequired('class'); + $resolver->setAllowedTypes('class', 'string'); + $resolver->addAllowedValues('class', enum_exists(...)); + + return $this; + } + + /** + * @return class-string + */ + public function getEnumClass(): string + { + return $this->options['class']; + } +} diff --git a/src/DataTables/LogDataTable.php b/src/DataTables/LogDataTable.php index 7b6ba834..87317f40 100644 --- a/src/DataTables/LogDataTable.php +++ b/src/DataTables/LogDataTable.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\DataTables; +use App\DataTables\Column\EnumColumn; +use App\Entity\LogSystem\LogTargetType; use Symfony\Bundle\SecurityBundle\Security; use App\DataTables\Column\IconLinkColumn; use App\DataTables\Column\LocaleDateTimeColumn; @@ -186,11 +188,12 @@ class LogDataTable implements DataTableTypeInterface }, ]); - $dataTable->add('target_type', TextColumn::class, [ + $dataTable->add('target_type', EnumColumn::class, [ 'label' => 'log.target_type', 'visible' => false, - 'render' => function ($value, AbstractLogEntry $context) { - $class = $context->getTargetClass(); + 'class' => LogTargetType::class, + 'render' => function (LogTargetType $value, AbstractLogEntry $context) { + $class = $value->toClass(); if (null !== $class) { return $this->elementTypeNameGenerator->getLocalizedTypeLabel($class); } @@ -277,8 +280,8 @@ class LogDataTable implements DataTableTypeInterface ->andWhere('log.target_type NOT IN (:disallowed)'); $builder->setParameter('disallowed', [ - AbstractLogEntry::targetTypeClassToID(User::class), - AbstractLogEntry::targetTypeClassToID(Group::class), + LogTargetType::USER, + LogTargetType::GROUP, ]); } @@ -286,9 +289,12 @@ class LogDataTable implements DataTableTypeInterface foreach ($options['filter_elements'] as $element) { /** @var AbstractDBElement $element */ - $target_type = AbstractLogEntry::targetTypeClassToID($element::class); + $target_type = LogTargetType::fromElementClass($element); $target_id = $element->getID(); - $builder->orWhere("log.target_type = ${target_type} AND log.target_id = ${target_id}"); + + $builder->orWhere('log.target_type = :filter_target_type AND log.target_id = :filter_target_id'); + $builder->setParameter('filter_target_type', $target_type); + $builder->setParameter('filter_target_id', $target_id); } } } diff --git a/src/Entity/LogSystem/AbstractLogEntry.php b/src/Entity/LogSystem/AbstractLogEntry.php index eb16e3ca..1041cd6d 100644 --- a/src/Entity/LogSystem/AbstractLogEntry.php +++ b/src/Entity/LogSystem/AbstractLogEntry.php @@ -63,50 +63,6 @@ use App\Repository\LogEntryRepository; #[ORM\Index(columns: ['datetime'], name: 'log_idx_datetime')] abstract class AbstractLogEntry extends AbstractDBElement { - protected const TARGET_TYPE_NONE = 0; - protected const TARGET_TYPE_USER = 1; - protected const TARGET_TYPE_ATTACHEMENT = 2; - protected const TARGET_TYPE_ATTACHEMENTTYPE = 3; - protected const TARGET_TYPE_CATEGORY = 4; - protected const TARGET_TYPE_DEVICE = 5; - protected const TARGET_TYPE_DEVICEPART = 6; - protected const TARGET_TYPE_FOOTPRINT = 7; - protected const TARGET_TYPE_GROUP = 8; - protected const TARGET_TYPE_MANUFACTURER = 9; - protected const TARGET_TYPE_PART = 10; - protected const TARGET_TYPE_STORELOCATION = 11; - protected const TARGET_TYPE_SUPPLIER = 12; - protected const TARGET_TYPE_PARTLOT = 13; - protected const TARGET_TYPE_CURRENCY = 14; - protected const TARGET_TYPE_ORDERDETAIL = 15; - protected const TARGET_TYPE_PRICEDETAIL = 16; - protected const TARGET_TYPE_MEASUREMENTUNIT = 17; - protected const TARGET_TYPE_PARAMETER = 18; - protected const TARGET_TYPE_LABEL_PROFILE = 19; - - - protected const TARGET_CLASS_MAPPING = [ - self::TARGET_TYPE_USER => User::class, - self::TARGET_TYPE_ATTACHEMENT => Attachment::class, - self::TARGET_TYPE_ATTACHEMENTTYPE => AttachmentType::class, - self::TARGET_TYPE_CATEGORY => Category::class, - self::TARGET_TYPE_DEVICE => Project::class, - self::TARGET_TYPE_DEVICEPART => ProjectBOMEntry::class, - self::TARGET_TYPE_FOOTPRINT => Footprint::class, - self::TARGET_TYPE_GROUP => Group::class, - self::TARGET_TYPE_MANUFACTURER => Manufacturer::class, - self::TARGET_TYPE_PART => Part::class, - self::TARGET_TYPE_STORELOCATION => Storelocation::class, - self::TARGET_TYPE_SUPPLIER => Supplier::class, - self::TARGET_TYPE_PARTLOT => PartLot::class, - self::TARGET_TYPE_CURRENCY => Currency::class, - self::TARGET_TYPE_ORDERDETAIL => Orderdetail::class, - self::TARGET_TYPE_PRICEDETAIL => Pricedetail::class, - self::TARGET_TYPE_MEASUREMENTUNIT => MeasurementUnit::class, - self::TARGET_TYPE_PARAMETER => AbstractParameter::class, - self::TARGET_TYPE_LABEL_PROFILE => LabelProfile::class, - ]; - /** @var User|null The user which has caused this log entry */ #[ORM\ManyToOne(targetEntity: User::class, fetch: 'EAGER')] @@ -135,10 +91,10 @@ abstract class AbstractLogEntry extends AbstractDBElement #[ORM\Column(name: 'target_id', type: Types::INTEGER)] protected int $target_id = 0; - /** @var int The Type of the targeted element + /** @var LogTargetType The Type of the targeted element */ - #[ORM\Column(name: 'target_type', type: Types::SMALLINT)] - protected int $target_type = 0; + #[ORM\Column(name: 'target_type', type: Types::SMALLINT, enumType: LogTargetType::class)] + protected LogTargetType $target_type = LogTargetType::NONE; /** @var string The type of this log entry, aka the description what has happened. * The mapping between the log entry class and the discriminator column is done by doctrine. @@ -299,11 +255,16 @@ abstract class AbstractLogEntry extends AbstractDBElement */ public function getTargetClass(): ?string { - if (self::TARGET_TYPE_NONE === $this->target_type) { - return null; - } + return $this->target_type->toClass(); + } - return self::targetTypeIdToClass($this->target_type); + /** + * Returns the type of the target element associated with this log entry. + * @return LogTargetType + */ + public function getTargetType(): LogTargetType + { + return $this->target_type; } /** @@ -340,14 +301,14 @@ abstract class AbstractLogEntry extends AbstractDBElement */ public function setTargetElement(?AbstractDBElement $element): self { - if (!$element instanceof AbstractDBElement) { + if ($element === null) { $this->target_id = 0; - $this->target_type = self::TARGET_TYPE_NONE; + $this->target_type = LogTargetType::NONE; return $this; } - $this->target_type = static::targetTypeClassToID($element::class); + $this->target_type = LogTargetType::fromElementClass($element); $this->target_id = $element->getID(); return $this; @@ -370,42 +331,4 @@ abstract class AbstractLogEntry extends AbstractDBElement return $this->extra; } - /** - * Converts a target type id to a full qualified class name. - * - * @param int $type_id The target type ID - */ - final public static function targetTypeIdToClass(int $type_id): string - { - if (!isset(self::TARGET_CLASS_MAPPING[$type_id])) { - throw new InvalidArgumentException('No target type with this ID is existing!'); - } - - return self::TARGET_CLASS_MAPPING[$type_id]; - } - - /** - * Convert a class name to a target type ID. - * - * @param string $class The name of the class (FQN) that should be converted to id - * - * @return int the ID of the associated target type ID - */ - final public static function targetTypeClassToID(string $class): int - { - $tmp = array_flip(self::TARGET_CLASS_MAPPING); - //Check if we can use a key directly - if (isset($tmp[$class])) { - return $tmp[$class]; - } - - //Otherwise we have to iterate over everything and check for inheritance - foreach ($tmp as $compare_class => $class_id) { - if (is_a($class, $compare_class, true)) { - return $class_id; - } - } - - throw new InvalidArgumentException('No target ID for this class is existing! (Class: '.$class.')'); - } } diff --git a/src/Entity/LogSystem/CollectionElementDeleted.php b/src/Entity/LogSystem/CollectionElementDeleted.php index bc4a5bd9..50eed6ab 100644 --- a/src/Entity/LogSystem/CollectionElementDeleted.php +++ b/src/Entity/LogSystem/CollectionElementDeleted.php @@ -89,6 +89,8 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU { protected string $typeString = 'collection_element_deleted'; + + public function __construct(AbstractDBElement $changed_element, string $collection_name, AbstractDBElement $deletedElement) { parent::__construct(); @@ -97,7 +99,7 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU $this->setTargetElement($changed_element); $this->extra['n'] = $collection_name; - $this->extra['c'] = self::targetTypeClassToID($deletedElement::class); + $this->extra['c'] = LogTargetType::fromElementClass($deletedElement)->value; $this->extra['i'] = $deletedElement->getID(); if ($deletedElement instanceof NamedElementInterface) { $this->extra['o'] = $deletedElement->getName(); @@ -127,7 +129,7 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU public function getDeletedElementClass(): string { //The class name of our target element - $tmp = self::targetTypeIdToClass($this->extra['c']); + $tmp = LogTargetType::from($this->extra['c'])->toClass(); $reflection_class = new \ReflectionClass($tmp); //If the class is abstract, we have to map it to an instantiable class diff --git a/src/Entity/LogSystem/LogTargetType.php b/src/Entity/LogSystem/LogTargetType.php new file mode 100644 index 00000000..38d1f1ed --- /dev/null +++ b/src/Entity/LogSystem/LogTargetType.php @@ -0,0 +1,123 @@ +. + */ + +namespace App\Entity\LogSystem; + +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentType; +use App\Entity\LabelSystem\LabelProfile; +use App\Entity\Parameters\AbstractParameter; +use App\Entity\Parts\Category; +use App\Entity\Parts\Footprint; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; +use App\Entity\Parts\Supplier; +use App\Entity\PriceInformations\Currency; +use App\Entity\PriceInformations\Orderdetail; +use App\Entity\PriceInformations\Pricedetail; +use App\Entity\ProjectSystem\Project; +use App\Entity\ProjectSystem\ProjectBOMEntry; +use App\Entity\UserSystem\Group; +use App\Entity\UserSystem\User; + +enum LogTargetType: int +{ + case NONE = 0; + case USER = 1; + case ATTACHMENT = 2; + case ATTACHMENT_TYPE = 3; + case CATEGORY = 4; + case PROJECT = 5; + case BOM_ENTRY = 6; + case FOOTPRINT = 7; + case GROUP = 8; + case MANUFACTURER = 9; + case PART = 10; + case STORELOCATION = 11; + case SUPPLIER = 12; + case PART_LOT = 13; + case CURRENCY = 14; + case ORDERDETAIL = 15; + case PRICEDETAIL = 16; + case MEASUREMENT_UNIT = 17; + case PARAMETER = 18; + case LABEL_PROFILE = 19; + + /** + * Returns the class name of the target type or null if the target type is NONE. + * @return string|null + */ + public function toClass(): ?string + { + return match ($this) { + self::NONE => null, + self::USER => User::class, + self::ATTACHMENT => Attachment::class, + self::ATTACHMENT_TYPE => AttachmentType::class, + self::CATEGORY => Category::class, + self::PROJECT => Project::class, + self::BOM_ENTRY => ProjectBOMEntry::class, + self::FOOTPRINT => Footprint::class, + self::GROUP => Group::class, + self::MANUFACTURER => Manufacturer::class, + self::PART => Part::class, + self::STORELOCATION => Storelocation::class, + self::SUPPLIER => Supplier::class, + self::PART_LOT => PartLot::class, + self::CURRENCY => Currency::class, + self::ORDERDETAIL => Orderdetail::class, + self::PRICEDETAIL => Pricedetail::class, + self::MEASUREMENT_UNIT => MeasurementUnit::class, + self::PARAMETER => AbstractParameter::class, + self::LABEL_PROFILE => LabelProfile::class, + }; + } + + /** + * Determines the target type from the given class name or object. + * @param object|string $element + * @phpstan-param object|class-string $element + * @return self + */ + public static function fromElementClass(object|string $element): self + { + //Iterate over all possible types + foreach (self::cases() as $case) { + $class = $case->toClass(); + + //Skip NONE + if ($class === null) { + continue; + } + + //Check if the given element is a instance of the class + if (is_a($element, $class, true)) { + return $case; + } + } + + $elementClass = is_object($element) ? get_class($element) : $element; + //If no matching type was found, throw an exception + throw new \InvalidArgumentException("The given class $elementClass is not a valid log target type."); + } +} diff --git a/src/Form/Filters/LogFilterType.php b/src/Form/Filters/LogFilterType.php index 2db1aa14..0d8257f4 100644 --- a/src/Form/Filters/LogFilterType.php +++ b/src/Form/Filters/LogFilterType.php @@ -26,6 +26,7 @@ use App\DataTables\Filters\LogFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; use App\Entity\LogSystem\LogLevel; +use App\Entity\LogSystem\LogTargetType; use App\Entity\LogSystem\PartStockChangedLogEntry; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; @@ -123,29 +124,31 @@ class LogFilterType extends AbstractType 'label' => 'log.user', ]); - $builder->add('targetType', ChoiceConstraintType::class, [ + $builder->add('targetType', EnumConstraintType::class, [ 'label' => 'log.target_type', - 'choices' => [ - 'user.label' => AbstractLogEntry::targetTypeClassToID(User::class), - 'attachment.label' => AbstractLogEntry::targetTypeClassToID(Attachment::class), - 'attachment_type.label' => AbstractLogEntry::targetTypeClassToID(AttachmentType::class), - 'category.label' => AbstractLogEntry::targetTypeClassToID(Category::class), - 'project.label' => AbstractLogEntry::targetTypeClassToID(Project::class), - 'project_bom_entry.label' => AbstractLogEntry::targetTypeClassToID(ProjectBOMEntry::class), - 'footprint.label' => AbstractLogEntry::targetTypeClassToID(Footprint::class), - 'group.label' => AbstractLogEntry::targetTypeClassToID(Group::class), - 'manufacturer.label' => AbstractLogEntry::targetTypeClassToID(Manufacturer::class), - 'part.label' => AbstractLogEntry::targetTypeClassToID(Part::class), - 'storelocation.label' => AbstractLogEntry::targetTypeClassToID(Storelocation::class), - 'supplier.label' => AbstractLogEntry::targetTypeClassToID(Supplier::class), - 'part_lot.label' => AbstractLogEntry::targetTypeClassToID(PartLot::class), - 'currency.label' => AbstractLogEntry::targetTypeClassToID(Currency::class), - 'orderdetail.label' => AbstractLogEntry::targetTypeClassToID(Orderdetail::class), - 'pricedetail.label' => AbstractLogEntry::targetTypeClassToID(Pricedetail::class), - 'measurement_unit.label' => AbstractLogEntry::targetTypeClassToID(MeasurementUnit::class), - 'parameter.label' => AbstractLogEntry::targetTypeClassToID(AbstractParameter::class), - 'label_profile.label' => AbstractLogEntry::targetTypeClassToID(LabelProfile::class), - ] + 'enum_class' => LogTargetType::class, + 'choice_label' => fn(LogTargetType $type): string => match ($type) { + LogTargetType::NONE => 'log.target_type.none', + LogTargetType::USER => 'user.label', + LogTargetType::ATTACHMENT => 'attachment.label', + LogTargetType::ATTACHMENT_TYPE => 'attachment_type.label', + LogTargetType::CATEGORY => 'category.label', + LogTargetType::PROJECT => 'project.label', + LogTargetType::BOM_ENTRY => 'project_bom_entry.label', + LogTargetType::FOOTPRINT => 'footprint.label', + LogTargetType::GROUP => 'group.label', + LogTargetType::MANUFACTURER => 'manufacturer.label', + LogTargetType::PART => 'part.label', + LogTargetType::STORELOCATION => 'storelocation.label', + LogTargetType::SUPPLIER => 'supplier.label', + LogTargetType::PART_LOT => 'part_lot.label', + LogTargetType::CURRENCY => 'currency.label', + LogTargetType::ORDERDETAIL => 'orderdetail.label', + LogTargetType::PRICEDETAIL => 'pricedetail.label', + LogTargetType::MEASUREMENT_UNIT => 'measurement_unit.label', + LogTargetType::PARAMETER => 'parameter.label', + LogTargetType::LABEL_PROFILE => 'label_profile.label', + }, ]); $builder->add('targetId', NumberConstraintType::class, [ diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index b50c5204..472993a7 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -28,6 +28,7 @@ use App\Entity\LogSystem\CollectionElementDeleted; use App\Entity\LogSystem\ElementCreatedLogEntry; use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry; +use App\Entity\LogSystem\LogTargetType; use App\Entity\UserSystem\User; use DateTime; use RuntimeException; @@ -45,7 +46,7 @@ class LogEntryRepository extends DBElementRepository /** @var AbstractDBElement $element */ $element = $criteria['target']; $criteria['target_id'] = $element->getID(); - $criteria['target_type'] = AbstractLogEntry::targetTypeClassToID($element::class); + $criteria['target_type'] = LogTargetType::fromElementClass($element); unset($criteria['target']); } @@ -86,7 +87,7 @@ class LogEntryRepository extends DBElementRepository ->setMaxResults(1); $qb->setParameters([ - 'target_type' => AbstractLogEntry::targetTypeClassToID($class), + 'target_type' => LogTargetType::fromElementClass($class), 'target_id' => $id, ]); @@ -122,7 +123,7 @@ class LogEntryRepository extends DBElementRepository ->orderBy('log.timestamp', 'DESC'); $qb->setParameters([ - 'target_type' => AbstractLogEntry::targetTypeClassToID($element::class), + 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), 'until' => $until, ]); @@ -148,7 +149,7 @@ class LogEntryRepository extends DBElementRepository ->orderBy('log.timestamp', 'DESC'); $qb->setParameters([ - 'target_type' => AbstractLogEntry::targetTypeClassToID($element::class), + 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), 'until' => $timestamp, ]); @@ -209,18 +210,24 @@ class LogEntryRepository extends DBElementRepository return $this->getLastUser($element, ElementCreatedLogEntry::class); } - protected function getLastUser(AbstractDBElement $element, string $class): ?User + /** + * Returns the last user that has created a log entry with the given class on the given element. + * @param AbstractDBElement $element + * @param string $log_class + * @return User|null + */ + protected function getLastUser(AbstractDBElement $element, string $log_class): ?User { $qb = $this->createQueryBuilder('log'); $qb->select('log') //->where('log INSTANCE OF App\Entity\LogSystem\ElementEditedLogEntry') - ->where('log INSTANCE OF '.$class) + ->where('log INSTANCE OF '.$log_class) ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') ->orderBy('log.timestamp', 'DESC'); $qb->setParameters([ - 'target_type' => AbstractLogEntry::targetTypeClassToID($element::class), + 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), ]); diff --git a/tests/Entity/LogSystem/AbstractLogEntryTest.php b/tests/Entity/LogSystem/AbstractLogEntryTest.php index 9d9d823d..134d80ce 100644 --- a/tests/Entity/LogSystem/AbstractLogEntryTest.php +++ b/tests/Entity/LogSystem/AbstractLogEntryTest.php @@ -60,53 +60,6 @@ use PHPUnit\Framework\TestCase; class AbstractLogEntryTest extends TestCase { - public function targetTypeDataProvider(): array - { - return [ - [1, User::class], - [2, Attachment::class], - [3, AttachmentType::class], - [4, Category::class], - [5, Project::class], - [6, ProjectBOMEntry::class], - [7, Footprint::class], - [8, Group::class], - [9, Manufacturer::class], - [10, Part::class], - [11, Storelocation::class], - [12, Supplier::class], - [-1, 'blablub', true], - ]; - } - - /** - * @dataProvider targetTypeDataProvider - */ - public function testTargetTypeIdToClass(int $int, string $expected_class, bool $expect_exception = false): void - { - if ($expect_exception) { - $this->expectException(\InvalidArgumentException::class); - } - $this->assertSame($expected_class, AbstractLogEntry::targetTypeIdToClass($int)); - } - - /** - * @dataProvider targetTypeDataProvider - */ - public function testTypeClassToID(int $expected_id, string $class, bool $expect_exception = false): void - { - if ($expect_exception) { - $this->expectException(\InvalidArgumentException::class); - } - $this->assertSame($expected_id, AbstractLogEntry::targetTypeClassToID($class)); - } - - public function testTypeClassToIDSubclasses(): void - { - //Test if class mapping works for subclasses - $this->assertSame(2, AbstractLogEntry::targetTypeClassToID(PartAttachment::class)); - } - public function testSetGetTarget(): void { $part = $this->createMock(Part::class); diff --git a/tests/Entity/LogSystem/LogTargetTypeTest.php b/tests/Entity/LogSystem/LogTargetTypeTest.php new file mode 100644 index 00000000..2d7675da --- /dev/null +++ b/tests/Entity/LogSystem/LogTargetTypeTest.php @@ -0,0 +1,62 @@ +. + */ + +namespace App\Tests\Entity\LogSystem; + +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\PartAttachment; +use App\Entity\LogSystem\LogTargetType; +use App\Entity\Parameters\PartParameter; +use App\Entity\Parts\Category; +use App\Entity\UserSystem\User; +use PHPUnit\Framework\TestCase; + +class LogTargetTypeTest extends TestCase +{ + + public function testToClass(): void + { + $this->assertNull(LogTargetType::NONE->toClass()); + $this->assertSame(User::class, LogTargetType::USER->toClass()); + $this->assertSame(Category::class, LogTargetType::CATEGORY->toClass()); + $this->assertSame(Attachment::class, LogTargetType::ATTACHMENT->toClass()); + } + + public function testFromElementClass(): void + { + //Test creation from string class + $this->assertSame(LogTargetType::CATEGORY, LogTargetType::fromElementClass(Category::class)); + $this->assertSame(LogTargetType::USER, LogTargetType::fromElementClass(User::class)); + + //Test creation from object + $this->assertSame(LogTargetType::CATEGORY, LogTargetType::fromElementClass(new Category())); + $this->assertSame(LogTargetType::USER, LogTargetType::fromElementClass(new User())); + + //Test creation from subclass + $this->assertSame(LogTargetType::ATTACHMENT, LogTargetType::fromElementClass(new PartAttachment())); + $this->assertSame(LogTargetType::PARAMETER, LogTargetType::fromElementClass(new PartParameter())); + } + + public function testFromElementClassInvalid(): void + { + $this->expectException(\InvalidArgumentException::class); + LogTargetType::fromElementClass(new \stdClass()); + } +} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 8b029fc5..c19f6515 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -11439,5 +11439,11 @@ Element 3 An error occurred during the registration of the security key. Try again or use another security key! + + + log.target_type.none + None + + diff --git a/translations/security.en.xlf b/translations/security.en.xlf index 43f2a92f..3ca2bee3 100644 --- a/translations/security.en.xlf +++ b/translations/security.en.xlf @@ -2,13 +2,13 @@ - + user.login_error.user_disabled Your account is disabled! Contact an administrator if you think this is wrong. - + saml.error.cannot_login_local_user_per_saml You cannot login as local user via SSO! Use your local user password instead. diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index c8ef6e3b..1f3438de 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -37,7 +37,7 @@ Part-DB1\src\Entity\UserSystem\Group.php:0 Part-DB1\src\Entity\UserSystem\User.php:0 - + part.master_attachment.must_be_picture The preview attachment must be a valid picture! @@ -82,7 +82,7 @@ src\Entity\StructuralDBElement.php:0 src\Entity\Supplier.php:0 - + structural.entity.unique_name An element with this name already exists on this level! @@ -102,7 +102,7 @@ Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0 Part-DB1\src\Entity\Parameters\SupplierParameter.php:0 - + parameters.validator.min_lesser_typical Value must be lesser or equal the the typical value ({{ compared_value }}). @@ -122,7 +122,7 @@ Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0 Part-DB1\src\Entity\Parameters\SupplierParameter.php:0 - + parameters.validator.min_lesser_max Value must be lesser than the maximum value ({{ compared_value }}). @@ -142,7 +142,7 @@ Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0 Part-DB1\src\Entity\Parameters\SupplierParameter.php:0 - + parameters.validator.max_greater_typical Value must be greater or equal than the typical value ({{ compared_value }}). @@ -152,7 +152,7 @@ Part-DB1\src\Entity\UserSystem\User.php:0 Part-DB1\src\Entity\UserSystem\User.php:0 - + validator.user.username_already_used A user with this name is already exisiting @@ -162,7 +162,7 @@ Part-DB1\src\Entity\UserSystem\User.php:0 Part-DB1\src\Entity\UserSystem\User.php:0 - + user.invalid_username The username must contain only letters, numbers, underscores, dots, pluses or minuses! @@ -171,7 +171,7 @@ obsolete - + validator.noneofitschild.self An element can not be its own parent! @@ -180,139 +180,139 @@ obsolete - + validator.noneofitschild.children You can not assign children element as parent (This would cause loops)! - + validator.select_valid_category Please select a valid category! - + validator.part_lot.only_existing Can not add new parts to this location as it is marked as "Only Existing" - + validator.part_lot.location_full.no_increase Location is full. Amount can not be increased (new value must be smaller than {{ old_amount }}). - + validator.part_lot.location_full Location is full. Can not add new parts to it. - + validator.part_lot.single_part This location can only contain a single part and it is already full! - + validator.attachment.must_not_be_null You must select an attachment type! - + validator.orderdetail.supplier_must_not_be_null You must select an supplier! - + validator.measurement_unit.use_si_prefix_needs_unit To enable SI prefixes, you have to set a unit symbol! - + part.ipn.must_be_unique The internal part number must be unique. {{ value }} is already in use! - + validator.project.bom_entry.name_or_part_needed You have to choose a part for a part BOM entry or set a name for a non-part BOM entry. - + project.bom_entry.name_already_in_bom There is already an BOM entry with this name! - + project.bom_entry.part_already_in_bom This part already exists in the BOM! - + project.bom_entry.mountnames_quantity_mismatch The number of mountnames has to match the BOMs quantity! - + project.bom_entry.can_not_add_own_builds_part You can not add a project's own builds part to the BOM. - + project.bom_has_to_include_all_subelement_parts The project BOM has to include all subprojects builds parts. Part %part_name% of project %project_name% missing! - + project.bom_entry.price_not_allowed_on_parts Prices are not allowed on BOM entries associated with a part. Define the price on the part instead. - + validator.project_build.lot_bigger_than_needed You have selected more quantity to withdraw than needed! Remove unnecessary quantity. - + validator.project_build.lot_smaller_than_needed You have selected less quantity to withdraw than needed for the build! Add additional quantity. - + part.name.must_match_category_regex The part name does not match the regular expression stated by the category: %regex% - + validator.attachment.name_not_blank Set a value here, or upload a file to automatically use its filename as name for the attachment. - + validator.part_lot.owner_must_match_storage_location_owner The owner of this lot must match the owner of the selected storage location (%owner_name%)! - + validator.part_lot.owner_must_not_be_anonymous A lot owner must not be the anonymous user!