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_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 ###
# Choose one of the stores below

View file

@ -129,7 +129,7 @@ jobs:
run: ./bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage
uses: codecov/codecov-action@v7
uses: codecov/codecov-action@v6
with:
env_vars: PHP_VERSION,DB_TYPE
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
if(localStorage.getItem(STORAGE_KEY) === 'true') {
this.hideSidebar();
sidebarHide();
}
}

View file

@ -70,7 +70,6 @@
"symfony/flex": "^v2.3.1",
"symfony/form": "7.4.*",
"symfony/framework-bundle": "7.4.*",
"symfony/html-sanitizer": "7.4.*",
"symfony/http-client": "7.4.*",
"symfony/http-kernel": "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
channels: [deprecation]
path: "%kernel.logs_dir%/%kernel.environment%_deprecations.log"
level: "%env(DEPRECATION_LOG_LEVEL)%"
when@docker:
monolog:
@ -76,4 +75,3 @@ when@docker:
type: stream
channels: [deprecation]
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,
* },
* html_sanitizer?: bool|array{ // HtmlSanitizer configuration
* enabled?: bool|Param, // Default: true
* enabled?: bool|Param, // Default: false
* sanitizers?: array<string, array{ // Default: []
* 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
@ -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.
* 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.
* 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.
* 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.
@ -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.
* 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.
* 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.
* 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.
@ -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.
* 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.
* 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.
* 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.

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
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
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
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

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
Audio_Module:Reverb_BTDR-1H
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
4xxx:14528
4xxx:14529
@ -5300,39 +5300,21 @@ Converter_DCDC:TMR-4812
Converter_DCDC:TMR-4821
Converter_DCDC:TMR-4822
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-2411WI
Converter_DCDC:TMR10-2411WIR
Converter_DCDC:TMR10-2412WI
Converter_DCDC:TMR10-2412WIR
Converter_DCDC:TMR10-2413WI
Converter_DCDC:TMR10-2413WIR
Converter_DCDC:TMR10-2415WI
Converter_DCDC:TMR10-2415WIR
Converter_DCDC:TMR10-2421WIR
Converter_DCDC:TMR10-2422WI
Converter_DCDC:TMR10-2422WIR
Converter_DCDC:TMR10-2423WI
Converter_DCDC:TMR10-2423WIR
Converter_DCDC:TMR10-4810WIR
Converter_DCDC:TMR10-4811WI
Converter_DCDC:TMR10-4811WIR
Converter_DCDC:TMR10-4812WI
Converter_DCDC:TMR10-4812WIR
Converter_DCDC:TMR10-4813WI
Converter_DCDC:TMR10-4813WIR
Converter_DCDC:TMR10-4815WI
Converter_DCDC:TMR10-4815WIR
Converter_DCDC:TMR10-4821WIR
Converter_DCDC:TMR10-4822WI
Converter_DCDC:TMR10-4822WIR
Converter_DCDC:TMR10-4823WI
Converter_DCDC:TMR10-4823WIR
Converter_DCDC:TMR10-7210WIR
Converter_DCDC:TMR10-7211WIR
@ -14611,8 +14593,6 @@ MCU_Texas:LM4F111C4QR
MCU_Texas:LM4F111E5QR
MCU_Texas:LM4F111H5QR
MCU_Texas:MSP432E401Y
MCU_Texas:MSPM0C110xSDDF
MCU_Texas:MSPM0C110xSDSG
MCU_Texas:TM4C1230C3PM
MCU_Texas:TM4C1230D5PM
MCU_Texas:TM4C1230E6PM
@ -15691,7 +15671,6 @@ Power_Management:LM5069MM-1
Power_Management:LM5069MM-2
Power_Management:LM66100DCK
Power_Management:LM74700
Power_Management:LM74701-Q1
Power_Management:LMG3410
Power_Management:LMG5200
Power_Management:LT1641-1
@ -15793,7 +15772,6 @@ Power_Management:TPS22810DBV
Power_Management:TPS22810DRV
Power_Management:TPS22917DBV
Power_Management:TPS22917LDBV
Power_Management:TPS22919DCK
Power_Management:TPS22929D
Power_Management:TPS22993
Power_Management:TPS2412D
@ -16705,19 +16683,6 @@ Reference_Voltage:LM4040LP-4.1
Reference_Voltage:LM4040LP-5
Reference_Voltage:LM4040LP-8.2
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:LM4125IM5-2.0
Reference_Voltage:LM4125IM5-2.5
@ -16865,6 +16830,7 @@ Reference_Voltage:MCP1501-25xCH
Reference_Voltage:MCP1501-25xRW
Reference_Voltage:MCP1501-25xSN
Reference_Voltage:MCP1501-30xCH
Reference_Voltage:MCP1501-30xRW
Reference_Voltage:MCP1501-30xSN
Reference_Voltage:MCP1501-33xCH
Reference_Voltage:MCP1501-33xRW
@ -21025,7 +20991,6 @@ Sensor_Temperature:MCP9501
Sensor_Temperature:MCP9502
Sensor_Temperature:MCP9503
Sensor_Temperature:MCP9504
Sensor_Temperature:MCP96xx01x-x-MX
Sensor_Temperature:MCP9700Ax-ELT
Sensor_Temperature:MCP9700Ax-ETT
Sensor_Temperature:MCP9700Ax-HLT

View file

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace App\Controller;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Response;
use App\Entity\ProjectSystem\Project;
use App\Entity\Parts\Category;
@ -56,7 +55,7 @@ class TreeController extends AbstractController
#[Route(path: '/category/{id}', name: 'tree_category')]
#[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')) {
$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: '/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')) {
$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: '/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')) {
$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: '/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')) {
$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: '/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')) {
$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: '/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')) {
$tree = $this->treeGenerator->getTreeView(Project::class, $device, 'devices');

View file

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace App\DataTables;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\PrettyBoolColumn;
use App\DataTables\Column\RowClassColumn;
@ -41,19 +40,14 @@ use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableTypeInterface;
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
{
/*************************************************************************************************************
* 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, [
'render' => function ($value, Attachment $context): string {
//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' => '',
'className' => 'no-colvis',
'data' => function (Attachment $context): string {
'render' => function ($value, Attachment $context): string {
if ($context->isPicture()
&& $this->attachmentHelper->isInternalFileExisting($context)) {
@ -101,65 +95,65 @@ final readonly class AttachmentDataTable implements DataTableTypeInterface
'orderField' => 'NATSORT(attachment.name)',
]);
$dataTable->add('attachment_type', HTMLColumn::class, [
$dataTable->add('attachment_type', TextColumn::class, [
'label' => 'attachment.table.type',
'field' => '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>',
$this->entityURLGenerator->editURL($context->getAttachmentType()),
htmlspecialchars((string) $value)
),
]);
$dataTable->add('element', HTMLColumn::class, [
$dataTable->add('element', TextColumn::class, [
'label' => 'attachment.table.element',
//'propertyPath' => 'element.name',
'data' => fn(Attachment $context): string => sprintf(
'render' => fn($value, Attachment $context): string => sprintf(
'<a href="%s">%s</a>',
$this->entityURLGenerator->infoURL($context->getElement()),
$this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true)
),
]);
$dataTable->add('internal_link', HTMLColumn::class, [
$dataTable->add('internal_link', TextColumn::class, [
'label' => 'attachment.table.internal_file',
'propertyPath' => 'filename',
'orderField' => 'NATSORT(attachment.original_filename)',
'data' => function (Attachment $context, $value) {
'render' => function ($value, Attachment $context) {
if ($this->attachmentHelper->isInternalFileExisting($context)) {
return sprintf(
'<a href="%s" target="_blank" data-no-ajax>%s</a>',
$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',
'propertyPath' => 'host',
'orderField' => 'attachment.external_path',
'data' => function (Attachment $context, $value) {
'render' => function ($value, Attachment $context) {
if ($context->hasExternal()) {
return sprintf(
'<a href="%s" class="link-external" title="%s" target="_blank" rel="noopener">%s</a>',
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'),
'data' => function (Attachment $context) {
'render' => function ($value, Attachment $context) {
if (!$context->hasInternal()) {
return sprintf(
'<span class="badge bg-primary">
@ -174,7 +168,7 @@ final readonly class AttachmentDataTable implements DataTableTypeInterface
'<span class="badge bg-secondary">
<i class="fas fa-hdd fa-fw"></i> %s
</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 '';

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(
'<a class="btn btn-primary btn-sm %s" href="%s" title="%s"><i class="%s"></i></a>',
$disabled ? 'disabled' : '',
htmlspecialchars($href),
htmlspecialchars($title ?? ''),
htmlspecialchars($icon ?? '')
$href,
$title,
$icon
);
}

View file

@ -22,9 +22,9 @@ declare(strict_types=1);
*/
namespace App\DataTables;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\RowClassColumn;
use Omines\DataTablesBundle\Adapter\ArrayAdapter;
use Omines\DataTablesBundle\Column\TextColumn;
use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableFactory;
use Omines\DataTablesBundle\DataTableTypeInterface;
@ -32,7 +32,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
final readonly class ErrorDataTable implements DataTableTypeInterface
class ErrorDataTable implements DataTableTypeInterface
{
public function configureOptions(OptionsResolver $optionsResolver): void
{
@ -49,11 +49,6 @@ final readonly class ErrorDataTable implements DataTableTypeInterface
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();
$this->configureOptions($optionsResolver);
$options = $optionsResolver->resolve($options);
@ -63,9 +58,9 @@ final readonly class ErrorDataTable implements DataTableTypeInterface
'render' => fn($value, $context): string => 'table-warning',
])
->add('error', HTMLColumn::class, [
->add('error', TextColumn::class, [
'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) {
$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\Entity\LogSystem\LogTargetType;
use Symfony\Bundle\SecurityBundle\Security;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\IconLinkColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\LogEntryExtraColumn;
@ -60,7 +59,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class LogDataTable implements DataTableTypeInterface
class LogDataTable implements DataTableTypeInterface
{
protected LogEntryRepository $logRepo;
@ -96,11 +95,6 @@ final readonly class LogDataTable implements DataTableTypeInterface
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();
$this->configureOptions($resolver);
$options = $resolver->resolve($options);
@ -110,10 +104,10 @@ final readonly class LogDataTable implements DataTableTypeInterface
'render' => fn($value, AbstractLogEntry $context) => $this->logLevelHelper->logLevelToTableColorClass($context->getLevelString()),
]);
$dataTable->add('symbol', HTMLColumn::class, [
$dataTable->add('symbol', TextColumn::class, [
'label' => '',
'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>',
$this->logLevelHelper->logLevelToIconClass($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',
'propertyPath' => 'type',
'data' => function (AbstractLogEntry $context, string $value) {
'render' => function (string $value, AbstractLogEntry $context) {
$text = $this->translator->trans('log.type.'.$value);
if ($context instanceof PartStockChangedLogEntry) {
@ -155,20 +149,20 @@ final readonly class LogDataTable implements DataTableTypeInterface
'label' => 'log.level',
'visible' => 'system_log' === $options['mode'],
'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',
'orderField' => 'NATSORT(user.name)',
'data' => function (AbstractLogEntry $context): string {
'render' => function ($value, AbstractLogEntry $context): string {
$user = $context->getUser();
//If user was deleted, show the info from the username field
if (!$user instanceof User) {
if ($context->isCLIEntry()) {
return sprintf('%s [%s]',
htmlspecialchars((string) $context->getCLIUsername()),
htmlentities((string) $context->getCLIUsername()),
$this->translator->trans('log.cli_user')
);
}
@ -176,7 +170,7 @@ final readonly class LogDataTable implements DataTableTypeInterface
//Else we just deal with a deleted user
return sprintf(
'@%s [%s]',
htmlspecialchars($context->getUsername()),
htmlentities($context->getUsername()),
$this->translator->trans('log.target_deleted'),
);
}
@ -188,7 +182,7 @@ final readonly class LogDataTable implements DataTableTypeInterface
$img_url,
$this->userAvatarHelper->getAvatarMdURL($user),
$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) {
$class = $value->toClass();
if (null !== $class) {
return $this->elementTypeNameGenerator->typeLabel($class);
return $this->elementTypeNameGenerator->getLocalizedTypeLabel($class);
}
return '';
@ -222,9 +216,9 @@ final readonly class LogDataTable implements DataTableTypeInterface
'icon' => 'fas fa-fw fa-eye',
'href' => function ($value, AbstractLogEntry $context) {
if (
$context instanceof CollectionElementDeleted ||
($context instanceof TimeTravelInterface
&& $context->hasOldDataInformation())
|| $context instanceof CollectionElementDeleted
) {
try {
$target = $this->logRepo->getTargetElement($context);

View file

@ -25,7 +25,6 @@ namespace App\DataTables;
use App\DataTables\Adapters\TwoStepORMAdapter;
use App\DataTables\Column\EntityColumn;
use App\DataTables\Column\EnumColumn;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\IconLinkColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\MarkdownColumn;
@ -59,7 +58,7 @@ use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\OptionsResolver\OptionsResolver;
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"]];
@ -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!
*************************************************************************************************************/
/*************************************************************************************************************
* 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
//Color the table rows depending on the review and favorite status
->add('row_color', RowClassColumn::class, [
@ -115,23 +109,23 @@ final readonly class PartsDataTable implements DataTableTypeInterface
},
], visibility_configurable: false)
->add('select', SelectColumn::class, visibility_configurable: false)
->add('picture', HTMLColumn::class, [
->add('picture', TextColumn::class, [
'label' => '',
'className' => 'no-colvis',
'data' => fn(Part $context) => $this->partDataTableHelper->renderPicture($context),
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderPicture($context),
], visibility_configurable: false)
->add('name', HTMLColumn::class, [
->add('name', TextColumn::class, [
'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)'
])
->add('si_value', TextColumn::class, [
'label' => $this->translator->trans('part.table.si_value'),
'data' => function (Part $context): string {
'render' => function ($value, Part $context): string {
$siValue = SiValueSort::sqliteSiValue($context->getName());
if ($siValue !== null) {
//Output it as scientific number with a big E
return sprintf('%G', $siValue);
return htmlspecialchars(sprintf('%G', $siValue));
}
return '';
},
@ -162,38 +156,38 @@ final readonly class PartsDataTable implements DataTableTypeInterface
'label' => $this->translator->trans('part.table.manufacturer'),
'orderField' => 'NATSORT(_manufacturer.name)'
])
->add('storelocation', HTMLColumn::class, [
->add('storelocation', TextColumn::class, [
'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
'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')
->add('amount', HTMLColumn::class, [
->add('amount', TextColumn::class, [
'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'
])
->add('minamount', TextColumn::class, [
'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,
$context->getPartUnit()
),
)),
])
->add('partUnit', TextColumn::class, [
'label' => $this->translator->trans('part.table.partUnit'),
'orderField' => 'NATSORT(_partUnit.name)',
'data' => function (Part $context): string {
'render' => function ($value, Part $context): string {
$partUnit = $context->getPartUnit();
if ($partUnit === null) {
return '';
}
$tmp = $partUnit->getName();
$tmp = htmlspecialchars($partUnit->getName());
if ($partUnit->getUnit()) {
$tmp .= ' (' . $partUnit->getUnit() . ')';
$tmp .= ' (' . htmlspecialchars($partUnit->getUnit()) . ')';
}
return $tmp;
}
@ -201,14 +195,14 @@ final readonly class PartsDataTable implements DataTableTypeInterface
->add('partCustomState', TextColumn::class, [
'label' => $this->translator->trans('part.table.partCustomState'),
'orderField' => 'NATSORT(_partCustomState.name)',
'data' => function(Part $context): string {
'render' => function($value, Part $context): string {
$partCustomState = $context->getPartCustomState();
if ($partCustomState === null) {
return '';
}
return $partCustomState->getName();
return htmlspecialchars($partCustomState->getName());
}
])
->add('addedDate', LocaleDateTimeColumn::class, [
@ -254,25 +248,25 @@ final readonly class PartsDataTable implements DataTableTypeInterface
])
->add('eda_reference', TextColumn::class, [
'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)'
])
->add('eda_value', TextColumn::class, [
'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)'
])
->add('eda_status', HTMLColumn::class, [
->add('eda_status', TextColumn::class, [
'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',
]);
//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)) {
$this->csh->add('projects', HTMLColumn::class, [
$this->csh->add('projects', TextColumn::class, [
'label' => $this->translator->trans('project.labelp'),
'data' => function (Part $context): string {
'render' => function ($value, Part $context): string {
//Only show the first 5 projects names
$projects = $context->getProjects();
$tmp = "";
@ -292,7 +286,7 @@ final readonly class PartsDataTable implements DataTableTypeInterface
}
return $tmp;
},
}
]);
}

View file

@ -25,7 +25,6 @@ namespace App\DataTables;
use App\DataTables\Adapters\TwoStepORMAdapter;
use App\DataTables\Column\EntityColumn;
use App\DataTables\Column\EnumColumn;
use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\MarkdownColumn;
use App\DataTables\Helpers\PartDataTableHelper;
@ -49,7 +48,7 @@ use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableTypeInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterface
class ProjectBomEntriesDataTable implements DataTableTypeInterface
{
public function __construct(
protected EntityURLGenerator $entityURLGenerator,
@ -64,22 +63,17 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac
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('select', SelectColumn::class)
->add('picture', HTMLColumn::class, [
->add('picture', TextColumn::class, [
'label' => '',
'className' => 'no-colvis',
'data' => function (ProjectBOMEntry $context) {
'render' => function ($value, ProjectBOMEntry $context) {
if(!$context->getPart() instanceof Part) {
return '';
}
return $this->partDataTableHelper->renderPicture($context->getPart());
},
}
])
->add('id', TextColumn::class, [
@ -91,27 +85,27 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac
'label' => $this->translator->trans('project.bom.quantity'),
'className' => 'text-center',
'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 (!$context->getPart() instanceof Part) {
return round($context->getQuantity());
}
//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, [
'label' => $this->translator->trans('project.bom.part_id'),
'visible' => true,
'orderField' => 'part.id',
'data' => function (ProjectBOMEntry $context) {
'render' => function ($value, ProjectBOMEntry $context) {
return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : '';
},
])
->add('name', HTMLColumn::class, [
->add('name', TextColumn::class, [
'label' => $this->translator->trans('part.table.name'),
'orderField' => 'NATSORT(part.name)',
'data' => function (ProjectBOMEntry $context) {
'render' => function ($value, ProjectBOMEntry $context) {
if(!$context->getPart() instanceof Part) {
return htmlspecialchars((string) $context->getName());
}
@ -129,7 +123,11 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac
'label' => $this->translator->trans('part.table.ipn'),
'orderField' => 'NATSORT(part.ipn)',
'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, [
'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',
'data' => function (ProjectBOMEntry $context) {
'render' => function ($value, ProjectBOMEntry $context) {
$html = '';
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',
'visible' => false,
'data' => function (ProjectBOMEntry $context) {
'render' => function ($value, ProjectBOMEntry $context) {
if ($context->getPart() !== null) {
return $this->partDataTableHelper->renderAmount($context->getPart());
}
return '';
},
}
])
->add('storelocation', HTMLColumn::class, [
->add('storelocation', TextColumn::class, [
'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
'orderField' => 'NATSORT(MIN(_storelocations.name))',
'visible' => false,
'data' => function (ProjectBOMEntry $context) {
'render' => function ($value, ProjectBOMEntry $context) {
if ($context->getPart() !== null) {
return $this->partDataTableHelper->renderStorageLocations($context->getPart());
}
return '';
},
}
])
->add('price', TextColumn::class, [
'label' => 'project.bom.price',
'visible' => false,
'data' => function (ProjectBOMEntry $context) {
'render' => function ($value, ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
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, [
'label' => 'project.bom.ext_price',
'visible' => false,
'data' => function (ProjectBOMEntry $context) {
'render' => function ($value, ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
return $this->moneyFormatter->format(
$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
if($params['driver'] === 'pdo_mysql') {
//PDO::MYSQL_ATTR_INIT_COMMAND is deprecated since PHP 8.5 in favor of Pdo\Mysql::ATTR_INIT_COMMAND,
//but the Pdo\Mysql class only exists since PHP 8.4. Both constants have the same value (1002).
$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\', \'\'))';
//1002 is \PDO::MYSQL_ATTR_INIT_COMMAND constant value
$params['driverOptions'][\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))';
}
return parent::connect($params);

View file

@ -80,7 +80,7 @@ use Symfony\Component\Validator\Constraints as Assert;
)
],
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']
)]

View file

@ -29,13 +29,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/**
* 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(
private EntityManagerInterface $entityManager,
private TranslatorInterface $translator
private readonly EntityManagerInterface $entityManager,
private readonly TranslatorInterface $translator
) {
}
@ -473,4 +473,4 @@ readonly class BOMValidationService
: 0,
];
}
}
}

View file

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

View file

@ -68,7 +68,7 @@
<p class="mb-2">{% trans %}project.bom_import.validation.errors.description{% endtrans %}</p>
<ul class="mb-0">
{% for error in validation_result.errors %}
<li>{{ error }}</li>
<li>{{ error|raw }}</li>
{% endfor %}
</ul>
</div>
@ -80,7 +80,7 @@
<p class="mb-2">{% trans %}project.bom_import.validation.warnings.description{% endtrans %}</p>
<ul class="mb-0">
{% for warning in validation_result.warnings %}
<li>{{ warning }}</li>
<li>{{ warning|raw }}</li>
{% endfor %}
</ul>
</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>
<ul class="mb-0">
{% for info in validation_result.info %}
<li>{{ info }}</li>
<li>{{ info|raw }}</li>
{% endfor %}
</ul>
</div>
@ -139,21 +139,21 @@
{% if line_result.errors is not empty %}
<div class="text-danger">
{% 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 %}
</div>
{% endif %}
{% if line_result.warnings is not empty %}
<div class="text-warning">
{% 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 %}
</div>
{% endif %}
{% if line_result.info is not empty %}
<div class="text-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 %}
</div>
{% endif %}

View file

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

View file

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