diff --git a/.env b/.env index 869d4154..982d4bbd 100644 --- a/.env +++ b/.env @@ -50,8 +50,6 @@ EMAIL_SENDER_EMAIL=noreply@partdb.changeme EMAIL_SENDER_NAME="Part-DB Mailer" # Set this to 1 to allow reset of a password per email ALLOW_EMAIL_PW_RESET=0 -# Set this to 0 to allow to enter already available IPN. In this case a unique increment is appended to the user input. -ENFORCE_UNIQUE_IPN=1 ################################################################################### # Error pages settings @@ -118,9 +116,6 @@ REDIRECT_TO_HTTPS=0 # Set this to zero, if you want to disable the year 2038 bug check on 32-bit systems (it will cause errors with current 32-bit PHP versions) DISABLE_YEAR2038_BUG_CHECK=0 -# Define the number of digits used for the incremental numbering of parts in the IPN (Internal Part Number) autocomplete system. -AUTOCOMPLETE_PART_DIGITS=4 - # Set the trusted IPs here, when using an reverse proxy #TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 #TRUSTED_HOSTS='^(localhost|example\.com)$' diff --git a/config/parameters.yaml b/config/parameters.yaml index 30c38957..5b40899d 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -9,7 +9,6 @@ parameters: # This is used as workaround for places where we can not access the settings directly (like the 2FA application names) partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage) partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu - partdb.autocomplete_part_digits: '%env(trim:string:AUTOCOMPLETE_PART_DIGITS)%' # The number of digits used for the incremental numbering of parts in the IPN (Internal Part Number) autocomplete system. partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails @@ -20,7 +19,6 @@ parameters: ###################################################################################################################### partdb.gdpr_compliance: true # If this option is activated, IP addresses are anonymized to be GDPR compliant partdb.users.email_pw_reset: '%env(bool:ALLOW_EMAIL_PW_RESET)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured. - partdb.users.enforce_unique_ipn: '%env(bool:ENFORCE_UNIQUE_IPN)%' # Config if users are able, to enter an already available IPN. In this case a unique increment is appended to the user input. ###################################################################################################################### # Mail settings diff --git a/config/services.yaml b/config/services.yaml index fa70e87c..f78f5209 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -231,28 +231,16 @@ services: tags: - { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' } - App\Controller\PartController: - bind: - $autocompletePartDigits: '%partdb.autocomplete_part_digits%' - - App\Controller\TypeaheadController: - bind: - $autocompletePartDigits: '%partdb.autocomplete_part_digits%' - App\Repository\PartRepository: arguments: $translator: '@translator' tags: ['doctrine.repository_service'] App\EventSubscriber\UserSystem\PartUniqueIpnSubscriber: - arguments: - $enforceUniqueIpn: '%partdb.users.enforce_unique_ipn%' tags: - { name: doctrine.event_subscriber } App\Validator\Constraints\UniquePartIpnValidator: - arguments: - $enforceUniqueIpn: '%partdb.users.enforce_unique_ipn%' tags: [ 'validator.constraint_validator' ] # We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container. diff --git a/docs/configuration.md b/docs/configuration.md index 0292242c..8f48940f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -116,7 +116,9 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept value should be handled as confidential data and not shared publicly. * `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the part image gallery -* `AUTOCOMPLETE_PART_DIGITS`: Defines the fixed number of digits used as the increment at the end of an IPN (Internal Part Number). +* `IPN_ENABLE_UNIQUE_CHECK`: Set this value to false, if you want to allow users to enter a already available IPN for a part entry. + In this case a unique increment is appended to the user input. +* `IPN_SUGGEST_PART_DIGITS`: Defines the fixed number of digits used as the increment at the end of an IPN (Internal Part Number). IPN prefixes, maintained within part categories and their hierarchy, form the foundation for suggesting complete IPNs. These suggestions become accessible during IPN input of a part. The constant specifies the digits used to calculate and assign unique increments for parts within a category hierarchy, ensuring consistency and uniqueness in IPN generation. @@ -132,8 +134,6 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept sent from. * `ALLOW_EMAIL_PW_RESET`: Set this value to true, if you want to allow users to reset their password via an email notification. You have to configure the mail provider first before via the MAILER_DSN setting. -* `ENFORCE_UNIQUE_IPN`: Set this value to false, if you want to allow users to enter a already available IPN for a part entry. - In this case a unique increment is appended to the user input. ### Table related settings diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 0c7a24e3..6e9d8bc7 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -47,6 +47,7 @@ use App\Services\Parts\PartLotWithdrawAddHelper; use App\Services\Parts\PricedetailHelper; use App\Services\ProjectSystem\ProjectBuildPartHelper; use App\Settings\BehaviorSettings\PartInfoSettings; +use App\Settings\MiscSettings\IpnSuggestSettings; use DateTime; use Doctrine\ORM\EntityManagerInterface; use Exception; @@ -74,7 +75,7 @@ final class PartController extends AbstractController private readonly EntityManagerInterface $em, private readonly EventCommentHelper $commentHelper, private readonly PartInfoSettings $partInfoSettings, - private readonly int $autocompletePartDigits, + private readonly IpnSuggestSettings $ipnSuggestSettings, ) { } @@ -451,7 +452,7 @@ final class PartController extends AbstractController $template, [ 'part' => $new_part, - 'ipnSuggestions' => $partRepository->autoCompleteIpn($data, base64_encode($data->getDescription()), $this->autocompletePartDigits), + 'ipnSuggestions' => $partRepository->autoCompleteIpn($data, base64_encode($data->getDescription()), $this->ipnSuggestSettings->suggestPartDigits), 'form' => $form, 'merge_old_name' => $merge_infos['tname_before'] ?? null, 'merge_other' => $merge_infos['other_part'] ?? null, diff --git a/src/Controller/TypeaheadController.php b/src/Controller/TypeaheadController.php index f1e83d21..8262506d 100644 --- a/src/Controller/TypeaheadController.php +++ b/src/Controller/TypeaheadController.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Controller; use App\Entity\Parameters\AbstractParameter; +use App\Settings\MiscSettings\IpnSuggestSettings; use Symfony\Component\HttpFoundation\Response; use App\Entity\Attachments\Attachment; use App\Entity\Parts\Category; @@ -63,7 +64,7 @@ class TypeaheadController extends AbstractController public function __construct( protected AttachmentURLGenerator $urlGenerator, protected Packages $assets, - protected int $autocompletePartDigits + protected IpnSuggestSettings $ipnSuggestSettings, ) { } @@ -207,7 +208,7 @@ class TypeaheadController extends AbstractController $clonedPart->setCategory($category); $partRepository = $entityManager->getRepository(Part::class); - $ipnSuggestions = $partRepository->autoCompleteIpn($clonedPart, $description, $this->autocompletePartDigits); + $ipnSuggestions = $partRepository->autoCompleteIpn($clonedPart, $description, $this->ipnSuggestSettings->suggestPartDigits); return new JsonResponse($ipnSuggestions); } diff --git a/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php b/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php index 9cff3166..498a9e88 100644 --- a/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php +++ b/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php @@ -3,6 +3,7 @@ namespace App\EventSubscriber\UserSystem; use App\Entity\Parts\Part; +use App\Settings\MiscSettings\IpnSuggestSettings; use Doctrine\Common\EventSubscriber; use Doctrine\Persistence\Event\LifecycleEventArgs; use Doctrine\ORM\Events; @@ -12,7 +13,7 @@ class PartUniqueIpnSubscriber implements EventSubscriber { public function __construct( private EntityManagerInterface $entityManager, - private readonly bool $enforceUniqueIpn = false + private IpnSuggestSettings $ipnSuggestSettings ) { } @@ -53,7 +54,7 @@ class PartUniqueIpnSubscriber implements EventSubscriber ->findOneBy(['ipn' => $part->getIpn()]); if ($existingPart && $existingPart->getId() !== $part->getId()) { - if ($this->enforceUniqueIpn) { + if ($this->ipnSuggestSettings->enableUniqueCheck) { return; } @@ -70,4 +71,4 @@ class PartUniqueIpnSubscriber implements EventSubscriber $part->setIpn($originalIpn . "_$increment"); } } -} \ No newline at end of file +} diff --git a/src/Repository/PartRepository.php b/src/Repository/PartRepository.php index 69361553..c6588731 100644 --- a/src/Repository/PartRepository.php +++ b/src/Repository/PartRepository.php @@ -118,13 +118,13 @@ class PartRepository extends NamedDBElementRepository * * @param Part $part The part for which autocomplete suggestions are generated. * @param string $description Base64-encoded description to assist in generating suggestions. - * @param int $autocompletePartDigits The number of digits used in autocomplete increments. + * @param int $suggestPartDigits The number of digits used in autocomplete increments. * * @return array An associative array containing the following keys: * - 'commonPrefixes': List of common prefixes found for the part. * - 'prefixesPartIncrement': Increments for the generated prefixes, including hierarchical prefixes. */ - public function autoCompleteIpn(Part $part, string $description, int $autocompletePartDigits): array + public function autoCompleteIpn(Part $part, string $description, int $suggestPartDigits): array { $category = $part->getCategory(); $ipnSuggestions = ['commonPrefixes' => [], 'prefixesPartIncrement' => []]; @@ -140,7 +140,7 @@ class PartRepository extends NamedDBElementRepository $directIpnPrefixEmpty = $category->getPartIpnPrefix() === ''; $currentPath = $currentPath === '' ? 'n.a.' : $currentPath; - $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $autocompletePartDigits); + $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits); $ipnSuggestions['commonPrefixes'][] = [ 'title' => $currentPath . '-', @@ -181,7 +181,7 @@ class PartRepository extends NamedDBElementRepository 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment') ]; - $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $autocompletePartDigits); + $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits); $ipnSuggestions['prefixesPartIncrement'][] = [ 'title' => $currentPath . '-' . $increment, @@ -249,18 +249,18 @@ class PartRepository extends NamedDBElementRepository * * @param string $currentPath The base path or prefix for the part's identifier. * @param Part $currentPart The part entity for which the increment is being generated. - * @param int $autocompletePartDigits The number of digits reserved for the increment. + * @param int $suggestPartDigits The number of digits reserved for the increment. * * @return string|null The next possible increment as a zero-padded string, or null if it cannot be generated. * * @throws NonUniqueResultException If the query returns non-unique results. * @throws NoResultException If the query fails to return a result. */ - private function generateNextPossiblePartIncrement(string $currentPath, Part $currentPart, int $autocompletePartDigits): ?string + private function generateNextPossiblePartIncrement(string $currentPath, Part $currentPart, int $suggestPartDigits): ?string { $qb = $this->createQueryBuilder('part'); - $expectedLength = strlen($currentPath) + 1 + $autocompletePartDigits; // Path + '-' + $autocompletePartDigits digits + $expectedLength = strlen($currentPath) + 1 + $suggestPartDigits; // Path + '-' + $suggestPartDigits digits // Fetch all parts in the given category, sorted by their ID in ascending order $qb->select('part') @@ -281,14 +281,14 @@ class PartRepository extends NamedDBElementRepository if ($part->getId() === $currentPart->getId()) { // Extract and return the current part's increment directly - $incrementPart = substr($part->getIpn(), -$autocompletePartDigits); + $incrementPart = substr($part->getIpn(), -$suggestPartDigits); if (is_numeric($incrementPart)) { - return str_pad((string) $incrementPart, $autocompletePartDigits, '0', STR_PAD_LEFT); + return str_pad((string) $incrementPart, $suggestPartDigits, '0', STR_PAD_LEFT); } } // Extract last $autocompletePartDigits digits for possible available part increment - $incrementPart = substr($part->getIpn(), -$autocompletePartDigits); + $incrementPart = substr($part->getIpn(), -$suggestPartDigits); if (is_numeric($incrementPart)) { $usedIncrements[] = (int) $incrementPart; } @@ -302,7 +302,7 @@ class PartRepository extends NamedDBElementRepository $nextIncrement++; } - return str_pad((string) $nextIncrement, $autocompletePartDigits, '0', STR_PAD_LEFT); + return str_pad((string) $nextIncrement, $suggestPartDigits, '0', STR_PAD_LEFT); } /** diff --git a/src/Settings/MiscSettings/IpnSuggestSettings.php b/src/Settings/MiscSettings/IpnSuggestSettings.php new file mode 100644 index 00000000..1ef94b2f --- /dev/null +++ b/src/Settings/MiscSettings/IpnSuggestSettings.php @@ -0,0 +1,54 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\MiscSettings; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.misc.ipn_suggest"))] +#[SettingsIcon("fa-list")] +class IpnSuggestSettings +{ + use SettingsTrait; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.enableUniqueCheck"), + envVar: "bool:IPN_ENABLE_UNIQUE_CHECK", envVarMode: EnvVarMode::OVERWRITE, + )] + public bool $enableUniqueCheck = true; + + #[SettingsParameter(label: new TM("settings.misc.ipn_suggest.suggestPartDigits"), + description: new TM("settings.misc.ipn_suggest.suggestPartDigits.help"), + formOptions: ['attr' => ['min' => 1, 'max' => 100]], + envVar: "int:IPN_SUGGEST_PART_DIGITS", envVarMode: EnvVarMode::OVERWRITE + )] + #[Assert\Range(min: 1, max: 6)] + public int $suggestPartDigits = 4; +} diff --git a/src/Settings/MiscSettings/MiscSettings.php b/src/Settings/MiscSettings/MiscSettings.php index b8a3a73f..fa6a7349 100644 --- a/src/Settings/MiscSettings/MiscSettings.php +++ b/src/Settings/MiscSettings/MiscSettings.php @@ -34,4 +34,7 @@ class MiscSettings #[EmbeddedSettings] public ?ExchangeRateSettings $exchangeRate = null; -} \ No newline at end of file + + #[EmbeddedSettings] + public ?IpnSuggestSettings $ipnSuggestSettings = null; +} diff --git a/src/Validator/Constraints/UniquePartIpnValidator.php b/src/Validator/Constraints/UniquePartIpnValidator.php index 019202f8..641ffe47 100644 --- a/src/Validator/Constraints/UniquePartIpnValidator.php +++ b/src/Validator/Constraints/UniquePartIpnValidator.php @@ -3,6 +3,7 @@ namespace App\Validator\Constraints; use App\Entity\Parts\Part; +use App\Settings\MiscSettings\IpnSuggestSettings; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Doctrine\ORM\EntityManagerInterface; @@ -10,12 +11,12 @@ use Doctrine\ORM\EntityManagerInterface; class UniquePartIpnValidator extends ConstraintValidator { private EntityManagerInterface $entityManager; - private bool $enforceUniqueIpn; + private IpnSuggestSettings $ipnSuggestSettings; - public function __construct(EntityManagerInterface $entityManager, bool $enforceUniqueIpn) + public function __construct(EntityManagerInterface $entityManager, IpnSuggestSettings $ipnSuggestSettings) { $this->entityManager = $entityManager; - $this->enforceUniqueIpn = $enforceUniqueIpn; + $this->ipnSuggestSettings = $ipnSuggestSettings; } public function validate($value, Constraint $constraint) @@ -24,7 +25,7 @@ class UniquePartIpnValidator extends ConstraintValidator return; } - if (!$this->enforceUniqueIpn) { + if (!$this->ipnSuggestSettings->enableUniqueCheck) { return; } @@ -40,12 +41,10 @@ class UniquePartIpnValidator extends ConstraintValidator foreach ($existingParts as $existingPart) { if ($currentPart->getId() !== $existingPart->getId()) { - if ($this->enforceUniqueIpn) { - $this->context->buildViolation($constraint->message) - ->setParameter('{{ value }}', $value) - ->addViolation(); - } + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $value) + ->addViolation(); } } } -} \ No newline at end of file +} diff --git a/translations/messages.cs.xlf b/translations/messages.cs.xlf index b7d5c3e9..b8d24325 100644 --- a/translations/messages.cs.xlf +++ b/translations/messages.cs.xlf @@ -13053,6 +13053,30 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz Pokud potřebujete směnné kurzy mezi měnami mimo eurozónu, můžete zde zadat API klíč z fixer.io. + + + settings.misc.ipn_suggest + Seznam návrhů IPN součástek + + + + + settings.misc.ipn_suggest.enableUniqueCheck + Kontrola jedinečnosti IPN aktivní. Odznačte, pokud chcete při opětovném zadání existujícího IPN při ukládání přidat k uživatelskému vstupu inkrementální číslo. + + + + + settings.misc.ipn_suggest.suggestPartDigits + Počet čísel pro inkrement + + + + + settings.misc.ipn_suggest.suggestPartDigits.help + Počet číslic použitých pro inkrementální číslování součástí v návrhovém systému IPN (Interní číslo součástky). + + settings.behavior.part_info diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index a726c3cd..3ea13fdc 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -13133,6 +13133,30 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön Wenn Sie Wechselkurse zwischen Nicht-Euro-Währungen benötigen, können Sie hier einen API-Schlüssel von fixer.io eingeben. + + + settings.misc.ipn_suggest + Bauteil IPN-Vorschlagsliste + + + + + settings.misc.ipn_suggest.enableUniqueCheck + Check auf Eindeutigkeit der IPN aktiv. Deselektieren Sie, wenn Sie bei erneuter Eingabe einer vorhandenen IPN eine inkrementelle Zahl an die Benutzereingabe beim Speichern erhalten möchten. + + + + + settings.misc.ipn_suggest.suggestPartDigits + Stellen für numerisches Inkrement + + + + + settings.misc.ipn_suggest.suggestPartDigits.help + Die Anzahl der Ziffern, die für die inkrementale Nummerierung von Teilen im IPN-Vorschlagssystem verwendet werden. + + settings.behavior.part_info diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index a231458b..82dad84f 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -13134,6 +13134,30 @@ Please note, that you can not impersonate a disabled user. If you try you will g If you need exchange rates between non-euro currencies, you can input an API key from fixer.io here. + + + settings.misc.ipn_suggest + Part IPN Suggest + + + + + settings.misc.ipn_suggest.enableUniqueCheck + IPN uniqueness check active. Deselect if you want an incremental number to be added to the user input when entering an existing IPN again upon saving. + + + + + settings.misc.ipn_suggest.suggestPartDigits + Increment Digits + + + + + settings.misc.ipn_suggest.suggestPartDigits.help + The number of digits used for the incremental numbering of parts in the IPN (Internal Part Number) suggestion system. + + settings.behavior.part_info