Compare commits

..

22 commits

Author SHA1 Message Date
Jan Böhmer
18bf68cb6a Use release versions of settings-bundle
Some checks failed
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / docker (push) Has been cancelled
Docker Image Build (FrankenPHP) / docker (push) Has been cancelled
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
2025-07-06 22:30:17 +02:00
Jan Böhmer
38c826713f Check for permissions to access settings menu and added settings menu to sidebar menu 2025-07-06 22:29:35 +02:00
Jan Böhmer
b9c3358f7f Use a doctrine entity for storing the settings 2025-07-06 22:15:48 +02:00
Jan Böhmer
2f2f4b2b3e Allow to leave the enforceComments option empty 2025-07-06 22:08:10 +02:00
Jan Böhmer
423cdf6ca2 Set envVarMode to override everywhere 2025-07-06 22:00:46 +02:00
Jan Böhmer
dc81a5b361 Moved SHOW_PART_IMAGE_OVERLAY to settings-bundle 2025-07-06 21:53:06 +02:00
Jan Böhmer
a4f2a7a7df Moved fixer API config to settings bundle 2025-07-06 21:42:58 +02:00
Jan Böhmer
c0c847c5be Moved octopart / nexar integration configuration to the new settings system 2025-07-06 20:18:14 +02:00
Jan Böhmer
1dbcff66d1 Moved digikey IPS configuration to new settings system 2025-07-06 19:32:55 +02:00
Jan Böhmer
6e28f2a74e Moved rootNodeRedirectsToNewEntity option to new settings system 2025-07-06 19:09:10 +02:00
Jan Böhmer
9812671a89 Merge master into settings-bundle 2025-07-06 19:05:22 +02:00
Jan Böhmer
8b417d6441 Bumped version to 1.17.2
Some checks failed
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / docker (push) Has been cancelled
Docker Image Build (FrankenPHP) / docker (push) Has been cancelled
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
2025-07-06 14:02:36 +02:00
Jan Böhmer
ff57b5b270 Fixed static analyis issue 2025-07-06 14:02:16 +02:00
Jan Böhmer
c00edef69c Add fields parsable by KiCost to KiCad part info 2025-07-06 13:58:51 +02:00
Jan Böhmer
a235f05794 Do not update yarn dependencies to maintain compatibility with nodejs 18 2025-07-06 13:41:36 +02:00
Jan Böhmer
bd411ba13b Revert "Downgrade minimatch to to still support nodejs 18"
This reverts commit f8bdbf1fde.
2025-07-06 13:40:47 +02:00
Jan Böhmer
f8bdbf1fde Downgrade minimatch to to still support nodejs 18 2025-07-06 13:24:03 +02:00
Blaž Aristovnik
beea572c47
Add supplier information to KiCad part exports (#955)
* Add supplier information to KiCad part exports

- Include supplier name and part numbers from order details in KiCad exports
- Handle multiple suppliers with sequential numbering (Supplier 2, Supplier 3, etc.)
- Include both active and obsolete order details for comprehensive supplier info
- Add null checks to prevent errors when supplier or part number is missing

* Add SPN suffix to field name

---------

Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2025-07-06 13:12:04 +02:00
Jan Böhmer
442a7aa235 Updated dependencies
Some checks are pending
Build assets artifact / Build assets artifact (push) Waiting to run
Docker Image Build / docker (push) Waiting to run
Docker Image Build (FrankenPHP) / docker (push) Waiting to run
Static analysis / Static analysis (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Waiting to run
2025-07-06 00:43:02 +02:00
d-buchmann
2226b72d1c
Update AbstractParameter.php (#959)
* Update AbstractParameter.php

Make lazy null conditionals explicit.
Try to handle LaTeX special chars gracefully.
Fixes #958

* Only escape the percentage sign, so that you can still use latex for units

* Only escape previously unescaped percentage signs

* simplify regex

* Render the percentage sign correctly in units in the frontend.

---------

Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2025-07-06 00:20:29 +02:00
d-buchmann
00a74ed96a
Add env option to disable part image overlay (#960)
Some checks failed
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / docker (push) Has been cancelled
Docker Image Build (FrankenPHP) / docker (push) Has been cancelled
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
* Add env option to disable part image overlay

Fixes #369 while preserving the state as-is

* Added documentation and use 1 instead of true for new env

---------

Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2025-07-02 22:31:13 +02:00
d-buchmann
699a5c935f
fix sidebar root node links (#957)
* fix sidebar root node links

link sidebar root nodes to their corresponding "new" route

* Use "Show all parts" for most root categories and started to make it configurable for the future

---------

Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2025-07-02 22:11:28 +02:00
40 changed files with 903 additions and 393 deletions

View file

@ -47,6 +47,7 @@
PassEnv PROVIDER_REICHELT_ENABLED PROVIDER_REICHELT_CURRENCY PROVIDER_REICHELT_COUNTRY PROVIDER_REICHELT_LANGUAGE PROVIDER_REICHELT_INCLUDE_VAT PassEnv PROVIDER_REICHELT_ENABLED PROVIDER_REICHELT_CURRENCY PROVIDER_REICHELT_COUNTRY PROVIDER_REICHELT_LANGUAGE PROVIDER_REICHELT_INCLUDE_VAT
PassEnv PROVIDER_POLLIN_ENABLED PassEnv PROVIDER_POLLIN_ENABLED
PassEnv EDA_KICAD_CATEGORY_DEPTH PassEnv EDA_KICAD_CATEGORY_DEPTH
PassEnv SHOW_PART_IMAGE_OVERLAY
# For most configuration files from conf-available/, which are # For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to # enabled or disabled at a global level, it is possible to

31
.env
View file

@ -67,34 +67,6 @@ ERROR_PAGE_ADMIN_EMAIL=''
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them... # If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
ERROR_PAGE_SHOW_HELP=1 ERROR_PAGE_SHOW_HELP=1
##################################################################################
# Info provider settings
##################################################################################
# Digikey Provider:
# You can get your client id and secret from https://developer.digikey.com/
PROVIDER_DIGIKEY_CLIENT_ID=
PROVIDER_DIGIKEY_SECRET=
# The currency to get prices in
PROVIDER_DIGIKEY_CURRENCY=EUR
# The language to get results in (en, de, fr, it, es, zh, ja, ko)
PROVIDER_DIGIKEY_LANGUAGE=en
# The country to get results for
PROVIDER_DIGIKEY_COUNTRY=DE
# Octopart / Nexar Provider:
# You can get your API key from https://nexar.com/api
PROVIDER_OCTOPART_CLIENT_ID=
PROVIDER_OCTOPART_SECRET=
# The currency and country to get prices for (you have to set both to get meaningful results)
# 3 letter ISO currency code (e.g. EUR, USD, GBP)
PROVIDER_OCTOPART_CURRENCY=EUR
# 2 letter ISO country code (e.g. DE, US, GB)
PROVIDER_OCTOPART_COUNTRY=DE
# The number of results to get from Octopart while searching (please note that this counts towards your API limits)
PROVIDER_OCTOPART_SEARCH_LIMIT=10
# Set to false to include non authorized offers in the results
PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS=1
################################################################################## ##################################################################################
# EDA integration related settings # EDA integration related settings
@ -157,9 +129,6 @@ NO_URL_REWRITE_AVAILABLE=0
# Set to 1, if Part-DB should redirect all HTTP requests to HTTPS. You dont need to configure this, if your webserver already does this. # Set to 1, if Part-DB should redirect all HTTP requests to HTTPS. You dont need to configure this, if your webserver already does this.
REDIRECT_TO_HTTPS=0 REDIRECT_TO_HTTPS=0
# If you want to use fixer.io for currency conversion, you have to set this to your API key
FIXER_API_KEY=CHANGEME
# Override value if you want to show to show a given text on homepage. # Override value if you want to show to show a given text on homepage.
# When this is empty the content of config/banner.md is used as banner # When this is empty the content of config/banner.md is used as banner
BANNER="" BANNER=""

View file

@ -33,7 +33,10 @@ export default class extends Controller {
{ {
let value = ""; let value = "";
if (this.unitValue) { if (this.unitValue) {
value = "\\mathrm{" + this.inputTarget.value + "}"; //Escape percentage signs
value = this.inputTarget.value.replace(/%/g, '\\%');
value = "\\mathrm{" + value + "}";
} else { } else {
value = this.inputTarget.value; value = this.inputTarget.value;
} }

View file

@ -85,7 +85,9 @@ export default class extends Controller
tmp += '<span>' + katex.renderToString(data.symbol) + '</span>' tmp += '<span>' + katex.renderToString(data.symbol) + '</span>'
} }
if (data.unit) { if (data.unit) {
tmp += '<span class="ms-2">' + katex.renderToString('[' + data.unit + ']') + '</span>' let unit = data.unit.replace(/%/g, '\\%');
unit = "\\mathrm{" + unit + "}";
tmp += '<span class="ms-2">' + katex.renderToString('[' + unit + ']') + '</span>'
} }

View file

@ -30,7 +30,7 @@
"hshn/base64-encoded-file": "^5.0", "hshn/base64-encoded-file": "^5.0",
"jbtronics/2fa-webauthn": "^v2.2.0", "jbtronics/2fa-webauthn": "^v2.2.0",
"jbtronics/dompdf-font-loader-bundle": "^1.0.0", "jbtronics/dompdf-font-loader-bundle": "^1.0.0",
"jbtronics/settings-bundle": "dev-master", "jbtronics/settings-bundle": "^v2.6.0",
"jfcherng/php-diff": "^6.14", "jfcherng/php-diff": "^6.14",
"knpuniversity/oauth2-client-bundle": "^2.15", "knpuniversity/oauth2-client-bundle": "^2.15",
"league/csv": "^9.8.0", "league/csv": "^9.8.0",

541
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,8 +6,8 @@ knpu_oauth2_client:
type: generic type: generic
provider_class: '\League\OAuth2\Client\Provider\GenericProvider' provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
client_id: '%env(PROVIDER_DIGIKEY_CLIENT_ID)%' client_id: '%env(settings:digikey:clientId)%'
client_secret: '%env(PROVIDER_DIGIKEY_SECRET)%' client_secret: '%env(settings:digikey:secret)%'
redirect_route: 'oauth_client_check' redirect_route: 'oauth_client_check'
redirect_params: {name: 'ip_digikey_oauth'} redirect_params: {name: 'ip_digikey_oauth'}
@ -26,8 +26,8 @@ knpu_oauth2_client:
type: generic type: generic
provider_class: '\League\OAuth2\Client\Provider\GenericProvider' provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
client_id: '%env(PROVIDER_OCTOPART_CLIENT_ID)%' client_id: '%env(settings:octopart:clientId)%'
client_secret: '%env(PROVIDER_OCTOPART_SECRET)%' client_secret: '%env(settings:octopart:secret)%'
redirect_route: 'oauth_client_check' redirect_route: 'oauth_client_check'
redirect_params: { name: 'ip_octopart_oauth' } redirect_params: { name: 'ip_octopart_oauth' }

View file

@ -1,2 +1,8 @@
jbtronics_settings: jbtronics_settings:
default_storage_adapter: Jbtronics\SettingsBundle\Storage\PHPFileStorageAdapter default_storage_adapter: Jbtronics\SettingsBundle\Storage\ORMStorageAdapter
cache:
default_cacheable: true
orm_storage:
default_entity_class: App\Entity\SettingsEntry

View file

@ -6,5 +6,5 @@ florianv_swap:
providers: providers:
european_central_bank: ~ # European Central Bank (only works for EUR base currency) european_central_bank: ~ # European Central Bank (only works for EUR base currency)
fixer: # Fixer.io (needs an API key) fixer: # Fixer.io (needs an API key)
access_key: "%env(FIXER_API_KEY)%" access_key: "%env(string:default:settings:exchange_rate:fixerApiKey:INVALID)%"
#exchange_rates_api: ~ #exchange_rates_api: ~

View file

@ -265,17 +265,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
# label: "perm.database.write_db_settings" # label: "perm.database.write_db_settings"
# alsoSet: ['read_db_settings', 'see_status'] # alsoSet: ['read_db_settings', 'see_status']
#config: config:
# label: "perm.config" label: "perm.config"
# group: "system" group: "system"
# operations: operations:
# read_config: change_system_settings:
# label: "perm.config.read_config" label: "perm.config.change_system_settings"
# edit_config: apiTokenRole: ROLE_API_ADMIN
# label: "perm.config.edit_config"
# alsoSet: 'read_config'
# server_info:
# label: "perm.config.server_info"
system: system:
label: "perm.system" label: "perm.system"

View file

@ -199,23 +199,6 @@ services:
arguments: arguments:
$providers: !tagged_iterator 'app.info_provider' $providers: !tagged_iterator 'app.info_provider'
App\Services\InfoProviderSystem\Providers\DigikeyProvider:
arguments:
$clientId: '%env(string:PROVIDER_DIGIKEY_CLIENT_ID)%'
$currency: '%env(string:PROVIDER_DIGIKEY_CURRENCY)%'
$language: '%env(string:PROVIDER_DIGIKEY_LANGUAGE)%'
$country: '%env(string:PROVIDER_DIGIKEY_COUNTRY)%'
App\Services\InfoProviderSystem\Providers\OctopartProvider:
arguments:
$clientId: '&env(string:PROVIDER_OCTOPART_CLIENT_ID)%'
$secret: '%env(string:PROVIDER_OCTOPART_SECRET)%'
$country: '%env(string:PROVIDER_OCTOPART_COUNTRY)%'
$currency: '%env(string:PROVIDER_OCTOPART_CURRENCY)%'
$search_limit: '%env(int:PROVIDER_OCTOPART_SEARCH_LIMIT)%'
$onlyAuthorizedSellers: '%env(bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS)%'
#################################################################################################################### ####################################################################################################################
# API system # API system
#################################################################################################################### ####################################################################################################################

View file

@ -95,6 +95,8 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
particularly for securing and protecting various aspects of your application. It's a secret key that is used for particularly for securing and protecting various aspects of your application. It's a secret key that is used for
cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this
value should be handled as confidential data and not shared publicly. 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
### E-Mail settings ### E-Mail settings

View file

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
final class Version20250706201121 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Add settings_entry table for storing settings';
}
public function mySQLUp(Schema $schema): void
{
$this->addSql('CREATE TABLE settings_entry (`key` VARCHAR(255) NOT NULL, `data` JSON DEFAULT NULL, id INT AUTO_INCREMENT NOT NULL, UNIQUE INDEX UNIQ_93F8DB394E645A7E (`key`), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`');
}
public function mySQLDown(Schema $schema): void
{
$this->addSql('DROP TABLE settings_entry');
}
public function sqLiteUp(Schema $schema): void
{
$this->addSql('CREATE TABLE settings_entry ("key" VARCHAR(255) NOT NULL, "data" CLOB DEFAULT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_93F8DB39F48571EB ON settings_entry ("key")');
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('DROP TABLE settings_entry');
}
public function postgreSQLUp(Schema $schema): void
{
$this->addSql('CREATE TABLE settings_entry ("key" VARCHAR(255) NOT NULL, "data" JSON DEFAULT NULL, id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_93F8DB39F48571EB ON settings_entry ("key")');
}
public function postgreSQLDown(Schema $schema): void
{
$this->addSql('DROP TABLE settings_entry');
}
}

View file

@ -40,6 +40,8 @@ class SettingsController extends AbstractController
#[Route("/settings", name: "system_settings")] #[Route("/settings", name: "system_settings")]
public function systemSettings(Request $request, TagAwareCacheInterface $cache): Response public function systemSettings(Request $request, TagAwareCacheInterface $cache): Response
{ {
$this->denyAccessUnlessGranted('@config.change_system_settings');
//Create a clone of the settings object //Create a clone of the settings object
$settings = $this->settingsManager->createTemporaryCopy(AppSettings::class); $settings = $this->settingsManager->createTemporaryCopy(AppSettings::class);
@ -62,9 +64,6 @@ class SettingsController extends AbstractController
$cache->invalidateTags(['tree_treeview', 'sidebar_tree_update']); $cache->invalidateTags(['tree_treeview', 'sidebar_tree_update']);
} }
//Render the form //Render the form
return $this->render('settings/settings.html.twig', [ return $this->render('settings/settings.html.twig', [
'form' => $form 'form' => $form

View file

@ -217,7 +217,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
$str = ''; $str = '';
$bracket_opened = false; $bracket_opened = false;
if ($this->value_typical) { if ($this->value_typical !== null) {
$str .= $this->getValueTypicalWithUnit($latex_formatted); $str .= $this->getValueTypicalWithUnit($latex_formatted);
if ($this->value_min || $this->value_max) { if ($this->value_min || $this->value_max) {
$bracket_opened = true; $bracket_opened = true;
@ -225,11 +225,11 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
} }
} }
if ($this->value_max && $this->value_min) { if ($this->value_max !== null && $this->value_min !== null) {
$str .= $this->getValueMinWithUnit($latex_formatted).' ... '.$this->getValueMaxWithUnit($latex_formatted); $str .= $this->getValueMinWithUnit($latex_formatted).' ... '.$this->getValueMaxWithUnit($latex_formatted);
} elseif ($this->value_max) { } elseif ($this->value_max !== null) {
$str .= 'max. '.$this->getValueMaxWithUnit($latex_formatted); $str .= 'max. '.$this->getValueMaxWithUnit($latex_formatted);
} elseif ($this->value_min) { } elseif ($this->value_min !== null) {
$str .= 'min. '.$this->getValueMinWithUnit($latex_formatted); $str .= 'min. '.$this->getValueMinWithUnit($latex_formatted);
} }
@ -449,7 +449,10 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
if (!$with_latex) { if (!$with_latex) {
$unit = $this->unit; $unit = $this->unit;
} else { } else {
$unit = '$\mathrm{'.$this->unit.'}$'; //Escape the percentage sign for convenience (as latex uses it as comment and it is often used in units)
$escaped = preg_replace('/\\\\?%/', "\\\\%", $this->unit);
$unit = '$\mathrm{'.$escaped.'}$';
} }
return $str.' '.$unit; return $str.' '.$unit;
@ -457,7 +460,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
return $str; return $str;
} }
/** /**
* Returns the class of the element that is allowed to be associated with this attachment. * Returns the class of the element that is allowed to be associated with this attachment.
* @return string * @return string

View file

@ -0,0 +1,35 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Entity;
use Doctrine\DBAL\Types\Types;
use Jbtronics\SettingsBundle\Entity\AbstractSettingsORMEntry;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class SettingsEntry extends AbstractSettingsORMEntry
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column(type: Types::INTEGER)]
protected int $id;
}

View file

@ -241,6 +241,49 @@ class KiCadHelper
$result["fields"]["Part-DB IPN"] = $this->createField($part->getIpn()); $result["fields"]["Part-DB IPN"] = $this->createField($part->getIpn());
} }
// Add supplier information from orderdetails (include obsolete orderdetails)
if ($part->getOrderdetails(false)->count() > 0) {
$supplierCounts = [];
foreach ($part->getOrderdetails(false) as $orderdetail) {
if ($orderdetail->getSupplier() !== null && $orderdetail->getSupplierPartNr() !== '') {
$supplierName = $orderdetail->getSupplier()->getName();
$supplierName .= " SPN"; // Append "SPN" to the supplier name to indicate Supplier Part Number
if (!isset($supplierCounts[$supplierName])) {
$supplierCounts[$supplierName] = 0;
}
$supplierCounts[$supplierName]++;
// Create field name with sequential number if more than one from same supplier (e.g. "Mouser", "Mouser 2", etc.)
$fieldName = $supplierCounts[$supplierName] > 1
? $supplierName . ' ' . $supplierCounts[$supplierName]
: $supplierName;
$result["fields"][$fieldName] = $this->createField($orderdetail->getSupplierPartNr());
}
}
}
//Add fields for KiCost:
if ($part->getManufacturer() !== null) {
$result["fields"]["manf"] = $this->createField($part->getManufacturer()->getName());
}
if ($part->getManufacturerProductNumber() !== "") {
$result['fields']['manf#'] = $this->createField($part->getManufacturerProductNumber());
}
//For each supplier, add a field with the supplier name and the supplier part number for KiCost
if ($part->getOrderdetails(false)->count() > 0) {
foreach ($part->getOrderdetails(false) as $orderdetail) {
if ($orderdetail->getSupplier() !== null && $orderdetail->getSupplierPartNr() !== '') {
$fieldName = mb_strtolower($orderdetail->getSupplier()->getName()) . '#';
$result["fields"][$fieldName] = $this->createField($orderdetail->getSupplierPartNr());
}
}
}
return $result; return $result;
} }

View file

@ -31,6 +31,7 @@ use App\Services\InfoProviderSystem\DTOs\PriceDTO;
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
use App\Services\OAuth\OAuthTokenManager; use App\Services\OAuth\OAuthTokenManager;
use App\Settings\InfoProviderSystem\DigikeySettings;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
class DigikeyProvider implements InfoProviderInterface class DigikeyProvider implements InfoProviderInterface
@ -55,17 +56,16 @@ class DigikeyProvider implements InfoProviderInterface
]; ];
public function __construct(HttpClientInterface $httpClient, private readonly OAuthTokenManager $authTokenManager, public function __construct(HttpClientInterface $httpClient, private readonly OAuthTokenManager $authTokenManager,
private readonly string $currency, private readonly string $clientId, private readonly DigikeySettings $settings,)
private readonly string $language, private readonly string $country)
{ {
//Create the HTTP client with some default options //Create the HTTP client with some default options
$this->digikeyClient = $httpClient->withOptions([ $this->digikeyClient = $httpClient->withOptions([
"base_uri" => self::BASE_URI, "base_uri" => self::BASE_URI,
"headers" => [ "headers" => [
"X-DIGIKEY-Client-Id" => $clientId, "X-DIGIKEY-Client-Id" => $this->settings->clientId,
"X-DIGIKEY-Locale-Site" => $this->country, "X-DIGIKEY-Locale-Site" => $this->settings->country,
"X-DIGIKEY-Locale-Language" => $this->language, "X-DIGIKEY-Locale-Language" => $this->settings->language,
"X-DIGIKEY-Locale-Currency" => $this->currency, "X-DIGIKEY-Locale-Currency" => $this->settings->currency,
"X-DIGIKEY-Customer-Id" => 0, "X-DIGIKEY-Customer-Id" => 0,
] ]
]); ]);
@ -101,7 +101,7 @@ class DigikeyProvider implements InfoProviderInterface
public function isActive(): bool public function isActive(): bool
{ {
//The client ID has to be set and a token has to be available (user clicked connect) //The client ID has to be set and a token has to be available (user clicked connect)
return $this->clientId !== '' && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); return $this->settings->clientId !== '' && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME);
} }
public function searchByKeyword(string $keyword): array public function searchByKeyword(string $keyword): array
@ -268,7 +268,7 @@ class DigikeyProvider implements InfoProviderInterface
$prices = []; $prices = [];
foreach ($price_breaks as $price_break) { foreach ($price_breaks as $price_break) {
$prices[] = new PriceDTO(minimum_discount_amount: $price_break['BreakQuantity'], price: (string) $price_break['UnitPrice'], currency_iso_code: $this->currency); $prices[] = new PriceDTO(minimum_discount_amount: $price_break['BreakQuantity'], price: (string) $price_break['UnitPrice'], currency_iso_code: $this->settings->currency);
} }
return [ return [

View file

@ -30,6 +30,7 @@ use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\PriceDTO;
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
use App\Services\OAuth\OAuthTokenManager; use App\Services\OAuth\OAuthTokenManager;
use App\Settings\InfoProviderSystem\OctopartSettings;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\HttpClient\HttpOptions; use Symfony\Component\HttpClient\HttpOptions;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
@ -114,9 +115,8 @@ class OctopartProvider implements InfoProviderInterface
public function __construct(private readonly HttpClientInterface $httpClient, public function __construct(private readonly HttpClientInterface $httpClient,
private readonly OAuthTokenManager $authTokenManager, private readonly CacheItemPoolInterface $partInfoCache, private readonly OAuthTokenManager $authTokenManager, private readonly CacheItemPoolInterface $partInfoCache,
private readonly string $clientId, private readonly string $secret, private readonly OctopartSettings $settings,
private readonly string $currency, private readonly string $country, )
private readonly int $search_limit, private readonly bool $onlyAuthorizedSellers)
{ {
} }
@ -183,7 +183,7 @@ class OctopartProvider implements InfoProviderInterface
{ {
//The client ID has to be set and a token has to be available (user clicked connect) //The client ID has to be set and a token has to be available (user clicked connect)
//return /*!empty($this->clientId) && */ $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); //return /*!empty($this->clientId) && */ $this->authTokenManager->hasToken(self::OAUTH_APP_NAME);
return $this->clientId !== '' && $this->secret !== ''; return $this->settings->clientId !== '' && $this->settings->secret !== '';
} }
private function mapLifeCycleStatus(?string $value): ?ManufacturingStatus private function mapLifeCycleStatus(?string $value): ?ManufacturingStatus
@ -347,10 +347,10 @@ class OctopartProvider implements InfoProviderInterface
$result = $this->makeGraphQLCall($graphQL, [ $result = $this->makeGraphQLCall($graphQL, [
'keyword' => $keyword, 'keyword' => $keyword,
'limit' => $this->search_limit, 'limit' => $this->settings->searchLimit,
'currency' => $this->currency, 'currency' => $this->settings->currency,
'country' => $this->country, 'country' => $this->settings->country,
'authorizedOnly' => $this->onlyAuthorizedSellers, 'authorizedOnly' => $this->settings->onlyAuthorizedSellers,
]); ]);
$tmp = []; $tmp = [];
@ -383,9 +383,9 @@ class OctopartProvider implements InfoProviderInterface
$result = $this->makeGraphQLCall($graphql, [ $result = $this->makeGraphQLCall($graphql, [
'ids' => [$id], 'ids' => [$id],
'currency' => $this->currency, 'currency' => $this->settings->currency,
'country' => $this->country, 'country' => $this->settings->country,
'authorizedOnly' => $this->onlyAuthorizedSellers, 'authorizedOnly' => $this->settings->onlyAuthorizedSellers,
]); ]);
$tmp = $this->partResultToDTO($result['data']['supParts'][0]); $tmp = $this->partResultToDTO($result['data']['supParts'][0]);

View file

@ -289,6 +289,13 @@ class ToolsTreeBuilder
))->setIcon('fa-fw fa-treeview fa-solid fa-database'); ))->setIcon('fa-fw fa-treeview fa-solid fa-database');
} }
if ($this->security->isGranted('@config.change_system_settings')) {
$nodes[] = (new TreeViewNode(
$this->translator->trans('tree.tools.system.settings'),
$this->urlGenerator->generate('system_settings')
))->setIcon('fa fa-fw fa-gears fa-solid');
}
return $nodes; return $nodes;
} }
} }

View file

@ -179,10 +179,7 @@ class TreeViewGenerator
} }
if (($mode === 'list_parts_root' || $mode === 'devices') && $this->rootNodeEnabled) { if (($mode === 'list_parts_root' || $mode === 'devices') && $this->rootNodeEnabled) {
//We show the root node as a link to the list of all parts $root_node = new TreeViewNode($this->entityClassToRootNodeString($class), $this->entityClassToRootNodeHref($class), $generic);
$show_all_parts_url = $this->router->generate('parts_show_all');
$root_node = new TreeViewNode($this->entityClassToRootNodeString($class), $show_all_parts_url, $generic);
$root_node->setExpanded($this->rootNodeExpandedByDefault); $root_node->setExpanded($this->rootNodeExpandedByDefault);
$root_node->setIcon($this->entityClassToRootNodeIcon($class)); $root_node->setIcon($this->entityClassToRootNodeIcon($class));
@ -192,6 +189,27 @@ class TreeViewGenerator
return array_merge($head, $generic); return array_merge($head, $generic);
} }
protected function entityClassToRootNodeHref(string $class): ?string
{
//If the root node should redirect to the new entity page, we return the URL for the new entity.
if ($this->sidebarSettings->rootNodeRedirectsToNewEntity) {
return match ($class) {
Category::class => $this->router->generate('category_new'),
StorageLocation::class => $this->router->generate('store_location_new'),
Footprint::class => $this->router->generate('footprint_new'),
Manufacturer::class => $this->router->generate('manufacturer_new'),
Supplier::class => $this->router->generate('supplier_new'),
Project::class => $this->router->generate('project_new'),
default => null,
};
}
return match ($class) {
Project::class => $this->router->generate('project_new'),
default => $this->router->generate('parts_show_all')
};
}
protected function entityClassToRootNodeString(string $class): string protected function entityClassToRootNodeString(string $class): string
{ {
return match ($class) { return match ($class) {

View file

@ -105,6 +105,9 @@ class PermissionPresetsHelper
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'suppliers', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'suppliers', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'projects', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'projects', PermissionData::ALLOW);
//Allow to change system settings
$this->permissionResolver->setPermission($perm_holder, 'config', 'change_system_settings', PermissionData::ALLOW);
//Allow to manage Oauth tokens //Allow to manage Oauth tokens
$this->permissionResolver->setPermission($perm_holder, 'system', 'manage_oauth_tokens', PermissionData::ALLOW); $this->permissionResolver->setPermission($perm_holder, 'system', 'manage_oauth_tokens', PermissionData::ALLOW);
//Allow to show updates //Allow to show updates

View file

@ -37,4 +37,7 @@ class BehaviorSettings
#[EmbeddedSettings] #[EmbeddedSettings]
public ?TableSettings $table = null; public ?TableSettings $table = null;
#[EmbeddedSettings]
public ?PartInfoSettings $partInfo = null;
} }

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Settings\BehaviorSettings;
use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Symfony\Component\Translation\TranslatableMessage as TM;
#[Settings(name: "part_info", label: new TM("settings.behavior.part_info"))]
#[SettingsIcon('fa-circle-info')]
class PartInfoSettings
{
/**
* Whether to show the part image overlays in the part info view
* @var bool
*/
#[SettingsParameter(label: new TM("settings.behavior.part_info.show_part_image_overlay"), description: new TM("settings.behavior.part_info.show_part_image_overlay.help"),
envVar: "bool:SHOW_PART_IMAGE_OVERLAY", envVarMode: EnvVarMode::OVERWRITE)]
public bool $showPartImageOverlay = true;
}

View file

@ -67,4 +67,10 @@ class SidebarSettings
*/ */
#[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeExpanded"))] #[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeExpanded"))]
public bool $rootNodeExpanded = true; public bool $rootNodeExpanded = true;
/**
* @var bool Whether the root node should redirect to a new entity creation page when clicked.
*/
#[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeRedirectsToNewEntity"))]
public bool $rootNodeRedirectsToNewEntity = false;
} }

View file

@ -0,0 +1,70 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Settings\InfoProviderSystem;
use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsTrait;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
use Symfony\Component\Translation\TranslatableMessage as TM;
use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Symfony\Component\Validator\Constraints as Assert;
#[Settings(label: new TM("settings.ips.digikey"))]
#[SettingsIcon("fa-plug")]
class DigikeySettings
{
use SettingsTrait;
#[SettingsParameter(
label: new TM("settings.ips.digikey.client_id"),
envVar: "PROVIDER_DIGIKEY_CLIENT_ID", envVarMode: EnvVarMode::OVERWRITE
)]
public ?string $clientId = null;
#[SettingsParameter(
label: new TM("settings.ips.digikey.secret"),
envVar: "PROVIDER_DIGIKEY_SECRET", envVarMode: EnvVarMode::OVERWRITE
)]
public ?string $secret = null;
#[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class,
formOptions: ["preferred_choices" => ["EUR", "USD", "CHF", "GBP"]],
envVar: "PROVIDER_DIGIKEY_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Currency()]
public string $currency = "EUR";
#[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class,
envVar: "PROVIDER_DIGIKEY_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Country]
public string $country = "DE";
#[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class,
envVar: "PROVIDER_DIGIKEY_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Language]
public string $language = "en";
}

View file

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Settings\InfoProviderSystem; namespace App\Settings\InfoProviderSystem;
use App\Settings\SettingsIcon; use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Jbtronics\SettingsBundle\Settings\SettingsTrait; use Jbtronics\SettingsBundle\Settings\SettingsTrait;
@ -35,9 +36,11 @@ class Element14Settings
{ {
use SettingsTrait; use SettingsTrait;
#[SettingsParameter(label: new TM("settings.ips.element14.apiKey"), description: new TM("settings.ips.element14.apiKey.help"), formOptions: ["help_html" => true], envVar: "PROVIDER_ELEMENT14_KEY")] #[SettingsParameter(label: new TM("settings.ips.element14.apiKey"), description: new TM("settings.ips.element14.apiKey.help"),
formOptions: ["help_html" => true], envVar: "PROVIDER_ELEMENT14_KEY", envVarMode: EnvVarMode::OVERWRITE)]
public ?string $apiKey = null; public ?string $apiKey = null;
#[SettingsParameter(label: new TM("settings.ips.element14.storeId"), description: new TM("settings.ips.element14.storeId.help"), formOptions: ["help_html" => true], envVar: "PROVIDER_ELEMENT14_STORE_ID")] #[SettingsParameter(label: new TM("settings.ips.element14.storeId"), description: new TM("settings.ips.element14.storeId.help"),
formOptions: ["help_html" => true], envVar: "PROVIDER_ELEMENT14_STORE_ID", envVarMode: EnvVarMode::OVERWRITE)]
public string $storeId = "de.farnell.com"; public string $storeId = "de.farnell.com";
} }

View file

@ -32,6 +32,9 @@ class InfoProviderSettings
{ {
use SettingsTrait; use SettingsTrait;
#[EmbeddedSettings]
public ?DigikeySettings $digikey = null;
#[EmbeddedSettings] #[EmbeddedSettings]
public ?MouserSettings $mouser = null; public ?MouserSettings $mouser = null;
@ -41,6 +44,9 @@ class InfoProviderSettings
#[EmbeddedSettings] #[EmbeddedSettings]
public ?Element14Settings $element14 = null; public ?Element14Settings $element14 = null;
#[EmbeddedSettings]
public ?OctopartSettings $octopartSettings = null;
#[EmbeddedSettings] #[EmbeddedSettings]
public ?LCSCSettings $lcsc = null; public ?LCSCSettings $lcsc = null;

View file

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Settings\InfoProviderSystem; namespace App\Settings\InfoProviderSystem;
use App\Settings\SettingsIcon; use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Jbtronics\SettingsBundle\Settings\SettingsTrait; use Jbtronics\SettingsBundle\Settings\SettingsTrait;
@ -37,10 +38,12 @@ class LCSCSettings
{ {
use SettingsTrait; use SettingsTrait;
#[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"), envVar: "bool:PROVIDER_LCSC_ENABLED")] #[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"),
envVar: "bool:PROVIDER_LCSC_ENABLED", envVarMode: EnvVarMode::OVERWRITE)]
public bool $enabled = false; public bool $enabled = false;
#[SettingsParameter(label: new TM("settings.ips.lcsc.currency"), formType: CurrencyType::class, envVar: "string:PROVIDER_LCSC_CURRENCY")] #[SettingsParameter(label: new TM("settings.ips.lcsc.currency"), formType: CurrencyType::class,
envVar: "string:PROVIDER_LCSC_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Currency()] #[Assert\Currency()]
public string $currency = 'EUR'; public string $currency = 'EUR';
} }

View file

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Settings\InfoProviderSystem; namespace App\Settings\InfoProviderSystem;
use App\Settings\SettingsIcon; use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@ -33,22 +34,25 @@ use Symfony\Component\Translation\TranslatableMessage as TM;
#[SettingsIcon("fa-plug")] #[SettingsIcon("fa-plug")]
class MouserSettings class MouserSettings
{ {
#[SettingsParameter(label: new TM("settings.ips.mouser.apiKey"), description: new TM("settings.ips.mouser.apiKey.help"), formOptions: ["help_html" => true], envVar: "PROVIDER_MOUSER_KEY")] #[SettingsParameter(label: new TM("settings.ips.mouser.apiKey"), description: new TM("settings.ips.mouser.apiKey.help"),
formOptions: ["help_html" => true], envVar: "PROVIDER_MOUSER_KEY", envVarMode: EnvVarMode::OVERWRITE)]
public ?string $apiKey = null; public ?string $apiKey = null;
/** @var int The number of results to get from Mouser while searching (please note that this value is max 50) */ /** @var int The number of results to get from Mouser while searching (please note that this value is max 50) */
#[SettingsParameter(label: new TM("settings.ips.mouser.searchLimit"), description: new TM("settings.ips.mouser.searchLimit.help"), envVar: "int:PROVIDER_MOUSER_SEARCH_LIMIT")] #[SettingsParameter(label: new TM("settings.ips.mouser.searchLimit"), description: new TM("settings.ips.mouser.searchLimit.help"),
envVar: "int:PROVIDER_MOUSER_SEARCH_LIMIT", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Range(min: 1, max: 50)] #[Assert\Range(min: 1, max: 50)]
public int $searchLimit = 50; public int $searchLimit = 50;
/** @var MouserSearchOptions Filter search results by RoHS compliance and stock availability */ /** @var MouserSearchOptions Filter search results by RoHS compliance and stock availability */
#[SettingsParameter(label: new TM("settings.ips.mouser.searchOptions"), description: new TM("settings.ips.mouser.searchOptions.help"), envVar: "PROVIDER_MOUSER_SEARCH_OPTION", envVarMapper: [self::class, "mapSearchOptionEnvVar"])] #[SettingsParameter(label: new TM("settings.ips.mouser.searchOptions"), description: new TM("settings.ips.mouser.searchOptions.help"),
envVar: "PROVIDER_MOUSER_SEARCH_OPTION", envVarMode: EnvVarMode::OVERWRITE, envVarMapper: [self::class, "mapSearchOptionEnvVar"])]
public MouserSearchOptions $searchOption = MouserSearchOptions::NONE; public MouserSearchOptions $searchOption = MouserSearchOptions::NONE;
/** @var bool It is recommended to leave this set to 'true'. The option is not really documented by Mouser: /** @var bool It is recommended to leave this set to 'true'. The option is not really documented by Mouser:
* Used when searching for keywords in the language specified when you signed up for Search API. */ * Used when searching for keywords in the language specified when you signed up for Search API. */
//TODO: Put this into some expert mode only //TODO: Put this into some expert mode only
#[SettingsParameter(envVar: "bool:PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE")] //#[SettingsParameter(envVar: "bool:PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE")]
public bool $searchWithSignUpLanguage = true; public bool $searchWithSignUpLanguage = true;
public static function mapSearchOptionEnvVar(?string $value): MouserSearchOptions public static function mapSearchOptionEnvVar(?string $value): MouserSearchOptions

View file

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Settings\InfoProviderSystem; namespace App\Settings\InfoProviderSystem;
use App\Settings\SettingsIcon; use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Jbtronics\SettingsBundle\Settings\SettingsTrait; use Jbtronics\SettingsBundle\Settings\SettingsTrait;
@ -41,14 +42,17 @@ class OEMSecretsSettings
public const SUPPORTED_CURRENCIES = ["AUD", "CAD", "CHF", "CNY", "DKK", "EUR", "GBP", "HKD", "ILS", "INR", "JPY", "KRW", "NOK", public const SUPPORTED_CURRENCIES = ["AUD", "CAD", "CHF", "CNY", "DKK", "EUR", "GBP", "HKD", "ILS", "INR", "JPY", "KRW", "NOK",
"NZD", "RUB", "SEK", "SGD", "TWD", "USD"]; "NZD", "RUB", "SEK", "SGD", "TWD", "USD"];
#[SettingsParameter(label: new TM("settings.ips.element14.apiKey"), envVar: "PROVIDER_OEMSECRETS_KEY")] #[SettingsParameter(label: new TM("settings.ips.element14.apiKey"),
envVar: "PROVIDER_OEMSECRETS_KEY", envVarMode: EnvVarMode::OVERWRITE)]
public ?string $apiKey = null; public ?string $apiKey = null;
#[Assert\Country] #[Assert\Country]
#[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, formOptions: ["preferred_choices" => ["DE", "PL", "GB", "FR", "US"]], envVar: "PROVIDER_OEMSECRETS_COUNTRY_CODE")] #[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, formOptions: ["preferred_choices" => ["DE", "PL", "GB", "FR", "US"]],
envVar: "PROVIDER_OEMSECRETS_COUNTRY_CODE", envVarMode: EnvVarMode::OVERWRITE)]
public ?string $country = "DE"; public ?string $country = "DE";
#[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, formOptions: ["preferred_choices" => self::SUPPORTED_CURRENCIES], envVar: "PROVIDER_OEMSECRETS_CURRENCY")] #[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, formOptions: ["preferred_choices" => self::SUPPORTED_CURRENCIES],
envVar: "PROVIDER_OEMSECRETS_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Choice(choices: self::SUPPORTED_CURRENCIES)] #[Assert\Choice(choices: self::SUPPORTED_CURRENCIES)]
public string $currency = "EUR"; public string $currency = "EUR";
@ -56,7 +60,8 @@ class OEMSecretsSettings
* @var bool If this is enabled, distributors with zero prices * @var bool If this is enabled, distributors with zero prices
* will be discarded from the creation of a new part * will be discarded from the creation of a new part
*/ */
#[SettingsParameter(label: new TM("settings.ips.oemsecrets.keepZeroPrices"), description: new TM("settings.ips.oemsecrets.keepZeroPrices.help"), envVar: "bool:PROVIDER_OEMSECRETS_ZERO_PRICE")] #[SettingsParameter(label: new TM("settings.ips.oemsecrets.keepZeroPrices"), description: new TM("settings.ips.oemsecrets.keepZeroPrices.help"),
envVar: "bool:PROVIDER_OEMSECRETS_ZERO_PRICE", envVarMode: EnvVarMode::OVERWRITE)]
public bool $keepZeroPrices = false; public bool $keepZeroPrices = false;
/** /**
@ -64,7 +69,8 @@ class OEMSecretsSettings
* # from the description transforming unstructured descriptions into structured parameters; * # from the description transforming unstructured descriptions into structured parameters;
* # each parameter in description should have the form: "...;name1:value1;name2:value2" * # each parameter in description should have the form: "...;name1:value1;name2:value2"
*/ */
#[SettingsParameter(label: new TM("settings.ips.oemsecrets.parseParams"), description: new TM("settings.ips.oemsecrets.parseParams.help"), envVar: "bool:PROVIDER_OEMSECRETS_SET_PARAM")] #[SettingsParameter(label: new TM("settings.ips.oemsecrets.parseParams"), description: new TM("settings.ips.oemsecrets.parseParams.help"),
envVar: "bool:PROVIDER_OEMSECRETS_SET_PARAM", envVarMode: EnvVarMode::OVERWRITE)]
public bool $parseParams = true; public bool $parseParams = true;
#[SettingsParameter(label: new TM("settings.ips.oemsecrets.sortMode"), envVar: "PROVIDER_OEMSECRETS_SORT_CRITERIA", envVarMapper: [self::class, "mapSortModeEnvVar"])] #[SettingsParameter(label: new TM("settings.ips.oemsecrets.sortMode"), envVar: "PROVIDER_OEMSECRETS_SORT_CRITERIA", envVarMapper: [self::class, "mapSortModeEnvVar"])]

View file

@ -0,0 +1,78 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Settings\InfoProviderSystem;
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\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Translation\TranslatableMessage as TM;
use Symfony\Component\Validator\Constraints as Assert;
#[Settings(label: new TM("settings.ips.octopart"))]
#[SettingsIcon("fa-plug")]
class OctopartSettings
{
use SettingsTrait;
#[SettingsParameter(
label: new TM("settings.ips.digikey.client_id"),
envVar: "PROVIDER_OCTOPART_CLIENT_ID", envVarMode: EnvVarMode::OVERWRITE
)]
public ?string $clientId = null;
#[SettingsParameter(
label: new TM("settings.ips.digikey.secret"),
envVar: "PROVIDER_OCTOPART_SECRET", envVarMode: EnvVarMode::OVERWRITE
)]
public ?string $secret = null;
#[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class,
formOptions: ["preferred_choices" => ["EUR", "USD", "CHF", "GBP"]],
envVar: "PROVIDER_OCTOPART_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Currency()]
public string $currency = "EUR";
#[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class,
envVar: "PROVIDER_OCTOPART_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Country]
public string $country = "DE";
#[SettingsParameter(label: new TM("settings.ips.octopart.searchLimit"), description: new TM("settings.ips.octopart.searchLimit.help"),
formType: NumberType::class, formOptions: ["attr" => ["min" => 1, "max" => 100]],
envVar: "int:PROVIDER_OCTOPART_SEARCH_LIMIT", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Range(min: 1, max: 100)]
public int $searchLimit = 10;
#[SettingsParameter(label: new TM("settings.ips.octopart.onlyAuthorizedSellers"),
description: new TM("settings.ips.octopart.onlyAuthorizedSellers.help"),
envVar: "bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS", envVarMode: EnvVarMode::OVERWRITE
)]
public bool $onlyAuthorizedSellers = true;
}

View file

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Settings\InfoProviderSystem; namespace App\Settings\InfoProviderSystem;
use App\Settings\SettingsIcon; use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Symfony\Component\Translation\TranslatableMessage as TM; use Symfony\Component\Translation\TranslatableMessage as TM;
@ -32,6 +33,7 @@ use Symfony\Component\Translation\TranslatableMessage as TM;
#[SettingsIcon("fa-plug")] #[SettingsIcon("fa-plug")]
class PollinSettings class PollinSettings
{ {
#[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"), envVar: "bool:PROVIDER_POLLIN_ENABLED")] #[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"),
envVar: "bool:PROVIDER_POLLIN_ENABLED", envVarMode: EnvVarMode::OVERWRITE)]
public bool $enabled = false; public bool $enabled = false;
} }

View file

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Settings\InfoProviderSystem; namespace App\Settings\InfoProviderSystem;
use App\Settings\SettingsIcon; use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Jbtronics\SettingsBundle\Settings\SettingsTrait; use Jbtronics\SettingsBundle\Settings\SettingsTrait;
@ -41,22 +42,27 @@ class ReicheltSettings
public const SUPPORTED_LANGUAGE = ["en", "de", "fr", "nl", "pl", "it", "es"]; public const SUPPORTED_LANGUAGE = ["en", "de", "fr", "nl", "pl", "it", "es"];
#[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"), envVar: "bool:PROVIDER_REICHELT_ENABLED")] #[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"),
envVar: "bool:PROVIDER_REICHELT_ENABLED", envVarMode: EnvVarMode::OVERWRITE)]
public bool $enabled = false; public bool $enabled = false;
#[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, formOptions: ["preferred_choices" => ["EUR"]], envVar: "PROVIDER_REICHELT_CURRENCY")] #[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, formOptions: ["preferred_choices" => ["EUR"]],
envVar: "PROVIDER_REICHELT_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)]
public string $currency = "EUR"; public string $currency = "EUR";
#[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class, formOptions: ["preferred_choices" => self::SUPPORTED_LANGUAGE], envVar: "PROVIDER_REICHELT_LANGUAGE")] #[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class, formOptions: ["preferred_choices" => self::SUPPORTED_LANGUAGE],
envVar: "PROVIDER_REICHELT_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Language()] #[Assert\Language()]
#[Assert\Choice(choices: self::SUPPORTED_LANGUAGE)] #[Assert\Choice(choices: self::SUPPORTED_LANGUAGE)]
public string $language = "en"; public string $language = "en";
#[SettingsParameter(label: new TM("settings.ips.tme.country"), envVar: "PROVIDER_REICHELT_COUNTRY", formType: CountryType::class, formOptions: ["preferred_choices" => ["DE", "PL", "GB", "FR"]])] #[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, formOptions: ["preferred_choices" => ["DE", "PL", "GB", "FR"]],
envVar: "PROVIDER_REICHELT_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Country] #[Assert\Country]
public string $country = "DE"; public string $country = "DE";
#[SettingsParameter(label: new TM("settings.ips.reichelt.include_vat"), envVar: "bool:PROVIDER_REICHELT_INCLUDE_VAT")] #[SettingsParameter(label: new TM("settings.ips.reichelt.include_vat"),
envVar: "bool:PROVIDER_REICHELT_INCLUDE_VAT", envVarMode: EnvVarMode::OVERWRITE)]
public bool $includeVAT = true; public bool $includeVAT = true;
} }

View file

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Settings\InfoProviderSystem; namespace App\Settings\InfoProviderSystem;
use App\Settings\SettingsIcon; use App\Settings\SettingsIcon;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Jbtronics\SettingsBundle\Settings\SettingsTrait; use Jbtronics\SettingsBundle\Settings\SettingsTrait;
@ -42,24 +43,30 @@ class TMESettings
private const SUPPORTED_CURRENCIES = ["EUR", "USD", "PLN", "GBP"]; private const SUPPORTED_CURRENCIES = ["EUR", "USD", "PLN", "GBP"];
#[SettingsParameter(label: new TM("settings.ips.tme.token"), #[SettingsParameter(label: new TM("settings.ips.tme.token"),
description: new TM("settings.ips.tme.token.help"), formOptions: ["help_html" => true], envVar: "PROVIDER_TME_KEY")] description: new TM("settings.ips.tme.token.help"), formOptions: ["help_html" => true],
envVar: "PROVIDER_TME_KEY", envVarMode: EnvVarMode::OVERWRITE)]
public ?string $apiToken = null; public ?string $apiToken = null;
#[SettingsParameter(label: new TM("settings.ips.tme.secret"), envVar: "PROVIDER_TME_SECRET")] #[SettingsParameter(label: new TM("settings.ips.tme.secret"),
envVar: "PROVIDER_TME_SECRET", envVarMode: EnvVarMode::OVERWRITE)]
public ?string $apiSecret = null; public ?string $apiSecret = null;
#[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, formOptions: ["preferred_choices" => self::SUPPORTED_CURRENCIES], envVar: "PROVIDER_TME_CURRENCY")] #[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, formOptions: ["preferred_choices" => self::SUPPORTED_CURRENCIES],
envVar: "PROVIDER_TME_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Choice(choices: self::SUPPORTED_CURRENCIES)] #[Assert\Choice(choices: self::SUPPORTED_CURRENCIES)]
public string $currency = "EUR"; public string $currency = "EUR";
#[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class, formOptions: ["preferred_choices" => ["en", "de", "fr", "pl"]], envVar: "PROVIDER_TME_LANGUAGE")] #[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class, formOptions: ["preferred_choices" => ["en", "de", "fr", "pl"]],
envVar: "PROVIDER_TME_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Language] #[Assert\Language]
public string $language = "en"; public string $language = "en";
#[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, formOptions: ["preferred_choices" => ["DE", "PL", "GB", "FR"]], envVar: "PROVIDER_TME_COUNTRY")] #[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, formOptions: ["preferred_choices" => ["DE", "PL", "GB", "FR"]],
envVar: "PROVIDER_TME_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)]
#[Assert\Country] #[Assert\Country]
public string $country = "DE"; public string $country = "DE";
#[SettingsParameter(label: new TM("settings.ips.tme.grossPrices"), envVar: "bool:PROVIDER_TME_GET_GROSS_PRICES")] #[SettingsParameter(label: new TM("settings.ips.tme.grossPrices"),
envVar: "bool:PROVIDER_TME_GET_GROSS_PRICES", envVarMode: EnvVarMode::OVERWRITE)]
public bool $grossPrices = true; public bool $grossPrices = true;
} }

View file

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 Symfony\Component\Translation\TranslatableMessage as TM;
#[Settings(name: "exchange_rate", label: new TM("settings.misc.exchange_rate"))]
#[SettingsIcon("fa-money-bill-transfer")]
class ExchangeRateSettings
{
#[SettingsParameter(label: new TM("settings.misc.exchange_rate.fixer_api_key"),
description: new TM("settings.misc.exchange_rate.fixer_api_key.help"),
envVar: "FIXER_API_KEY", envVarMode: EnvVarMode::OVERWRITE,
)]
public ?string $fixerApiKey = null;
}

View file

@ -31,4 +31,7 @@ class MiscSettings
{ {
#[EmbeddedSettings] #[EmbeddedSettings]
public ?KiCadEDASettings $kicadEDA = null; public ?KiCadEDASettings $kicadEDA = null;
#[EmbeddedSettings]
public ?ExchangeRateSettings $exchangeRate = null;
} }

View file

@ -70,6 +70,7 @@ class HistorySettings
description: new TM("settings.system.history.enforceComments.description"), description: new TM("settings.system.history.enforceComments.description"),
options: ['type' => EnumType::class, 'nullable' => false, 'options' => ['class' => EventCommentType::class]], options: ['type' => EnumType::class, 'nullable' => false, 'options' => ['class' => EventCommentType::class]],
formType: EnforceEventCommentTypesType::class, formType: EnforceEventCommentTypesType::class,
formOptions: ['required' => false, "empty_data" => []],
envVar: "ENFORCE_CHANGE_COMMENTS_FOR", envVarMode: EnvVarMode::OVERWRITE, envVarMapper: [self::class, 'mapEnforceComments'] envVar: "ENFORCE_CHANGE_COMMENTS_FOR", envVarMode: EnvVarMode::OVERWRITE, envVarMapper: [self::class, 'mapEnforceComments']
)] )]
public array $enforceComments = []; public array $enforceComments = [];

View file

@ -13,6 +13,7 @@
<div class="carousel-item {% if loop.first %}active{% endif %}"> <div class="carousel-item {% if loop.first %}active{% endif %}">
<a href="{{ entity_url(pic, 'file_view') }}" data-turbo="false" target="_blank" rel="noopener"> <a href="{{ entity_url(pic, 'file_view') }}" data-turbo="false" target="_blank" rel="noopener">
<img class="d-block w-100 img-fluid img-thumbnail bg-light part-info-image" src="{{ entity_url(pic, 'file_view') }}" alt=""> <img class="d-block w-100 img-fluid img-thumbnail bg-light part-info-image" src="{{ entity_url(pic, 'file_view') }}" alt="">
{% if settings_instance("part_info").showPartImageOverlay %}
<div class="mask"></div> <div class="mask"></div>
<div class="carousel-caption-hover"> <div class="carousel-caption-hover">
<div class="carousel-caption text-white"> <div class="carousel-caption text-white">
@ -21,6 +22,7 @@
<div>{{ entity_type_label(pic.element) }}</div> <div>{{ entity_type_label(pic.element) }}</div>
</div> </div>
</div> </div>
{% endif %}
</a> </a>
</div> </div>
{% endfor %} {% endfor %}

View file

@ -12922,5 +12922,107 @@ Please note, that you can not impersonate a disabled user. If you try you will g
<target>Pollin.de offers no official API, so this info provider webscrapes the website to extract info. It could break at any time, use it at your own risk.</target> <target>Pollin.de offers no official API, so this info provider webscrapes the website to extract info. It could break at any time, use it at your own risk.</target>
</segment> </segment>
</unit> </unit>
<unit id="TEm7uIg" name="settings.behavior.sidebar.rootNodeRedirectsToNewEntity">
<segment>
<source>settings.behavior.sidebar.rootNodeRedirectsToNewEntity</source>
<target>Root nodes redirect to new entity pages</target>
</segment>
</unit>
<unit id="j7HiQ80" name="settings.ips.digikey">
<segment>
<source>settings.ips.digikey</source>
<target>Digikey</target>
</segment>
</unit>
<unit id="_ViyVdh" name="settings.ips.digikey.client_id">
<segment>
<source>settings.ips.digikey.client_id</source>
<target>Client ID</target>
</segment>
</unit>
<unit id="eB9dDyp" name="settings.ips.digikey.secret">
<segment>
<source>settings.ips.digikey.secret</source>
<target>Secret</target>
</segment>
</unit>
<unit id="5xjmMzf" name="settings.ips.octopart">
<segment>
<source>settings.ips.octopart</source>
<target>Octopart / Nexar</target>
</segment>
</unit>
<unit id="vGv90iO" name="settings.ips.octopart.searchLimit">
<segment>
<source>settings.ips.octopart.searchLimit</source>
<target>Number of results</target>
</segment>
</unit>
<unit id="au4Yeps" name="settings.ips.octopart.searchLimit.help">
<segment>
<source>settings.ips.octopart.searchLimit.help</source>
<target>The number of results to get from Octopart while searching (please note that this counts towards your API limits)</target>
</segment>
</unit>
<unit id="Tiqmk.8" name="settings.ips.octopart.onlyAuthorizedSellers">
<segment>
<source>settings.ips.octopart.onlyAuthorizedSellers</source>
<target>Only authorized sellers</target>
</segment>
</unit>
<unit id="ECQkeJy" name="settings.ips.octopart.onlyAuthorizedSellers.help">
<segment>
<source>settings.ips.octopart.onlyAuthorizedSellers.help</source>
<target>Set to false to include non-authorized offers in the results</target>
</segment>
</unit>
<unit id="iRDDtdU" name="settings.misc.exchange_rate">
<segment>
<source>settings.misc.exchange_rate</source>
<target>Money exchange rates</target>
</segment>
</unit>
<unit id="0REngfi" name="settings.misc.exchange_rate.fixer_api_key">
<segment>
<source>settings.misc.exchange_rate.fixer_api_key</source>
<target>Fixer.io API Key</target>
</segment>
</unit>
<unit id="COLhoWD" name="settings.misc.exchange_rate.fixer_api_key.help">
<segment>
<source>settings.misc.exchange_rate.fixer_api_key.help</source>
<target>If you need exchange rates between non-euro currencies, you can input an API key from fixer.io here.</target>
</segment>
</unit>
<unit id="Ffr5xYM" name="settings.behavior.part_info">
<segment>
<source>settings.behavior.part_info</source>
<target>Part info page</target>
</segment>
</unit>
<unit id="weH3j.a" name="settings.behavior.part_info.show_part_image_overlay">
<segment>
<source>settings.behavior.part_info.show_part_image_overlay</source>
<target>Show image overlay</target>
</segment>
</unit>
<unit id="SCUs3WS" name="settings.behavior.part_info.show_part_image_overlay.help">
<segment>
<source>settings.behavior.part_info.show_part_image_overlay.help</source>
<target>Show the image overlay with attachment details on hovering over the part image gallery.</target>
</segment>
</unit>
<unit id="ALfPkeR" name="perm.config.change_system_settings">
<segment>
<source>perm.config.change_system_settings</source>
<target>Change system settings</target>
</segment>
</unit>
<unit id="TlHeIjk" name="tree.tools.system.settings">
<segment>
<source>tree.tools.system.settings</source>
<target>System settings</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>