mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-08-04 07:31:40 +00:00
Merge tag 'v2.12.3' into order-details
# Conflicts: # src/DataTables/ProjectBomEntriesDataTable.php
This commit is contained in:
commit
472eca8c9d
47 changed files with 1862 additions and 1411 deletions
|
|
@ -51,5 +51,18 @@
|
|||
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
|
||||
header ?Permissions-Policy "browsing-topics=()"
|
||||
|
||||
# Set a strict CSP and nosniff for all static assets not handled by PHP.
|
||||
# ? means "set only if not already present", so PHP responses carrying a Nelmio CSP are left untouched.
|
||||
header ?Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;"
|
||||
header ?X-Content-Type-Options "nosniff"
|
||||
|
||||
# SVG files get a slightly different CSP because they can embed resources and must not be framed.
|
||||
@svg path *.svg *.svg.gz *.svg.br
|
||||
header @svg Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; sandbox;"
|
||||
|
||||
# Prevent PHP execution in the media upload directory
|
||||
@php_in_media path_regexp (?i)^/media/.*\.(php[3-8]?|phar|phtml|pht|phps)$
|
||||
respond @php_in_media 403
|
||||
|
||||
php_server
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,14 @@
|
|||
AllowOverride All
|
||||
</Directory>
|
||||
|
||||
# Prevent PHP execution in the media upload directory (server-level, not .htaccess,
|
||||
# because public/media is a Docker volume and .htaccess there may not be present)
|
||||
<Directory /var/www/html/public/media>
|
||||
<FilesMatch "(?i)\.(php[3-8]?|phar|phtml|pht|phps)$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
</Directory>
|
||||
|
||||
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
|
||||
# error, crit, alert, emerg.
|
||||
# It is also possible to configure the loglevel for particular
|
||||
|
|
|
|||
15
.env
15
.env
|
|
@ -1,6 +1,10 @@
|
|||
#### Part-DB Configuration
|
||||
# See https://docs.part-db.de/configuration.html for documentation of available options
|
||||
|
||||
# Change this to a random value to secure your installation! You can generate a random string with "openssl rand -hex 16"
|
||||
# Share that value with nobody and keep it secret
|
||||
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
|
||||
|
||||
###################################################################################
|
||||
# Database settings
|
||||
###################################################################################
|
||||
|
|
@ -145,6 +149,16 @@ 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
|
||||
|
|
@ -158,7 +172,6 @@ CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
|||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=prod
|
||||
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
|
||||
APP_SHARE_DIR=var/share
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
|
|
|
|||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -129,7 +129,7 @@ jobs:
|
|||
run: ./bin/phpunit --coverage-clover=coverage.xml
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v6
|
||||
uses: codecov/codecov-action@v7
|
||||
with:
|
||||
env_vars: PHP_VERSION,DB_TYPE
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ RUN a2dissite 000-default.conf && \
|
|||
a2enmod proxy_fcgi setenvif && \
|
||||
a2enconf php${PHP_VERSION}-fpm && \
|
||||
a2enconf docker-php && \
|
||||
a2enmod rewrite
|
||||
a2enmod rewrite headers
|
||||
|
||||
# Install composer and yarn dependencies for Part-DB
|
||||
USER www-data
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
2.12.0
|
||||
2.12.3
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export default class extends Controller {
|
|||
|
||||
//Make the state persistent over reloads
|
||||
if(localStorage.getItem(STORAGE_KEY) === 'true') {
|
||||
sidebarHide();
|
||||
this.hideSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
"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.*",
|
||||
|
|
|
|||
1150
composer.lock
generated
1150
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -51,6 +51,7 @@ when@prod:
|
|||
type: stream
|
||||
channels: [deprecation]
|
||||
path: "%kernel.logs_dir%/%kernel.environment%_deprecations.log"
|
||||
level: "%env(DEPRECATION_LOG_LEVEL)%"
|
||||
|
||||
when@docker:
|
||||
monolog:
|
||||
|
|
@ -75,3 +76,4 @@ when@docker:
|
|||
type: stream
|
||||
channels: [deprecation]
|
||||
path: "%kernel.logs_dir%/%kernel.environment%_deprecations.log"
|
||||
level: "%env(DEPRECATION_LOG_LEVEL)%"
|
||||
|
|
|
|||
|
|
@ -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: false
|
||||
* enabled?: bool|Param, // Default: true
|
||||
* 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 connexion.
|
||||
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection.
|
||||
* 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 connexion.
|
||||
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection.
|
||||
* 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 connexion.
|
||||
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection.
|
||||
* 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.
|
||||
|
|
@ -2436,6 +2436,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||
* },
|
||||
* jsonapi?: array{
|
||||
* use_iri_as_id?: bool|Param, // Set to false to use entity identifiers instead of IRIs as the "id" field in JSON:API responses. // Default: true
|
||||
* allow_client_generated_id?: bool|Param, // Allow client-generated IDs on JSON:API POST per https://jsonapi.org/format/#crud-creating-client-ids. Off by default to prevent id spoofing on public endpoints. // Default: false
|
||||
* },
|
||||
* eager_loading?: bool|array{
|
||||
* enabled?: bool|Param, // Default: true
|
||||
|
|
|
|||
|
|
@ -114,10 +114,21 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
|
|||
* `datastructure_create`: Creation of a new data structure (e.g. category, manufacturer, ...)
|
||||
* `CHECK_FOR_UPDATES` (default `1`): Set this to 0 if you do not want Part-DB to connect to GitHub to check for new
|
||||
versions, or if your server cannot connect to the internet.
|
||||
* `APP_SECRET` (env only): This variable is a configuration parameter used for various security-related purposes,
|
||||
particularly for securing and protecting various aspects of your application. It's a secret key that is used for
|
||||
cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this
|
||||
value should be handled as confidential data and not shared publicly.
|
||||
* `APP_SECRET` (env only): A secret key used by Symfony for cryptographic operations — signing cookies, generating
|
||||
CSRF tokens, and other security-sensitive tasks. **You must change this from the default value before exposing
|
||||
Part-DB to any network.** The default value shipped with Part-DB is publicly known; leaving it in place would allow
|
||||
an attacker to forge signed cookies and bypass CSRF protection.
|
||||
|
||||
Generate a secure value and add it to `.env.local`:
|
||||
```bash
|
||||
echo "APP_SECRET=$(openssl rand -hex 32)" >> .env.local
|
||||
```
|
||||
For Docker, pass it in the `environment` section of your `docker-compose.yaml`:
|
||||
```yaml
|
||||
environment:
|
||||
- APP_SECRET=<output of: openssl rand -hex 32>
|
||||
```
|
||||
Part-DB displays a warning on the homepage (visible to administrators only) as long as the default value is in use.
|
||||
* `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the
|
||||
part image gallery
|
||||
* `IPN_SUGGEST_REGEX`: A global regular expression, that part IPNs have to fulfill. Enforce your own format for your users.
|
||||
|
|
@ -268,9 +279,13 @@ 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
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,10 @@ services:
|
|||
- DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db
|
||||
# In docker env logs will be redirected to stderr
|
||||
- APP_ENV=docker
|
||||
|
||||
# Secret key used to sign cookies and CSRF tokens. MUST be changed to a unique random value before going live!
|
||||
# Generate one with: openssl rand -hex 32
|
||||
- APP_SECRET=CHANGE_ME
|
||||
|
||||
# Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to
|
||||
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/
|
||||
# folder (under .automigration-backup), so you can restore it, if the migration fails.
|
||||
|
|
@ -89,7 +92,11 @@ services:
|
|||
|
||||
4. Customize the settings by changing the environment variables (or adding new ones). See [Configuration]({% link
|
||||
configuration.md %}) for more information.
|
||||
5. Inside the folder, run
|
||||
5. Make sure to change the `APP_SECRET` variable to a unique random value (32 characters). You can generate one with the following command:
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
6. Inside the folder, run
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
|
|
@ -100,7 +107,7 @@ services:
|
|||
> Otherwise Part-DB console might use the wrong configuration to execute commands.
|
||||
|
||||
|
||||
6. Create the initial database with
|
||||
7. Create the initial database with
|
||||
|
||||
```bash
|
||||
docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate
|
||||
|
|
@ -108,7 +115,7 @@ docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate
|
|||
|
||||
and watch for the password output
|
||||
|
||||
6. Part-DB is available under `http://localhost:8080` and you can log in with the username `admin` and the password shown
|
||||
8. Part-DB is available under `http://localhost:8080` and you can log in with the username `admin` and the password shown
|
||||
before
|
||||
|
||||
The docker image uses a SQLite database and all data (database, uploads, and other media) is put into folders relative to
|
||||
|
|
@ -121,6 +128,7 @@ If you want to use MySQL as a database, you can use the following docker-compose
|
|||
{: .warning }
|
||||
> You have to replace the values for MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD with your own passwords!!
|
||||
> You have to change MYSQL_PASSWORD in the database section and for the DATABASE_URL in the partdb section.
|
||||
> Generate a random string for APP_SECRET.
|
||||
|
||||
```yaml
|
||||
version: '3.3'
|
||||
|
|
@ -142,14 +150,19 @@ services:
|
|||
environment:
|
||||
# Replace SECRET_USER_PASSWORD with the value of MYSQL_PASSWORD from below
|
||||
- DATABASE_URL=mysql://partdb:SECRET_USER_PASSWORD@database:3306/partdb
|
||||
|
||||
# Secret key used to sign cookies. MUST be changed to a unique random value before going live!
|
||||
# Generate one with: openssl rand -hex 32
|
||||
- APP_SECRET=CHANGE_ME
|
||||
|
||||
# In docker env logs will be redirected to stderr
|
||||
- APP_ENV=docker
|
||||
|
||||
# Uncomment this, if you want to use the automatic database migration feature. With this you do not have to
|
||||
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/
|
||||
# folder (under .automigration-backup), so you can restore it, if the migration fails.
|
||||
# This feature is currently experimental, so use it at your own risk!
|
||||
# - DB_AUTOMIGRATE=true
|
||||
|
||||
# Uncomment this, if you want to use the automatic database migration feature. With this you do not have to
|
||||
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/
|
||||
# folder (under .automigration-backup), so you can restore it, if the migration fails.
|
||||
# This feature is currently experimental, so use it at your own risk!
|
||||
# - DB_AUTOMIGRATE=true
|
||||
|
||||
# You can configure Part-DB using the webUI or environment variables
|
||||
# However you can add any other environment configuration you want here
|
||||
|
|
|
|||
|
|
@ -136,6 +136,15 @@ cp .env .env.local
|
|||
In your `.env.local` you can configure Part-DB according to your wishes and overwrite web interface settings.
|
||||
A full list of configuration options can be found [here](../configuration.md).
|
||||
|
||||
{: .important }
|
||||
> **Change `APP_SECRET` before going live.** The default value shipped with Part-DB is publicly known and must not be
|
||||
> used in production — it would allow an attacker to forge signed cookies and bypass CSRF protection.
|
||||
> Generate a new value and add it to your `.env.local`:
|
||||
> ```bash
|
||||
> echo "APP_SECRET=$(openssl rand -hex 32)" >> .env.local
|
||||
> ```
|
||||
> or edit the file with a text editor and add a new value for `APP_SECRET` (you can generate a random value with `openssl rand -hex 32`).
|
||||
|
||||
Please check that the configured base currency matches your mainly used currency, as
|
||||
this can not be changed after creating price information.
|
||||
|
||||
|
|
@ -223,7 +232,7 @@ sudo ln -s /etc/apache2/sites-available/partdb.conf /etc/apache2/sites-enabled/p
|
|||
Configure apache to show pretty URL paths for Part-DB (`/label/dialog` instead of `/index.php/label/dialog`):
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
sudo a2enmod rewrite headers
|
||||
```
|
||||
|
||||
If you want to access Part-DB via the IP-Address of the server, instead of the domain name, you have to remove the
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ server {
|
|||
root /var/www/partdb/public;
|
||||
|
||||
location / {
|
||||
# Headers are set here for static assets. PHP responses are served via the index.php location
|
||||
# below and inherit neither of these headers, so Nelmio's PHP-side CSP is unaffected.
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
try_files $uri /index.php$is_args$args;
|
||||
}
|
||||
|
||||
|
|
@ -52,10 +56,17 @@ server {
|
|||
location ~ \.php$ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Set Content-Security-Policy for svg files, to block embedded javascript in there
|
||||
|
||||
# Prevent PHP execution in the media upload directory
|
||||
location ~* ^/media/.*\.(php[3-8]?|phar|phtml|pht|phps)$ {
|
||||
return 403;
|
||||
}
|
||||
|
||||
# SVG files get a slightly different CSP because they can embed resources and must not be framed.
|
||||
# This regex location takes precedence over location /, so headers must be repeated here.
|
||||
location ~* \.svg$ {
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';";
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; sandbox;" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
|
||||
error_log /var/log/nginx/parts.error.log;
|
||||
|
|
|
|||
|
|
@ -119,9 +119,14 @@ DirectoryIndex index.php
|
|||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# Set Content-Security-Policy for svg files (and compressed variants), to block embedded javascript in there
|
||||
<IfModule mod_headers.c>
|
||||
# Set a strict CSP for all static assets not handled by PHP.
|
||||
# PHP responses already carry their own CSP via NelmioSecurityBundle, so setifempty leaves those untouched.
|
||||
Header always setifempty Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;"
|
||||
Header always setifempty X-Content-Type-Options "nosniff"
|
||||
|
||||
# SVG files get a slightly different CSP because they can embed resources and must not be framed.
|
||||
<FilesMatch "\.(svg|svg\.gz|svg\.br)$">
|
||||
Header set Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';"
|
||||
Header always set Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; sandbox;"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Generated on Mon May 25 06:41:46 UTC 2026
|
||||
# Generated on Mon Jun 15 07:28:00 UTC 2026
|
||||
# This file contains all footprints available in the offical KiCAD library
|
||||
Audio_Module:Reverb_BTDR-1H
|
||||
Audio_Module:Reverb_BTDR-1V
|
||||
|
|
@ -12012,7 +12012,6 @@ Package_DFN_QFN:WDFN-8-1EP_4x3mm_P0.65mm_EP2.4x1.8mm_ThermalVias
|
|||
Package_DFN_QFN:WDFN-8-1EP_6x5mm_P1.27mm_EP3.4x4mm
|
||||
Package_DFN_QFN:WDFN-8-1EP_8x6mm_P1.27mm_EP6x4.8mm
|
||||
Package_DFN_QFN:WDFN-8-1EP_8x6mm_P1.27mm_EP6x4.8mm_ThermalVias
|
||||
Package_DFN_QFN:WDFN-8_2x2mm_P0.5mm
|
||||
Package_DFN_QFN:WFDFPN-8-1EP_3x2mm_P0.5mm_EP1.25x1.35mm
|
||||
Package_DFN_QFN:WQFN-14-1EP_2.5x2.5mm_P0.5mm_EP1.45x1.45mm
|
||||
Package_DFN_QFN:WQFN-14-1EP_2.5x2.5mm_P0.5mm_EP1.45x1.45mm_ThermalVias
|
||||
|
|
@ -12358,7 +12357,6 @@ Package_DirectFET:DirectFET_SQ
|
|||
Package_DirectFET:DirectFET_ST
|
||||
Package_LCC:Analog_LCC-8_5x5mm_P1.27mm
|
||||
Package_LCC:MO047AD_PLCC-52_19.1x19.1mm_P1.27mm
|
||||
Package_LCC:PLCC-20
|
||||
Package_LCC:PLCC-20_9.0x9.0mm_P1.27mm
|
||||
Package_LCC:PLCC-20_SMD-Socket
|
||||
Package_LCC:PLCC-20_THT-Socket
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Generated on Mon May 25 06:42:27 UTC 2026
|
||||
# Generated on Mon Jun 15 07:28:38 UTC 2026
|
||||
# This file contains all symbols available in the offical KiCAD library
|
||||
4xxx:14528
|
||||
4xxx:14529
|
||||
|
|
@ -2255,11 +2255,11 @@ Battery_Management:BQ76200PW
|
|||
Battery_Management:BQ76920PW
|
||||
Battery_Management:BQ76930DBT
|
||||
Battery_Management:BQ76940DBT
|
||||
Battery_Management:BQ7695201PFBR
|
||||
Battery_Management:BQ7695202PFBR
|
||||
Battery_Management:BQ7695203PFBR
|
||||
Battery_Management:BQ7695204PFBR
|
||||
Battery_Management:BQ76952PFBR
|
||||
Battery_Management:BQ7695201PFB
|
||||
Battery_Management:BQ7695202PFB
|
||||
Battery_Management:BQ7695203PFB
|
||||
Battery_Management:BQ7695204PFB
|
||||
Battery_Management:BQ76952PFB
|
||||
Battery_Management:BQ78350DBT
|
||||
Battery_Management:BQ78350DBT-R1
|
||||
Battery_Management:CN3063
|
||||
|
|
@ -5300,21 +5300,39 @@ 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
|
||||
|
|
@ -14593,6 +14611,8 @@ 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
|
||||
|
|
@ -15115,6 +15135,7 @@ Memory_Flash:AM29F400Bx-xxEx
|
|||
Memory_Flash:AM29F400Bx-xxSx
|
||||
Memory_Flash:AM29PDL128G
|
||||
Memory_Flash:AT25DF041x-UxN-x
|
||||
Memory_Flash:AT25SF041B-SSHD-X
|
||||
Memory_Flash:AT25SF081-SSHD-X
|
||||
Memory_Flash:AT25SF081-SSHF-X
|
||||
Memory_Flash:AT25SF081-XMHD-X
|
||||
|
|
@ -15670,6 +15691,7 @@ 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
|
||||
|
|
@ -15771,6 +15793,7 @@ Power_Management:TPS22810DBV
|
|||
Power_Management:TPS22810DRV
|
||||
Power_Management:TPS22917DBV
|
||||
Power_Management:TPS22917LDBV
|
||||
Power_Management:TPS22919DCK
|
||||
Power_Management:TPS22929D
|
||||
Power_Management:TPS22993
|
||||
Power_Management:TPS2412D
|
||||
|
|
@ -16037,6 +16060,7 @@ Power_Supervisor:TPS3831
|
|||
Power_Supervisor:TPS3839DBZ
|
||||
Power_Supervisor:TPS3839DQN
|
||||
RF:0900PC15J0013
|
||||
RF:AD8302xRU
|
||||
RF:ADC-10-1R
|
||||
RF:ADCH-80
|
||||
RF:ADCH-80A
|
||||
|
|
@ -16479,6 +16503,7 @@ RF_Module:DWM3000
|
|||
RF_Module:E18-MS1-PCB
|
||||
RF_Module:E73-2G4M04S-52810
|
||||
RF_Module:E73-2G4M04S-52832
|
||||
RF_Module:ESP-01
|
||||
RF_Module:ESP-07
|
||||
RF_Module:ESP-12E
|
||||
RF_Module:ESP-12F
|
||||
|
|
@ -16680,6 +16705,19 @@ 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
|
||||
|
|
@ -16827,7 +16865,6 @@ 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
|
||||
|
|
@ -20988,6 +21025,7 @@ 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
|
||||
|
|
|
|||
3
public/media/.gitignore
vendored
3
public/media/.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
# Ignore everything except this .gitignore
|
||||
*
|
||||
!.gitignore
|
||||
!.gitignore
|
||||
!.htaccess
|
||||
|
|
|
|||
10
public/media/.htaccess
Normal file
10
public/media/.htaccess
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Deny access to PHP and PHP-like files to prevent remote code execution
|
||||
<FilesMatch "(?i)\.(php[3-8]?|phar|phtml|pht|phps)$">
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace App\Command;
|
||||
|
||||
use App\Services\System\AppSecretChecker;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
|
@ -33,7 +34,9 @@ use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
|
|||
#[AsCommand('partdb:check-requirements', 'Checks if the requirements Part-DB needs or recommends are fulfilled.')]
|
||||
class CheckRequirementsCommand extends Command
|
||||
{
|
||||
public function __construct(protected ContainerBagInterface $params)
|
||||
public function __construct(protected ContainerBagInterface $params,
|
||||
private readonly AppSecretChecker $appSecretChecker
|
||||
)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
|
@ -121,6 +124,16 @@ class CheckRequirementsCommand extends Command
|
|||
$io->success('Debug mode disabled.');
|
||||
}
|
||||
|
||||
//Check if APP_SECRET has been changed from the default
|
||||
if ($io->isVerbose()) {
|
||||
$io->comment('Checking APP_SECRET...');
|
||||
}
|
||||
if ($this->appSecretChecker->isInsecureSecret()) {
|
||||
$io->warning('APP_SECRET is set to the default value shipped with Part-DB. This is a security risk! Generate a new secret (e.g. using "openssl rand -hex 32") and set it as APP_SECRET in your .env.local file.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('APP_SECRET has been changed from the default value.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function checkPHPExtensions(SymfonyStyle $io, bool $only_issues = false): void
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ class AttachmentFileController extends AbstractController
|
|||
//Set header content disposition, so that the file will be downloaded
|
||||
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $attachment->getFilename());
|
||||
|
||||
$this->setAttachmentCSPHeaders($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +114,16 @@ class AttachmentFileController extends AbstractController
|
|||
//Set header content disposition, so that the file will be downloaded
|
||||
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $attachment->getFilename());
|
||||
|
||||
$this->setAttachmentCSPHeaders($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function setAttachmentCSPHeaders(Response $response): Response
|
||||
{
|
||||
//Set an CSP that disallow to run any scripts, styles or images from the attachment render page, as it is not used anywhere else for now and can be a security risk if used without proper precautions, so it should be opt-in
|
||||
$response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;");
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace App\Controller;
|
|||
|
||||
use App\DataTables\LogDataTable;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\System\AppSecretChecker;
|
||||
use App\Services\System\BannerHelper;
|
||||
use App\Services\System\GitVersionInfoProvider;
|
||||
use App\Services\System\UpdateAvailableFacade;
|
||||
|
|
@ -36,8 +37,11 @@ use Symfony\Component\Routing\Attribute\Route;
|
|||
|
||||
class HomepageController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly DataTableFactory $dataTable, private readonly BannerHelper $bannerHelper)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DataTableFactory $dataTable,
|
||||
private readonly BannerHelper $bannerHelper,
|
||||
private readonly AppSecretChecker $appSecretChecker,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -84,6 +88,8 @@ class HomepageController extends AbstractController
|
|||
'new_version_available' => $updateAvailableManager->isUpdateAvailable(),
|
||||
'new_version' => $updateAvailableManager->getLatestVersionString(),
|
||||
'new_version_url' => $updateAvailableManager->getLatestVersionUrl(),
|
||||
'insecure_app_secret' => $this->appSecretChecker->isInsecureSecret(),
|
||||
'suggested_app_secret' => $this->appSecretChecker->isInsecureSecret() ? $this->appSecretChecker->generateSecret() : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class PartImportExportController extends AbstractController
|
|||
goto ret;
|
||||
}
|
||||
|
||||
if (!isset($errors) || $errors) {
|
||||
if (!isset($errors) || $errors) { //@phpstan-ignore-line
|
||||
$this->addFlash('error', 'parts.import.flash.error');
|
||||
} else {
|
||||
$this->addFlash('success', 'parts.import.flash.success');
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ 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;
|
||||
|
|
@ -55,7 +56,7 @@ class TreeController extends AbstractController
|
|||
|
||||
#[Route(path: '/category/{id}', name: 'tree_category')]
|
||||
#[Route(path: '/categories', name: 'tree_category_root')]
|
||||
public function categoryTree(?Category $category = null): JsonResponse
|
||||
public function categoryTree(#[MapEntity(id: 'id')] ?Category $category = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@categories.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Category::class, $category, 'list_parts_root');
|
||||
|
|
@ -68,7 +69,7 @@ class TreeController extends AbstractController
|
|||
|
||||
#[Route(path: '/footprint/{id}', name: 'tree_footprint')]
|
||||
#[Route(path: '/footprints', name: 'tree_footprint_root')]
|
||||
public function footprintTree(?Footprint $footprint = null): JsonResponse
|
||||
public function footprintTree(#[MapEntity(id: 'id')] ?Footprint $footprint = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@footprints.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Footprint::class, $footprint, 'list_parts_root');
|
||||
|
|
@ -80,7 +81,7 @@ class TreeController extends AbstractController
|
|||
|
||||
#[Route(path: '/location/{id}', name: 'tree_location')]
|
||||
#[Route(path: '/locations', name: 'tree_location_root')]
|
||||
public function locationTree(?StorageLocation $location = null): JsonResponse
|
||||
public function locationTree(#[MapEntity(id: 'id')] ?StorageLocation $location = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(StorageLocation::class, $location, 'list_parts_root');
|
||||
|
|
@ -93,7 +94,7 @@ class TreeController extends AbstractController
|
|||
|
||||
#[Route(path: '/manufacturer/{id}', name: 'tree_manufacturer')]
|
||||
#[Route(path: '/manufacturers', name: 'tree_manufacturer_root')]
|
||||
public function manufacturerTree(?Manufacturer $manufacturer = null): JsonResponse
|
||||
public function manufacturerTree(#[MapEntity(id: 'id')] ?Manufacturer $manufacturer = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@manufacturers.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Manufacturer::class, $manufacturer, 'list_parts_root');
|
||||
|
|
@ -106,7 +107,7 @@ class TreeController extends AbstractController
|
|||
|
||||
#[Route(path: '/supplier/{id}', name: 'tree_supplier')]
|
||||
#[Route(path: '/suppliers', name: 'tree_supplier_root')]
|
||||
public function supplierTree(?Supplier $supplier = null): JsonResponse
|
||||
public function supplierTree(#[MapEntity(id: 'id')] ?Supplier $supplier = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@suppliers.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Supplier::class, $supplier, 'list_parts_root');
|
||||
|
|
@ -119,7 +120,7 @@ class TreeController extends AbstractController
|
|||
|
||||
#[Route(path: '/device/{id}', name: 'tree_device')]
|
||||
#[Route(path: '/devices', name: 'tree_device_root')]
|
||||
public function deviceTree(?Project $device = null): JsonResponse
|
||||
public function deviceTree(#[MapEntity(id: 'id')] ?Project $device = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@projects.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Project::class, $device, 'devices');
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ 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;
|
||||
|
|
@ -40,14 +41,19 @@ use Omines\DataTablesBundle\DataTable;
|
|||
use Omines\DataTablesBundle\DataTableTypeInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class AttachmentDataTable implements DataTableTypeInterface
|
||||
final readonly class AttachmentDataTable implements DataTableTypeInterface
|
||||
{
|
||||
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 __construct(private TranslatorInterface $translator, private EntityURLGenerator $entityURLGenerator, private AttachmentManager $attachmentHelper, private AttachmentURLGenerator $attachmentURLGenerator, private 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
|
||||
|
|
@ -59,10 +65,10 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
|||
},
|
||||
]);
|
||||
|
||||
$dataTable->add('picture', TextColumn::class, [
|
||||
$dataTable->add('picture', HTMLColumn::class, [
|
||||
'label' => '',
|
||||
'className' => 'no-colvis',
|
||||
'render' => function ($value, Attachment $context): string {
|
||||
'data' => function (Attachment $context): string {
|
||||
if ($context->isPicture()
|
||||
&& $this->attachmentHelper->isInternalFileExisting($context)) {
|
||||
|
||||
|
|
@ -95,65 +101,65 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
|||
'orderField' => 'NATSORT(attachment.name)',
|
||||
]);
|
||||
|
||||
$dataTable->add('attachment_type', TextColumn::class, [
|
||||
$dataTable->add('attachment_type', HTMLColumn::class, [
|
||||
'label' => 'attachment.table.type',
|
||||
'field' => 'attachment_type.name',
|
||||
'orderField' => 'NATSORT(attachment_type.name)',
|
||||
'render' => fn($value, Attachment $context): string => sprintf(
|
||||
'data' => fn(Attachment $context, $value): string => sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
$this->entityURLGenerator->editURL($context->getAttachmentType()),
|
||||
htmlspecialchars((string) $value)
|
||||
),
|
||||
]);
|
||||
|
||||
$dataTable->add('element', TextColumn::class, [
|
||||
$dataTable->add('element', HTMLColumn::class, [
|
||||
'label' => 'attachment.table.element',
|
||||
//'propertyPath' => 'element.name',
|
||||
'render' => fn($value, Attachment $context): string => sprintf(
|
||||
'data' => fn(Attachment $context): string => sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
$this->entityURLGenerator->infoURL($context->getElement()),
|
||||
$this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true)
|
||||
),
|
||||
]);
|
||||
|
||||
$dataTable->add('internal_link', TextColumn::class, [
|
||||
$dataTable->add('internal_link', HTMLColumn::class, [
|
||||
'label' => 'attachment.table.internal_file',
|
||||
'propertyPath' => 'filename',
|
||||
'orderField' => 'NATSORT(attachment.original_filename)',
|
||||
'render' => function ($value, Attachment $context) {
|
||||
'data' => function (Attachment $context, $value) {
|
||||
if ($this->attachmentHelper->isInternalFileExisting($context)) {
|
||||
return sprintf(
|
||||
'<a href="%s" target="_blank" data-no-ajax>%s</a>',
|
||||
$this->entityURLGenerator->viewURL($context),
|
||||
htmlspecialchars($value)
|
||||
htmlspecialchars((string) $value)
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
return htmlspecialchars((string) $value);
|
||||
},
|
||||
]);
|
||||
|
||||
$dataTable->add('external_link', TextColumn::class, [
|
||||
$dataTable->add('external_link', HTMLColumn::class, [
|
||||
'label' => 'attachment.table.external_link',
|
||||
'propertyPath' => 'host',
|
||||
'orderField' => 'attachment.external_path',
|
||||
'render' => function ($value, Attachment $context) {
|
||||
'data' => function (Attachment $context, $value) {
|
||||
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($value),
|
||||
htmlspecialchars((string) $value),
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
return htmlspecialchars((string) $value);
|
||||
},
|
||||
]);
|
||||
|
||||
$dataTable->add('filesize', TextColumn::class, [
|
||||
$dataTable->add('filesize', HTMLColumn::class, [
|
||||
'label' => $this->translator->trans('attachment.table.filesize'),
|
||||
'render' => function ($value, Attachment $context) {
|
||||
'data' => function (Attachment $context) {
|
||||
if (!$context->hasInternal()) {
|
||||
return sprintf(
|
||||
'<span class="badge bg-primary">
|
||||
|
|
@ -168,7 +174,7 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
|||
'<span class="badge bg-secondary">
|
||||
<i class="fas fa-hdd fa-fw"></i> %s
|
||||
</span>',
|
||||
$this->attachmentHelper->getHumanFileSize($context)
|
||||
htmlspecialchars($this->attachmentHelper->getHumanFileSize($context))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class EntityColumn extends AbstractColumn
|
|||
);
|
||||
}
|
||||
|
||||
return sprintf('<i>%s</i>', $value);
|
||||
return sprintf('<i>%s</i>', htmlspecialchars($value));
|
||||
}
|
||||
|
||||
return '';
|
||||
|
|
|
|||
37
src/DataTables/Column/HTMLColumn.php
Normal file
37
src/DataTables/Column/HTMLColumn.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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' : '',
|
||||
$href,
|
||||
$title,
|
||||
$icon
|
||||
htmlspecialchars($href),
|
||||
htmlspecialchars($title ?? ''),
|
||||
htmlspecialchars($icon ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
class ErrorDataTable implements DataTableTypeInterface
|
||||
final readonly class ErrorDataTable implements DataTableTypeInterface
|
||||
{
|
||||
public function configureOptions(OptionsResolver $optionsResolver): void
|
||||
{
|
||||
|
|
@ -49,6 +49,11 @@ 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);
|
||||
|
|
@ -58,9 +63,9 @@ class ErrorDataTable implements DataTableTypeInterface
|
|||
'render' => fn($value, $context): string => 'table-warning',
|
||||
])
|
||||
|
||||
->add('error', TextColumn::class, [
|
||||
->add('error', HTMLColumn::class, [
|
||||
'label' => 'error_table.error',
|
||||
'render' => fn($value, $context): string => '<i class="fa-solid fa-triangle-exclamation fa-fw"></i> ' . $value,
|
||||
'data' => fn($context, $value): string => '<i class="fa-solid fa-triangle-exclamation fa-fw"></i> ' . htmlspecialchars((string) $value),
|
||||
])
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -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').': '.$context->getBuiltProject()->getName());
|
||||
$this->translator->trans('part.info.projectBuildPart.hint').': '.htmlspecialchars($context->getBuiltProject()->getName()));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ 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;
|
||||
|
|
@ -59,7 +60,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class LogDataTable implements DataTableTypeInterface
|
||||
final readonly class LogDataTable implements DataTableTypeInterface
|
||||
{
|
||||
protected LogEntryRepository $logRepo;
|
||||
|
||||
|
|
@ -95,6 +96,11 @@ 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);
|
||||
|
|
@ -104,10 +110,10 @@ class LogDataTable implements DataTableTypeInterface
|
|||
'render' => fn($value, AbstractLogEntry $context) => $this->logLevelHelper->logLevelToTableColorClass($context->getLevelString()),
|
||||
]);
|
||||
|
||||
$dataTable->add('symbol', TextColumn::class, [
|
||||
$dataTable->add('symbol', HTMLColumn::class, [
|
||||
'label' => '',
|
||||
'className' => 'no-colvis',
|
||||
'render' => fn($value, AbstractLogEntry $context): string => sprintf(
|
||||
'data' => fn(AbstractLogEntry $context): string => sprintf(
|
||||
'<i class="fas fa-fw %s" title="%s"></i>',
|
||||
$this->logLevelHelper->logLevelToIconClass($context->getLevelString()),
|
||||
$context->getLevelString()
|
||||
|
|
@ -128,10 +134,10 @@ class LogDataTable implements DataTableTypeInterface
|
|||
)
|
||||
]);
|
||||
|
||||
$dataTable->add('type', TextColumn::class, [
|
||||
$dataTable->add('type', HTMLColumn::class, [
|
||||
'label' => 'log.type',
|
||||
'propertyPath' => 'type',
|
||||
'render' => function (string $value, AbstractLogEntry $context) {
|
||||
'data' => function (AbstractLogEntry $context, string $value) {
|
||||
$text = $this->translator->trans('log.type.'.$value);
|
||||
|
||||
if ($context instanceof PartStockChangedLogEntry) {
|
||||
|
|
@ -149,20 +155,20 @@ class LogDataTable implements DataTableTypeInterface
|
|||
'label' => 'log.level',
|
||||
'visible' => 'system_log' === $options['mode'],
|
||||
'propertyPath' => 'levelString',
|
||||
'render' => fn(string $value, AbstractLogEntry $context) => $this->translator->trans('log.level.'.$value),
|
||||
'data' => fn(AbstractLogEntry $context, string $value) => $this->translator->trans('log.level.'.$value),
|
||||
]);
|
||||
|
||||
$dataTable->add('user', TextColumn::class, [
|
||||
$dataTable->add('user', HTMLColumn::class, [
|
||||
'label' => 'log.user',
|
||||
'orderField' => 'NATSORT(user.name)',
|
||||
'render' => function ($value, AbstractLogEntry $context): string {
|
||||
'data' => function (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]',
|
||||
htmlentities((string) $context->getCLIUsername()),
|
||||
htmlspecialchars((string) $context->getCLIUsername()),
|
||||
$this->translator->trans('log.cli_user')
|
||||
);
|
||||
}
|
||||
|
|
@ -170,7 +176,7 @@ class LogDataTable implements DataTableTypeInterface
|
|||
//Else we just deal with a deleted user
|
||||
return sprintf(
|
||||
'@%s [%s]',
|
||||
htmlentities($context->getUsername()),
|
||||
htmlspecialchars($context->getUsername()),
|
||||
$this->translator->trans('log.target_deleted'),
|
||||
);
|
||||
}
|
||||
|
|
@ -182,7 +188,7 @@ class LogDataTable implements DataTableTypeInterface
|
|||
$img_url,
|
||||
$this->userAvatarHelper->getAvatarMdURL($user),
|
||||
$this->urlGenerator->generate('user_info', ['id' => $user->getID()]),
|
||||
htmlentities($user->getFullName(true))
|
||||
htmlspecialchars($user->getFullName(true))
|
||||
);
|
||||
},
|
||||
]);
|
||||
|
|
@ -194,7 +200,7 @@ class LogDataTable implements DataTableTypeInterface
|
|||
'render' => function (LogTargetType $value, AbstractLogEntry $context) {
|
||||
$class = $value->toClass();
|
||||
if (null !== $class) {
|
||||
return $this->elementTypeNameGenerator->getLocalizedTypeLabel($class);
|
||||
return $this->elementTypeNameGenerator->typeLabel($class);
|
||||
}
|
||||
|
||||
return '';
|
||||
|
|
@ -216,9 +222,9 @@ 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);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ 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;
|
||||
|
|
@ -58,7 +59,7 @@ use Symfony\Bundle\SecurityBundle\Security;
|
|||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class PartsDataTable implements DataTableTypeInterface
|
||||
final readonly class PartsDataTable implements DataTableTypeInterface
|
||||
{
|
||||
public const LENGTH_MENU = [[10, 25, 50, 100, 250, 500, -1], [10, 25, 50, 100, 250, 500, "All"]];
|
||||
|
||||
|
|
@ -94,6 +95,11 @@ final 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, [
|
||||
|
|
@ -109,23 +115,23 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
},
|
||||
], visibility_configurable: false)
|
||||
->add('select', SelectColumn::class, visibility_configurable: false)
|
||||
->add('picture', TextColumn::class, [
|
||||
->add('picture', HTMLColumn::class, [
|
||||
'label' => '',
|
||||
'className' => 'no-colvis',
|
||||
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderPicture($context),
|
||||
'data' => fn(Part $context) => $this->partDataTableHelper->renderPicture($context),
|
||||
], visibility_configurable: false)
|
||||
->add('name', TextColumn::class, [
|
||||
->add('name', HTMLColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.name'),
|
||||
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context),
|
||||
'data' => fn(Part $context) => $this->partDataTableHelper->renderName($context),
|
||||
'orderField' => 'NATSORT(part.name)'
|
||||
])
|
||||
->add('si_value', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.si_value'),
|
||||
'render' => function ($value, Part $context): string {
|
||||
'data' => function (Part $context): string {
|
||||
$siValue = SiValueSort::sqliteSiValue($context->getName());
|
||||
if ($siValue !== null) {
|
||||
//Output it as scientific number with a big E
|
||||
return htmlspecialchars(sprintf('%G', $siValue));
|
||||
return sprintf('%G', $siValue);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
|
@ -156,24 +162,24 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
'label' => $this->translator->trans('part.table.manufacturer'),
|
||||
'orderField' => 'NATSORT(_manufacturer.name)'
|
||||
])
|
||||
->add('storelocation', TextColumn::class, [
|
||||
->add('storelocation', HTMLColumn::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))',
|
||||
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
|
||||
'data' => fn(Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
|
||||
], alias: 'storage_location')
|
||||
|
||||
->add('amount', TextColumn::class, [
|
||||
->add('amount', HTMLColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.amount'),
|
||||
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderAmount($context),
|
||||
'data' => fn(Part $context) => $this->partDataTableHelper->renderAmount($context),
|
||||
'orderField' => 'amountSum'
|
||||
])
|
||||
->add('minamount', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.minamount'),
|
||||
'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format(
|
||||
'data' => fn(Part $context, $value): string => $this->amountFormatter->format(
|
||||
$value,
|
||||
$context->getPartUnit()
|
||||
)),
|
||||
),
|
||||
])
|
||||
->add('orderamount', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.orderamount'),
|
||||
|
|
@ -187,16 +193,16 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
->add('partUnit', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.partUnit'),
|
||||
'orderField' => 'NATSORT(_partUnit.name)',
|
||||
'render' => function ($value, Part $context): string {
|
||||
'data' => function (Part $context): string {
|
||||
$partUnit = $context->getPartUnit();
|
||||
if ($partUnit === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$tmp = htmlspecialchars($partUnit->getName());
|
||||
$tmp = $partUnit->getName();
|
||||
|
||||
if ($partUnit->getUnit()) {
|
||||
$tmp .= ' (' . htmlspecialchars($partUnit->getUnit()) . ')';
|
||||
$tmp .= ' (' . $partUnit->getUnit() . ')';
|
||||
}
|
||||
return $tmp;
|
||||
}
|
||||
|
|
@ -204,14 +210,14 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
->add('partCustomState', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.partCustomState'),
|
||||
'orderField' => 'NATSORT(_partCustomState.name)',
|
||||
'render' => function($value, Part $context): string {
|
||||
'data' => function(Part $context): string {
|
||||
$partCustomState = $context->getPartCustomState();
|
||||
|
||||
if ($partCustomState === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return htmlspecialchars($partCustomState->getName());
|
||||
return $partCustomState->getName();
|
||||
}
|
||||
])
|
||||
->add('addedDate', LocaleDateTimeColumn::class, [
|
||||
|
|
@ -257,25 +263,25 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
])
|
||||
->add('eda_reference', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.eda_reference'),
|
||||
'render' => static fn($value, Part $context) => htmlspecialchars($context->getEdaInfo()->getReferencePrefix() ?? ''),
|
||||
'data' => static fn(Part $context) => $context->getEdaInfo()->getReferencePrefix() ?? '',
|
||||
'orderField' => 'NATSORT(part.eda_info.reference_prefix)'
|
||||
])
|
||||
->add('eda_value', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.eda_value'),
|
||||
'render' => static fn($value, Part $context) => htmlspecialchars($context->getEdaInfo()->getValue() ?? ''),
|
||||
'data' => static fn(Part $context) => $context->getEdaInfo()->getValue() ?? '',
|
||||
'orderField' => 'NATSORT(part.eda_info.value)'
|
||||
])
|
||||
->add('eda_status', TextColumn::class, [
|
||||
->add('eda_status', HTMLColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.eda_status'),
|
||||
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderEdaStatus($context),
|
||||
'data' => fn(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', TextColumn::class, [
|
||||
$this->csh->add('projects', HTMLColumn::class, [
|
||||
'label' => $this->translator->trans('project.labelp'),
|
||||
'render' => function ($value, Part $context): string {
|
||||
'data' => function (Part $context): string {
|
||||
//Only show the first 5 projects names
|
||||
$projects = $context->getProjects();
|
||||
$tmp = "";
|
||||
|
|
@ -295,7 +301,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
}
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ 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;
|
||||
|
|
@ -48,7 +49,7 @@ use Omines\DataTablesBundle\DataTable;
|
|||
use Omines\DataTablesBundle\DataTableTypeInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||
final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected EntityURLGenerator $entityURLGenerator,
|
||||
|
|
@ -63,17 +64,22 @@ class ProjectBomEntriesDataTable 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
|
||||
************************************************************************************************************/
|
||||
|
||||
$dataTable
|
||||
//->add('select', SelectColumn::class)
|
||||
->add('picture', TextColumn::class, [
|
||||
->add('picture', HTMLColumn::class, [
|
||||
'label' => '',
|
||||
'className' => 'no-colvis',
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
'data' => function (ProjectBOMEntry $context) {
|
||||
if(!$context->getPart() instanceof Part) {
|
||||
return '';
|
||||
}
|
||||
return $this->partDataTableHelper->renderPicture($context->getPart());
|
||||
}
|
||||
},
|
||||
])
|
||||
|
||||
->add('id', TextColumn::class, [
|
||||
|
|
@ -85,27 +91,27 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
'label' => $this->translator->trans('project.bom.quantity'),
|
||||
'className' => 'text-center',
|
||||
'orderField' => 'bom_entry.quantity',
|
||||
'render' => function ($value, ProjectBOMEntry $context): float|string {
|
||||
'data' => function (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 htmlspecialchars($this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit()));
|
||||
return $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',
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
'data' => function (ProjectBOMEntry $context) {
|
||||
return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : '';
|
||||
},
|
||||
])
|
||||
->add('name', TextColumn::class, [
|
||||
->add('name', HTMLColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.name'),
|
||||
'orderField' => 'NATSORT(part.name)',
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
'data' => function (ProjectBOMEntry $context) {
|
||||
if(!$context->getPart() instanceof Part) {
|
||||
return htmlspecialchars((string) $context->getName());
|
||||
}
|
||||
|
|
@ -123,11 +129,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
'label' => $this->translator->trans('part.table.ipn'),
|
||||
'orderField' => 'NATSORT(part.ipn)',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
if($context->getPart() instanceof Part) {
|
||||
return $context->getPart()->getIpn();
|
||||
}
|
||||
}
|
||||
'data' => fn (ProjectBOMEntry $context) => $context->getPart()?->getIpn()
|
||||
])
|
||||
->add('description', MarkdownColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.description'),
|
||||
|
|
@ -172,9 +174,9 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
},
|
||||
])
|
||||
|
||||
->add('mountnames', TextColumn::class, [
|
||||
->add('mountnames', HTMLColumn::class, [
|
||||
'label' => 'project.bom.mountnames',
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
'data' => function (ProjectBOMEntry $context) {
|
||||
$html = '';
|
||||
|
||||
foreach (explode(',', $context->getMountnames()) as $mountname) {
|
||||
|
|
@ -184,58 +186,58 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
},
|
||||
])
|
||||
|
||||
->add('instockAmount', TextColumn::class, [
|
||||
->add('instockAmount', HTMLColumn::class, [
|
||||
'label' => 'project.bom.instockAmount',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
'data' => function (ProjectBOMEntry $context) {
|
||||
if ($context->getPart() !== null) {
|
||||
return $this->partDataTableHelper->renderAmount($context->getPart());
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
},
|
||||
])
|
||||
->add('minAmount', TextColumn::class, [
|
||||
->add('minAmount', HTMLColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.minamount'),
|
||||
'visible' => false,
|
||||
'orderField' => 'part.minamount',
|
||||
'render' => function ($value, ProjectBOMEntry $context): string {
|
||||
'data' => function (ProjectBOMEntry $context): string {
|
||||
if (!$context->getPart() instanceof Part) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return htmlspecialchars($this->amountFormatter->format($context->getPart()->getMinAmount(), $context->getPart()->getPartUnit()));
|
||||
return $this->amountFormatter->format($context->getPart()->getMinAmount(), $context->getPart()->getPartUnit());
|
||||
},
|
||||
])
|
||||
->add('orderAmount', TextColumn::class, [
|
||||
->add('orderAmount', HTMLColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.orderamount'),
|
||||
'visible' => false,
|
||||
'orderField' => 'part.orderamount',
|
||||
'render' => function ($value, ProjectBOMEntry $context): string {
|
||||
'data' => function (ProjectBOMEntry $context): string {
|
||||
if (!$context->getPart() instanceof Part) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return htmlspecialchars($this->amountFormatter->format($context->getPart()->getOrderAmount(), $context->getPart()->getPartUnit()));
|
||||
return $this->amountFormatter->format($context->getPart()->getOrderAmount(), $context->getPart()->getPartUnit());
|
||||
},
|
||||
])
|
||||
->add('storelocation', TextColumn::class, [
|
||||
->add('storelocation', HTMLColumn::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,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
'data' => function (ProjectBOMEntry $context) {
|
||||
if ($context->getPart() !== null) {
|
||||
return $this->partDataTableHelper->renderStorageLocations($context->getPart());
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
},
|
||||
])
|
||||
->add('price', TextColumn::class, [
|
||||
'label' => 'project.bom.price',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
'data' => function (ProjectBOMEntry $context) {
|
||||
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
|
||||
return $this->moneyFormatter->format($price->toScale(2, RoundingMode::Up)->toFloat(), null, 2, true);
|
||||
},
|
||||
|
|
@ -243,7 +245,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
->add('ext_price', TextColumn::class, [
|
||||
'label' => 'project.bom.ext_price',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
'data' => function (ProjectBOMEntry $context) {
|
||||
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
|
||||
return $this->moneyFormatter->format(
|
||||
$price->multipliedBy(BigDecimal::fromFloatShortest($context->getQuantity()))
|
||||
|
|
|
|||
|
|
@ -35,8 +35,10 @@ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware
|
|||
{
|
||||
//Only set this on MySQL connections, as other databases don't support this parameter
|
||||
if($params['driver'] === 'pdo_mysql') {
|
||||
//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\', \'\'))';
|
||||
//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\', \'\'))';
|
||||
}
|
||||
|
||||
return parent::connect($params);
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
)
|
||||
],
|
||||
uriVariables: [
|
||||
'id' => new Link(fromProperty: 'children', fromClass: Manufacturer::class)
|
||||
'id' => new Link(fromProperty: 'children', fromClass: StorageLocation::class)
|
||||
],
|
||||
normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read']
|
||||
)]
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class AttachmentSubmitHandler
|
|||
|
||||
protected const BLACKLISTED_EXTENSIONS = ['php', 'phtml', 'php3', 'ph3', 'php4', 'ph4', 'php5', 'ph5', 'phtm', 'sh',
|
||||
'asp', 'cgi', 'py', 'pl', 'exe', 'aspx', 'js', 'mjs', 'jsp', 'css', 'jar', 'html', 'htm', 'shtm', 'shtml', 'htaccess',
|
||||
'htpasswd', ''];
|
||||
'htpasswd', 'phar', 'phps', ''];
|
||||
|
||||
public function __construct(
|
||||
protected AttachmentPathResolver $pathResolver,
|
||||
|
|
@ -543,8 +543,10 @@ class AttachmentSubmitHandler
|
|||
return $attachment;
|
||||
}
|
||||
|
||||
$guessed_mime_type = $this->mimeTypes->guessMimeType($path);
|
||||
|
||||
//Check if the file is an SVG
|
||||
if ($attachment->getExtension() === "svg") {
|
||||
if ($guessed_mime_type === "image/svg+xml" || $attachment->getExtension() === "svg") {
|
||||
$this->SVGSanitizer->sanitizeFile($path);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
|
||||
/**
|
||||
* Service for validating BOM import data with comprehensive validation rules
|
||||
* and user-friendly error messages.
|
||||
* and user-friendly error messages. The results are not HTML safe, and must be escaped before display!
|
||||
*/
|
||||
class BOMValidationService
|
||||
readonly class BOMValidationService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly TranslatorInterface $translator
|
||||
private EntityManagerInterface $entityManager,
|
||||
private TranslatorInterface $translator
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -473,4 +473,4 @@ class BOMValidationService
|
|||
: 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,8 +169,8 @@ class LogEntryExtraFormatter
|
|||
$array['log.collection_deleted.deleted'] = sprintf(
|
||||
'%s: %s (%s)',
|
||||
$this->elementTypeNameGenerator->getLocalizedTypeLabel($context->getDeletedElementClass()),
|
||||
$context->getOldName() ?? (string) $context->getDeletedElementID(),
|
||||
$context->getCollectionName()
|
||||
htmlspecialchars($context->getOldName() ?? (string) $context->getDeletedElementID()),
|
||||
htmlspecialchars($context->getCollectionName())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -218,4 +218,4 @@ class LogEntryExtraFormatter
|
|||
|
||||
return implode(', ', $output);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/Services/System/AppSecretChecker.php
Normal file
63
src/Services/System/AppSecretChecker.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\System;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
/**
|
||||
* Checks whether APP_SECRET has been changed from the default value shipped with Part-DB.
|
||||
*/
|
||||
final readonly class AppSecretChecker
|
||||
{
|
||||
/** Known default/example secrets that must not be used in production. */
|
||||
public const INSECURE_SECRETS = [
|
||||
'a03498528f5a5fc089273ec9ae5b2849', // default in .env
|
||||
'318b5d659e07a0b3f96d9b3a83b254ca', // default in .env.dev
|
||||
'CHANGE_ME' //example secret used in documentation and error messages
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
#[Autowire('%kernel.secret%')]
|
||||
private string $appSecret,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if the app secret is one of the known insecure default secrets, false otherwise.
|
||||
*/
|
||||
public function isInsecureSecret(): bool
|
||||
{
|
||||
return in_array($this->appSecret, self::INSECURE_SECRETS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new random app secret that can be used to replace the default insecure one.
|
||||
* @return string
|
||||
* @throws \Random\RandomException
|
||||
*/
|
||||
public function generateSecret(): string
|
||||
{
|
||||
//Symfony docs recommend 32 characters for the app secret, which are 16 random bytes when hex-encoded.
|
||||
return bin2hex(random_bytes(16));
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +85,16 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
{% if insecure_app_secret and is_granted('@system.server_infos') %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<h5><i class="fa-solid fa-triangle-exclamation fa-fw"></i> {% trans %}system.app_secret.insecure.title{% endtrans %}</h5>
|
||||
<p class="mb-1">{% trans %}system.app_secret.insecure.message{% endtrans %}</p>
|
||||
<p class="mb-0">{% trans %}system.app_secret.insecure.suggestion{% endtrans %}
|
||||
<br><code>APP_SECRET={{ suggested_app_secret }}</code></p>
|
||||
<small>{% trans %}update_manager.new_version_available.only_administrators_can_see{% endtrans %}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('@system.show_updates') %}
|
||||
{{ nv.new_version_alert(new_version_available, new_version, new_version_url) }}
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans %}log.target{% endtrans %}</td>
|
||||
<td>{{ target_html|raw }}</td>
|
||||
<td>{{ target_html|sanitize_html }}</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 | raw }}
|
||||
{{ extra_html | sanitize_html }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -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|raw }}</li>
|
||||
<li>{{ error }}</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|raw }}</li>
|
||||
<li>{{ warning }}</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|raw }}</li>
|
||||
<li>{{ info }}</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|raw }}</div>
|
||||
<div><i class="fa-solid fa-exclamation-triangle fa-fw"></i> {{ error }}</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|raw }}</div>
|
||||
<div><i class="fa-solid fa-exclamation-circle fa-fw"></i> {{ warning }}</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|raw }}</div>
|
||||
<div><i class="fa-solid fa-info-circle fa-fw"></i> {{ info }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -132,8 +132,8 @@
|
|||
<script nonce="{{ csp_nonce('script') }}">
|
||||
// Function to initialize the field mapping page
|
||||
function initializeFieldMapping() {
|
||||
const suggestions = {{ suggested_mapping|json_encode|raw }};
|
||||
const fieldNameMapping = {{ field_name_mapping|json_encode|raw }};
|
||||
const suggestions = JSON.parse("{{ suggested_mapping|json_encode|escape('js')}}");
|
||||
const fieldNameMapping = JSON.parse("{{ field_name_mapping|json_encode|escape('js') }}");
|
||||
|
||||
Object.keys(suggestions).forEach(function(field) {
|
||||
// Use the sanitized field name from the server-side mapping
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
<unit id="KSFhj_3" name="currency.iso_code.caption">
|
||||
<segment state="translated">
|
||||
<source>currency.iso_code.caption</source>
|
||||
<target>ISO Code</target>
|
||||
<target>ISO-Code</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="UqMuNZQ" name="currency.symbol.caption">
|
||||
|
|
@ -13677,5 +13677,23 @@ Buerklin-API-Authentication-Server:
|
|||
<target>Aktiviert das Browser-Plugin für diese Instanz.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="JqcvhX_" name="system.app_secret.insecure.title">
|
||||
<segment state="translated">
|
||||
<source>system.app_secret.insecure.title</source>
|
||||
<target>Unsicheres APP_SECRET</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="XSSYWTJ" name="system.app_secret.insecure.message">
|
||||
<segment state="translated">
|
||||
<source>system.app_secret.insecure.message</source>
|
||||
<target>Die Umgebungsvariable <code>APP_SECRET</code> ist auf den Standardwert gesetzt, der mit Part-DB ausgeliefert wird. Dies stellt ein Sicherheitsrisiko dar! Setzen Sie sie in Ihrer Datei <code>.env.local</code> oder <code>docker-compose.yaml</code> auf einen neuen Zufallswert.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="e7.f3m0" name="system.app_secret.insecure.suggestion">
|
||||
<segment state="translated">
|
||||
<source>system.app_secret.insecure.suggestion</source>
|
||||
<target>Sie können diesen zufällig generierten Wert verwenden (geben Sie ihn niemandem weiter):</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
|
|
|
|||
|
|
@ -13679,5 +13679,23 @@ Buerklin-API Authentication server:
|
|||
<target>When enabled users with the info provider permission can submit pages to Part-DB and retrieve them later.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="JqcvhX_" name="system.app_secret.insecure.title">
|
||||
<segment state="translated">
|
||||
<source>system.app_secret.insecure.title</source>
|
||||
<target>Insecure APP_SECRET</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="XSSYWTJ" name="system.app_secret.insecure.message">
|
||||
<segment state="translated">
|
||||
<source>system.app_secret.insecure.message</source>
|
||||
<target>The <code>APP_SECRET</code> environment variable is set to the default value shipped with Part-DB. This is a security risk! Set it to a new random value in your <code>.env.local</code> or <code>docker-compose.yaml</code> file.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="e7.f3m0" name="system.app_secret.insecure.suggestion">
|
||||
<segment state="translated">
|
||||
<source>system.app_secret.insecure.suggestion</source>
|
||||
<target>You can use this randomly generated value (share it with nobody):</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue