mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-13 13:49:33 +00:00
Merge branch 'Part-DB:master' into master
This commit is contained in:
commit
964df4b2df
160 changed files with 7655 additions and 5815 deletions
3
.env
3
.env
|
|
@ -143,7 +143,8 @@ PROVIDER_TME_CURRENCY=EUR
|
||||||
PROVIDER_TME_LANGUAGE=en
|
PROVIDER_TME_LANGUAGE=en
|
||||||
# The country to get results for
|
# The country to get results for
|
||||||
PROVIDER_TME_COUNTRY=DE
|
PROVIDER_TME_COUNTRY=DE
|
||||||
# Set this to 1 to get gross prices (including VAT) instead of net prices
|
# [DEPRECATED] Set this to 1 to get gross prices (including VAT) instead of net prices
|
||||||
|
# With private API keys, this option cannot be used anymore is ignored by Part-DB. The VAT inclusion depends on your TME account settings.
|
||||||
PROVIDER_TME_GET_GROSS_PRICES=1
|
PROVIDER_TME_GET_GROSS_PRICES=1
|
||||||
|
|
||||||
# Octopart / Nexar Provider:
|
# Octopart / Nexar Provider:
|
||||||
|
|
|
||||||
0
.env.dev
Normal file
0
.env.dev
Normal file
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php-versions: [ '8.1', '8.2', '8.3' ]
|
php-versions: [ '8.1', '8.2', '8.3', '8.4' ]
|
||||||
db-type: [ 'mysql', 'sqlite', 'postgres' ]
|
db-type: [ 'mysql', 'sqlite', 'postgres' ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
@ -126,7 +126,7 @@ jobs:
|
||||||
run: ./bin/phpunit --coverage-clover=coverage.xml
|
run: ./bin/phpunit --coverage-clover=coverage.xml
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
env_vars: PHP_VERSION,DB_TYPE
|
env_vars: PHP_VERSION,DB_TYPE
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
ARG BASE_IMAGE=debian:bookworm-slim
|
ARG BASE_IMAGE=debian:bookworm-slim
|
||||||
ARG PHP_VERSION=8.2
|
ARG PHP_VERSION=8.3
|
||||||
|
|
||||||
FROM ${BASE_IMAGE} AS base
|
FROM ${BASE_IMAGE} AS base
|
||||||
ARG PHP_VERSION
|
ARG PHP_VERSION
|
||||||
|
|
@ -125,6 +125,7 @@ upload_max_filesize=256M
|
||||||
post_max_size=300M
|
post_max_size=300M
|
||||||
opcache.preload_user=www-data
|
opcache.preload_user=www-data
|
||||||
opcache.preload=/var/www/html/config/preload.php
|
opcache.preload=/var/www/html/config/preload.php
|
||||||
|
log_limit=8096
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
COPY ./.docker/symfony.conf /etc/apache2/sites-available/symfony.conf
|
COPY ./.docker/symfony.conf /etc/apache2/sites-available/symfony.conf
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.14.3
|
1.15.2
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ export default class extends Controller {
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
language: language,
|
language: language,
|
||||||
|
licenseKey: "GPL",
|
||||||
}
|
}
|
||||||
|
|
||||||
const watchdog = new EditorWatchdog();
|
const watchdog = new EditorWatchdog();
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
import {Controller} from "@hotwired/stimulus";
|
import {Controller} from "@hotwired/stimulus";
|
||||||
//import * as ZXing from "@zxing/library";
|
//import * as ZXing from "@zxing/library";
|
||||||
|
|
||||||
import {Html5QrcodeScanner, Html5Qrcode} from "html5-qrcode";
|
import {Html5QrcodeScanner, Html5Qrcode} from "@part-db/html5-qrcode";
|
||||||
|
|
||||||
/* stimulusFetch: 'lazy' */
|
/* stimulusFetch: 'lazy' */
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
|
|
@ -50,7 +50,7 @@ export default class extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
this._scanner = new Html5QrcodeScanner(this.element.id, {
|
this._scanner = new Html5QrcodeScanner(this.element.id, {
|
||||||
fps: 2,
|
fps: 10,
|
||||||
qrbox: qrboxFunction,
|
qrbox: qrboxFunction,
|
||||||
experimentalFeatures: {
|
experimentalFeatures: {
|
||||||
//This option improves reading quality on android chrome
|
//This option improves reading quality on android chrome
|
||||||
|
|
@ -61,6 +61,11 @@ export default class extends Controller {
|
||||||
this._scanner.render(this.onScanSuccess.bind(this));
|
this._scanner.render(this.onScanSuccess.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
this._scanner.pause();
|
||||||
|
this._scanner.clear();
|
||||||
|
}
|
||||||
|
|
||||||
onScanSuccess(decodedText, decodedResult) {
|
onScanSuccess(decodedText, decodedResult) {
|
||||||
//Put our decoded Text into the input box
|
//Put our decoded Text into the input box
|
||||||
document.getElementById('scan_dialog_input').value = decodedText;
|
document.getElementById('scan_dialog_input').value = decodedText;
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,8 @@
|
||||||
/** Should be the same settings, as in label_style.css */
|
/** Should be the same settings, as in label_style.css */
|
||||||
.ck-html-label .ck-content {
|
.ck-html-label .ck-content {
|
||||||
font-family: "DejaVu Sans Mono", monospace;
|
font-family: "DejaVu Sans Mono", monospace;
|
||||||
font-size: 12px;
|
font-size: 12pt;
|
||||||
line-height: 1.0;
|
line-height: 1.0;
|
||||||
font-size-adjust: 1.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ck-html-label .ck-content p {
|
.ck-html-label .ck-content p {
|
||||||
|
|
|
||||||
|
|
@ -44,4 +44,18 @@ import "./register_events";
|
||||||
import "./tristate_checkboxes";
|
import "./tristate_checkboxes";
|
||||||
|
|
||||||
//Define jquery globally
|
//Define jquery globally
|
||||||
window.$ = window.jQuery = require("jquery")
|
window.$ = window.jQuery = require("jquery");
|
||||||
|
|
||||||
|
//Use the local WASM file for the ZXing library
|
||||||
|
import {
|
||||||
|
setZXingModuleOverrides,
|
||||||
|
} from "barcode-detector/pure";
|
||||||
|
import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm";
|
||||||
|
setZXingModuleOverrides({
|
||||||
|
locateFile: (path, prefix) => {
|
||||||
|
if (path.endsWith(".wasm")) {
|
||||||
|
return wasmFile;
|
||||||
|
}
|
||||||
|
return prefix + path;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
use App\Kernel;
|
use App\Kernel;
|
||||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
|
|
||||||
|
if (!is_dir(dirname(__DIR__).'/vendor')) {
|
||||||
|
throw new LogicException('Dependencies are missing. Try running "composer install".');
|
||||||
|
}
|
||||||
|
|
||||||
//Increase xdebug.max_nesting_level to 1000 if required (see issue #411)
|
//Increase xdebug.max_nesting_level to 1000 if required (see issue #411)
|
||||||
//Check if xdebug extension is active, and xdebug.max_nesting_level is set to 256 or lower
|
//Check if xdebug extension is active, and xdebug.max_nesting_level is set to 256 or lower
|
||||||
if (extension_loaded('xdebug') && ((int) ini_get('xdebug.max_nesting_level')) <= 256) {
|
if (extension_loaded('xdebug') && ((int) ini_get('xdebug.max_nesting_level')) <= 256) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"name": "part-db/part-db-server",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -16,7 +17,7 @@
|
||||||
"brick/math": "0.12.1 as 0.11.0",
|
"brick/math": "0.12.1 as 0.11.0",
|
||||||
"composer/ca-bundle": "^1.3",
|
"composer/ca-bundle": "^1.3",
|
||||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||||
"doctrine/data-fixtures": "^1.6.6",
|
"doctrine/data-fixtures": "^2.0.0",
|
||||||
"doctrine/dbal": "^4.0.0",
|
"doctrine/dbal": "^4.0.0",
|
||||||
"doctrine/doctrine-bundle": "^2.0",
|
"doctrine/doctrine-bundle": "^2.0",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||||
|
|
@ -39,12 +40,9 @@
|
||||||
"nelmio/cors-bundle": "^2.3",
|
"nelmio/cors-bundle": "^2.3",
|
||||||
"nelmio/security-bundle": "^3.0",
|
"nelmio/security-bundle": "^3.0",
|
||||||
"nyholm/psr7": "^1.1",
|
"nyholm/psr7": "^1.1",
|
||||||
"ocramius/proxy-manager": "2.2.*",
|
"omines/datatables-bundle": "^0.9.1",
|
||||||
"omines/datatables-bundle": "^0.8.0",
|
|
||||||
"paragonie/sodium_compat": "^1.21",
|
"paragonie/sodium_compat": "^1.21",
|
||||||
"part-db/label-fonts": "^1.0",
|
"part-db/label-fonts": "^1.0",
|
||||||
"phpdocumentor/reflection-docblock": "^5.2",
|
|
||||||
"phpstan/phpdoc-parser": "^1.23",
|
|
||||||
"runtime/frankenphp-symfony": "^0.2.0",
|
"runtime/frankenphp-symfony": "^0.2.0",
|
||||||
"s9e/text-formatter": "^2.1",
|
"s9e/text-formatter": "^2.1",
|
||||||
"scheb/2fa-backup-code": "^6.8.0",
|
"scheb/2fa-backup-code": "^6.8.0",
|
||||||
|
|
@ -69,7 +67,6 @@
|
||||||
"symfony/process": "6.4.*",
|
"symfony/process": "6.4.*",
|
||||||
"symfony/property-access": "6.4.*",
|
"symfony/property-access": "6.4.*",
|
||||||
"symfony/property-info": "6.4.*",
|
"symfony/property-info": "6.4.*",
|
||||||
"symfony/proxy-manager-bridge": "6.4.*",
|
|
||||||
"symfony/rate-limiter": "6.4.*",
|
"symfony/rate-limiter": "6.4.*",
|
||||||
"symfony/runtime": "6.4.*",
|
"symfony/runtime": "6.4.*",
|
||||||
"symfony/security-bundle": "6.4.*",
|
"symfony/security-bundle": "6.4.*",
|
||||||
|
|
@ -91,21 +88,20 @@
|
||||||
"twig/intl-extra": "^3.8",
|
"twig/intl-extra": "^3.8",
|
||||||
"twig/markdown-extra": "^3.8",
|
"twig/markdown-extra": "^3.8",
|
||||||
"twig/string-extra": "^3.8",
|
"twig/string-extra": "^3.8",
|
||||||
"web-auth/webauthn-symfony-bundle": "^4.0.0",
|
"web-auth/webauthn-symfony-bundle": "^4.0.0"
|
||||||
"webmozart/assert": "^1.4"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"dama/doctrine-test-bundle": "^v8.0.0",
|
"dama/doctrine-test-bundle": "^v8.0.0",
|
||||||
"doctrine/doctrine-fixtures-bundle": "^3.2",
|
"doctrine/doctrine-fixtures-bundle": "^4.0.0",
|
||||||
"ekino/phpstan-banned-code": "^v1.0.0",
|
"ekino/phpstan-banned-code": "^v3.0.0",
|
||||||
"jbtronics/translation-editor-bundle": "^1.0",
|
"jbtronics/translation-editor-bundle": "^1.0",
|
||||||
"phpstan/extension-installer": "^1.0",
|
"phpstan/extension-installer": "^1.0",
|
||||||
"phpstan/phpstan": "^1.4.7",
|
"phpstan/phpstan": "^2.0.4",
|
||||||
"phpstan/phpstan-doctrine": "^1.2.11",
|
"phpstan/phpstan-doctrine": "^2.0.1",
|
||||||
"phpstan/phpstan-strict-rules": "^1.5",
|
"phpstan/phpstan-strict-rules": "^2.0.1",
|
||||||
"phpstan/phpstan-symfony": "^1.1.7",
|
"phpstan/phpstan-symfony": "^2.0.0",
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/phpunit": "^9.5",
|
||||||
"rector/rector": "^1.1.1",
|
"rector/rector": "^2.0.4",
|
||||||
"roave/security-advisories": "dev-latest",
|
"roave/security-advisories": "dev-latest",
|
||||||
"symfony/browser-kit": "6.4.*",
|
"symfony/browser-kit": "6.4.*",
|
||||||
"symfony/css-selector": "6.4.*",
|
"symfony/css-selector": "6.4.*",
|
||||||
|
|
|
||||||
2159
composer.lock
generated
2159
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,7 +8,7 @@ datatables:
|
||||||
|
|
||||||
# Set options, as documented at https://datatables.net/reference/option/
|
# Set options, as documented at https://datatables.net/reference/option/
|
||||||
options:
|
options:
|
||||||
lengthMenu : [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]
|
lengthMenu : [[10, 25, 50, 100], [10, 25, 50, 100]] # We add the "All" option, when part tables are generated
|
||||||
pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
|
pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
|
||||||
dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>>
|
dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>>
|
||||||
<'card'
|
<'card'
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ doctrine:
|
||||||
field2: App\Doctrine\Functions\Field2
|
field2: App\Doctrine\Functions\Field2
|
||||||
natsort: App\Doctrine\Functions\Natsort
|
natsort: App\Doctrine\Functions\Natsort
|
||||||
array_position: App\Doctrine\Functions\ArrayPosition
|
array_position: App\Doctrine\Functions\ArrayPosition
|
||||||
|
ilike: App\Doctrine\Functions\ILike
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
doctrine:
|
doctrine:
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ when@prod:
|
||||||
type: stream
|
type: stream
|
||||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
level: debug
|
level: debug
|
||||||
formatter: monolog.formatter.json
|
|
||||||
console:
|
console:
|
||||||
type: console
|
type: console
|
||||||
process_psr_3_messages: false
|
process_psr_3_messages: false
|
||||||
|
|
@ -74,7 +73,6 @@ when@docker:
|
||||||
type: stream
|
type: stream
|
||||||
path: "php://stderr"
|
path: "php://stderr"
|
||||||
level: debug
|
level: debug
|
||||||
formatter: monolog.formatter.json
|
|
||||||
console:
|
console:
|
||||||
type: console
|
type: console
|
||||||
process_psr_3_messages: false
|
process_psr_3_messages: false
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,16 @@ nelmio_security:
|
||||||
img-src:
|
img-src:
|
||||||
- '*'
|
- '*'
|
||||||
- 'data:'
|
- 'data:'
|
||||||
|
# Required for be able to load pictures in the QR code scanner
|
||||||
|
- 'blob:'
|
||||||
style-src:
|
style-src:
|
||||||
- 'self'
|
- 'self'
|
||||||
- 'unsafe-inline'
|
- 'unsafe-inline'
|
||||||
- 'data:'
|
- 'data:'
|
||||||
script-src:
|
script-src:
|
||||||
- 'self'
|
- 'self'
|
||||||
|
# Required for loading the Wasm for the barcode scanner:
|
||||||
|
- 'wasm-unsafe-eval'
|
||||||
object-src:
|
object-src:
|
||||||
- 'self'
|
- 'self'
|
||||||
- 'data:'
|
- 'data:'
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ each other so that it does not matter which one of your 1000 things of Part you
|
||||||
A part entity has many fields, which can be used to describe it better. Most of the fields are optional:
|
A part entity has many fields, which can be used to describe it better. Most of the fields are optional:
|
||||||
|
|
||||||
* **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer-provided name, or a
|
* **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer-provided name, or a
|
||||||
name you thought of yourself. The name have to be unique in a single category.
|
name you thought of yourself. Each name needs to be unique and must exist in a single category.
|
||||||
* **Description**: A short (single-line) description of what this part is/does. For longer information, you should use
|
* **Description**: A short (single-line) description of what this part is/does. For longer information, you should use
|
||||||
the comment field or the specifications
|
the comment field or the specifications
|
||||||
* **Category** (Required): The category (see there) to which this part belongs to.
|
* **Category** (Required): The category (see there) to which this part belongs to.
|
||||||
|
|
@ -239,4 +239,4 @@ replaced with data for the actual thing.
|
||||||
|
|
||||||
You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label
|
You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label
|
||||||
dialog), however, if you want to generate many labels, it is recommended to save the settings as a label profile, to save
|
dialog), however, if you want to generate many labels, it is recommended to save the settings as a label profile, to save
|
||||||
it for later usage. This ensures that all generated labels look the same.
|
it for later usage. This ensures that all generated labels look the same.
|
||||||
|
|
|
||||||
|
|
@ -150,9 +150,9 @@ In the `serverVersion` parameter you can specify the version of the PostgreSQL s
|
||||||
|
|
||||||
The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly.
|
The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly.
|
||||||
|
|
||||||
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `unix_socket` parameter.
|
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `host` parameter.
|
||||||
```shell
|
```shell
|
||||||
DATABASE_URL="postgresql://db_user:db_password@localhost/db_name?serverVersion=12.19&charset=utf8&unix_socket=/var/run/postgresql/.s.PGSQL.5432"
|
DATABASE_URL="postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,6 @@ has_children: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
Below you can find some guides to install Part-DB.
|
Below you can find some guides to install Part-DB.
|
||||||
|
|
||||||
|
For the hobbyists without much experience, we recommend the docker installation or direct installation on debian.
|
||||||
42
docs/installation/kubernetes.md
Normal file
42
docs/installation/kubernetes.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
title: Kubernetes / Helm
|
||||||
|
layout: default
|
||||||
|
parent: Installation
|
||||||
|
nav_order: 5
|
||||||
|
---
|
||||||
|
|
||||||
|
# Kubernetes / Helm Charts
|
||||||
|
|
||||||
|
If you are using Kubernetes, you can use the [helm charts](https://helm.sh/) provided in this [repository](https://github.com/Part-DB/helm-charts).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
[Helm](https://helm.sh) must be installed to use the charts. Please refer to
|
||||||
|
Helm's [documentation](https://helm.sh/docs) to get started.
|
||||||
|
|
||||||
|
Once Helm has been set up correctly, add the repo as follows:
|
||||||
|
|
||||||
|
`helm repo add part-db https://part-db.github.io/helm-charts`
|
||||||
|
|
||||||
|
If you had already added this repo earlier, run `helm repo update` to retrieve
|
||||||
|
the latest versions of the packages. You can then run `helm search repo
|
||||||
|
part-db` to see the charts.
|
||||||
|
|
||||||
|
To install the part-db chart:
|
||||||
|
|
||||||
|
helm install my-part-db part-db/part-db
|
||||||
|
|
||||||
|
To uninstall the chart:
|
||||||
|
|
||||||
|
helm delete my-part-db
|
||||||
|
|
||||||
|
This repository is also available at [ArtifactHUB](https://artifacthub.io/packages/search?repo=part-db).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
See the README in the [chart directory](https://github.com/Part-DB/helm-charts/tree/main/charts/part-db) for more
|
||||||
|
information on the available configuration options.
|
||||||
|
|
||||||
|
## Bugreports
|
||||||
|
|
||||||
|
If you find issues related to the helm charts, please open an issue in the [helm-charts repository](https://github.com/Part-DB/helm-charts).
|
||||||
31
docs/installation/proxmox.md
Normal file
31
docs/installation/proxmox.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
title: Proxmox VE LXC
|
||||||
|
layout: default
|
||||||
|
parent: Installation
|
||||||
|
nav_order: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proxmox VE LXC
|
||||||
|
|
||||||
|
{: .warning }
|
||||||
|
> The proxmox VE LXC script for Part-DB is developed and maintained by [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/)
|
||||||
|
> and not by the Part-DB developers. Keep in mind that the script is not officially supported by the Part-DB developers.
|
||||||
|
|
||||||
|
If you are using Proxmox VE you can use the scripts provided by [Proxmox VE Helper-Scripts community](https://community-scripts.github.io/ProxmoxVE/scripts?id=part-db)
|
||||||
|
to easily install Part-DB in a LXC container.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To create a new LXC container with Part-DB, you can use the following command in the Proxmox VE shell:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/part-db.sh)"
|
||||||
|
```
|
||||||
|
|
||||||
|
The same command can be used to update an existing Part-DB container.
|
||||||
|
|
||||||
|
See the [helper script website](https://community-scripts.github.io/ProxmoxVE/scripts?id=part-db) for more information.
|
||||||
|
|
||||||
|
## Bugreports
|
||||||
|
|
||||||
|
If you find issues related to the proxmox VE LXC script, please open an issue in the [Proxmox VE Helper-Scripts repository](https://github.com/community-scripts/ProxmoxVE).
|
||||||
|
|
@ -107,7 +107,7 @@ The following env configuration options are available:
|
||||||
default: `EUR`). If an offer is only available in a certain currency,
|
default: `EUR`). If an offer is only available in a certain currency,
|
||||||
Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert
|
Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert
|
||||||
it to your preferred currency.
|
it to your preferred currency.
|
||||||
* `PROVIDER_OCOTPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code,
|
* `PROVIDER_OCTOPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code,
|
||||||
default: `DE`). To get the correct prices, you have to set this and the currency setting to the correct value.
|
default: `DE`). To get the correct prices, you have to set this and the currency setting to the correct value.
|
||||||
* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This
|
* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This
|
||||||
affects how quickly your monthly limit is used up.
|
affects how quickly your monthly limit is used up.
|
||||||
|
|
@ -127,6 +127,9 @@ You must create an organization there and create a "Production app". Most settin
|
||||||
grant access to the "Product Information" API.
|
grant access to the "Product Information" API.
|
||||||
You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below).
|
You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below).
|
||||||
|
|
||||||
|
**Attention**: Currently only the "Product Information V3 (Deprecated)" is supported by Part-DB.
|
||||||
|
Using "Product Information V4" will not work.
|
||||||
|
|
||||||
The following env configuration options are available:
|
The following env configuration options are available:
|
||||||
|
|
||||||
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
|
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,6 @@ For a German keyboard layout, replace `[` with `0`, and `]` with `´`.
|
||||||
| Key | Character |
|
| Key | Character |
|
||||||
|--------------------------------|--------------------|
|
|--------------------------------|--------------------|
|
||||||
| **Alt + [** (code 219) | © (Copyright char) |
|
| **Alt + [** (code 219) | © (Copyright char) |
|
||||||
| **Alt + Shift + [** (code 219) | (Registered char) |
|
| **Alt + Shift + [** (code 219) | ® (Registered char) |
|
||||||
| **Alt + ]** (code 221) | ™ (Trademark char) |
|
| **Alt + ]** (code 221) | ™ (Trademark char) |
|
||||||
| **Alt + Shift + ]** (code 221) | (Degree char) |
|
| **Alt + Shift + ]** (code 221) | ° (Degree char) |
|
||||||
|
|
|
||||||
81
package.json
81
package.json
|
|
@ -9,7 +9,7 @@
|
||||||
"@symfony/stimulus-bridge": "^3.2.0",
|
"@symfony/stimulus-bridge": "^3.2.0",
|
||||||
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
||||||
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
|
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
|
||||||
"@symfony/webpack-encore": "^4.1.0",
|
"@symfony/webpack-encore": "^5.0.0",
|
||||||
"bootstrap": "^5.1.3",
|
"bootstrap": "^5.1.3",
|
||||||
"core-js": "^3.23.0",
|
"core-js": "^3.23.0",
|
||||||
"intl-messageformat": "^10.2.5",
|
"intl-messageformat": "^10.2.5",
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
"regenerator-runtime": "^0.13.9",
|
"regenerator-runtime": "^0.13.9",
|
||||||
"webpack": "^5.74.0",
|
"webpack": "^5.74.0",
|
||||||
"webpack-bundle-analyzer": "^4.3.0",
|
"webpack-bundle-analyzer": "^4.3.0",
|
||||||
"webpack-cli": "^4.10.0",
|
"webpack-cli": "^5.1.0",
|
||||||
"webpack-notifier": "^1.15.0"
|
"webpack-notifier": "^1.15.0"
|
||||||
},
|
},
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|
@ -33,50 +33,52 @@
|
||||||
"@algolia/autocomplete-js": "^1.17.0",
|
"@algolia/autocomplete-js": "^1.17.0",
|
||||||
"@algolia/autocomplete-plugin-recent-searches": "^1.17.0",
|
"@algolia/autocomplete-plugin-recent-searches": "^1.17.0",
|
||||||
"@algolia/autocomplete-theme-classic": "^1.17.0",
|
"@algolia/autocomplete-theme-classic": "^1.17.0",
|
||||||
"@ckeditor/ckeditor5-alignment": "^41.0.0",
|
"@ckeditor/ckeditor5-alignment": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-autoformat": "^41.0.0",
|
"@ckeditor/ckeditor5-autoformat": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-basic-styles": "^41.0.0",
|
"@ckeditor/ckeditor5-basic-styles": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-block-quote": "^41.0.0",
|
"@ckeditor/ckeditor5-block-quote": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-code-block": "^41.0.0",
|
"@ckeditor/ckeditor5-code-block": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-dev-translations": "^39.1.0",
|
"@ckeditor/ckeditor5-dev-translations": "^43.0.1",
|
||||||
"@ckeditor/ckeditor5-dev-utils": "^39.1.0",
|
"@ckeditor/ckeditor5-dev-utils": "^43.0.1",
|
||||||
"@ckeditor/ckeditor5-editor-classic": "^41.0.0",
|
"@ckeditor/ckeditor5-editor-classic": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-essentials": "^41.0.0",
|
"@ckeditor/ckeditor5-essentials": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-find-and-replace": "^41.0.0",
|
"@ckeditor/ckeditor5-find-and-replace": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-font": "^41.0.0",
|
"@ckeditor/ckeditor5-font": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-heading": "^41.0.0",
|
"@ckeditor/ckeditor5-heading": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-highlight": "^41.0.0",
|
"@ckeditor/ckeditor5-highlight": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-horizontal-line": "^41.0.0",
|
"@ckeditor/ckeditor5-horizontal-line": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-html-embed": "^41.0.0",
|
"@ckeditor/ckeditor5-html-embed": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-html-support": "^41.0.0",
|
"@ckeditor/ckeditor5-html-support": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-image": "^41.0.0",
|
"@ckeditor/ckeditor5-image": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-indent": "^41.0.0",
|
"@ckeditor/ckeditor5-indent": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-link": "^41.0.0",
|
"@ckeditor/ckeditor5-link": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-list": "^41.0.0",
|
"@ckeditor/ckeditor5-list": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-markdown-gfm": "^41.0.0",
|
"@ckeditor/ckeditor5-markdown-gfm": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-media-embed": "^41.0.0",
|
"@ckeditor/ckeditor5-media-embed": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-paragraph": "^41.0.0",
|
"@ckeditor/ckeditor5-paragraph": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-paste-from-office": "^41.0.0",
|
"@ckeditor/ckeditor5-paste-from-office": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-remove-format": "^41.0.0",
|
"@ckeditor/ckeditor5-remove-format": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-source-editing": "^41.0.0",
|
"@ckeditor/ckeditor5-source-editing": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-special-characters": "^41.0.0",
|
"@ckeditor/ckeditor5-special-characters": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-table": "^41.0.0",
|
"@ckeditor/ckeditor5-table": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-theme-lark": "^41.0.0",
|
"@ckeditor/ckeditor5-theme-lark": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-upload": "^41.0.0",
|
"@ckeditor/ckeditor5-upload": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-watchdog": "^41.0.0",
|
"@ckeditor/ckeditor5-watchdog": "^44.0.0",
|
||||||
"@ckeditor/ckeditor5-word-count": "^41.0.0",
|
"@ckeditor/ckeditor5-word-count": "^44.0.0",
|
||||||
"@jbtronics/bs-treeview": "^1.0.1",
|
"@jbtronics/bs-treeview": "^1.0.1",
|
||||||
|
"@part-db/html5-qrcode": "^3.1.0",
|
||||||
"@zxcvbn-ts/core": "^3.0.2",
|
"@zxcvbn-ts/core": "^3.0.2",
|
||||||
"@zxcvbn-ts/language-common": "^3.0.3",
|
"@zxcvbn-ts/language-common": "^3.0.3",
|
||||||
"@zxcvbn-ts/language-de": "^3.0.1",
|
"@zxcvbn-ts/language-de": "^3.0.1",
|
||||||
"@zxcvbn-ts/language-en": "^3.0.1",
|
"@zxcvbn-ts/language-en": "^3.0.1",
|
||||||
"@zxcvbn-ts/language-fr": "^3.0.1",
|
"@zxcvbn-ts/language-fr": "^3.0.1",
|
||||||
"@zxcvbn-ts/language-ja": "^3.0.1",
|
"@zxcvbn-ts/language-ja": "^3.0.1",
|
||||||
|
"barcode-detector": "^2.3.1",
|
||||||
"bootbox": "^6.0.0",
|
"bootbox": "^6.0.0",
|
||||||
"bootswatch": "^5.1.3",
|
"bootswatch": "^5.1.3",
|
||||||
"bs-custom-file-input": "^1.3.4",
|
"bs-custom-file-input": "^1.3.4",
|
||||||
"clipboard": "^2.0.4",
|
"clipboard": "^2.0.4",
|
||||||
"compression-webpack-plugin": "^10.0.0",
|
"compression-webpack-plugin": "^11.1.0",
|
||||||
"datatables.net": "^2.0.0",
|
"datatables.net": "^2.0.0",
|
||||||
"datatables.net-bs5": "^2.0.0",
|
"datatables.net-bs5": "^2.0.0",
|
||||||
"datatables.net-buttons-bs5": "^3.0.0",
|
"datatables.net-buttons-bs5": "^3.0.0",
|
||||||
|
|
@ -86,18 +88,17 @@
|
||||||
"datatables.net-select-bs5": "^2.0.0",
|
"datatables.net-select-bs5": "^2.0.0",
|
||||||
"dompurify": "^3.0.3",
|
"dompurify": "^3.0.3",
|
||||||
"emoji.json": "^15.0.0",
|
"emoji.json": "^15.0.0",
|
||||||
"exports-loader": "^3.0.0",
|
"exports-loader": "^5.0.0",
|
||||||
"html5-qrcode": "^2.2.1",
|
|
||||||
"json-formatter-js": "^2.3.4",
|
"json-formatter-js": "^2.3.4",
|
||||||
"jszip": "^3.2.0",
|
"jszip": "^3.2.0",
|
||||||
"katex": "^0.16.0",
|
"katex": "^0.16.0",
|
||||||
"marked": "^12.0.0",
|
"marked": "^15.0.4",
|
||||||
"marked-gfm-heading-id": "^3.0.4",
|
"marked-gfm-heading-id": "^4.1.1",
|
||||||
"marked-mangle": "^1.0.1",
|
"marked-mangle": "^1.0.1",
|
||||||
"pdfmake": "^0.2.2",
|
"pdfmake": "^0.2.2",
|
||||||
"stimulus-use": "^0.52.0",
|
"stimulus-use": "^0.52.0",
|
||||||
"tom-select": "^2.1.0",
|
"tom-select": "^2.1.0",
|
||||||
"ts-loader": "^9.2.6",
|
"ts-loader": "^9.2.6",
|
||||||
"typescript": "^4.0.2"
|
"typescript": "^5.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ parameters:
|
||||||
treatPhpDocTypesAsCertain: false
|
treatPhpDocTypesAsCertain: false
|
||||||
|
|
||||||
symfony:
|
symfony:
|
||||||
container_xml_path: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml'
|
containerXmlPath: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml'
|
||||||
|
|
||||||
doctrine:
|
doctrine:
|
||||||
objectManagerLoader: tests/object-manager.php
|
objectManagerLoader: tests/object-manager.php
|
||||||
|
|
@ -30,11 +30,6 @@ parameters:
|
||||||
|
|
||||||
checkFunctionNameCase: false
|
checkFunctionNameCase: false
|
||||||
|
|
||||||
checkAlwaysTrueInstanceof: false
|
|
||||||
checkAlwaysTrueCheckTypeFunctionCall: false
|
|
||||||
checkAlwaysTrueStrictComparison: false
|
|
||||||
reportAlwaysTrueInLastCondition: false
|
|
||||||
|
|
||||||
reportMaybesInPropertyPhpDocTypes: false
|
reportMaybesInPropertyPhpDocTypes: false
|
||||||
reportMaybesInMethodSignatures: false
|
reportMaybesInMethodSignatures: false
|
||||||
|
|
||||||
|
|
@ -43,14 +38,14 @@ parameters:
|
||||||
booleansInConditions: false
|
booleansInConditions: false
|
||||||
uselessCast: false
|
uselessCast: false
|
||||||
requireParentConstructorCall: true
|
requireParentConstructorCall: true
|
||||||
disallowedConstructs: false
|
|
||||||
overwriteVariablesWithLoop: false
|
overwriteVariablesWithLoop: false
|
||||||
closureUsesThis: false
|
closureUsesThis: false
|
||||||
matchingInheritedMethodNames: true
|
matchingInheritedMethodNames: true
|
||||||
numericOperandsInArithmeticOperators: true
|
numericOperandsInArithmeticOperators: true
|
||||||
strictCalls: true
|
|
||||||
switchConditionsMatchingType: false
|
switchConditionsMatchingType: false
|
||||||
noVariableVariables: false
|
noVariableVariables: false
|
||||||
|
disallowedEmpty: false
|
||||||
|
disallowedShortTernary: false
|
||||||
|
|
||||||
ignoreErrors:
|
ignoreErrors:
|
||||||
# Ignore errors caused by complex mapping with AbstractStructuralDBElement
|
# Ignore errors caused by complex mapping with AbstractStructuralDBElement
|
||||||
|
|
@ -62,4 +57,7 @@ parameters:
|
||||||
- '#Part::getParameters\(\) should return .*AbstractParameter#'
|
- '#Part::getParameters\(\) should return .*AbstractParameter#'
|
||||||
|
|
||||||
# Ignore doctrine type mapping mismatch
|
# Ignore doctrine type mapping mismatch
|
||||||
- '#Property .* type mapping mismatch: property can contain .* but database expects .*#'
|
- '#Property .* type mapping mismatch: property can contain .* but database expects .*#'
|
||||||
|
|
||||||
|
# Ignore error of unused WithPermPresetsTrait, as it is used in the migrations which are not analyzed by Phpstan
|
||||||
|
- '#Trait App\\Migration\\WithPermPresetsTrait is used zero times and is not analysed#'
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterf
|
||||||
string $className,
|
string $className,
|
||||||
string $format = 'json',
|
string $format = 'json',
|
||||||
string $type = Schema::TYPE_OUTPUT,
|
string $type = Schema::TYPE_OUTPUT,
|
||||||
Operation $operation = null,
|
?Operation $operation = null,
|
||||||
Schema $schema = null,
|
?Schema $schema = null,
|
||||||
array $serializerContext = null,
|
?array $serializerContext = null,
|
||||||
bool $forceCollection = false
|
bool $forceCollection = false
|
||||||
): Schema {
|
): Schema {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class EntityFilter extends AbstractFilter
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ManagerRegistry $managerRegistry,
|
ManagerRegistry $managerRegistry,
|
||||||
private readonly EntityFilterHelper $filter_helper,
|
private readonly EntityFilterHelper $filter_helper,
|
||||||
LoggerInterface $logger = null,
|
?LoggerInterface $logger = null,
|
||||||
?array $properties = null,
|
?array $properties = null,
|
||||||
?NameConverterInterface $nameConverter = null
|
?NameConverterInterface $nameConverter = null
|
||||||
) {
|
) {
|
||||||
|
|
@ -50,7 +50,7 @@ class EntityFilter extends AbstractFilter
|
||||||
QueryBuilder $queryBuilder,
|
QueryBuilder $queryBuilder,
|
||||||
QueryNameGeneratorInterface $queryNameGenerator,
|
QueryNameGeneratorInterface $queryNameGenerator,
|
||||||
string $resourceClass,
|
string $resourceClass,
|
||||||
Operation $operation = null,
|
?Operation $operation = null,
|
||||||
array $context = []
|
array $context = []
|
||||||
): void {
|
): void {
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ final class LikeFilter extends AbstractFilter
|
||||||
QueryBuilder $queryBuilder,
|
QueryBuilder $queryBuilder,
|
||||||
QueryNameGeneratorInterface $queryNameGenerator,
|
QueryNameGeneratorInterface $queryNameGenerator,
|
||||||
string $resourceClass,
|
string $resourceClass,
|
||||||
Operation $operation = null,
|
?Operation $operation = null,
|
||||||
array $context = []
|
array $context = []
|
||||||
): void {
|
): void {
|
||||||
// Otherwise filter is applied to order and page as well
|
// Otherwise filter is applied to order and page as well
|
||||||
|
|
@ -50,7 +50,7 @@ final class LikeFilter extends AbstractFilter
|
||||||
}
|
}
|
||||||
$parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
|
$parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
|
||||||
$queryBuilder
|
$queryBuilder
|
||||||
->andWhere(sprintf('o.%s LIKE :%s', $property, $parameterName))
|
->andWhere(sprintf('ILIKE(o.%s, :%s) = TRUE', $property, $parameterName))
|
||||||
->setParameter($parameterName, $value);
|
->setParameter($parameterName, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class PartStoragelocationFilter extends AbstractFilter
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ManagerRegistry $managerRegistry,
|
ManagerRegistry $managerRegistry,
|
||||||
private readonly EntityFilterHelper $filter_helper,
|
private readonly EntityFilterHelper $filter_helper,
|
||||||
LoggerInterface $logger = null,
|
?LoggerInterface $logger = null,
|
||||||
?array $properties = null,
|
?array $properties = null,
|
||||||
?NameConverterInterface $nameConverter = null
|
?NameConverterInterface $nameConverter = null
|
||||||
) {
|
) {
|
||||||
|
|
@ -51,7 +51,7 @@ class PartStoragelocationFilter extends AbstractFilter
|
||||||
QueryBuilder $queryBuilder,
|
QueryBuilder $queryBuilder,
|
||||||
QueryNameGeneratorInterface $queryNameGenerator,
|
QueryNameGeneratorInterface $queryNameGenerator,
|
||||||
string $resourceClass,
|
string $resourceClass,
|
||||||
Operation $operation = null,
|
?Operation $operation = null,
|
||||||
array $context = []
|
array $context = []
|
||||||
): void {
|
): void {
|
||||||
//Do not check for mapping here, as we are using a virtual property
|
//Do not check for mapping here, as we are using a virtual property
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,10 @@ final class TagFilter extends AbstractFilter
|
||||||
$expr = $queryBuilder->expr();
|
$expr = $queryBuilder->expr();
|
||||||
|
|
||||||
$tmp = $expr->orX(
|
$tmp = $expr->orX(
|
||||||
$expr->like('o.'.$property, ':' . $tag_identifier_prefix . '_1'),
|
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_1) = TRUE',
|
||||||
$expr->like('o.'.$property, ':' . $tag_identifier_prefix . '_2'),
|
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_2) = TRUE',
|
||||||
$expr->like('o.'.$property, ':' . $tag_identifier_prefix . '_3'),
|
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_3) = TRUE',
|
||||||
$expr->eq('o.'.$property, ':' . $tag_identifier_prefix . '_4'),
|
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_4) = TRUE',
|
||||||
);
|
);
|
||||||
|
|
||||||
$queryBuilder->andWhere($tmp);
|
$queryBuilder->andWhere($tmp);
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ class CheckRequirementsCommand extends Command
|
||||||
//Checking 32-bit system
|
//Checking 32-bit system
|
||||||
if (PHP_INT_SIZE === 4) {
|
if (PHP_INT_SIZE === 4) {
|
||||||
$io->warning('You are using a 32-bit system. You will have problems with working with dates after the year 2038, therefore a 64-bit system is recommended.');
|
$io->warning('You are using a 32-bit system. You will have problems with working with dates after the year 2038, therefore a 64-bit system is recommended.');
|
||||||
} elseif (PHP_INT_SIZE === 8) {
|
} elseif (PHP_INT_SIZE === 8) { //@phpstan-ignore-line //PHP_INT_SIZE is always 4 or 8
|
||||||
if (!$only_issues) {
|
if (!$only_issues) {
|
||||||
$io->success('You are using a 64-bit system.');
|
$io->success('You are using a 64-bit system.');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ class ConvertBBCodeCommand extends Command
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list which entities and which properties need to be checked.
|
* Returns a list which entities and which properties need to be checked.
|
||||||
|
* @return array<class-string<AbstractNamedDBElement>, string[]>
|
||||||
*/
|
*/
|
||||||
protected function getTargetsLists(): array
|
protected function getTargetsLists(): array
|
||||||
{
|
{
|
||||||
|
|
@ -109,7 +110,6 @@ class ConvertBBCodeCommand extends Command
|
||||||
$class
|
$class
|
||||||
));
|
));
|
||||||
//Determine which entities of this type we need to modify
|
//Determine which entities of this type we need to modify
|
||||||
/** @var EntityRepository $repo */
|
|
||||||
$repo = $this->em->getRepository($class);
|
$repo = $this->em->getRepository($class);
|
||||||
$qb = $repo->createQueryBuilder('e')
|
$qb = $repo->createQueryBuilder('e')
|
||||||
->select('e');
|
->select('e');
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
#[AsCommand('partdb:users:enable|partdb:user:enable', 'Enables/Disable the login of one or more users')]
|
#[AsCommand('partdb:users:enable|partdb:user:enable', 'Enables/Disable the login of one or more users')]
|
||||||
class UserEnableCommand extends Command
|
class UserEnableCommand extends Command
|
||||||
{
|
{
|
||||||
public function __construct(protected EntityManagerInterface $entityManager, string $name = null)
|
public function __construct(protected EntityManagerInterface $entityManager, ?string $name = null)
|
||||||
{
|
{
|
||||||
parent::__construct($name);
|
parent::__construct($name);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,12 +206,15 @@ class UsersPermissionsCommand extends Command
|
||||||
return '<fg=green>Allow</>';
|
return '<fg=green>Allow</>';
|
||||||
} elseif ($permission_value === false) {
|
} elseif ($permission_value === false) {
|
||||||
return '<fg=red>Disallow</>';
|
return '<fg=red>Disallow</>';
|
||||||
} elseif ($permission_value === null && !$inherit) {
|
}
|
||||||
|
// Permission value is null by this point
|
||||||
|
elseif (!$inherit) {
|
||||||
return '<fg=blue>Inherit</>';
|
return '<fg=blue>Inherit</>';
|
||||||
} elseif ($permission_value === null && $inherit) {
|
} elseif ($inherit) {
|
||||||
return '<fg=red>Disallow (Inherited)</>';
|
return '<fg=red>Disallow (Inherited)</>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//@phpstan-ignore-next-line This line is never reached, but PHPstorm complains otherwise
|
||||||
return '???';
|
return '???';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ abstract class BaseAdminController extends AbstractController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var AbstractPartsContainingRepository $repo */
|
|
||||||
$repo = $this->entityManager->getRepository($this->entity_class);
|
$repo = $this->entityManager->getRepository($this->entity_class);
|
||||||
|
|
||||||
return $this->render($this->twig_template, [
|
return $this->render($this->twig_template, [
|
||||||
|
|
@ -397,7 +396,7 @@ abstract class BaseAdminController extends AbstractController
|
||||||
{
|
{
|
||||||
if ($entity instanceof AbstractPartsContainingDBElement) {
|
if ($entity instanceof AbstractPartsContainingDBElement) {
|
||||||
/** @var AbstractPartsContainingRepository $repo */
|
/** @var AbstractPartsContainingRepository $repo */
|
||||||
$repo = $this->entityManager->getRepository($this->entity_class);
|
$repo = $this->entityManager->getRepository($this->entity_class); //@phpstan-ignore-line
|
||||||
if ($repo->getPartsCount($entity) > 0) {
|
if ($repo->getPartsCount($entity) > 0) {
|
||||||
$this->addFlash('error', t('entity.delete.must_not_contain_parts', ['%PATH%' => $entity->getFullPath()]));
|
$this->addFlash('error', t('entity.delete.must_not_contain_parts', ['%PATH%' => $entity->getFullPath()]));
|
||||||
|
|
||||||
|
|
@ -468,6 +467,11 @@ abstract class BaseAdminController extends AbstractController
|
||||||
$this->denyAccessUnlessGranted('read', $entity);
|
$this->denyAccessUnlessGranted('read', $entity);
|
||||||
$entities = $em->getRepository($this->entity_class)->findAll();
|
$entities = $em->getRepository($this->entity_class)->findAll();
|
||||||
|
|
||||||
|
if (count($entities) === 0) {
|
||||||
|
$this->addFlash('error', 'entity.export.flash.error.no_entities');
|
||||||
|
return $this->redirectToRoute($this->route_base.'_new');
|
||||||
|
}
|
||||||
|
|
||||||
return $exporter->exportEntityFromRequest($entities, $request);
|
return $exporter->exportEntityFromRequest($entities, $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,13 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Parts\Manufacturer;
|
||||||
use App\Entity\Parts\Part;
|
use App\Entity\Parts\Part;
|
||||||
use App\Form\InfoProviderSystem\PartSearchType;
|
use App\Form\InfoProviderSystem\PartSearchType;
|
||||||
|
use App\Services\InfoProviderSystem\ExistingPartFinder;
|
||||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||||
use App\Services\InfoProviderSystem\ProviderRegistry;
|
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
|
@ -42,7 +45,9 @@ class InfoProviderController extends AbstractController
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct(private readonly ProviderRegistry $providerRegistry,
|
public function __construct(private readonly ProviderRegistry $providerRegistry,
|
||||||
private readonly PartInfoRetriever $infoRetriever)
|
private readonly PartInfoRetriever $infoRetriever,
|
||||||
|
private readonly ExistingPartFinder $existingPartFinder
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -72,21 +77,49 @@ class InfoProviderController extends AbstractController
|
||||||
//When we are updating a part, use its name as keyword, to make searching easier
|
//When we are updating a part, use its name as keyword, to make searching easier
|
||||||
//However we can only do this, if the form was not submitted yet
|
//However we can only do this, if the form was not submitted yet
|
||||||
if ($update_target !== null && !$form->isSubmitted()) {
|
if ($update_target !== null && !$form->isSubmitted()) {
|
||||||
$form->get('keyword')->setData($update_target->getName());
|
//Use the provider reference if available, otherwise use the manufacturer product number
|
||||||
|
$keyword = $update_target->getProviderReference()->getProviderId() ?? $update_target->getManufacturerProductNumber();
|
||||||
|
//Or the name if both are not available
|
||||||
|
if ($keyword === "") {
|
||||||
|
$keyword = $update_target->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
$form->get('keyword')->setData($keyword);
|
||||||
|
|
||||||
|
//If we are updating a part, which already has a provider, preselect that provider in the form
|
||||||
|
if ($update_target->getProviderReference()->getProviderKey() !== null) {
|
||||||
|
try {
|
||||||
|
$form->get('providers')->setData([$this->providerRegistry->getProviderByKey($update_target->getProviderReference()->getProviderKey())]);
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
//If the provider is not found, just ignore it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$keyword = $form->get('keyword')->getData();
|
$keyword = $form->get('keyword')->getData();
|
||||||
$providers = $form->get('providers')->getData();
|
$providers = $form->get('providers')->getData();
|
||||||
|
|
||||||
|
$dtos = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
|
$dtos = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
|
||||||
} catch (ClientException $e) {
|
} catch (ClientException $e) {
|
||||||
$this->addFlash('error', t('info_providers.search.error.client_exception'));
|
$this->addFlash('error', t('info_providers.search.error.client_exception'));
|
||||||
$this->addFlash('error',$e->getMessage());
|
$this->addFlash('error',$e->getMessage());
|
||||||
//Log the exception
|
//Log the exception
|
||||||
$exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
|
$exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modify the array to an array of arrays that has a field for a matching local Part
|
||||||
|
// the advantage to use that format even when we don't look for local parts is that we
|
||||||
|
// always work with the same interface
|
||||||
|
$results = array_map(function ($result) {return ['dto' => $result, 'localPart' => null];}, $dtos);
|
||||||
|
if(!$update_target) {
|
||||||
|
foreach ($results as $index => $result) {
|
||||||
|
$results[$index]['localPart'] = $this->existingPartFinder->findFirstExisting($result['dto']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('info_providers/search/part_search.html.twig', [
|
return $this->render('info_providers/search/part_search.html.twig', [
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,31 @@ class LabelController extends AbstractController
|
||||||
$pdf_data = null;
|
$pdf_data = null;
|
||||||
$filename = 'invalid.pdf';
|
$filename = 'invalid.pdf';
|
||||||
|
|
||||||
//Generate PDF either when the form is submitted and valid, or the form was not submit yet, and generate is set
|
|
||||||
if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && $profile instanceof LabelProfile)) {
|
if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && $profile instanceof LabelProfile)) {
|
||||||
|
|
||||||
|
//Check if the label should be saved as profile
|
||||||
|
if ($form->get('save_profile')->isClicked() && $this->isGranted('@labels.create_profiles')) { //@phpstan-ignore-line Phpstan does not recognize the isClicked method
|
||||||
|
//Retrieve the profile name from the form
|
||||||
|
$new_name = $form->get('save_profile_name')->getData();
|
||||||
|
//ensure that the name is not empty
|
||||||
|
if ($new_name === '' || $new_name === null) {
|
||||||
|
$form->get('save_profile_name')->addError(new FormError($this->translator->trans('label_generator.profile_name_empty')));
|
||||||
|
goto render;
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile = new LabelProfile();
|
||||||
|
$profile->setName($form->get('save_profile_name')->getData());
|
||||||
|
$profile->setOptions($form_options);
|
||||||
|
$this->em->persist($profile);
|
||||||
|
$this->em->flush();
|
||||||
|
$this->addFlash('success', 'label_generator.profile_saved');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('label_dialog_profile', [
|
||||||
|
'profile' => $profile->getID(),
|
||||||
|
'target_id' => (string) $form->get('target_id')->getData()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$target_id = (string) $form->get('target_id')->getData();
|
$target_id = (string) $form->get('target_id')->getData();
|
||||||
$targets = $this->findObjects($form_options->getSupportedElement(), $target_id);
|
$targets = $this->findObjects($form_options->getSupportedElement(), $target_id);
|
||||||
if ($targets !== []) {
|
if ($targets !== []) {
|
||||||
|
|
@ -132,6 +155,7 @@ class LabelController extends AbstractController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render:
|
||||||
return $this->render('label_system/dialog.html.twig', [
|
return $this->render('label_system/dialog.html.twig', [
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
'pdf_data' => $pdf_data,
|
'pdf_data' => $pdf_data,
|
||||||
|
|
@ -152,7 +176,7 @@ class LabelController extends AbstractController
|
||||||
{
|
{
|
||||||
$id_array = $this->rangeParser->parse($ids);
|
$id_array = $this->rangeParser->parse($ids);
|
||||||
|
|
||||||
/** @var DBElementRepository $repo */
|
/** @var DBElementRepository<AbstractDBElement> $repo */
|
||||||
$repo = $this->em->getRepository($type->getEntityClass());
|
$repo = $this->em->getRepository($type->getEntityClass());
|
||||||
|
|
||||||
return $repo->getElementsFromIDArray($id_array);
|
return $repo->getElementsFromIDArray($id_array);
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,10 @@ class PartController extends AbstractController
|
||||||
$dto = $infoRetriever->getDetails($providerKey, $providerId);
|
$dto = $infoRetriever->getDetails($providerKey, $providerId);
|
||||||
$new_part = $infoRetriever->dtoToPart($dto);
|
$new_part = $infoRetriever->dtoToPart($dto);
|
||||||
|
|
||||||
|
if ($new_part->getCategory() === null || $new_part->getCategory()->getID() === null) {
|
||||||
|
$this->addFlash('warning', t("part.create_from_info_provider.no_category_yet"));
|
||||||
|
}
|
||||||
|
|
||||||
return $this->renderPartForm('new', $request, $new_part, [
|
return $this->renderPartForm('new', $request, $new_part, [
|
||||||
'info_provider_dto' => $dto,
|
'info_provider_dto' => $dto,
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,9 @@ class PartImportExportController extends AbstractController
|
||||||
$ids = $request->query->get('ids', '');
|
$ids = $request->query->get('ids', '');
|
||||||
$parts = $this->partsTableActionHandler->idStringToArray($ids);
|
$parts = $this->partsTableActionHandler->idStringToArray($ids);
|
||||||
|
|
||||||
if ($parts === []) {
|
if (count($parts) === 0) {
|
||||||
throw new \RuntimeException('No parts found!');
|
$this->addFlash('error', 'entity.export.flash.error.no_entities');
|
||||||
|
return $this->redirectToRoute('homepage');
|
||||||
}
|
}
|
||||||
|
|
||||||
//Ensure that we have access to the parts
|
//Ensure that we have access to the parts
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ class PartListsController extends AbstractController
|
||||||
$ids = $request->request->get('ids');
|
$ids = $request->request->get('ids');
|
||||||
$action = $request->request->get('action');
|
$action = $request->request->get('action');
|
||||||
$target = $request->request->get('target');
|
$target = $request->request->get('target');
|
||||||
|
$redirectResponse = null;
|
||||||
|
|
||||||
if (!$this->isCsrfTokenValid('table_action', $request->request->get('_token'))) {
|
if (!$this->isCsrfTokenValid('table_action', $request->request->get('_token'))) {
|
||||||
$this->addFlash('error', 'csfr_invalid');
|
$this->addFlash('error', 'csfr_invalid');
|
||||||
|
|
@ -80,7 +81,7 @@ class PartListsController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
//If the action handler returned a response, we use it, otherwise we redirect back to the previous page.
|
//If the action handler returned a response, we use it, otherwise we redirect back to the previous page.
|
||||||
if (isset($redirectResponse) && $redirectResponse instanceof Response) {
|
if ($redirectResponse !== null) {
|
||||||
return $redirectResponse;
|
return $redirectResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,7 +132,11 @@ class PartListsController extends AbstractController
|
||||||
|
|
||||||
$filterForm->handleRequest($formRequest);
|
$filterForm->handleRequest($formRequest);
|
||||||
|
|
||||||
$table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge(['filter' => $filter], $additional_table_vars))
|
$table = $this->dataTableFactory->createFromType(
|
||||||
|
PartsDataTable::class,
|
||||||
|
array_merge(['filter' => $filter], $additional_table_vars),
|
||||||
|
['lengthMenu' => PartsDataTable::LENGTH_MENU]
|
||||||
|
)
|
||||||
->handleRequest($request);
|
->handleRequest($request);
|
||||||
|
|
||||||
if ($table->isCallback()) {
|
if ($table->isCallback()) {
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,10 @@ declare(strict_types=1);
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Form\LabelSystem\ScanDialogType;
|
use App\Form\LabelSystem\ScanDialogType;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeScanHelper;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeRedirector;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
|
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
|
||||||
use Doctrine\ORM\EntityNotFoundException;
|
use Doctrine\ORM\EntityNotFoundException;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
|
@ -77,13 +77,21 @@ class ScanController extends AbstractController
|
||||||
$mode = $form['mode']->getData();
|
$mode = $form['mode']->getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$infoModeData = null;
|
||||||
|
|
||||||
if ($input !== null) {
|
if ($input !== null) {
|
||||||
try {
|
try {
|
||||||
$scan_result = $this->barcodeNormalizer->scanBarcodeContent($input, $mode ?? null);
|
$scan_result = $this->barcodeNormalizer->scanBarcodeContent($input, $mode ?? null);
|
||||||
try {
|
//Perform a redirect if the info mode is not enabled
|
||||||
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
|
if (!$form['info_mode']->getData()) {
|
||||||
} catch (EntityNotFoundException) {
|
try {
|
||||||
$this->addFlash('success', 'scan.qr_not_found');
|
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
|
||||||
|
} catch (EntityNotFoundException) {
|
||||||
|
$this->addFlash('success', 'scan.qr_not_found');
|
||||||
|
}
|
||||||
|
} else { //Otherwise retrieve infoModeData
|
||||||
|
$infoModeData = $scan_result->getDecodedForInfoMode();
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (InvalidArgumentException) {
|
} catch (InvalidArgumentException) {
|
||||||
$this->addFlash('error', 'scan.format_unknown');
|
$this->addFlash('error', 'scan.format_unknown');
|
||||||
|
|
@ -92,6 +100,7 @@ class ScanController extends AbstractController
|
||||||
|
|
||||||
return $this->render('label_system/scanner/scanner.html.twig', [
|
return $this->render('label_system/scanner/scanner.html.twig', [
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
|
'infoModeData' => $infoModeData,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,7 +118,7 @@ class ScanController extends AbstractController
|
||||||
throw new InvalidArgumentException('Unknown type: '.$type);
|
throw new InvalidArgumentException('Unknown type: '.$type);
|
||||||
}
|
}
|
||||||
//Construct the scan result manually, as we don't have a barcode here
|
//Construct the scan result manually, as we don't have a barcode here
|
||||||
$scan_result = new BarcodeScanResult(
|
$scan_result = new LocalBarcodeScanResult(
|
||||||
target_type: BarcodeScanHelper::QR_TYPE_MAP[$type],
|
target_type: BarcodeScanHelper::QR_TYPE_MAP[$type],
|
||||||
target_id: $id,
|
target_id: $id,
|
||||||
//The routes are only used on the internal generated QR codes
|
//The routes are only used on the internal generated QR codes
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Parameters\AbstractParameter;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use App\Entity\Attachments\Attachment;
|
use App\Entity\Attachments\Attachment;
|
||||||
use App\Entity\Parts\Category;
|
use App\Entity\Parts\Category;
|
||||||
|
|
@ -92,7 +93,7 @@ class TypeaheadController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function map the parameter type to the class, so we can access its repository
|
* This function map the parameter type to the class, so we can access its repository
|
||||||
* @return class-string
|
* @return class-string<AbstractParameter>
|
||||||
*/
|
*/
|
||||||
private function typeToParameterClass(string $type): string
|
private function typeToParameterClass(string $type): string
|
||||||
{
|
{
|
||||||
|
|
@ -155,7 +156,7 @@ class TypeaheadController extends AbstractController
|
||||||
//Ensure user has the correct permissions
|
//Ensure user has the correct permissions
|
||||||
$this->denyAccessUnlessGranted('read', $test_obj);
|
$this->denyAccessUnlessGranted('read', $test_obj);
|
||||||
|
|
||||||
/** @var ParameterRepository $repository */
|
/** @var ParameterRepository<AbstractParameter> $repository */
|
||||||
$repository = $entityManager->getRepository($class);
|
$repository = $entityManager->getRepository($class);
|
||||||
|
|
||||||
$data = $repository->autocompleteParamName($query);
|
$data = $repository->autocompleteParamName($query);
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,10 @@ class UserSettingsController extends AbstractController
|
||||||
$page_need_reload = true;
|
$page_need_reload = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Form $form We need a form implementation for the next calls */
|
if (!$form instanceof Form) {
|
||||||
|
throw new RuntimeException('Form is not an instance of Form, so we cannot retrieve the clicked button!');
|
||||||
|
}
|
||||||
|
|
||||||
//Remove the avatar attachment from the user if requested
|
//Remove the avatar attachment from the user if requested
|
||||||
if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName() && $user->getMasterPictureAttachment() instanceof Attachment) {
|
if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName() && $user->getMasterPictureAttachment() instanceof Attachment) {
|
||||||
$em->remove($user->getMasterPictureAttachment());
|
$em->remove($user->getMasterPictureAttachment());
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class APITokenFixtures extends Fixture implements DependentFixtureInterface
|
||||||
public function load(ObjectManager $manager): void
|
public function load(ObjectManager $manager): void
|
||||||
{
|
{
|
||||||
/** @var User $admin_user */
|
/** @var User $admin_user */
|
||||||
$admin_user = $this->getReference(UserFixtures::ADMIN);
|
$admin_user = $this->getReference(UserFixtures::ADMIN, User::class);
|
||||||
|
|
||||||
$read_only_token = new ApiToken();
|
$read_only_token = new ApiToken();
|
||||||
$read_only_token->setUser($admin_user);
|
$read_only_token->setUser($admin_user);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ use Doctrine\Persistence\ObjectManager;
|
||||||
class LogEntryFixtures extends Fixture implements DependentFixtureInterface
|
class LogEntryFixtures extends Fixture implements DependentFixtureInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
public function load(ObjectManager $manager)
|
public function load(ObjectManager $manager): void
|
||||||
{
|
{
|
||||||
$this->createCategoryEntries($manager);
|
$this->createCategoryEntries($manager);
|
||||||
$this->createDeletedCategory($manager);
|
$this->createDeletedCategory($manager);
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
|
||||||
$partLot2->setComment('Test');
|
$partLot2->setComment('Test');
|
||||||
$partLot2->setNeedsRefill(true);
|
$partLot2->setNeedsRefill(true);
|
||||||
$partLot2->setStorageLocation($manager->find(StorageLocation::class, 3));
|
$partLot2->setStorageLocation($manager->find(StorageLocation::class, 3));
|
||||||
$partLot2->setVendorBarcode('lot2_vendor_barcode');
|
$partLot2->setUserBarcode('lot2_vendor_barcode');
|
||||||
$part->addPartLot($partLot2);
|
$part->addPartLot($partLot2);
|
||||||
|
|
||||||
$orderdetail = new Orderdetail();
|
$orderdetail = new Orderdetail();
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\DataFixtures;
|
namespace App\DataFixtures;
|
||||||
|
|
||||||
|
use App\Entity\UserSystem\Group;
|
||||||
use App\Entity\UserSystem\User;
|
use App\Entity\UserSystem\User;
|
||||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||||
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
|
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
|
||||||
|
|
@ -41,7 +42,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface
|
||||||
{
|
{
|
||||||
$anonymous = new User();
|
$anonymous = new User();
|
||||||
$anonymous->setName('anonymous');
|
$anonymous->setName('anonymous');
|
||||||
$anonymous->setGroup($this->getReference(GroupFixtures::READONLY));
|
$anonymous->setGroup($this->getReference(GroupFixtures::READONLY, Group::class));
|
||||||
$anonymous->setNeedPwChange(false);
|
$anonymous->setNeedPwChange(false);
|
||||||
$anonymous->setPassword($this->encoder->hashPassword($anonymous, 'test'));
|
$anonymous->setPassword($this->encoder->hashPassword($anonymous, 'test'));
|
||||||
$manager->persist($anonymous);
|
$manager->persist($anonymous);
|
||||||
|
|
@ -50,7 +51,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface
|
||||||
$admin->setName('admin');
|
$admin->setName('admin');
|
||||||
$admin->setPassword($this->encoder->hashPassword($admin, 'test'));
|
$admin->setPassword($this->encoder->hashPassword($admin, 'test'));
|
||||||
$admin->setNeedPwChange(false);
|
$admin->setNeedPwChange(false);
|
||||||
$admin->setGroup($this->getReference(GroupFixtures::ADMINS));
|
$admin->setGroup($this->getReference(GroupFixtures::ADMINS, Group::class));
|
||||||
$manager->persist($admin);
|
$manager->persist($admin);
|
||||||
$this->addReference(self::ADMIN, $admin);
|
$this->addReference(self::ADMIN, $admin);
|
||||||
|
|
||||||
|
|
@ -60,7 +61,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface
|
||||||
$user->setEmail('user@invalid.invalid');
|
$user->setEmail('user@invalid.invalid');
|
||||||
$user->setFirstName('Test')->setLastName('User');
|
$user->setFirstName('Test')->setLastName('User');
|
||||||
$user->setPassword($this->encoder->hashPassword($user, 'test'));
|
$user->setPassword($this->encoder->hashPassword($user, 'test'));
|
||||||
$user->setGroup($this->getReference(GroupFixtures::USERS));
|
$user->setGroup($this->getReference(GroupFixtures::USERS, Group::class));
|
||||||
$manager->persist($user);
|
$manager->persist($user);
|
||||||
|
|
||||||
$noread = new User();
|
$noread = new User();
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class TwoStepORMAdapter extends ORMAdapter
|
||||||
|
|
||||||
private \Closure|null $query_modifier = null;
|
private \Closure|null $query_modifier = null;
|
||||||
|
|
||||||
public function __construct(ManagerRegistry $registry = null)
|
public function __construct(?ManagerRegistry $registry = null)
|
||||||
{
|
{
|
||||||
parent::__construct($registry);
|
parent::__construct($registry);
|
||||||
$this->detailQueryCallable = static function (QueryBuilder $qb, array $ids): never {
|
$this->detailQueryCallable = static function (QueryBuilder $qb, array $ids): never {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ abstract class AbstractConstraint implements FilterInterface
|
||||||
* @var string The property where this BooleanConstraint should apply to
|
* @var string The property where this BooleanConstraint should apply to
|
||||||
*/
|
*/
|
||||||
protected string $property,
|
protected string $property,
|
||||||
string $identifier = null)
|
?string $identifier = null)
|
||||||
{
|
{
|
||||||
$this->identifier = $identifier ?? $this->generateParameterIdentifier($property);
|
$this->identifier = $identifier ?? $this->generateParameterIdentifier($property);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class BooleanConstraint extends AbstractConstraint
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $property,
|
string $property,
|
||||||
string $identifier = null,
|
?string $identifier = null,
|
||||||
/** @var bool|null The value of our constraint */
|
/** @var bool|null The value of our constraint */
|
||||||
protected ?bool $value = null
|
protected ?bool $value = null
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class DateTimeConstraint extends AbstractConstraint
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $property,
|
string $property,
|
||||||
string $identifier = null,
|
?string $identifier = null,
|
||||||
/**
|
/**
|
||||||
* The value1 used for comparison (this is the main one used for all mono-value comparisons)
|
* The value1 used for comparison (this is the main one used for all mono-value comparisons)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ class EntityConstraint extends AbstractConstraint
|
||||||
public function __construct(protected ?NodesListBuilder $nodesListBuilder,
|
public function __construct(protected ?NodesListBuilder $nodesListBuilder,
|
||||||
protected string $class,
|
protected string $class,
|
||||||
string $property,
|
string $property,
|
||||||
string $identifier = null,
|
?string $identifier = null,
|
||||||
protected ?AbstractDBElement $value = null,
|
protected ?AbstractDBElement $value = null,
|
||||||
protected ?string $operator = null)
|
protected ?string $operator = null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class NumberConstraint extends AbstractConstraint
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $property,
|
string $property,
|
||||||
string $identifier = null,
|
?string $identifier = null,
|
||||||
/**
|
/**
|
||||||
* The value1 used for comparison (this is the main one used for all mono-value comparisons)
|
* The value1 used for comparison (this is the main one used for all mono-value comparisons)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
class LessThanDesiredConstraint extends BooleanConstraint
|
class LessThanDesiredConstraint extends BooleanConstraint
|
||||||
{
|
{
|
||||||
public function __construct(string $property = null, string $identifier = null, ?bool $default_value = null)
|
public function __construct(?string $property = null, ?string $identifier = null, ?bool $default_value = null)
|
||||||
{
|
{
|
||||||
parent::__construct($property ?? '(
|
parent::__construct($property ?? '(
|
||||||
SELECT COALESCE(SUM(ld_partLot.amount), 0.0)
|
SELECT COALESCE(SUM(ld_partLot.amount), 0.0)
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class TagsConstraint extends AbstractConstraint
|
||||||
{
|
{
|
||||||
final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE'];
|
final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE'];
|
||||||
|
|
||||||
public function __construct(string $property, string $identifier = null,
|
public function __construct(string $property, ?string $identifier = null,
|
||||||
protected ?string $value = null,
|
protected ?string $value = null,
|
||||||
protected ?string $operator = '')
|
protected ?string $operator = '')
|
||||||
{
|
{
|
||||||
|
|
@ -93,10 +93,10 @@ class TagsConstraint extends AbstractConstraint
|
||||||
$expr = $queryBuilder->expr();
|
$expr = $queryBuilder->expr();
|
||||||
|
|
||||||
$tmp = $expr->orX(
|
$tmp = $expr->orX(
|
||||||
$expr->like($this->property, ':' . $tag_identifier_prefix . '_1'),
|
'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_1) = TRUE',
|
||||||
$expr->like($this->property, ':' . $tag_identifier_prefix . '_2'),
|
'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_2) = TRUE',
|
||||||
$expr->like($this->property, ':' . $tag_identifier_prefix . '_3'),
|
'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_3) = TRUE',
|
||||||
$expr->eq($this->property, ':' . $tag_identifier_prefix . '_4'),
|
'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_4) = TRUE',
|
||||||
);
|
);
|
||||||
|
|
||||||
//Set the parameters for the LIKE expression, in each variation of the tag (so with a comma, at the end, at the beginning, and on both ends, and equaling the tag)
|
//Set the parameters for the LIKE expression, in each variation of the tag (so with a comma, at the end, at the beginning, and on both ends, and equaling the tag)
|
||||||
|
|
@ -133,6 +133,7 @@ class TagsConstraint extends AbstractConstraint
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//@phpstan-ignore-next-line Keep this check to ensure that everything has the same structure even if we add a new operator
|
||||||
if ($this->operator === 'NONE') {
|
if ($this->operator === 'NONE') {
|
||||||
$queryBuilder->andWhere($queryBuilder->expr()->not($queryBuilder->expr()->orX(...$tagsExpressions)));
|
$queryBuilder->andWhere($queryBuilder->expr()->not($queryBuilder->expr()->orX(...$tagsExpressions)));
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class TextConstraint extends AbstractConstraint
|
||||||
/**
|
/**
|
||||||
* @param string $value
|
* @param string $value
|
||||||
*/
|
*/
|
||||||
public function __construct(string $property, string $identifier = null, /**
|
public function __construct(string $property, ?string $identifier = null, /**
|
||||||
* @var string|null The value to compare to
|
* @var string|null The value to compare to
|
||||||
*/
|
*/
|
||||||
protected ?string $value = null, /**
|
protected ?string $value = null, /**
|
||||||
|
|
@ -107,7 +107,8 @@ class TextConstraint extends AbstractConstraint
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($like_value !== null) {
|
if ($like_value !== null) {
|
||||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'LIKE', $like_value);
|
$queryBuilder->andWhere(sprintf('ILIKE(%s, :%s) = TRUE', $this->property, $this->identifier));
|
||||||
|
$queryBuilder->setParameter($this->identifier, $like_value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
namespace App\DataTables\Filters;
|
namespace App\DataTables\Filters;
|
||||||
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
class PartSearchFilter implements FilterInterface
|
class PartSearchFilter implements FilterInterface
|
||||||
|
|
@ -132,15 +131,15 @@ class PartSearchFilter implements FilterInterface
|
||||||
return sprintf("REGEXP(%s, :search_query) = TRUE", $field);
|
return sprintf("REGEXP(%s, :search_query) = TRUE", $field);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf("%s LIKE :search_query", $field);
|
return sprintf("ILIKE(%s, :search_query) = TRUE", $field);
|
||||||
}, $fields_to_search);
|
}, $fields_to_search);
|
||||||
|
|
||||||
//Add Or concatation of the expressions to our query
|
//Add Or concatenation of the expressions to our query
|
||||||
$queryBuilder->andWhere(
|
$queryBuilder->andWhere(
|
||||||
$queryBuilder->expr()->orX(...$expressions)
|
$queryBuilder->expr()->orX(...$expressions)
|
||||||
);
|
);
|
||||||
|
|
||||||
//For regex we pass the query as is, for like we add % to the start and end as wildcards
|
//For regex, we pass the query as is, for like we add % to the start and end as wildcards
|
||||||
if ($this->regex) {
|
if ($this->regex) {
|
||||||
$queryBuilder->setParameter('search_query', $this->keyword);
|
$queryBuilder->setParameter('search_query', $this->keyword);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
final class PartsDataTable implements DataTableTypeInterface
|
final class PartsDataTable implements DataTableTypeInterface
|
||||||
{
|
{
|
||||||
|
const LENGTH_MENU = [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly EntityURLGenerator $urlGenerator,
|
private readonly EntityURLGenerator $urlGenerator,
|
||||||
private readonly TranslatorInterface $translator,
|
private readonly TranslatorInterface $translator,
|
||||||
|
|
|
||||||
|
|
@ -87,16 +87,14 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||||
if(!$context->getPart() instanceof Part) {
|
if(!$context->getPart() instanceof Part) {
|
||||||
return htmlspecialchars((string) $context->getName());
|
return htmlspecialchars((string) $context->getName());
|
||||||
}
|
}
|
||||||
if($context->getPart() instanceof Part) {
|
|
||||||
$tmp = $this->partDataTableHelper->renderName($context->getPart());
|
|
||||||
if($context->getName() !== null && $context->getName() !== '') {
|
|
||||||
$tmp .= '<br><b>'.htmlspecialchars($context->getName()).'</b>';
|
|
||||||
}
|
|
||||||
return $tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
//@phpstan-ignore-next-line
|
//Part exists if we reach this point
|
||||||
throw new \RuntimeException('This should never happen!');
|
|
||||||
|
$tmp = $this->partDataTableHelper->renderName($context->getPart());
|
||||||
|
if($context->getName() !== null && $context->getName() !== '') {
|
||||||
|
$tmp .= '<br><b>'.htmlspecialchars($context->getName()).'</b>';
|
||||||
|
}
|
||||||
|
return $tmp;
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
->add('ipn', TextColumn::class, [
|
->add('ipn', TextColumn::class, [
|
||||||
|
|
|
||||||
71
src/Doctrine/Functions/ILike.php
Normal file
71
src/Doctrine/Functions/ILike.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?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\Doctrine\Functions;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||||
|
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||||
|
use Doctrine\DBAL\Platforms\SQLitePlatform;
|
||||||
|
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||||
|
use Doctrine\ORM\Query\Parser;
|
||||||
|
use Doctrine\ORM\Query\SqlWalker;
|
||||||
|
use Doctrine\ORM\Query\TokenType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A platform invariant version of the case-insensitive LIKE operation.
|
||||||
|
* On MySQL and SQLite this is the normal LIKE, but on PostgreSQL it is the ILIKE operator.
|
||||||
|
*/
|
||||||
|
class ILike extends FunctionNode
|
||||||
|
{
|
||||||
|
|
||||||
|
public $value = null;
|
||||||
|
|
||||||
|
public $expr = null;
|
||||||
|
|
||||||
|
public function parse(Parser $parser): void
|
||||||
|
{
|
||||||
|
$parser->match(TokenType::T_IDENTIFIER);
|
||||||
|
$parser->match(TokenType::T_OPEN_PARENTHESIS);
|
||||||
|
$this->value = $parser->StringPrimary();
|
||||||
|
$parser->match(TokenType::T_COMMA);
|
||||||
|
$this->expr = $parser->StringExpression();
|
||||||
|
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSql(SqlWalker $sqlWalker): string
|
||||||
|
{
|
||||||
|
$platform = $sqlWalker->getConnection()->getDatabasePlatform();
|
||||||
|
|
||||||
|
//
|
||||||
|
if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) {
|
||||||
|
$operator = 'LIKE';
|
||||||
|
} elseif ($platform instanceof PostgreSQLPlatform) {
|
||||||
|
//Use the case-insensitive operator, to have the same behavior as MySQL
|
||||||
|
$operator = 'ILIKE';
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support case insensitive like expressions.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,15 +44,13 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
|
||||||
$native_connection = $connection->getNativeConnection();
|
$native_connection = $connection->getNativeConnection();
|
||||||
|
|
||||||
//Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation
|
//Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation
|
||||||
if($native_connection instanceof \PDO && method_exists($native_connection, 'sqliteCreateFunction' )) {
|
if($native_connection instanceof \PDO) {
|
||||||
$native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC);
|
$native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC);
|
||||||
$native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC);
|
$native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC);
|
||||||
$native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC);
|
$native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC);
|
||||||
|
|
||||||
//Create a new collation for natural sorting
|
//Create a new collation for natural sorting
|
||||||
if (method_exists($native_connection, 'sqliteCreateCollation')) {
|
$native_connection->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...));
|
||||||
$native_connection->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class DoNotUsePurgerFactory implements PurgerFactory
|
||||||
throw new \LogicException('Do not use doctrine:fixtures:load directly. Use partdb:fixtures:load instead!');
|
throw new \LogicException('Do not use doctrine:fixtures:load directly. Use partdb:fixtures:load instead!');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setEntityManager(EntityManagerInterface $em)
|
public function setEntityManager(EntityManagerInterface $em): void
|
||||||
{
|
{
|
||||||
// TODO: Implement setEntityManager() method.
|
// TODO: Implement setEntityManager() method.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -531,7 +531,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
$url = str_replace(' ', '%20', $url);
|
$url = str_replace(' ', '%20', $url);
|
||||||
|
|
||||||
//Only set if the URL is not empty
|
//Only set if the URL is not empty
|
||||||
if ($url !== null && $url !== '') {
|
if ($url !== '') {
|
||||||
if (str_contains($url, '%BASE%') || str_contains($url, '%MEDIA%')) {
|
if (str_contains($url, '%BASE%') || str_contains($url, '%MEDIA%')) {
|
||||||
throw new InvalidArgumentException('You can not reference internal files via the url field! But nice try!');
|
throw new InvalidArgumentException('You can not reference internal files via the url field! But nice try!');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation\Groups;
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template-covariant AT of Attachment
|
* @template AT of Attachment
|
||||||
*/
|
*/
|
||||||
#[ORM\MappedSuperclass(repositoryClass: AttachmentContainingDBElementRepository::class)]
|
#[ORM\MappedSuperclass(repositoryClass: AttachmentContainingDBElementRepository::class)]
|
||||||
abstract class AttachmentContainingDBElement extends AbstractNamedDBElement implements HasMasterAttachmentInterface, HasAttachmentsInterface
|
abstract class AttachmentContainingDBElement extends AbstractNamedDBElement implements HasMasterAttachmentInterface, HasAttachmentsInterface
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||||
/**
|
/**
|
||||||
* This abstract class is used for companies like suppliers or manufacturers.
|
* This abstract class is used for companies like suppliers or manufacturers.
|
||||||
*
|
*
|
||||||
* @template-covariant AT of Attachment
|
* @template AT of Attachment
|
||||||
* @template-covariant PT of AbstractParameter
|
* @template PT of AbstractParameter
|
||||||
* @extends AbstractPartsContainingDBElement<AT, PT>
|
* @extends AbstractPartsContainingDBElement<AT, PT>
|
||||||
*/
|
*/
|
||||||
#[ORM\MappedSuperclass]
|
#[ORM\MappedSuperclass]
|
||||||
|
|
@ -162,7 +162,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement
|
||||||
*
|
*
|
||||||
* @return string the link to the article
|
* @return string the link to the article
|
||||||
*/
|
*/
|
||||||
public function getAutoProductUrl(string $partnr = null): string
|
public function getAutoProductUrl(?string $partnr = null): string
|
||||||
{
|
{
|
||||||
if (is_string($partnr)) {
|
if (is_string($partnr)) {
|
||||||
return str_replace('%PARTNUMBER%', $partnr, $this->auto_product_url);
|
return str_replace('%PARTNUMBER%', $partnr, $this->auto_product_url);
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation\Groups;
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template-covariant AT of Attachment
|
* @template AT of Attachment
|
||||||
* @template-covariant PT of AbstractParameter
|
* @template PT of AbstractParameter
|
||||||
* @extends AbstractStructuralDBElement<AT, PT>
|
* @extends AbstractStructuralDBElement<AT, PT>
|
||||||
*/
|
*/
|
||||||
#[ORM\MappedSuperclass(repositoryClass: AbstractPartsContainingRepository::class)]
|
#[ORM\MappedSuperclass(repositoryClass: AbstractPartsContainingRepository::class)]
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,8 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
*
|
*
|
||||||
* @see \App\Tests\Entity\Base\AbstractStructuralDBElementTest
|
* @see \App\Tests\Entity\Base\AbstractStructuralDBElementTest
|
||||||
*
|
*
|
||||||
* @template-covariant AT of Attachment
|
* @template AT of Attachment
|
||||||
* @template-covariant PT of AbstractParameter
|
* @template PT of AbstractParameter
|
||||||
* @template-use ParametersTrait<PT>
|
* @template-use ParametersTrait<PT>
|
||||||
* @extends AttachmentContainingDBElement<AT>
|
* @extends AttachmentContainingDBElement<AT>
|
||||||
* @uses ParametersTrait<PT>
|
* @uses ParametersTrait<PT>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ declare(strict_types=1);
|
||||||
*/
|
*/
|
||||||
namespace App\Entity\LabelSystem;
|
namespace App\Entity\LabelSystem;
|
||||||
|
|
||||||
|
use App\Entity\Base\AbstractDBElement;
|
||||||
|
use App\Entity\Base\AbstractNamedDBElement;
|
||||||
use App\Entity\Parts\Part;
|
use App\Entity\Parts\Part;
|
||||||
use App\Entity\Parts\PartLot;
|
use App\Entity\Parts\PartLot;
|
||||||
use App\Entity\Parts\StorageLocation;
|
use App\Entity\Parts\StorageLocation;
|
||||||
|
|
@ -34,7 +36,7 @@ enum LabelSupportedElement: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the entity class for the given element type
|
* Returns the entity class for the given element type
|
||||||
* @return string
|
* @return class-string<AbstractDBElement>
|
||||||
*/
|
*/
|
||||||
public function getEntityClass(): string
|
public function getEntityClass(): string
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,9 @@ namespace App\Entity\LogSystem;
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\UserSystem\User;
|
use App\Entity\UserSystem\User;
|
||||||
use App\Events\SecurityEvents;
|
use App\Events\SecurityEvents;
|
||||||
|
use App\Helpers\IPAnonymizer;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Symfony\Component\HttpFoundation\IpUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This log entry is created when something security related to a user happens.
|
* This log entry is created when something security related to a user happens.
|
||||||
|
|
@ -134,7 +134,7 @@ class SecurityEventLogEntry extends AbstractLogEntry
|
||||||
public function setIPAddress(string $ip, bool $anonymize = true): self
|
public function setIPAddress(string $ip, bool $anonymize = true): self
|
||||||
{
|
{
|
||||||
if ($anonymize) {
|
if ($anonymize) {
|
||||||
$ip = IpUtils::anonymize($ip);
|
$ip = IPAnonymizer::anonymize($ip);
|
||||||
}
|
}
|
||||||
$this->extra['i'] = $ip;
|
$this->extra['i'] = $ip;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Entity\LogSystem;
|
namespace App\Entity\LogSystem;
|
||||||
|
|
||||||
|
use App\Helpers\IPAnonymizer;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\HttpFoundation\IpUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This log entry is created when a user logs in.
|
* This log entry is created when a user logs in.
|
||||||
|
|
@ -59,7 +60,7 @@ class UserLoginLogEntry extends AbstractLogEntry
|
||||||
public function setIPAddress(string $ip, bool $anonymize = true): self
|
public function setIPAddress(string $ip, bool $anonymize = true): self
|
||||||
{
|
{
|
||||||
if ($anonymize) {
|
if ($anonymize) {
|
||||||
$ip = IpUtils::anonymize($ip);
|
$ip = IPAnonymizer::anonymize($ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->extra['i'] = $ip;
|
$this->extra['i'] = $ip;
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Entity\LogSystem;
|
namespace App\Entity\LogSystem;
|
||||||
|
|
||||||
|
use App\Helpers\IPAnonymizer;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\HttpFoundation\IpUtils;
|
|
||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
class UserLogoutLogEntry extends AbstractLogEntry
|
class UserLogoutLogEntry extends AbstractLogEntry
|
||||||
|
|
@ -56,7 +56,7 @@ class UserLogoutLogEntry extends AbstractLogEntry
|
||||||
public function setIPAddress(string $ip, bool $anonymize = true): self
|
public function setIPAddress(string $ip, bool $anonymize = true): self
|
||||||
{
|
{
|
||||||
if ($anonymize) {
|
if ($anonymize) {
|
||||||
$ip = IpUtils::anonymize($ip);
|
$ip = IPAnonymizer::anonymize($ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->extra['i'] = $ip;
|
$this->extra['i'] = $ip;
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface
|
||||||
*/
|
*/
|
||||||
private const DEFAULT_EXPIRATION_TIME = 3600;
|
private const DEFAULT_EXPIRATION_TIME = 3600;
|
||||||
|
|
||||||
public function __construct(string $name, ?string $refresh_token, ?string $token = null, \DateTimeImmutable $expires_at = null)
|
public function __construct(string $name, ?string $refresh_token, ?string $token = null, ?\DateTimeImmutable $expires_at = null)
|
||||||
{
|
{
|
||||||
//If token is given, you also have to give the expires_at date
|
//If token is given, you also have to give the expires_at date
|
||||||
if ($token !== null && $expires_at === null) {
|
if ($token !== null && $expires_at === null) {
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
#[ORM\Index(columns: ['needs_refill'], name: 'part_lots_idx_needs_refill')]
|
#[ORM\Index(columns: ['needs_refill'], name: 'part_lots_idx_needs_refill')]
|
||||||
#[ORM\Index(columns: ['vendor_barcode'], name: 'part_lots_idx_barcode')]
|
#[ORM\Index(columns: ['vendor_barcode'], name: 'part_lots_idx_barcode')]
|
||||||
#[ValidPartLot]
|
#[ValidPartLot]
|
||||||
#[UniqueEntity(['vendor_barcode'], message: 'validator.part_lot.vendor_barcode_must_be_unique')]
|
#[UniqueEntity(['user_barcode'], message: 'validator.part_lot.vendor_barcode_must_be_unique')]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(security: 'is_granted("read", object)'),
|
new Get(security: 'is_granted("read", object)'),
|
||||||
|
|
@ -166,10 +166,10 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
||||||
/**
|
/**
|
||||||
* @var string|null The content of the barcode of this part lot (e.g. a barcode on the package put by the vendor)
|
* @var string|null The content of the barcode of this part lot (e.g. a barcode on the package put by the vendor)
|
||||||
*/
|
*/
|
||||||
#[ORM\Column(type: Types::STRING, nullable: true)]
|
#[ORM\Column(name: "vendor_barcode", type: Types::STRING, nullable: true)]
|
||||||
#[Groups(['part_lot:read', 'part_lot:write'])]
|
#[Groups(['part_lot:read', 'part_lot:write'])]
|
||||||
#[Length(max: 255)]
|
#[Length(max: 255)]
|
||||||
protected ?string $vendor_barcode = null;
|
protected ?string $user_barcode = null;
|
||||||
|
|
||||||
public function __clone()
|
public function __clone()
|
||||||
{
|
{
|
||||||
|
|
@ -185,7 +185,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
||||||
*
|
*
|
||||||
* @return bool|null True, if the part lot is expired. Returns null, if no expiration date was set.
|
* @return bool|null True, if the part lot is expired. Returns null, if no expiration date was set.
|
||||||
*
|
*
|
||||||
* @throws Exception If an error with the DateTime occurs
|
|
||||||
*/
|
*/
|
||||||
public function isExpired(): ?bool
|
public function isExpired(): ?bool
|
||||||
{
|
{
|
||||||
|
|
@ -376,19 +375,19 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
||||||
* null if no barcode is set.
|
* null if no barcode is set.
|
||||||
* @return string|null
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function getVendorBarcode(): ?string
|
public function getUserBarcode(): ?string
|
||||||
{
|
{
|
||||||
return $this->vendor_barcode;
|
return $this->user_barcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the content of the barcode of this part lot (e.g. a barcode on the package put by the vendor).
|
* Set the content of the barcode of this part lot (e.g. a barcode on the package put by the vendor).
|
||||||
* @param string|null $vendor_barcode
|
* @param string|null $user_barcode
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setVendorBarcode(?string $vendor_barcode): PartLot
|
public function setUserBarcode(?string $user_barcode): PartLot
|
||||||
{
|
{
|
||||||
$this->vendor_barcode = $vendor_barcode;
|
$this->user_barcode = $user_barcode;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ class Supplier extends AbstractCompany
|
||||||
protected ?AbstractStructuralDBElement $parent = null;
|
protected ?AbstractStructuralDBElement $parent = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, Orderdetail>|Orderdetail[]
|
* @var Collection<int, Orderdetail>
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(mappedBy: 'supplier', targetEntity: Orderdetail::class)]
|
#[ORM\OneToMany(mappedBy: 'supplier', targetEntity: Orderdetail::class)]
|
||||||
protected Collection $orderdetails;
|
protected Collection $orderdetails;
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,6 @@ class Project extends AbstractStructuralDBElement
|
||||||
{
|
{
|
||||||
//If this project has subprojects, and these have builds part, they must be included in the BOM
|
//If this project has subprojects, and these have builds part, they must be included in the BOM
|
||||||
foreach ($this->getChildren() as $child) {
|
foreach ($this->getChildren() as $child) {
|
||||||
/** @var $child Project */
|
|
||||||
if (!$child->getBuildPart() instanceof Part) {
|
if (!$child->getBuildPart() instanceof Part) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||||
protected ?string $password = null;
|
protected ?string $password = null;
|
||||||
|
|
||||||
#[Assert\NotBlank]
|
#[Assert\NotBlank]
|
||||||
#[Assert\Regex('/^[\w\.\+\-\$]+$/', message: 'user.invalid_username')]
|
#[Assert\Regex('/^[\w\.\+\-\$]+[\w\.\+\-\$\@]*$/', message: 'user.invalid_username')]
|
||||||
#[Groups(['user:read'])]
|
#[Groups(['user:read'])]
|
||||||
protected string $name = '';
|
protected string $name = '';
|
||||||
|
|
||||||
|
|
@ -893,8 +893,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||||
* @param string[] $codes An array containing the backup codes
|
* @param string[] $codes An array containing the backup codes
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*
|
|
||||||
* @throws Exception If an error with the datetime occurs
|
|
||||||
*/
|
*/
|
||||||
public function setBackupCodes(array $codes): self
|
public function setBackupCodes(array $codes): self
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
<?php
|
|
||||||
/*
|
|
||||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
|
||||||
*
|
|
||||||
* Copyright (C) 2019 - 2023 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\EventSubscriber;
|
|
||||||
|
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
||||||
use Symfony\WebpackEncoreBundle\Event\RenderAssetTagEvent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class fixes the wrong pathes generated by webpack using the auto publicPath mode.
|
|
||||||
* Basically it replaces the wrong /auto/ part of the path with the correct /build/ in all encore entrypoints.
|
|
||||||
*/
|
|
||||||
class WebpackAutoPathSubscriber implements EventSubscriberInterface
|
|
||||||
{
|
|
||||||
public static function getSubscribedEvents(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
RenderAssetTagEvent::class => 'onRenderAssetTag'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onRenderAssetTag(RenderAssetTagEvent $event): void
|
|
||||||
{
|
|
||||||
if ($event->isScriptTag()) {
|
|
||||||
$event->setAttribute('src', $this->resolveAuto($event->getUrl()));
|
|
||||||
}
|
|
||||||
if ($event->isLinkTag()) {
|
|
||||||
$event->setAttribute('href', $this->resolveAuto($event->getUrl()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resolveAuto(string $path): string
|
|
||||||
{
|
|
||||||
//Replace the first occurence of /auto/ with /build/ to get the correct path
|
|
||||||
return preg_replace('/\/auto\//', '/build/', $path, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -71,6 +71,22 @@ class LabelDialogType extends AbstractType
|
||||||
'label' => false,
|
'label' => false,
|
||||||
'disabled' => !$this->security->isGranted('@labels.edit_options') || $options['disable_options'],
|
'disabled' => !$this->security->isGranted('@labels.edit_options') || $options['disable_options'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$builder->add('save_profile_name', TextType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'attr' =>[
|
||||||
|
'placeholder' => 'label_generator.save_profile_name',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$builder->add('save_profile', SubmitType::class, [
|
||||||
|
'label' => 'label_generator.save_profile',
|
||||||
|
'disabled' => !$this->security->isGranted('@labels.create_profiles'),
|
||||||
|
'attr' => [
|
||||||
|
'class' => 'btn btn-outline-success'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
$builder->add('update', SubmitType::class, [
|
$builder->add('update', SubmitType::class, [
|
||||||
'label' => 'label_generator.update',
|
'label' => 'label_generator.update',
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,9 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\LabelSystem;
|
namespace App\Form\LabelSystem;
|
||||||
|
|
||||||
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
|
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
|
@ -55,6 +56,8 @@ class ScanDialogType extends AbstractType
|
||||||
{
|
{
|
||||||
$builder->add('input', TextType::class, [
|
$builder->add('input', TextType::class, [
|
||||||
'label' => 'scan_dialog.input',
|
'label' => 'scan_dialog.input',
|
||||||
|
//Do not trim the input, otherwise this damages Format06 barcodes which end with non-printable characters
|
||||||
|
'trim' => false,
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'autofocus' => true,
|
'autofocus' => true,
|
||||||
'id' => 'scan_dialog_input',
|
'id' => 'scan_dialog_input',
|
||||||
|
|
@ -71,9 +74,14 @@ class ScanDialogType extends AbstractType
|
||||||
null => 'scan_dialog.mode.auto',
|
null => 'scan_dialog.mode.auto',
|
||||||
BarcodeSourceType::INTERNAL => 'scan_dialog.mode.internal',
|
BarcodeSourceType::INTERNAL => 'scan_dialog.mode.internal',
|
||||||
BarcodeSourceType::IPN => 'scan_dialog.mode.ipn',
|
BarcodeSourceType::IPN => 'scan_dialog.mode.ipn',
|
||||||
BarcodeSourceType::VENDOR => 'scan_dialog.mode.vendor',
|
BarcodeSourceType::USER_DEFINED => 'scan_dialog.mode.user',
|
||||||
|
BarcodeSourceType::EIGP114 => 'scan_dialog.mode.eigp'
|
||||||
},
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
$builder->add('info_mode', CheckboxType::class, [
|
||||||
|
'label' => 'scan_dialog.info_mode',
|
||||||
|
'required' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->add('submit', SubmitType::class, [
|
$builder->add('submit', SubmitType::class, [
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ class PartBaseType extends AbstractType
|
||||||
'dto_value' => $dto?->category,
|
'dto_value' => $dto?->category,
|
||||||
'label' => 'part.edit.category',
|
'label' => 'part.edit.category',
|
||||||
'disable_not_selectable' => true,
|
'disable_not_selectable' => true,
|
||||||
|
//Do not require category for new parts, so that the user must select the category by hand and cannot forget it (the requirement is handled by the constraint in the entity)
|
||||||
|
'required' => !$new_part,
|
||||||
])
|
])
|
||||||
->add('footprint', StructuralEntityType::class, [
|
->add('footprint', StructuralEntityType::class, [
|
||||||
'class' => Footprint::class,
|
'class' => Footprint::class,
|
||||||
|
|
|
||||||
|
|
@ -103,10 +103,12 @@ class PartLotType extends AbstractType
|
||||||
'help' => 'part_lot.owner.help',
|
'help' => 'part_lot.owner.help',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->add('vendor_barcode', TextType::class, [
|
$builder->add('user_barcode', TextType::class, [
|
||||||
'label' => 'part_lot.edit.vendor_barcode',
|
'label' => 'part_lot.edit.user_barcode',
|
||||||
'help' => 'part_lot.edit.vendor_barcode.help',
|
'help' => 'part_lot.edit.vendor_barcode.help',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
//Do not remove whitespace chars on the beginning and end of the string
|
||||||
|
'trim' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class ExponentialNumberType extends AbstractType
|
||||||
return NumberType::class;
|
return NumberType::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
//We want to allow the full precision of the number, so disable rounding
|
//We want to allow the full precision of the number, so disable rounding
|
||||||
|
|
@ -47,7 +47,7 @@ class ExponentialNumberType extends AbstractType
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder->resetViewTransformers();
|
$builder->resetViewTransformers();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class StructuralEntityChoiceHelper
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the choice attributes for the given AbstractStructuralDBElement.
|
* Generates the choice attributes for the given AbstractStructuralDBElement.
|
||||||
* @return array|string[]
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function generateChoiceAttr(AbstractNamedDBElement $choice, Options|array $options): array
|
public function generateChoiceAttr(AbstractNamedDBElement $choice, Options|array $options): array
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||||
namespace App\Form\Type\Helper;
|
namespace App\Form\Type\Helper;
|
||||||
|
|
||||||
use App\Entity\Base\AbstractNamedDBElement;
|
use App\Entity\Base\AbstractNamedDBElement;
|
||||||
|
use App\Entity\Base\AbstractStructuralDBElement;
|
||||||
use App\Repository\StructuralDBElementRepository;
|
use App\Repository\StructuralDBElementRepository;
|
||||||
use App\Services\Trees\NodesListBuilder;
|
use App\Services\Trees\NodesListBuilder;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
@ -33,6 +34,9 @@ use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\OptionsResolver\Options;
|
use Symfony\Component\OptionsResolver\Options;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T of AbstractStructuralDBElement
|
||||||
|
*/
|
||||||
class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
||||||
{
|
{
|
||||||
private ?string $additional_element = null;
|
private ?string $additional_element = null;
|
||||||
|
|
@ -90,10 +94,14 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** @var class-string<T> $class */
|
||||||
$class = $this->options['class'];
|
$class = $this->options['class'];
|
||||||
/** @var StructuralDBElementRepository $repo */
|
|
||||||
|
/** @var StructuralDBElementRepository<T> $repo */
|
||||||
$repo = $this->entityManager->getRepository($class);
|
$repo = $this->entityManager->getRepository($class);
|
||||||
|
|
||||||
|
|
||||||
$entities = $repo->getNewEntityFromPath($value, '->');
|
$entities = $repo->getNewEntityFromPath($value, '->');
|
||||||
|
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,6 @@ final class TriStateCheckboxType extends AbstractType implements DataTransformer
|
||||||
*
|
*
|
||||||
* @return mixed The value in the transformed representation
|
* @return mixed The value in the transformed representation
|
||||||
*
|
*
|
||||||
* @throws TransformationFailedException when the transformation fails
|
|
||||||
*/
|
*/
|
||||||
public function transform(mixed $value)
|
public function transform(mixed $value)
|
||||||
{
|
{
|
||||||
|
|
@ -142,8 +141,6 @@ final class TriStateCheckboxType extends AbstractType implements DataTransformer
|
||||||
* @param mixed $value The value in the transformed representation
|
* @param mixed $value The value in the transformed representation
|
||||||
*
|
*
|
||||||
* @return mixed The value in the original representation
|
* @return mixed The value in the original representation
|
||||||
*
|
|
||||||
* @throws TransformationFailedException when the transformation fails
|
|
||||||
*/
|
*/
|
||||||
public function reverseTransform(mixed $value)
|
public function reverseTransform(mixed $value)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
49
src/Helpers/IPAnonymizer.php
Normal file
49
src/Helpers/IPAnonymizer.php
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?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\Helpers;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\IpUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utils to assist with IP anonymization.
|
||||||
|
* The IPUtils::anonymize has a certain edgecase with local-link addresses, which is handled here.
|
||||||
|
* See: https://github.com/Part-DB/Part-DB-server/issues/782
|
||||||
|
*/
|
||||||
|
final class IPAnonymizer
|
||||||
|
{
|
||||||
|
public static function anonymize(string $ip): string
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* If the IP contains a % symbol, then it is a local-link address with scoping according to RFC 4007
|
||||||
|
* In that case, we only care about the part before the % symbol, as the following functions, can only work with
|
||||||
|
* the IP address itself. As the scope can leak information (containing interface name), we do not want to
|
||||||
|
* include it in our anonymized IP data.
|
||||||
|
*/
|
||||||
|
if (str_contains($ip, '%')) {
|
||||||
|
$ip = substr($ip, 0, strpos($ip, '%'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return IpUtils::anonymize($ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ trait WithPermPresetsTrait
|
||||||
return json_encode($user->getPermissions());
|
return json_encode($user->getPermissions());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setContainer(ContainerInterface $container = null): void
|
public function setContainer(?ContainerInterface $container = null): void
|
||||||
{
|
{
|
||||||
if ($container !== null) {
|
if ($container !== null) {
|
||||||
$this->container = $container;
|
$this->container = $container;
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,8 @@ class AttachmentRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('attachment');
|
$qb = $this->createQueryBuilder('attachment');
|
||||||
$qb->select('COUNT(attachment)')
|
$qb->select('COUNT(attachment)')
|
||||||
->where('attachment.path LIKE :like');
|
->where('attachment.path LIKE :like ESCAPE \'#\'');
|
||||||
$qb->setParameter('like', '\\%SECURE\\%%');
|
$qb->setParameter('like', '#%SECURE#%%');
|
||||||
$query = $qb->getQuery();
|
$query = $qb->getQuery();
|
||||||
|
|
||||||
return (int) $query->getSingleScalarResult();
|
return (int) $query->getSingleScalarResult();
|
||||||
|
|
@ -75,8 +75,8 @@ class AttachmentRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('attachment');
|
$qb = $this->createQueryBuilder('attachment');
|
||||||
$qb->select('COUNT(attachment)')
|
$qb->select('COUNT(attachment)')
|
||||||
->where('attachment.path LIKE :http')
|
->where('ILIKE(attachment.path, :http) = TRUE')
|
||||||
->orWhere('attachment.path LIKE :https');
|
->orWhere('ILIKE(attachment.path, :https) = TRUE');
|
||||||
$qb->setParameter('http', 'http://%');
|
$qb->setParameter('http', 'http://%');
|
||||||
$qb->setParameter('https', 'https://%');
|
$qb->setParameter('https', 'https://%');
|
||||||
$query = $qb->getQuery();
|
$query = $qb->getQuery();
|
||||||
|
|
@ -94,12 +94,12 @@ class AttachmentRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('attachment');
|
$qb = $this->createQueryBuilder('attachment');
|
||||||
$qb->select('COUNT(attachment)')
|
$qb->select('COUNT(attachment)')
|
||||||
->where('attachment.path LIKE :base')
|
->where('attachment.path LIKE :base ESCAPE \'#\'')
|
||||||
->orWhere('attachment.path LIKE :media')
|
->orWhere('attachment.path LIKE :media ESCAPE \'#\'')
|
||||||
->orWhere('attachment.path LIKE :secure');
|
->orWhere('attachment.path LIKE :secure ESCAPE \'#\'');
|
||||||
$qb->setParameter('secure', '\\%SECURE\\%%');
|
$qb->setParameter('secure', '#%SECURE#%%');
|
||||||
$qb->setParameter('base', '\\%BASE\\%%');
|
$qb->setParameter('base', '#%BASE#%%');
|
||||||
$qb->setParameter('media', '\\%MEDIA\\%%');
|
$qb->setParameter('media', '#%MEDIA#%%');
|
||||||
$query = $qb->getQuery();
|
$query = $qb->getQuery();
|
||||||
|
|
||||||
return (int) $query->getSingleScalarResult();
|
return (int) $query->getSingleScalarResult();
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ class LogEntryRepository extends DBElementRepository
|
||||||
* @param int|null $limit
|
* @param int|null $limit
|
||||||
* @param int|null $offset
|
* @param int|null $offset
|
||||||
*/
|
*/
|
||||||
public function getLogsOrderedByTimestamp(string $order = 'DESC', int $limit = null, int $offset = null): array
|
public function getLogsOrderedByTimestamp(string $order = 'DESC', ?int $limit = null, ?int $offset = null): array
|
||||||
{
|
{
|
||||||
return $this->findBy([], ['timestamp' => $order], $limit, $offset);
|
return $this->findBy([], ['timestamp' => $order], $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class ParameterRepository extends DBElementRepository
|
||||||
->select('parameter.name')
|
->select('parameter.name')
|
||||||
->addSelect('parameter.symbol')
|
->addSelect('parameter.symbol')
|
||||||
->addSelect('parameter.unit')
|
->addSelect('parameter.unit')
|
||||||
->where('parameter.name LIKE :name');
|
->where('ILIKE(parameter.name, :name) = TRUE');
|
||||||
if ($exact) {
|
if ($exact) {
|
||||||
$qb->setParameter('name', $name);
|
$qb->setParameter('name', $name);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,10 @@ class PartRepository extends NamedDBElementRepository
|
||||||
->leftJoin('part.category', 'category')
|
->leftJoin('part.category', 'category')
|
||||||
->leftJoin('part.footprint', 'footprint')
|
->leftJoin('part.footprint', 'footprint')
|
||||||
|
|
||||||
->where('part.name LIKE :query')
|
->where('ILIKE(part.name, :query) = TRUE')
|
||||||
->orWhere('part.description LIKE :query')
|
->orWhere('ILIKE(part.description, :query) = TRUE')
|
||||||
->orWhere('category.name LIKE :query')
|
->orWhere('ILIKE(category.name, :query) = TRUE')
|
||||||
->orWhere('footprint.name LIKE :query')
|
->orWhere('ILIKE(footprint.name, :query) = TRUE')
|
||||||
;
|
;
|
||||||
|
|
||||||
$qb->setParameter('query', '%'.$query.'%');
|
$qb->setParameter('query', '%'.$query.'%');
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit
|
||||||
}
|
}
|
||||||
if (null === $entity) {
|
if (null === $entity) {
|
||||||
$class = $this->getClassName();
|
$class = $this->getClassName();
|
||||||
/** @var AbstractStructuralDBElement $entity */
|
/** @var TEntityClass $entity */
|
||||||
$entity = new $class;
|
$entity = new $class;
|
||||||
$entity->setName($name);
|
$entity->setName($name);
|
||||||
$entity->setParent($parent);
|
$entity->setParent($parent);
|
||||||
|
|
@ -265,7 +265,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit
|
||||||
}
|
}
|
||||||
|
|
||||||
$class = $this->getClassName();
|
$class = $this->getClassName();
|
||||||
/** @var AbstractStructuralDBElement $entity */
|
/** @var TEntityClass $entity */
|
||||||
$entity = new $class;
|
$entity = new $class;
|
||||||
$entity->setName($name);
|
$entity->setName($name);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ class ApiTokenAuthenticator implements AuthenticatorInterface
|
||||||
/**
|
/**
|
||||||
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3
|
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3
|
||||||
*/
|
*/
|
||||||
private function getAuthenticateHeader(string $errorDescription = null): string
|
private function getAuthenticateHeader(?string $errorDescription = null): string
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
'realm' => $this->realm,
|
'realm' => $this->realm,
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function start(Request $request, AuthenticationException $authException = null): Response
|
public function start(Request $request, ?AuthenticationException $authException = null): Response
|
||||||
{
|
{
|
||||||
//Check if the request is an API request
|
//Check if the request is an API request
|
||||||
if ($this->isJSONRequest($request)) {
|
if ($this->isJSONRequest($request)) {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ namespace App\Security;
|
||||||
use App\Entity\UserSystem\User;
|
use App\Entity\UserSystem\User;
|
||||||
use Nbgrp\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken;
|
use Nbgrp\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||||
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
|
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
|
||||||
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
|
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
@ -50,13 +51,20 @@ class EnsureSAMLUserForSAMLLoginChecker implements EventSubscriberInterface
|
||||||
$token = $event->getAuthenticationToken();
|
$token = $event->getAuthenticationToken();
|
||||||
$user = $token->getUser();
|
$user = $token->getUser();
|
||||||
|
|
||||||
//If we are using SAML, we need to check that the user is a SAML user.
|
//Do not check for anonymous users
|
||||||
if ($token instanceof SamlToken) {
|
if (!$user instanceof User) {
|
||||||
if ($user instanceof User && !$user->isSamlUser()) {
|
return;
|
||||||
throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_local_user_per_saml', [], 'security'));
|
}
|
||||||
}
|
|
||||||
} elseif ($user instanceof User && $user->isSamlUser()) {
|
//Do not allow SAML users to login as local user
|
||||||
//Ensure that you can not login locally with a SAML user (even if this should not happen, as the password is not set)
|
if ($token instanceof SamlToken && !$user->isSamlUser()) {
|
||||||
|
throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_local_user_per_saml',
|
||||||
|
[], 'security'));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Do not allow local users to login as SAML user via local username and password
|
||||||
|
if ($token instanceof UsernamePasswordToken && $user->isSamlUser()) {
|
||||||
|
//Ensure that you can not login locally with a SAML user (even though this should not happen, as the password is not set)
|
||||||
throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_saml_user_locally', [], 'security'));
|
throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_saml_user_locally', [], 'security'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,10 +116,10 @@ class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterf
|
||||||
* Maps a list of SAML roles to a local group ID.
|
* Maps a list of SAML roles to a local group ID.
|
||||||
* The first available mapping will be used (so the order of the $map is important, first match wins).
|
* The first available mapping will be used (so the order of the $map is important, first match wins).
|
||||||
* @param array $roles The list of SAML roles
|
* @param array $roles The list of SAML roles
|
||||||
* @param array $map|null The mapping from SAML roles. If null, the global mapping will be used.
|
* @param array|null $map The mapping from SAML roles. If null, the global mapping will be used.
|
||||||
* @return int|null The ID of the local group or null if no mapping was found.
|
* @return int|null The ID of the local group or null if no mapping was found.
|
||||||
*/
|
*/
|
||||||
public function mapSAMLRolesToLocalGroupID(array $roles, array $map = null): ?int
|
public function mapSAMLRolesToLocalGroupID(array $roles, ?array $map = null): ?int
|
||||||
{
|
{
|
||||||
$map ??= $this->saml_role_mapping;
|
$map ??= $this->saml_role_mapping;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,10 @@ final class UserChecker implements UserCheckerInterface
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the user account before authentication.
|
* Checks the user account before authentication.
|
||||||
*
|
|
||||||
* @throws AccountStatusException
|
|
||||||
*/
|
*/
|
||||||
public function checkPreAuth(UserInterface $user): void
|
public function checkPreAuth(UserInterface $user): void
|
||||||
{
|
{
|
||||||
// TODO: Implement checkPreAuth() method.
|
//We don't need to check the user before authentication, just implemented to fulfill the interface
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class AttachmentNormalizer implements NormalizerInterface, NormalizerAwareInterf
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function normalize(mixed $object, string $format = null, array $context = []): array|null
|
public function normalize(mixed $object, ?string $format = null, array $context = []): array|null
|
||||||
{
|
{
|
||||||
if (!$object instanceof Attachment) {
|
if (!$object instanceof Attachment) {
|
||||||
throw new \InvalidArgumentException('This normalizer only supports Attachment objects!');
|
throw new \InvalidArgumentException('This normalizer only supports Attachment objects!');
|
||||||
|
|
@ -60,7 +60,7 @@ class AttachmentNormalizer implements NormalizerInterface, NormalizerAwareInterf
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
|
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
|
||||||
{
|
{
|
||||||
// avoid recursion: only call once per object
|
// avoid recursion: only call once per object
|
||||||
if (isset($context[self::ALREADY_CALLED])) {
|
if (isset($context[self::ALREADY_CALLED])) {
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface
|
class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
public function supportsNormalization($data, string $format = null, array $context = []): bool
|
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
|
||||||
{
|
{
|
||||||
return $data instanceof BigNumber;
|
return $data instanceof BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function normalize($object, string $format = null, array $context = []): string
|
public function normalize($object, ?string $format = null, array $context = []): string
|
||||||
{
|
{
|
||||||
if (!$object instanceof BigNumber) {
|
if (!$object instanceof BigNumber) {
|
||||||
throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!');
|
throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!');
|
||||||
|
|
@ -58,7 +58,7 @@ class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): BigNumber|null
|
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): BigNumber|null
|
||||||
{
|
{
|
||||||
if (!is_a($type, BigNumber::class, true)) {
|
if (!is_a($type, BigNumber::class, true)) {
|
||||||
throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!');
|
throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!');
|
||||||
|
|
@ -67,7 +67,7 @@ class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||||
return $type::of($data);
|
return $type::of($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
|
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
|
||||||
{
|
{
|
||||||
//data must be a string or a number (int, float, etc.) and the type must be BigNumber or BigDecimal
|
//data must be a string or a number (int, float, etc.) and the type must be BigNumber or BigDecimal
|
||||||
return (is_string($data) || is_numeric($data)) && (is_subclass_of($type, BigNumber::class));
|
return (is_string($data) || is_numeric($data)) && (is_subclass_of($type, BigNumber::class));
|
||||||
|
|
|
||||||
|
|
@ -63,13 +63,13 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Norm
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supportsNormalization($data, string $format = null, array $context = []): bool
|
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
|
||||||
{
|
{
|
||||||
//We only remove the type field for CSV export
|
//We only remove the type field for CSV export
|
||||||
return !isset($context[self::ALREADY_CALLED]) && $format === 'csv' && $data instanceof Part ;
|
return !isset($context[self::ALREADY_CALLED]) && $format === 'csv' && $data instanceof Part ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function normalize($object, string $format = null, array $context = []): array
|
public function normalize($object, ?string $format = null, array $context = []): array
|
||||||
{
|
{
|
||||||
if (!$object instanceof Part) {
|
if (!$object instanceof Part) {
|
||||||
throw new \InvalidArgumentException('This normalizer only supports Part objects!');
|
throw new \InvalidArgumentException('This normalizer only supports Part objects!');
|
||||||
|
|
@ -94,7 +94,14 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Norm
|
||||||
|
|
||||||
public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool
|
public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool
|
||||||
{
|
{
|
||||||
return !isset($context[self::ALREADY_CALLED]) && is_array($data) && is_a($type, Part::class, true);
|
//Only denormalize if we are doing a file import operation
|
||||||
|
if (!($context['partdb_import'] ?? false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Only make the denormalizer available on import operations
|
||||||
|
return !isset($context[self::ALREADY_CALLED])
|
||||||
|
&& is_array($data) && is_a($type, Part::class, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeKeys(array &$data): array
|
private function normalizeKeys(array &$data): array
|
||||||
|
|
@ -110,7 +117,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Norm
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function denormalize($data, string $type, string $format = null, array $context = []): ?Part
|
public function denormalize($data, string $type, ?string $format = null, array $context = []): ?Part
|
||||||
{
|
{
|
||||||
$this->normalizeKeys($data);
|
$this->normalizeKeys($data);
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue