Compare commits

..

1 commit

Author SHA1 Message Date
github-actions[bot]
3db982b60e Update KiCad symbols and footprints lists
Some checks failed
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / build (linux/amd64, amd64, ubuntu-latest) (push) Has been cancelled
Docker Image Build / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Has been cancelled
Docker Image Build / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Has been cancelled
Docker Image Build (FrankenPHP) / build (linux/amd64, amd64, ubuntu-latest) (push) Has been cancelled
Docker Image Build (FrankenPHP) / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Has been cancelled
Docker Image Build (FrankenPHP) / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Has been cancelled
Static analysis / Static analysis (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.5, mysql) (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.5, postgres) (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
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, sqlite) (push) Has been cancelled
Docker Image Build / merge (push) Has been cancelled
Docker Image Build (FrankenPHP) / merge (push) Has been cancelled
2026-06-08 06:52:55 +00:00
28 changed files with 346 additions and 538 deletions

10
.env
View file

@ -149,16 +149,6 @@ DISABLE_YEAR2038_BUG_CHECK=0
#TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 #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)$' #TRUSTED_HOSTS='^(localhost|example\.com)$'
###################################################################################
# Logging settings
###################################################################################
# The minimum level a deprecation notice must have to be written to the var/log/<env>_deprecations.log file.
# Deprecation notices are logged with level "info", so this disables the deprecation log by default.
# Set to debug to log all deprecation notices
DEPRECATION_LOG_LEVEL=emergency
###> symfony/lock ### ###> symfony/lock ###
# Choose one of the stores below # Choose one of the stores below

View file

@ -129,7 +129,7 @@ jobs:
run: ./bin/phpunit --coverage-clover=coverage.xml run: ./bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v7 uses: codecov/codecov-action@v6
with: with:
env_vars: PHP_VERSION,DB_TYPE env_vars: PHP_VERSION,DB_TYPE
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

View file

@ -1 +1 @@
2.12.2 2.12.1

View file

@ -51,7 +51,7 @@ export default class extends Controller {
//Make the state persistent over reloads //Make the state persistent over reloads
if(localStorage.getItem(STORAGE_KEY) === 'true') { if(localStorage.getItem(STORAGE_KEY) === 'true') {
this.hideSidebar(); sidebarHide();
} }
} }

View file

@ -70,7 +70,6 @@
"symfony/flex": "^v2.3.1", "symfony/flex": "^v2.3.1",
"symfony/form": "7.4.*", "symfony/form": "7.4.*",
"symfony/framework-bundle": "7.4.*", "symfony/framework-bundle": "7.4.*",
"symfony/html-sanitizer": "7.4.*",
"symfony/http-client": "7.4.*", "symfony/http-client": "7.4.*",
"symfony/http-kernel": "7.4.*", "symfony/http-kernel": "7.4.*",
"symfony/mailer": "7.4.*", "symfony/mailer": "7.4.*",

424
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -51,7 +51,6 @@ when@prod:
type: stream type: stream
channels: [deprecation] channels: [deprecation]
path: "%kernel.logs_dir%/%kernel.environment%_deprecations.log" path: "%kernel.logs_dir%/%kernel.environment%_deprecations.log"
level: "%env(DEPRECATION_LOG_LEVEL)%"
when@docker: when@docker:
monolog: monolog:
@ -76,4 +75,3 @@ when@docker:
type: stream type: stream
channels: [deprecation] channels: [deprecation]
path: "%kernel.logs_dir%/%kernel.environment%_deprecations.log" path: "%kernel.logs_dir%/%kernel.environment%_deprecations.log"
level: "%env(DEPRECATION_LOG_LEVEL)%"

View file

@ -653,7 +653,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* time_based_uuid_node?: scalar|Param|null, * time_based_uuid_node?: scalar|Param|null,
* }, * },
* html_sanitizer?: bool|array{ // HtmlSanitizer configuration * html_sanitizer?: bool|array{ // HtmlSanitizer configuration
* enabled?: bool|Param, // Default: true * enabled?: bool|Param, // Default: false
* sanitizers?: array<string, array{ // Default: [] * sanitizers?: array<string, array{ // Default: []
* allow_safe_elements?: bool|Param, // Allows "safe" elements and attributes. // Default: false * allow_safe_elements?: bool|Param, // Allows "safe" elements and attributes. // Default: false
* allow_static_elements?: bool|Param, // Allows all static elements and attributes from the W3C Sanitizer API standard. // Default: false * allow_static_elements?: bool|Param, // Allows all static elements and attributes from the W3C Sanitizer API standard. // Default: false
@ -718,7 +718,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter. * servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
* sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver * sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
* server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere. * server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection. * default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion.
* sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL. * sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
* sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities. * sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
* sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL. * sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
@ -769,7 +769,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter. * servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
* sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver * sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
* server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere. * server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection. * default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion.
* sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL. * sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
* sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities. * sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
* sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL. * sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
@ -801,7 +801,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter. * servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
* sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver * sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
* server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere. * server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection. * default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion.
* sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL. * sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
* sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities. * sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
* sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL. * sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.

View file

@ -279,13 +279,9 @@ See the [information providers]({% link usage/information_provider_system.md %})
* `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker * `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker
containers. In all other applications you can just change the `config/banner.md` file. containers. In all other applications you can just change the `config/banner.md` file.
* `DISABLE_YEAR2038_BUG_CHECK` (env only): If set to `1`, the year 2038 bug check is disabled on 32-bit systems, and dates after * `DISABLE_YEAR2038_BUG_CHECK` (env only): If set to `1`, the year 2038 bug check is disabled on 32-bit systems, and dates after
2038 are no longer forbidden. However, this will lead to 500 error messages when rendering dates after 2038 as all current 2038 are no longer forbidden. However this will lead to 500 error messages when rendering dates after 2038 as all current
32-bit PHP versions can not format these dates correctly. This setting is for the case that future PHP versions will 32-bit PHP versions can not format these dates correctly. This setting is for the case that future PHP versions will
handle this correctly on 32-bit systems. 64-bit systems are not affected by this bug, and the check is always disabled. handle this correctly on 32-bit systems. 64-bit systems are not affected by this bug, and the check is always disabled.
* `DEPRECATION_LOG_LEVEL` (default `emergency`) (env only): In the `prod` and `docker` environments, PHP/Symfony
deprecation notices are written to their own `var/log/<env>_deprecations.log` file. This option sets the minimum log
level a deprecation notice must have to be written there. Since deprecation notices are logged with level `info`,
the default value of `emergency` effectively disables this dedicated deprecation log. Set it to `debug` to enable it.
## Banner ## Banner

View file

@ -1,4 +1,4 @@
# Generated on Mon Jun 15 07:28:00 UTC 2026 # Generated on Mon Jun 8 06:51:27 UTC 2026
# This file contains all footprints available in the offical KiCAD library # This file contains all footprints available in the offical KiCAD library
Audio_Module:Reverb_BTDR-1H Audio_Module:Reverb_BTDR-1H
Audio_Module:Reverb_BTDR-1V Audio_Module:Reverb_BTDR-1V

View file

@ -1,4 +1,4 @@
# Generated on Mon Jun 15 07:28:38 UTC 2026 # Generated on Mon Jun 8 06:52:04 UTC 2026
# This file contains all symbols available in the offical KiCAD library # This file contains all symbols available in the offical KiCAD library
4xxx:14528 4xxx:14528
4xxx:14529 4xxx:14529
@ -5300,39 +5300,21 @@ Converter_DCDC:TMR-4812
Converter_DCDC:TMR-4821 Converter_DCDC:TMR-4821
Converter_DCDC:TMR-4822 Converter_DCDC:TMR-4822
Converter_DCDC:TMR-4823 Converter_DCDC:TMR-4823
Converter_DCDC:TMR10-1211WI
Converter_DCDC:TMR10-1212WI
Converter_DCDC:TMR10-1213WI
Converter_DCDC:TMR10-1215WI
Converter_DCDC:TMR10-1222WI
Converter_DCDC:TMR10-1223WI
Converter_DCDC:TMR10-2410WIR Converter_DCDC:TMR10-2410WIR
Converter_DCDC:TMR10-2411WI
Converter_DCDC:TMR10-2411WIR Converter_DCDC:TMR10-2411WIR
Converter_DCDC:TMR10-2412WI
Converter_DCDC:TMR10-2412WIR Converter_DCDC:TMR10-2412WIR
Converter_DCDC:TMR10-2413WI
Converter_DCDC:TMR10-2413WIR Converter_DCDC:TMR10-2413WIR
Converter_DCDC:TMR10-2415WI
Converter_DCDC:TMR10-2415WIR Converter_DCDC:TMR10-2415WIR
Converter_DCDC:TMR10-2421WIR Converter_DCDC:TMR10-2421WIR
Converter_DCDC:TMR10-2422WI
Converter_DCDC:TMR10-2422WIR Converter_DCDC:TMR10-2422WIR
Converter_DCDC:TMR10-2423WI
Converter_DCDC:TMR10-2423WIR Converter_DCDC:TMR10-2423WIR
Converter_DCDC:TMR10-4810WIR Converter_DCDC:TMR10-4810WIR
Converter_DCDC:TMR10-4811WI
Converter_DCDC:TMR10-4811WIR Converter_DCDC:TMR10-4811WIR
Converter_DCDC:TMR10-4812WI
Converter_DCDC:TMR10-4812WIR Converter_DCDC:TMR10-4812WIR
Converter_DCDC:TMR10-4813WI
Converter_DCDC:TMR10-4813WIR Converter_DCDC:TMR10-4813WIR
Converter_DCDC:TMR10-4815WI
Converter_DCDC:TMR10-4815WIR Converter_DCDC:TMR10-4815WIR
Converter_DCDC:TMR10-4821WIR Converter_DCDC:TMR10-4821WIR
Converter_DCDC:TMR10-4822WI
Converter_DCDC:TMR10-4822WIR Converter_DCDC:TMR10-4822WIR
Converter_DCDC:TMR10-4823WI
Converter_DCDC:TMR10-4823WIR Converter_DCDC:TMR10-4823WIR
Converter_DCDC:TMR10-7210WIR Converter_DCDC:TMR10-7210WIR
Converter_DCDC:TMR10-7211WIR Converter_DCDC:TMR10-7211WIR
@ -14611,8 +14593,6 @@ MCU_Texas:LM4F111C4QR
MCU_Texas:LM4F111E5QR MCU_Texas:LM4F111E5QR
MCU_Texas:LM4F111H5QR MCU_Texas:LM4F111H5QR
MCU_Texas:MSP432E401Y MCU_Texas:MSP432E401Y
MCU_Texas:MSPM0C110xSDDF
MCU_Texas:MSPM0C110xSDSG
MCU_Texas:TM4C1230C3PM MCU_Texas:TM4C1230C3PM
MCU_Texas:TM4C1230D5PM MCU_Texas:TM4C1230D5PM
MCU_Texas:TM4C1230E6PM MCU_Texas:TM4C1230E6PM
@ -15691,7 +15671,6 @@ Power_Management:LM5069MM-1
Power_Management:LM5069MM-2 Power_Management:LM5069MM-2
Power_Management:LM66100DCK Power_Management:LM66100DCK
Power_Management:LM74700 Power_Management:LM74700
Power_Management:LM74701-Q1
Power_Management:LMG3410 Power_Management:LMG3410
Power_Management:LMG5200 Power_Management:LMG5200
Power_Management:LT1641-1 Power_Management:LT1641-1
@ -15793,7 +15772,6 @@ Power_Management:TPS22810DBV
Power_Management:TPS22810DRV Power_Management:TPS22810DRV
Power_Management:TPS22917DBV Power_Management:TPS22917DBV
Power_Management:TPS22917LDBV Power_Management:TPS22917LDBV
Power_Management:TPS22919DCK
Power_Management:TPS22929D Power_Management:TPS22929D
Power_Management:TPS22993 Power_Management:TPS22993
Power_Management:TPS2412D Power_Management:TPS2412D
@ -16705,19 +16683,6 @@ Reference_Voltage:LM4040LP-4.1
Reference_Voltage:LM4040LP-5 Reference_Voltage:LM4040LP-5
Reference_Voltage:LM4040LP-8.2 Reference_Voltage:LM4040LP-8.2
Reference_Voltage:LM4041LP-ADJ Reference_Voltage:LM4041LP-ADJ
Reference_Voltage:LM4050xEM3-2.1
Reference_Voltage:LM4050xEM3-2.5
Reference_Voltage:LM4050xEM3-3.0
Reference_Voltage:LM4050xEM3-3.3
Reference_Voltage:LM4050xEM3-4.1
Reference_Voltage:LM4050xEM3-5.0
Reference_Voltage:LM4050xEX3-2.1
Reference_Voltage:LM4050xEX3-2.5
Reference_Voltage:LM4050xEX3-3.3
Reference_Voltage:LM4050xEX3-4.1
Reference_Voltage:LM4050xEX3-5.0
Reference_Voltage:LM4051xEM3-1.2
Reference_Voltage:LM4051xEX3-1.2
Reference_Voltage:LM4125AIM5-2.5 Reference_Voltage:LM4125AIM5-2.5
Reference_Voltage:LM4125IM5-2.0 Reference_Voltage:LM4125IM5-2.0
Reference_Voltage:LM4125IM5-2.5 Reference_Voltage:LM4125IM5-2.5
@ -16865,6 +16830,7 @@ Reference_Voltage:MCP1501-25xCH
Reference_Voltage:MCP1501-25xRW Reference_Voltage:MCP1501-25xRW
Reference_Voltage:MCP1501-25xSN Reference_Voltage:MCP1501-25xSN
Reference_Voltage:MCP1501-30xCH Reference_Voltage:MCP1501-30xCH
Reference_Voltage:MCP1501-30xRW
Reference_Voltage:MCP1501-30xSN Reference_Voltage:MCP1501-30xSN
Reference_Voltage:MCP1501-33xCH Reference_Voltage:MCP1501-33xCH
Reference_Voltage:MCP1501-33xRW Reference_Voltage:MCP1501-33xRW
@ -21025,7 +20991,6 @@ Sensor_Temperature:MCP9501
Sensor_Temperature:MCP9502 Sensor_Temperature:MCP9502
Sensor_Temperature:MCP9503 Sensor_Temperature:MCP9503
Sensor_Temperature:MCP9504 Sensor_Temperature:MCP9504
Sensor_Temperature:MCP96xx01x-x-MX
Sensor_Temperature:MCP9700Ax-ELT Sensor_Temperature:MCP9700Ax-ELT
Sensor_Temperature:MCP9700Ax-ETT Sensor_Temperature:MCP9700Ax-ETT
Sensor_Temperature:MCP9700Ax-HLT Sensor_Temperature:MCP9700Ax-HLT

View file

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace App\Controller; namespace App\Controller;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\Project;
use App\Entity\Parts\Category; use App\Entity\Parts\Category;
@ -56,7 +55,7 @@ class TreeController extends AbstractController
#[Route(path: '/category/{id}', name: 'tree_category')] #[Route(path: '/category/{id}', name: 'tree_category')]
#[Route(path: '/categories', name: 'tree_category_root')] #[Route(path: '/categories', name: 'tree_category_root')]
public function categoryTree(#[MapEntity(id: 'id')] ?Category $category = null): JsonResponse public function categoryTree(?Category $category = null): JsonResponse
{ {
if ($this->isGranted('@parts.read') && $this->isGranted('@categories.read')) { if ($this->isGranted('@parts.read') && $this->isGranted('@categories.read')) {
$tree = $this->treeGenerator->getTreeView(Category::class, $category, 'list_parts_root'); $tree = $this->treeGenerator->getTreeView(Category::class, $category, 'list_parts_root');
@ -69,7 +68,7 @@ class TreeController extends AbstractController
#[Route(path: '/footprint/{id}', name: 'tree_footprint')] #[Route(path: '/footprint/{id}', name: 'tree_footprint')]
#[Route(path: '/footprints', name: 'tree_footprint_root')] #[Route(path: '/footprints', name: 'tree_footprint_root')]
public function footprintTree(#[MapEntity(id: 'id')] ?Footprint $footprint = null): JsonResponse public function footprintTree(?Footprint $footprint = null): JsonResponse
{ {
if ($this->isGranted('@parts.read') && $this->isGranted('@footprints.read')) { if ($this->isGranted('@parts.read') && $this->isGranted('@footprints.read')) {
$tree = $this->treeGenerator->getTreeView(Footprint::class, $footprint, 'list_parts_root'); $tree = $this->treeGenerator->getTreeView(Footprint::class, $footprint, 'list_parts_root');
@ -81,7 +80,7 @@ class TreeController extends AbstractController
#[Route(path: '/location/{id}', name: 'tree_location')] #[Route(path: '/location/{id}', name: 'tree_location')]
#[Route(path: '/locations', name: 'tree_location_root')] #[Route(path: '/locations', name: 'tree_location_root')]
public function locationTree(#[MapEntity(id: 'id')] ?StorageLocation $location = null): JsonResponse public function locationTree(?StorageLocation $location = null): JsonResponse
{ {
if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) { if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) {
$tree = $this->treeGenerator->getTreeView(StorageLocation::class, $location, 'list_parts_root'); $tree = $this->treeGenerator->getTreeView(StorageLocation::class, $location, 'list_parts_root');
@ -94,7 +93,7 @@ class TreeController extends AbstractController
#[Route(path: '/manufacturer/{id}', name: 'tree_manufacturer')] #[Route(path: '/manufacturer/{id}', name: 'tree_manufacturer')]
#[Route(path: '/manufacturers', name: 'tree_manufacturer_root')] #[Route(path: '/manufacturers', name: 'tree_manufacturer_root')]
public function manufacturerTree(#[MapEntity(id: 'id')] ?Manufacturer $manufacturer = null): JsonResponse public function manufacturerTree(?Manufacturer $manufacturer = null): JsonResponse
{ {
if ($this->isGranted('@parts.read') && $this->isGranted('@manufacturers.read')) { if ($this->isGranted('@parts.read') && $this->isGranted('@manufacturers.read')) {
$tree = $this->treeGenerator->getTreeView(Manufacturer::class, $manufacturer, 'list_parts_root'); $tree = $this->treeGenerator->getTreeView(Manufacturer::class, $manufacturer, 'list_parts_root');
@ -107,7 +106,7 @@ class TreeController extends AbstractController
#[Route(path: '/supplier/{id}', name: 'tree_supplier')] #[Route(path: '/supplier/{id}', name: 'tree_supplier')]
#[Route(path: '/suppliers', name: 'tree_supplier_root')] #[Route(path: '/suppliers', name: 'tree_supplier_root')]
public function supplierTree(#[MapEntity(id: 'id')] ?Supplier $supplier = null): JsonResponse public function supplierTree(?Supplier $supplier = null): JsonResponse
{ {
if ($this->isGranted('@parts.read') && $this->isGranted('@suppliers.read')) { if ($this->isGranted('@parts.read') && $this->isGranted('@suppliers.read')) {
$tree = $this->treeGenerator->getTreeView(Supplier::class, $supplier, 'list_parts_root'); $tree = $this->treeGenerator->getTreeView(Supplier::class, $supplier, 'list_parts_root');
@ -120,7 +119,7 @@ class TreeController extends AbstractController
#[Route(path: '/device/{id}', name: 'tree_device')] #[Route(path: '/device/{id}', name: 'tree_device')]
#[Route(path: '/devices', name: 'tree_device_root')] #[Route(path: '/devices', name: 'tree_device_root')]
public function deviceTree(#[MapEntity(id: 'id')] ?Project $device = null): JsonResponse public function deviceTree(?Project $device = null): JsonResponse
{ {
if ($this->isGranted('@projects.read')) { if ($this->isGranted('@projects.read')) {
$tree = $this->treeGenerator->getTreeView(Project::class, $device, 'devices'); $tree = $this->treeGenerator->getTreeView(Project::class, $device, 'devices');

View file

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace App\DataTables; namespace App\DataTables;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\PrettyBoolColumn; use App\DataTables\Column\PrettyBoolColumn;
use App\DataTables\Column\RowClassColumn; use App\DataTables\Column\RowClassColumn;
@ -41,19 +40,14 @@ use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableTypeInterface; use Omines\DataTablesBundle\DataTableTypeInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class AttachmentDataTable implements DataTableTypeInterface final class AttachmentDataTable implements DataTableTypeInterface
{ {
public function __construct(private TranslatorInterface $translator, private EntityURLGenerator $entityURLGenerator, private AttachmentManager $attachmentHelper, private AttachmentURLGenerator $attachmentURLGenerator, private ElementTypeNameGenerator $elementTypeNameGenerator) public function __construct(private readonly TranslatorInterface $translator, private readonly EntityURLGenerator $entityURLGenerator, private readonly AttachmentManager $attachmentHelper, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly ElementTypeNameGenerator $elementTypeNameGenerator)
{ {
} }
public function configure(DataTable $dataTable, array $options): void public function configure(DataTable $dataTable, array $options): void
{ {
/*************************************************************************************************************
* Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
* HTMLColumn, if necessary
************************************************************************************************************/
$dataTable->add('dont_matter', RowClassColumn::class, [ $dataTable->add('dont_matter', RowClassColumn::class, [
'render' => function ($value, Attachment $context): string { 'render' => function ($value, Attachment $context): string {
//Mark attachments yellow which have an internal file linked that doesn't exist //Mark attachments yellow which have an internal file linked that doesn't exist
@ -65,10 +59,10 @@ final readonly class AttachmentDataTable implements DataTableTypeInterface
}, },
]); ]);
$dataTable->add('picture', HTMLColumn::class, [ $dataTable->add('picture', TextColumn::class, [
'label' => '', 'label' => '',
'className' => 'no-colvis', 'className' => 'no-colvis',
'data' => function (Attachment $context): string { 'render' => function ($value, Attachment $context): string {
if ($context->isPicture() if ($context->isPicture()
&& $this->attachmentHelper->isInternalFileExisting($context)) { && $this->attachmentHelper->isInternalFileExisting($context)) {
@ -101,65 +95,65 @@ final readonly class AttachmentDataTable implements DataTableTypeInterface
'orderField' => 'NATSORT(attachment.name)', 'orderField' => 'NATSORT(attachment.name)',
]); ]);
$dataTable->add('attachment_type', HTMLColumn::class, [ $dataTable->add('attachment_type', TextColumn::class, [
'label' => 'attachment.table.type', 'label' => 'attachment.table.type',
'field' => 'attachment_type.name', 'field' => 'attachment_type.name',
'orderField' => 'NATSORT(attachment_type.name)', 'orderField' => 'NATSORT(attachment_type.name)',
'data' => fn(Attachment $context, $value): string => sprintf( 'render' => fn($value, Attachment $context): string => sprintf(
'<a href="%s">%s</a>', '<a href="%s">%s</a>',
$this->entityURLGenerator->editURL($context->getAttachmentType()), $this->entityURLGenerator->editURL($context->getAttachmentType()),
htmlspecialchars((string) $value) htmlspecialchars((string) $value)
), ),
]); ]);
$dataTable->add('element', HTMLColumn::class, [ $dataTable->add('element', TextColumn::class, [
'label' => 'attachment.table.element', 'label' => 'attachment.table.element',
//'propertyPath' => 'element.name', //'propertyPath' => 'element.name',
'data' => fn(Attachment $context): string => sprintf( 'render' => fn($value, Attachment $context): string => sprintf(
'<a href="%s">%s</a>', '<a href="%s">%s</a>',
$this->entityURLGenerator->infoURL($context->getElement()), $this->entityURLGenerator->infoURL($context->getElement()),
$this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true) $this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true)
), ),
]); ]);
$dataTable->add('internal_link', HTMLColumn::class, [ $dataTable->add('internal_link', TextColumn::class, [
'label' => 'attachment.table.internal_file', 'label' => 'attachment.table.internal_file',
'propertyPath' => 'filename', 'propertyPath' => 'filename',
'orderField' => 'NATSORT(attachment.original_filename)', 'orderField' => 'NATSORT(attachment.original_filename)',
'data' => function (Attachment $context, $value) { 'render' => function ($value, Attachment $context) {
if ($this->attachmentHelper->isInternalFileExisting($context)) { if ($this->attachmentHelper->isInternalFileExisting($context)) {
return sprintf( return sprintf(
'<a href="%s" target="_blank" data-no-ajax>%s</a>', '<a href="%s" target="_blank" data-no-ajax>%s</a>',
$this->entityURLGenerator->viewURL($context), $this->entityURLGenerator->viewURL($context),
htmlspecialchars((string) $value) htmlspecialchars($value)
); );
} }
return htmlspecialchars((string) $value); return $value;
}, }
]); ]);
$dataTable->add('external_link', HTMLColumn::class, [ $dataTable->add('external_link', TextColumn::class, [
'label' => 'attachment.table.external_link', 'label' => 'attachment.table.external_link',
'propertyPath' => 'host', 'propertyPath' => 'host',
'orderField' => 'attachment.external_path', 'orderField' => 'attachment.external_path',
'data' => function (Attachment $context, $value) { 'render' => function ($value, Attachment $context) {
if ($context->hasExternal()) { if ($context->hasExternal()) {
return sprintf( return sprintf(
'<a href="%s" class="link-external" title="%s" target="_blank" rel="noopener">%s</a>', '<a href="%s" class="link-external" title="%s" target="_blank" rel="noopener">%s</a>',
htmlspecialchars((string) $context->getExternalPath()), htmlspecialchars((string) $context->getExternalPath()),
htmlspecialchars((string) $context->getExternalPath()), htmlspecialchars((string) $context->getExternalPath()),
htmlspecialchars((string) $value), htmlspecialchars($value),
); );
} }
return htmlspecialchars((string) $value); return $value;
}, }
]); ]);
$dataTable->add('filesize', HTMLColumn::class, [ $dataTable->add('filesize', TextColumn::class, [
'label' => $this->translator->trans('attachment.table.filesize'), 'label' => $this->translator->trans('attachment.table.filesize'),
'data' => function (Attachment $context) { 'render' => function ($value, Attachment $context) {
if (!$context->hasInternal()) { if (!$context->hasInternal()) {
return sprintf( return sprintf(
'<span class="badge bg-primary"> '<span class="badge bg-primary">
@ -174,7 +168,7 @@ final readonly class AttachmentDataTable implements DataTableTypeInterface
'<span class="badge bg-secondary"> '<span class="badge bg-secondary">
<i class="fas fa-hdd fa-fw"></i> %s <i class="fas fa-hdd fa-fw"></i> %s
</span>', </span>',
htmlspecialchars($this->attachmentHelper->getHumanFileSize($context)) $this->attachmentHelper->getHumanFileSize($context)
); );
} }

View file

@ -78,7 +78,7 @@ class EntityColumn extends AbstractColumn
); );
} }
return sprintf('<i>%s</i>', htmlspecialchars($value)); return sprintf('<i>%s</i>', $value);
} }
return ''; return '';

View file

@ -1,37 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2026 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/>.
*/
namespace App\DataTables\Column;
use Omines\DataTablesBundle\Column\TextColumn;
/**
* A TextColumn whose value is always treated as raw HTML and therefore never passed through htmlspecialchars().
* The value returned by the 'data' option must already contain properly escaped/sanitized HTML, as it is output as-is.
*/
class HTMLColumn extends TextColumn
{
public function isRaw(): bool
{
return true;
}
}

View file

@ -87,9 +87,9 @@ class IconLinkColumn extends AbstractColumn
return sprintf( return sprintf(
'<a class="btn btn-primary btn-sm %s" href="%s" title="%s"><i class="%s"></i></a>', '<a class="btn btn-primary btn-sm %s" href="%s" title="%s"><i class="%s"></i></a>',
$disabled ? 'disabled' : '', $disabled ? 'disabled' : '',
htmlspecialchars($href), $href,
htmlspecialchars($title ?? ''), $title,
htmlspecialchars($icon ?? '') $icon
); );
} }

View file

@ -22,9 +22,9 @@ declare(strict_types=1);
*/ */
namespace App\DataTables; namespace App\DataTables;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\RowClassColumn; use App\DataTables\Column\RowClassColumn;
use Omines\DataTablesBundle\Adapter\ArrayAdapter; use Omines\DataTablesBundle\Adapter\ArrayAdapter;
use Omines\DataTablesBundle\Column\TextColumn;
use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableFactory; use Omines\DataTablesBundle\DataTableFactory;
use Omines\DataTablesBundle\DataTableTypeInterface; use Omines\DataTablesBundle\DataTableTypeInterface;
@ -32,7 +32,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
final readonly class ErrorDataTable implements DataTableTypeInterface class ErrorDataTable implements DataTableTypeInterface
{ {
public function configureOptions(OptionsResolver $optionsResolver): void public function configureOptions(OptionsResolver $optionsResolver): void
{ {
@ -49,11 +49,6 @@ final readonly class ErrorDataTable implements DataTableTypeInterface
public function configure(DataTable $dataTable, array $options): void public function configure(DataTable $dataTable, array $options): void
{ {
/*************************************************************************************************************
* Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
* HTMLColumn, if necessary
************************************************************************************************************/
$optionsResolver = new OptionsResolver(); $optionsResolver = new OptionsResolver();
$this->configureOptions($optionsResolver); $this->configureOptions($optionsResolver);
$options = $optionsResolver->resolve($options); $options = $optionsResolver->resolve($options);
@ -63,9 +58,9 @@ final readonly class ErrorDataTable implements DataTableTypeInterface
'render' => fn($value, $context): string => 'table-warning', 'render' => fn($value, $context): string => 'table-warning',
]) ])
->add('error', HTMLColumn::class, [ ->add('error', TextColumn::class, [
'label' => 'error_table.error', 'label' => 'error_table.error',
'data' => fn($context, $value): string => '<i class="fa-solid fa-triangle-exclamation fa-fw"></i> ' . htmlspecialchars((string) $value), 'render' => fn($value, $context): string => '<i class="fa-solid fa-triangle-exclamation fa-fw"></i> ' . $value,
]) ])
; ;

View file

@ -62,7 +62,7 @@ class PartDataTableHelper
} }
if ($context->getBuiltProject() instanceof Project) { if ($context->getBuiltProject() instanceof Project) {
$icon = sprintf('<i class="fa-solid fa-box-archive fa-fw me-1" title="%s"></i>', $icon = sprintf('<i class="fa-solid fa-box-archive fa-fw me-1" title="%s"></i>',
$this->translator->trans('part.info.projectBuildPart.hint').': '.htmlspecialchars($context->getBuiltProject()->getName())); $this->translator->trans('part.info.projectBuildPart.hint').': '.$context->getBuiltProject()->getName());
} }

View file

@ -25,7 +25,6 @@ namespace App\DataTables;
use App\DataTables\Column\EnumColumn; use App\DataTables\Column\EnumColumn;
use App\Entity\LogSystem\LogTargetType; use App\Entity\LogSystem\LogTargetType;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\IconLinkColumn; use App\DataTables\Column\IconLinkColumn;
use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\LogEntryExtraColumn; use App\DataTables\Column\LogEntryExtraColumn;
@ -60,7 +59,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class LogDataTable implements DataTableTypeInterface class LogDataTable implements DataTableTypeInterface
{ {
protected LogEntryRepository $logRepo; protected LogEntryRepository $logRepo;
@ -96,11 +95,6 @@ final readonly class LogDataTable implements DataTableTypeInterface
public function configure(DataTable $dataTable, array $options): void public function configure(DataTable $dataTable, array $options): void
{ {
/*************************************************************************************************************
* Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
* HTMLColumn, if necessary
************************************************************************************************************/
$resolver = new OptionsResolver(); $resolver = new OptionsResolver();
$this->configureOptions($resolver); $this->configureOptions($resolver);
$options = $resolver->resolve($options); $options = $resolver->resolve($options);
@ -110,10 +104,10 @@ final readonly class LogDataTable implements DataTableTypeInterface
'render' => fn($value, AbstractLogEntry $context) => $this->logLevelHelper->logLevelToTableColorClass($context->getLevelString()), 'render' => fn($value, AbstractLogEntry $context) => $this->logLevelHelper->logLevelToTableColorClass($context->getLevelString()),
]); ]);
$dataTable->add('symbol', HTMLColumn::class, [ $dataTable->add('symbol', TextColumn::class, [
'label' => '', 'label' => '',
'className' => 'no-colvis', 'className' => 'no-colvis',
'data' => fn(AbstractLogEntry $context): string => sprintf( 'render' => fn($value, AbstractLogEntry $context): string => sprintf(
'<i class="fas fa-fw %s" title="%s"></i>', '<i class="fas fa-fw %s" title="%s"></i>',
$this->logLevelHelper->logLevelToIconClass($context->getLevelString()), $this->logLevelHelper->logLevelToIconClass($context->getLevelString()),
$context->getLevelString() $context->getLevelString()
@ -134,10 +128,10 @@ final readonly class LogDataTable implements DataTableTypeInterface
) )
]); ]);
$dataTable->add('type', HTMLColumn::class, [ $dataTable->add('type', TextColumn::class, [
'label' => 'log.type', 'label' => 'log.type',
'propertyPath' => 'type', 'propertyPath' => 'type',
'data' => function (AbstractLogEntry $context, string $value) { 'render' => function (string $value, AbstractLogEntry $context) {
$text = $this->translator->trans('log.type.'.$value); $text = $this->translator->trans('log.type.'.$value);
if ($context instanceof PartStockChangedLogEntry) { if ($context instanceof PartStockChangedLogEntry) {
@ -155,20 +149,20 @@ final readonly class LogDataTable implements DataTableTypeInterface
'label' => 'log.level', 'label' => 'log.level',
'visible' => 'system_log' === $options['mode'], 'visible' => 'system_log' === $options['mode'],
'propertyPath' => 'levelString', 'propertyPath' => 'levelString',
'data' => fn(AbstractLogEntry $context, string $value) => $this->translator->trans('log.level.'.$value), 'render' => fn(string $value, AbstractLogEntry $context) => $this->translator->trans('log.level.'.$value),
]); ]);
$dataTable->add('user', HTMLColumn::class, [ $dataTable->add('user', TextColumn::class, [
'label' => 'log.user', 'label' => 'log.user',
'orderField' => 'NATSORT(user.name)', 'orderField' => 'NATSORT(user.name)',
'data' => function (AbstractLogEntry $context): string { 'render' => function ($value, AbstractLogEntry $context): string {
$user = $context->getUser(); $user = $context->getUser();
//If user was deleted, show the info from the username field //If user was deleted, show the info from the username field
if (!$user instanceof User) { if (!$user instanceof User) {
if ($context->isCLIEntry()) { if ($context->isCLIEntry()) {
return sprintf('%s [%s]', return sprintf('%s [%s]',
htmlspecialchars((string) $context->getCLIUsername()), htmlentities((string) $context->getCLIUsername()),
$this->translator->trans('log.cli_user') $this->translator->trans('log.cli_user')
); );
} }
@ -176,7 +170,7 @@ final readonly class LogDataTable implements DataTableTypeInterface
//Else we just deal with a deleted user //Else we just deal with a deleted user
return sprintf( return sprintf(
'@%s [%s]', '@%s [%s]',
htmlspecialchars($context->getUsername()), htmlentities($context->getUsername()),
$this->translator->trans('log.target_deleted'), $this->translator->trans('log.target_deleted'),
); );
} }
@ -188,7 +182,7 @@ final readonly class LogDataTable implements DataTableTypeInterface
$img_url, $img_url,
$this->userAvatarHelper->getAvatarMdURL($user), $this->userAvatarHelper->getAvatarMdURL($user),
$this->urlGenerator->generate('user_info', ['id' => $user->getID()]), $this->urlGenerator->generate('user_info', ['id' => $user->getID()]),
htmlspecialchars($user->getFullName(true)) htmlentities($user->getFullName(true))
); );
}, },
]); ]);
@ -200,7 +194,7 @@ final readonly class LogDataTable implements DataTableTypeInterface
'render' => function (LogTargetType $value, AbstractLogEntry $context) { 'render' => function (LogTargetType $value, AbstractLogEntry $context) {
$class = $value->toClass(); $class = $value->toClass();
if (null !== $class) { if (null !== $class) {
return $this->elementTypeNameGenerator->typeLabel($class); return $this->elementTypeNameGenerator->getLocalizedTypeLabel($class);
} }
return ''; return '';
@ -222,9 +216,9 @@ final readonly class LogDataTable implements DataTableTypeInterface
'icon' => 'fas fa-fw fa-eye', 'icon' => 'fas fa-fw fa-eye',
'href' => function ($value, AbstractLogEntry $context) { 'href' => function ($value, AbstractLogEntry $context) {
if ( if (
$context instanceof CollectionElementDeleted ||
($context instanceof TimeTravelInterface ($context instanceof TimeTravelInterface
&& $context->hasOldDataInformation()) && $context->hasOldDataInformation())
|| $context instanceof CollectionElementDeleted
) { ) {
try { try {
$target = $this->logRepo->getTargetElement($context); $target = $this->logRepo->getTargetElement($context);

View file

@ -25,7 +25,6 @@ namespace App\DataTables;
use App\DataTables\Adapters\TwoStepORMAdapter; use App\DataTables\Adapters\TwoStepORMAdapter;
use App\DataTables\Column\EntityColumn; use App\DataTables\Column\EntityColumn;
use App\DataTables\Column\EnumColumn; use App\DataTables\Column\EnumColumn;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\IconLinkColumn; use App\DataTables\Column\IconLinkColumn;
use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\MarkdownColumn; use App\DataTables\Column\MarkdownColumn;
@ -59,7 +58,7 @@ use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class PartsDataTable implements DataTableTypeInterface final class PartsDataTable implements DataTableTypeInterface
{ {
public const LENGTH_MENU = [[10, 25, 50, 100, 250, 500, -1], [10, 25, 50, 100, 250, 500, "All"]]; public const LENGTH_MENU = [[10, 25, 50, 100, 250, 500, -1], [10, 25, 50, 100, 250, 500, "All"]];
@ -95,11 +94,6 @@ final readonly class PartsDataTable implements DataTableTypeInterface
* When adding columns here, add them also to PartTableColumns enum, to make them configurable in the settings! * When adding columns here, add them also to PartTableColumns enum, to make them configurable in the settings!
*************************************************************************************************************/ *************************************************************************************************************/
/*************************************************************************************************************
* Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
* HTMLColumn, if necessary
************************************************************************************************************/
$this->csh $this->csh
//Color the table rows depending on the review and favorite status //Color the table rows depending on the review and favorite status
->add('row_color', RowClassColumn::class, [ ->add('row_color', RowClassColumn::class, [
@ -115,23 +109,23 @@ final readonly class PartsDataTable implements DataTableTypeInterface
}, },
], visibility_configurable: false) ], visibility_configurable: false)
->add('select', SelectColumn::class, visibility_configurable: false) ->add('select', SelectColumn::class, visibility_configurable: false)
->add('picture', HTMLColumn::class, [ ->add('picture', TextColumn::class, [
'label' => '', 'label' => '',
'className' => 'no-colvis', 'className' => 'no-colvis',
'data' => fn(Part $context) => $this->partDataTableHelper->renderPicture($context), 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderPicture($context),
], visibility_configurable: false) ], visibility_configurable: false)
->add('name', HTMLColumn::class, [ ->add('name', TextColumn::class, [
'label' => $this->translator->trans('part.table.name'), 'label' => $this->translator->trans('part.table.name'),
'data' => fn(Part $context) => $this->partDataTableHelper->renderName($context), 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context),
'orderField' => 'NATSORT(part.name)' 'orderField' => 'NATSORT(part.name)'
]) ])
->add('si_value', TextColumn::class, [ ->add('si_value', TextColumn::class, [
'label' => $this->translator->trans('part.table.si_value'), 'label' => $this->translator->trans('part.table.si_value'),
'data' => function (Part $context): string { 'render' => function ($value, Part $context): string {
$siValue = SiValueSort::sqliteSiValue($context->getName()); $siValue = SiValueSort::sqliteSiValue($context->getName());
if ($siValue !== null) { if ($siValue !== null) {
//Output it as scientific number with a big E //Output it as scientific number with a big E
return sprintf('%G', $siValue); return htmlspecialchars(sprintf('%G', $siValue));
} }
return ''; return '';
}, },
@ -162,38 +156,38 @@ final readonly class PartsDataTable implements DataTableTypeInterface
'label' => $this->translator->trans('part.table.manufacturer'), 'label' => $this->translator->trans('part.table.manufacturer'),
'orderField' => 'NATSORT(_manufacturer.name)' 'orderField' => 'NATSORT(_manufacturer.name)'
]) ])
->add('storelocation', HTMLColumn::class, [ ->add('storelocation', TextColumn::class, [
'label' => $this->translator->trans('part.table.storeLocations'), 'label' => $this->translator->trans('part.table.storeLocations'),
//We need to use a aggregate function to get the first store location, as we have a one-to-many relation //We need to use a aggregate function to get the first store location, as we have a one-to-many relation
'orderField' => 'NATSORT(MIN(_storelocations.name))', 'orderField' => 'NATSORT(MIN(_storelocations.name))',
'data' => fn(Part $context) => $this->partDataTableHelper->renderStorageLocations($context), 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
], alias: 'storage_location') ], alias: 'storage_location')
->add('amount', HTMLColumn::class, [ ->add('amount', TextColumn::class, [
'label' => $this->translator->trans('part.table.amount'), 'label' => $this->translator->trans('part.table.amount'),
'data' => fn(Part $context) => $this->partDataTableHelper->renderAmount($context), 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderAmount($context),
'orderField' => 'amountSum' 'orderField' => 'amountSum'
]) ])
->add('minamount', TextColumn::class, [ ->add('minamount', TextColumn::class, [
'label' => $this->translator->trans('part.table.minamount'), 'label' => $this->translator->trans('part.table.minamount'),
'data' => fn(Part $context, $value): string => $this->amountFormatter->format( 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format(
$value, $value,
$context->getPartUnit() $context->getPartUnit()
), )),
]) ])
->add('partUnit', TextColumn::class, [ ->add('partUnit', TextColumn::class, [
'label' => $this->translator->trans('part.table.partUnit'), 'label' => $this->translator->trans('part.table.partUnit'),
'orderField' => 'NATSORT(_partUnit.name)', 'orderField' => 'NATSORT(_partUnit.name)',
'data' => function (Part $context): string { 'render' => function ($value, Part $context): string {
$partUnit = $context->getPartUnit(); $partUnit = $context->getPartUnit();
if ($partUnit === null) { if ($partUnit === null) {
return ''; return '';
} }
$tmp = $partUnit->getName(); $tmp = htmlspecialchars($partUnit->getName());
if ($partUnit->getUnit()) { if ($partUnit->getUnit()) {
$tmp .= ' (' . $partUnit->getUnit() . ')'; $tmp .= ' (' . htmlspecialchars($partUnit->getUnit()) . ')';
} }
return $tmp; return $tmp;
} }
@ -201,14 +195,14 @@ final readonly class PartsDataTable implements DataTableTypeInterface
->add('partCustomState', TextColumn::class, [ ->add('partCustomState', TextColumn::class, [
'label' => $this->translator->trans('part.table.partCustomState'), 'label' => $this->translator->trans('part.table.partCustomState'),
'orderField' => 'NATSORT(_partCustomState.name)', 'orderField' => 'NATSORT(_partCustomState.name)',
'data' => function(Part $context): string { 'render' => function($value, Part $context): string {
$partCustomState = $context->getPartCustomState(); $partCustomState = $context->getPartCustomState();
if ($partCustomState === null) { if ($partCustomState === null) {
return ''; return '';
} }
return $partCustomState->getName(); return htmlspecialchars($partCustomState->getName());
} }
]) ])
->add('addedDate', LocaleDateTimeColumn::class, [ ->add('addedDate', LocaleDateTimeColumn::class, [
@ -254,25 +248,25 @@ final readonly class PartsDataTable implements DataTableTypeInterface
]) ])
->add('eda_reference', TextColumn::class, [ ->add('eda_reference', TextColumn::class, [
'label' => $this->translator->trans('part.table.eda_reference'), 'label' => $this->translator->trans('part.table.eda_reference'),
'data' => static fn(Part $context) => $context->getEdaInfo()->getReferencePrefix() ?? '', 'render' => static fn($value, Part $context) => htmlspecialchars($context->getEdaInfo()->getReferencePrefix() ?? ''),
'orderField' => 'NATSORT(part.eda_info.reference_prefix)' 'orderField' => 'NATSORT(part.eda_info.reference_prefix)'
]) ])
->add('eda_value', TextColumn::class, [ ->add('eda_value', TextColumn::class, [
'label' => $this->translator->trans('part.table.eda_value'), 'label' => $this->translator->trans('part.table.eda_value'),
'data' => static fn(Part $context) => $context->getEdaInfo()->getValue() ?? '', 'render' => static fn($value, Part $context) => htmlspecialchars($context->getEdaInfo()->getValue() ?? ''),
'orderField' => 'NATSORT(part.eda_info.value)' 'orderField' => 'NATSORT(part.eda_info.value)'
]) ])
->add('eda_status', HTMLColumn::class, [ ->add('eda_status', TextColumn::class, [
'label' => $this->translator->trans('part.table.eda_status'), 'label' => $this->translator->trans('part.table.eda_status'),
'data' => fn(Part $context) => $this->partDataTableHelper->renderEdaStatus($context), 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderEdaStatus($context),
'className' => 'text-center', 'className' => 'text-center',
]); ]);
//Add a column to list the projects where the part is used, when the user has the permission to see the projects //Add a column to list the projects where the part is used, when the user has the permission to see the projects
if ($this->security->isGranted('read', Project::class)) { if ($this->security->isGranted('read', Project::class)) {
$this->csh->add('projects', HTMLColumn::class, [ $this->csh->add('projects', TextColumn::class, [
'label' => $this->translator->trans('project.labelp'), 'label' => $this->translator->trans('project.labelp'),
'data' => function (Part $context): string { 'render' => function ($value, Part $context): string {
//Only show the first 5 projects names //Only show the first 5 projects names
$projects = $context->getProjects(); $projects = $context->getProjects();
$tmp = ""; $tmp = "";
@ -292,7 +286,7 @@ final readonly class PartsDataTable implements DataTableTypeInterface
} }
return $tmp; return $tmp;
}, }
]); ]);
} }

View file

@ -25,7 +25,6 @@ namespace App\DataTables;
use App\DataTables\Adapters\TwoStepORMAdapter; use App\DataTables\Adapters\TwoStepORMAdapter;
use App\DataTables\Column\EntityColumn; use App\DataTables\Column\EntityColumn;
use App\DataTables\Column\EnumColumn; use App\DataTables\Column\EnumColumn;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\MarkdownColumn; use App\DataTables\Column\MarkdownColumn;
use App\DataTables\Helpers\PartDataTableHelper; use App\DataTables\Helpers\PartDataTableHelper;
@ -49,7 +48,7 @@ use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableTypeInterface; use Omines\DataTablesBundle\DataTableTypeInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterface class ProjectBomEntriesDataTable implements DataTableTypeInterface
{ {
public function __construct( public function __construct(
protected EntityURLGenerator $entityURLGenerator, protected EntityURLGenerator $entityURLGenerator,
@ -64,22 +63,17 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac
public function configure(DataTable $dataTable, array $options): void public function configure(DataTable $dataTable, array $options): void
{ {
/*************************************************************************************************************
* Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
* HTMLColumn, if necessary
************************************************************************************************************/
$dataTable $dataTable
//->add('select', SelectColumn::class) //->add('select', SelectColumn::class)
->add('picture', HTMLColumn::class, [ ->add('picture', TextColumn::class, [
'label' => '', 'label' => '',
'className' => 'no-colvis', 'className' => 'no-colvis',
'data' => function (ProjectBOMEntry $context) { 'render' => function ($value, ProjectBOMEntry $context) {
if(!$context->getPart() instanceof Part) { if(!$context->getPart() instanceof Part) {
return ''; return '';
} }
return $this->partDataTableHelper->renderPicture($context->getPart()); return $this->partDataTableHelper->renderPicture($context->getPart());
}, }
]) ])
->add('id', TextColumn::class, [ ->add('id', TextColumn::class, [
@ -91,27 +85,27 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac
'label' => $this->translator->trans('project.bom.quantity'), 'label' => $this->translator->trans('project.bom.quantity'),
'className' => 'text-center', 'className' => 'text-center',
'orderField' => 'bom_entry.quantity', 'orderField' => 'bom_entry.quantity',
'data' => function (ProjectBOMEntry $context): float|string { 'render' => function ($value, ProjectBOMEntry $context): float|string {
//If we have a non-part entry, only show the rounded quantity //If we have a non-part entry, only show the rounded quantity
if (!$context->getPart() instanceof Part) { if (!$context->getPart() instanceof Part) {
return round($context->getQuantity()); return round($context->getQuantity());
} }
//Otherwise use the unit of the part to format the quantity //Otherwise use the unit of the part to format the quantity
return $this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit()); return htmlspecialchars($this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit()));
}, },
]) ])
->add('partId', TextColumn::class, [ ->add('partId', TextColumn::class, [
'label' => $this->translator->trans('project.bom.part_id'), 'label' => $this->translator->trans('project.bom.part_id'),
'visible' => true, 'visible' => true,
'orderField' => 'part.id', 'orderField' => 'part.id',
'data' => function (ProjectBOMEntry $context) { 'render' => function ($value, ProjectBOMEntry $context) {
return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : ''; return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : '';
}, },
]) ])
->add('name', HTMLColumn::class, [ ->add('name', TextColumn::class, [
'label' => $this->translator->trans('part.table.name'), 'label' => $this->translator->trans('part.table.name'),
'orderField' => 'NATSORT(part.name)', 'orderField' => 'NATSORT(part.name)',
'data' => function (ProjectBOMEntry $context) { 'render' => function ($value, ProjectBOMEntry $context) {
if(!$context->getPart() instanceof Part) { if(!$context->getPart() instanceof Part) {
return htmlspecialchars((string) $context->getName()); return htmlspecialchars((string) $context->getName());
} }
@ -129,7 +123,11 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac
'label' => $this->translator->trans('part.table.ipn'), 'label' => $this->translator->trans('part.table.ipn'),
'orderField' => 'NATSORT(part.ipn)', 'orderField' => 'NATSORT(part.ipn)',
'visible' => false, 'visible' => false,
'data' => fn (ProjectBOMEntry $context) => $context->getPart()?->getIpn() 'render' => function ($value, ProjectBOMEntry $context) {
if($context->getPart() instanceof Part) {
return $context->getPart()->getIpn();
}
}
]) ])
->add('description', MarkdownColumn::class, [ ->add('description', MarkdownColumn::class, [
'label' => $this->translator->trans('part.table.description'), 'label' => $this->translator->trans('part.table.description'),
@ -174,9 +172,9 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac
}, },
]) ])
->add('mountnames', HTMLColumn::class, [ ->add('mountnames', TextColumn::class, [
'label' => 'project.bom.mountnames', 'label' => 'project.bom.mountnames',
'data' => function (ProjectBOMEntry $context) { 'render' => function ($value, ProjectBOMEntry $context) {
$html = ''; $html = '';
foreach (explode(',', $context->getMountnames()) as $mountname) { foreach (explode(',', $context->getMountnames()) as $mountname) {
@ -186,34 +184,34 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac
}, },
]) ])
->add('instockAmount', HTMLColumn::class, [ ->add('instockAmount', TextColumn::class, [
'label' => 'project.bom.instockAmount', 'label' => 'project.bom.instockAmount',
'visible' => false, 'visible' => false,
'data' => function (ProjectBOMEntry $context) { 'render' => function ($value, ProjectBOMEntry $context) {
if ($context->getPart() !== null) { if ($context->getPart() !== null) {
return $this->partDataTableHelper->renderAmount($context->getPart()); return $this->partDataTableHelper->renderAmount($context->getPart());
} }
return ''; return '';
}, }
]) ])
->add('storelocation', HTMLColumn::class, [ ->add('storelocation', TextColumn::class, [
'label' => $this->translator->trans('part.table.storeLocations'), 'label' => $this->translator->trans('part.table.storeLocations'),
//We need to use a aggregate function to get the first store location, as we have a one-to-many relation //We need to use a aggregate function to get the first store location, as we have a one-to-many relation
'orderField' => 'NATSORT(MIN(_storelocations.name))', 'orderField' => 'NATSORT(MIN(_storelocations.name))',
'visible' => false, 'visible' => false,
'data' => function (ProjectBOMEntry $context) { 'render' => function ($value, ProjectBOMEntry $context) {
if ($context->getPart() !== null) { if ($context->getPart() !== null) {
return $this->partDataTableHelper->renderStorageLocations($context->getPart()); return $this->partDataTableHelper->renderStorageLocations($context->getPart());
} }
return ''; return '';
}, }
]) ])
->add('price', TextColumn::class, [ ->add('price', TextColumn::class, [
'label' => 'project.bom.price', 'label' => 'project.bom.price',
'visible' => false, 'visible' => false,
'data' => function (ProjectBOMEntry $context) { 'render' => function ($value, ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context); $price = $this->projectBuildHelper->getEntryUnitPrice($context);
return $this->moneyFormatter->format($price->toScale(2, RoundingMode::Up)->toFloat(), null, 2, true); return $this->moneyFormatter->format($price->toScale(2, RoundingMode::Up)->toFloat(), null, 2, true);
}, },
@ -221,7 +219,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac
->add('ext_price', TextColumn::class, [ ->add('ext_price', TextColumn::class, [
'label' => 'project.bom.ext_price', 'label' => 'project.bom.ext_price',
'visible' => false, 'visible' => false,
'data' => function (ProjectBOMEntry $context) { 'render' => function ($value, ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context); $price = $this->projectBuildHelper->getEntryUnitPrice($context);
return $this->moneyFormatter->format( return $this->moneyFormatter->format(
$price->multipliedBy(BigDecimal::fromFloatShortest($context->getQuantity())) $price->multipliedBy(BigDecimal::fromFloatShortest($context->getQuantity()))

View file

@ -35,10 +35,8 @@ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware
{ {
//Only set this on MySQL connections, as other databases don't support this parameter //Only set this on MySQL connections, as other databases don't support this parameter
if($params['driver'] === 'pdo_mysql') { if($params['driver'] === 'pdo_mysql') {
//PDO::MYSQL_ATTR_INIT_COMMAND is deprecated since PHP 8.5 in favor of Pdo\Mysql::ATTR_INIT_COMMAND, //1002 is \PDO::MYSQL_ATTR_INIT_COMMAND constant value
//but the Pdo\Mysql class only exists since PHP 8.4. Both constants have the same value (1002). $params['driverOptions'][\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))';
$initCommandAttr = class_exists(\Pdo\Mysql::class) ? \Pdo\Mysql::ATTR_INIT_COMMAND : \PDO::MYSQL_ATTR_INIT_COMMAND;
$params['driverOptions'][$initCommandAttr] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))';
} }
return parent::connect($params); return parent::connect($params);

View file

@ -80,7 +80,7 @@ use Symfony\Component\Validator\Constraints as Assert;
) )
], ],
uriVariables: [ uriVariables: [
'id' => new Link(fromProperty: 'children', fromClass: StorageLocation::class) 'id' => new Link(fromProperty: 'children', fromClass: Manufacturer::class)
], ],
normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
)] )]

View file

@ -29,13 +29,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* Service for validating BOM import data with comprehensive validation rules * Service for validating BOM import data with comprehensive validation rules
* and user-friendly error messages. The results are not HTML safe, and must be escaped before display! * and user-friendly error messages.
*/ */
readonly class BOMValidationService class BOMValidationService
{ {
public function __construct( public function __construct(
private EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
private TranslatorInterface $translator private readonly TranslatorInterface $translator
) { ) {
} }

View file

@ -58,7 +58,7 @@
</tr> </tr>
<tr> <tr>
<td>{% trans %}log.target{% endtrans %}</td> <td>{% trans %}log.target{% endtrans %}</td>
<td>{{ target_html|sanitize_html }}</td> <td>{{ target_html|raw }}</td>
</tr> </tr>
</table> </table>
@ -111,7 +111,7 @@
{% elseif log_entry is instanceof('App\\Entity\\LogSystem\\CollectionElementDeleted') %} {% elseif log_entry is instanceof('App\\Entity\\LogSystem\\CollectionElementDeleted') %}
{% include "log_system/details/_extra_collection_element_deleted.html.twig" %} {% include "log_system/details/_extra_collection_element_deleted.html.twig" %}
{% else %} {% else %}
{{ extra_html | sanitize_html }} {{ extra_html | raw }}
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -68,7 +68,7 @@
<p class="mb-2">{% trans %}project.bom_import.validation.errors.description{% endtrans %}</p> <p class="mb-2">{% trans %}project.bom_import.validation.errors.description{% endtrans %}</p>
<ul class="mb-0"> <ul class="mb-0">
{% for error in validation_result.errors %} {% for error in validation_result.errors %}
<li>{{ error }}</li> <li>{{ error|raw }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -80,7 +80,7 @@
<p class="mb-2">{% trans %}project.bom_import.validation.warnings.description{% endtrans %}</p> <p class="mb-2">{% trans %}project.bom_import.validation.warnings.description{% endtrans %}</p>
<ul class="mb-0"> <ul class="mb-0">
{% for warning in validation_result.warnings %} {% for warning in validation_result.warnings %}
<li>{{ warning }}</li> <li>{{ warning|raw }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -91,7 +91,7 @@
<h4><i class="fa-solid fa-info-circle fa-fw"></i> {% trans %}project.bom_import.validation.info.title{% endtrans %}</h4> <h4><i class="fa-solid fa-info-circle fa-fw"></i> {% trans %}project.bom_import.validation.info.title{% endtrans %}</h4>
<ul class="mb-0"> <ul class="mb-0">
{% for info in validation_result.info %} {% for info in validation_result.info %}
<li>{{ info }}</li> <li>{{ info|raw }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -139,21 +139,21 @@
{% if line_result.errors is not empty %} {% if line_result.errors is not empty %}
<div class="text-danger"> <div class="text-danger">
{% for error in line_result.errors %} {% for error in line_result.errors %}
<div><i class="fa-solid fa-exclamation-triangle fa-fw"></i> {{ error }}</div> <div><i class="fa-solid fa-exclamation-triangle fa-fw"></i> {{ error|raw }}</div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
{% if line_result.warnings is not empty %} {% if line_result.warnings is not empty %}
<div class="text-warning"> <div class="text-warning">
{% for warning in line_result.warnings %} {% for warning in line_result.warnings %}
<div><i class="fa-solid fa-exclamation-circle fa-fw"></i> {{ warning }}</div> <div><i class="fa-solid fa-exclamation-circle fa-fw"></i> {{ warning|raw }}</div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
{% if line_result.info is not empty %} {% if line_result.info is not empty %}
<div class="text-info"> <div class="text-info">
{% for info in line_result.info %} {% for info in line_result.info %}
<div><i class="fa-solid fa-info-circle fa-fw"></i> {{ info }}</div> <div><i class="fa-solid fa-info-circle fa-fw"></i> {{ info|raw }}</div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}

View file

@ -132,8 +132,8 @@
<script nonce="{{ csp_nonce('script') }}"> <script nonce="{{ csp_nonce('script') }}">
// Function to initialize the field mapping page // Function to initialize the field mapping page
function initializeFieldMapping() { function initializeFieldMapping() {
const suggestions = JSON.parse("{{ suggested_mapping|json_encode|escape('js')}}"); const suggestions = {{ suggested_mapping|json_encode|raw }};
const fieldNameMapping = JSON.parse("{{ field_name_mapping|json_encode|escape('js') }}"); const fieldNameMapping = {{ field_name_mapping|json_encode|raw }};
Object.keys(suggestions).forEach(function(field) { Object.keys(suggestions).forEach(function(field) {
// Use the sanitized field name from the server-side mapping // Use the sanitized field name from the server-side mapping

View file

@ -1887,9 +1887,9 @@
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
"@types/node@*": "@types/node@*":
version "25.9.3" version "25.9.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.3.tgz#11dfe7a33e68fa5c560f0aa76cc5595621ef26b9" resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.2.tgz#fc8958e757994b71fee516f9634bdb03d1b19e9f"
integrity sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg== integrity sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==
dependencies: dependencies:
undici-types ">=7.24.0 <7.24.7" undici-types ">=7.24.0 <7.24.7"
@ -2116,9 +2116,9 @@ acorn-walk@^8.0.0:
acorn "^8.11.0" acorn "^8.11.0"
acorn@^8.0.4, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.16.0: acorn@^8.0.4, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.16.0:
version "8.17.0" version "8.16.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.17.0.tgz#1785adb84faf8d8add10369b93826fc2bd08f1fe" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
integrity sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg== integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
adjust-sourcemap-loader@^4.0.0: adjust-sourcemap-loader@^4.0.0:
version "4.0.0" version "4.0.0"
@ -2223,9 +2223,9 @@ base64-js@^1.1.2, base64-js@^1.3.0:
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
baseline-browser-mapping@^2.10.12: baseline-browser-mapping@^2.10.12:
version "2.10.37" version "2.10.34"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz#3e636475b6b293244e2b23e2c71a2ab9d9e6ba7d" resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz#dedb606362446777cfe328d30d4ee15056d06303"
integrity sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig== integrity sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==
big.js@^5.2.2: big.js@^5.2.2:
version "5.2.2" version "5.2.2"
@ -2310,9 +2310,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0" lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001782: caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001782:
version "1.0.30001799" version "1.0.30001797"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz#5c909138c27f1a61219d3e092071c1cc7d32dc55" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz#1332709e1439f01ff92085dd17001e0a45897ec0"
integrity sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw== integrity sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==
ccount@^2.0.0: ccount@^2.0.0:
version "2.0.1" version "2.0.1"
@ -2877,9 +2877,9 @@ domhandler@^5.0.2, domhandler@^5.0.3:
domelementtype "^2.3.0" domelementtype "^2.3.0"
dompurify@^3.0.3: dompurify@^3.0.3:
version "3.4.10" version "3.4.8"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.10.tgz#96704295b4d8aeefcc8c7a90caa579b0ad69e46a" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.8.tgz#6c54f8c207160e7f83fcb7f4fd05a82ac36b1cdc"
integrity sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w== integrity sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==
optionalDependencies: optionalDependencies:
"@types/trusted-types" "^2.0.7" "@types/trusted-types" "^2.0.7"
@ -2902,9 +2902,9 @@ domutils@^3.0.1:
domhandler "^5.0.3" domhandler "^5.0.3"
electron-to-chromium@^1.5.328: electron-to-chromium@^1.5.328:
version "1.5.372" version "1.5.368"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz#ae8ac69942a37b231773b8fb01759f73733599e3" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz#8e8d4600c5f5f01e891f8f843b4a941afb640412"
integrity sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA== integrity sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==
emoji-regex@^8.0.0: emoji-regex@^8.0.0:
version "8.0.0" version "8.0.0"
@ -2917,9 +2917,9 @@ emojis-list@^3.0.0:
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
enhanced-resolve@^5.0.0, enhanced-resolve@^5.22.0: enhanced-resolve@^5.0.0, enhanced-resolve@^5.22.0:
version "5.24.0" version "5.23.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz#cf14b9768a774cb6a5087220c0dc6e55df6ec35a" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.23.0.tgz#dfdf8d1c9065e4b52f8a598356138931c07305f9"
integrity sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ== integrity sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==
dependencies: dependencies:
graceful-fs "^4.2.4" graceful-fs "^4.2.4"
tapable "^2.3.3" tapable "^2.3.3"
@ -4153,25 +4153,25 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
pdfkit@^0.19.1: pdfkit@^0.18.0:
version "0.19.1" version "0.18.0"
resolved "https://registry.yarnpkg.com/pdfkit/-/pdfkit-0.19.1.tgz#633d5f031ce6f1ba6ce141325c6cf87fa6acb0a2" resolved "https://registry.yarnpkg.com/pdfkit/-/pdfkit-0.18.0.tgz#573efa7f4c78a8ab1362232a05a589b97b292216"
integrity sha512-6Gzk+wDwTs4VSxsR5rCMTnIl5nlmkye1oWB0l2hDB1EX6ZNSIBroKQEv+2+fPPn+stVjyqzmsqRJVDfB9fo5DA== integrity sha512-NvUwSDZ0eYEzqAiWwVQkRkjYUkZ48kcsHuCO31ykqPPIVkwoSDjDGiwIgHHNtsiwls3z3P/zy4q00hl2chg2Ug==
dependencies: dependencies:
"@noble/ciphers" "^1.0.0" "@noble/ciphers" "^1.0.0"
"@noble/hashes" "^1.6.0" "@noble/hashes" "^1.6.0"
fontkit "^2.0.4" fontkit "^2.0.4"
js-md5 "^0.8.3" js-md5 "^0.8.3"
linebreak "^1.1.0" linebreak "^1.1.0"
png-js "^1.1.0" png-js "^1.0.0"
pdfmake@^0.3.7: pdfmake@^0.3.7:
version "0.3.11" version "0.3.9"
resolved "https://registry.yarnpkg.com/pdfmake/-/pdfmake-0.3.11.tgz#b4504d19b8f31fa5063dc1b847b060faa4f7c5bb" resolved "https://registry.yarnpkg.com/pdfmake/-/pdfmake-0.3.9.tgz#c837b471aa81ea0797b7cdac2a03db8ba65e1b7f"
integrity sha512-Uc49J9hUMyuqJk+U+PxlpBpPr96A4HOOfesGx609EPr2ue82+5/Smq/KTAkEqh0/jUGSi1fumvqZ5yAWijJTJg== integrity sha512-V8VIcjMG/yk7oJYhYjtvyLXvop3w3zcIB0bHdG3z1vuW8nVeemzRWu7YrOG7QA9KYaf858MZnCUG6hQ1Bei0vA==
dependencies: dependencies:
linebreak "^1.1.0" linebreak "^1.1.0"
pdfkit "^0.19.1" pdfkit "^0.18.0"
xmldoc "^2.0.3" xmldoc "^2.0.3"
picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1:
@ -4196,7 +4196,7 @@ pkg-dir@^4.2.0:
dependencies: dependencies:
find-up "^4.0.0" find-up "^4.0.0"
png-js@^1.1.0: png-js@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.1.0.tgz#60a135216601f807b88a6d61ac93bd42a32c5ee1" resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.1.0.tgz#60a135216601f807b88a6d61ac93bd42a32c5ee1"
integrity sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q== integrity sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==
@ -4423,9 +4423,9 @@ postcss-reduce-transforms@^7.0.3:
postcss-value-parser "^4.2.0" postcss-value-parser "^4.2.0"
postcss-selector-parser@^7.0.0, postcss-selector-parser@^7.1.1: postcss-selector-parser@^7.0.0, postcss-selector-parser@^7.1.1:
version "7.1.4" version "7.1.1"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz#69dc7a526517572ff6b150e352b36a016017b485" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz#e75d2e0d843f620e5df69076166f4e16f891cb9f"
integrity sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg== integrity sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==
dependencies: dependencies:
cssesc "^3.0.0" cssesc "^3.0.0"
util-deprecate "^1.0.2" util-deprecate "^1.0.2"
@ -4549,9 +4549,9 @@ regjsgen@^0.8.0:
integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==
regjsparser@^0.13.0: regjsparser@^0.13.0:
version "0.13.2" version "0.13.1"
resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.2.tgz#f654734b5c588b22ba3e21693b30523417180808" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.1.tgz#0593cbacb27527927692030928ae4d3b878d6f8d"
integrity sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ== integrity sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==
dependencies: dependencies:
jsesc "~3.1.0" jsesc "~3.1.0"
@ -4733,9 +4733,9 @@ semver@^6.3.1:
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.2, semver@^7.3.4, semver@^7.6.3: semver@^7.3.2, semver@^7.3.4, semver@^7.6.3:
version "7.8.4" version "7.8.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.4.tgz#c73eceebae0616934be8dff28a7fd70757c8e696" resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.2.tgz#194bd65723a28cf82542d2bf176b91c26b343be1"
integrity sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA== integrity sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==
serialize-javascript@^6.0.2: serialize-javascript@^6.0.2:
version "6.0.2" version "6.0.2"
@ -5171,10 +5171,11 @@ vfile@^6.0.0:
vfile-message "^4.0.0" vfile-message "^4.0.0"
watchpack@^2.5.1: watchpack@^2.5.1:
version "2.5.2" version "2.5.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.2.tgz#e12e82d84674266fc1c6dbfe38891b92ff0522ec" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102"
integrity sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg== integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==
dependencies: dependencies:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2" graceful-fs "^4.1.2"
web-namespaces@^2.0.0: web-namespaces@^2.0.0: