diff --git a/.env b/.env index 8e48085e..cf39b10d 100644 --- a/.env +++ b/.env @@ -23,6 +23,10 @@ DATABASE_MYSQL_USE_SSL_CA=0 # Only do this, if you know what you are doing! DATABASE_MYSQL_SSL_VERIFY_CERT=1 +# Emulate natural sorting of strings even on databases that do not support it (like SQLite, MySQL or MariaDB < 10.7) +# This can be slow on big databases and might have some problems and quirks, so use it with caution +DATABASE_EMULATE_NATURAL_SORT=0 + ################################################################################### # General settings ################################################################################### diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 7b119277..64287d83 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -65,7 +65,7 @@ jobs: - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64,linux/arm/v7 diff --git a/.github/workflows/docker_frankenphp.yml b/.github/workflows/docker_frankenphp.yml index 54b50ae3..d8cd0695 100644 --- a/.github/workflows/docker_frankenphp.yml +++ b/.github/workflows/docker_frankenphp.yml @@ -65,7 +65,7 @@ jobs: - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: Dockerfile-frankenphp diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8f4610ae..62bdc123 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,9 +16,10 @@ jobs: runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: php-versions: [ '8.1', '8.2', '8.3' ] - db-type: [ 'mysql', 'sqlite' ] + db-type: [ 'mysql', 'sqlite', 'postgres' ] env: # Note that we set DATABASE URL later based on our db-type matrix value @@ -30,13 +31,17 @@ jobs: steps: - name: Set Database env for MySQL - run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/test" >> $GITHUB_ENV + run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/partdb?serverVersion=8.0.35" >> $GITHUB_ENV if: matrix.db-type == 'mysql' - name: Set Database env for SQLite run: echo "DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"" >> $GITHUB_ENV if: matrix.db-type == 'sqlite' + - name: Set Database env for PostgreSQL + run: echo "DATABASE_URL=postgresql://postgres:postgres @127.0.0.1:5432/partdb?serverVersion=14&charset=utf8" >> $GITHUB_ENV + if: matrix.db-type == 'postgres' + - name: Checkout uses: actions/checkout@v4 @@ -50,8 +55,17 @@ jobs: - name: Start MySQL run: sudo systemctl start mysql.service + if: matrix.db-type == 'mysql' - #- name: Setup MySQL + # Replace the scram-sha-256 with trust for host connections, to avoid password authentication + - name: Start PostgreSQL + run: | + sudo sed -i 's/^\(host.*all.*all.*\)scram-sha-256/\1trust/' /etc/postgresql/14/main/pg_hba.conf + sudo systemctl start postgresql.service + sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';" + if: matrix.db-type == 'postgres' + + #- name: Setup MySQL # uses: mirromutth/mysql-action@v1.1 # with: # mysql version: 5.7 @@ -99,12 +113,7 @@ jobs: - name: Create DB run: php bin/console --env test doctrine:database:create --if-not-exists -n - if: matrix.db-type == 'mysql' - - # Checkinf for existance is not supported for sqlite, so do it without it - - name: Create DB - run: php bin/console --env test doctrine:database:create -n - if: matrix.db-type == 'sqlite' + if: matrix.db-type == 'mysql' || matrix.db-type == 'postgres' - name: Do migrations run: php bin/console --env test doctrine:migrations:migrate -n @@ -135,11 +144,11 @@ jobs: - name: Test check-requirements command run: php bin/console partdb:check-requirements -n + - name: Test partdb:backup command + run: php bin/console partdb:backup -n --full /tmp/test_backup.zip + - name: Test legacy Part-DB import run: bash .github/assets/legacy_import/test_legacy_import.sh if: matrix.db-type == 'mysql' && matrix.php-versions == '8.2' env: DATABASE_URL: mysql://root:root@localhost:3306/legacy_db - - - diff --git a/README.md b/README.md index 023dd012..74ebfe7f 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ for the first time. * Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older versions. * Responsive design: You can use Part-DB on your PC, your tablet, and your smartphone using the same interface. -* MySQL and SQLite are supported as database backends +* MySQL, SQLite and PostgreSQL are supported as database backends * Support for rich text descriptions and comments in parts * Support for multiple currencies and automatic update of exchange rates supported * Powerful search and filter function, including parametric search (search for parts according to some specifications) @@ -74,10 +74,10 @@ Part-DB is also used by small companies and universities for managing their inve ## Requirements * A **web server** (like Apache2 or nginx) that is capable of - running [Symfony 5](https://symfony.com/doc/current/reference/requirements.html), + running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html), this includes a minimum PHP version of **PHP 8.1** -* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.2.2) database server if you do not want to use SQLite. -* Shell access to your server is highly suggested! +* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite. +* Shell access to your server is highly recommended! * For building the client-side assets **yarn** and **nodejs** (>= 18.0) is needed. ## Installation diff --git a/VERSION b/VERSION index f8f4f03b..a4ab692a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.12.1 +1.13.0-dev diff --git a/composer.json b/composer.json index 3d25300f..b976c84b 100644 --- a/composer.json +++ b/composer.json @@ -10,17 +10,17 @@ "ext-intl": "*", "ext-json": "*", "ext-mbstring": "*", + "amphp/http-client": "^5.1", "api-platform/core": "^3.1", "beberlei/doctrineextensions": "^1.2", "brick/math": "0.12.1 as 0.11.0", "composer/ca-bundle": "^1.3", "composer/package-versions-deprecated": "^1.11.99.5", - "doctrine/annotations": "1.14.3", "doctrine/data-fixtures": "^1.6.6", - "doctrine/dbal": "^3.4.6", + "doctrine/dbal": "^4.0.0", "doctrine/doctrine-bundle": "^2.0", "doctrine/doctrine-migrations-bundle": "^3.0", - "doctrine/orm": "^2.16", + "doctrine/orm": "^3.2.0", "dompdf/dompdf": "^v3.0.0", "erusev/parsedown": "^1.7", "florianv/swap": "^4.0", @@ -104,8 +104,7 @@ "phpstan/phpstan-strict-rules": "^1.5", "phpstan/phpstan-symfony": "^1.1.7", "phpunit/phpunit": "^9.5", - "psalm/plugin-symfony": "^v5.0.1", - "rector/rector": "^0.18.0", + "rector/rector": "^1.1.1", "roave/security-advisories": "dev-latest", "symfony/browser-kit": "6.4.*", "symfony/css-selector": "6.4.*", @@ -114,8 +113,7 @@ "symfony/phpunit-bridge": "6.4.*", "symfony/stopwatch": "6.4.*", "symfony/web-profiler-bundle": "6.4.*", - "symplify/easy-coding-standard": "^12.0", - "vimeo/psalm": "^5.6.0" + "symplify/easy-coding-standard": "^12.0" }, "suggest": { "ext-bcmath": "Used to improve price calculation performance", diff --git a/composer.lock b/composer.lock index 0db57d9b..aead05d7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,1028 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "97361e6ad226561d1407dbafd8303465", + "content-hash": "b9066d88608e5e8c968f4f297ecd7a80", "packages": [ { - "name": "api-platform/core", - "version": "v3.3.5", + "name": "amphp/amp", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/api-platform/core.git", - "reference": "b5a93fb0bb855273aabb0807505ba61b68813246" + "url": "https://github.com/amphp/amp.git", + "reference": "138801fb68cfc9c329da8a7b39d01ce7291ee4b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/b5a93fb0bb855273aabb0807505ba61b68813246", - "reference": "b5a93fb0bb855273aabb0807505ba61b68813246", + "url": "https://api.github.com/repos/amphp/amp/zipball/138801fb68cfc9c329da8a7b39d01ce7291ee4b0", + "reference": "138801fb68cfc9c329da8a7b39d01ce7291ee4b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Future/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v3.0.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-05-10T21:37:46+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/daa00f2efdbd71565bf64ffefa89e37542addf93", + "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/parser": "^1.1", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2.3" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.22.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v2.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-02-17T04:49:38+00:00" + }, + { + "name": "amphp/cache", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/cache.git", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Cache\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A fiber-aware cache API based on Amp and Revolt.", + "homepage": "https://amphp.org/cache", + "support": { + "issues": "https://github.com/amphp/cache/issues", + "source": "https://github.com/amphp/cache/tree/v2.0.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:38:06+00:00" + }, + { + "name": "amphp/dns", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/dns.git", + "reference": "758266b0ea7470e2e42cd098493bc6d6c7100cf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/dns/zipball/758266b0ea7470e2e42cd098493bc6d6c7100cf7", + "reference": "758266b0ea7470e2e42cd098493bc6d6c7100cf7", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/windows-registry": "^1.0.1", + "daverandom/libdns": "^2.0.2", + "ext-filter": "*", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Dns\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Wright", + "email": "addr@daverandom.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Async DNS resolution for Amp.", + "homepage": "https://github.com/amphp/dns", + "keywords": [ + "amp", + "amphp", + "async", + "client", + "dns", + "resolve" + ], + "support": { + "issues": "https://github.com/amphp/dns/issues", + "source": "https://github.com/amphp/dns/tree/v2.2.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-06-02T19:54:12+00:00" + }, + { + "name": "amphp/hpack", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/hpack.git", + "reference": "4f293064b15682a2b178b1367ddf0b8b5feb0239" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/hpack/zipball/4f293064b15682a2b178b1367ddf0b8b5feb0239", + "reference": "4f293064b15682a2b178b1367ddf0b8b5feb0239", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "http2jp/hpack-test-case": "^1", + "nikic/php-fuzzer": "^0.0.10", + "phpunit/phpunit": "^7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\Http\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Bob Weinand" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "HTTP/2 HPack implementation.", + "homepage": "https://github.com/amphp/hpack", + "keywords": [ + "headers", + "hpack", + "http-2" + ], + "support": { + "issues": "https://github.com/amphp/hpack/issues", + "source": "https://github.com/amphp/hpack/tree/v3.2.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:00:16+00:00" + }, + { + "name": "amphp/http", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/http.git", + "reference": "fe6b4dd50c1e70caf823092398074b5082e1d6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/http/zipball/fe6b4dd50c1e70caf823092398074b5082e1d6da", + "reference": "fe6b4dd50c1e70caf823092398074b5082e1d6da", + "shasum": "" + }, + "require": { + "amphp/hpack": "^3", + "amphp/parser": "^1.1", + "league/uri-components": "^2.4.2 | ^7.1", + "php": ">=8.1", + "psr/http-message": "^1 | ^2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "league/uri": "^6.8 | ^7.1", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/constants.php" + ], + "psr-4": { + "Amp\\Http\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Basic HTTP primitives which can be shared by servers and clients.", + "support": { + "issues": "https://github.com/amphp/http/issues", + "source": "https://github.com/amphp/http/tree/v2.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-03T18:00:53+00:00" + }, + { + "name": "amphp/http-client", + "version": "v5.1.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/http-client.git", + "reference": "483df9a856625fe4406c7fb6a64d6787105bd184" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/http-client/zipball/483df9a856625fe4406c7fb6a64d6787105bd184", + "reference": "483df9a856625fe4406c7fb6a64d6787105bd184", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/hpack": "^3", + "amphp/http": "^2", + "amphp/pipeline": "^1", + "amphp/socket": "^2", + "amphp/sync": "^2", + "league/uri": "^6 | ^7", + "league/uri-components": "^2.4 | ^7", + "league/uri-interfaces": "^7.1", + "php": ">=8.1", + "psr/http-message": "^1 | ^2", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/file": "^3", + "amphp/http-server": "^3", + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "ext-json": "*", + "kelunik/link-header-rfc5988": "^1", + "laminas/laminas-diactoros": "^2.3", + "phpunit/phpunit": "^9", + "psalm/phar": "~5.23" + }, + "suggest": { + "amphp/file": "Required for file request bodies and HTTP archive logging", + "ext-json": "Required for logging HTTP archives", + "ext-zlib": "Allows using compression for response bodies." + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\Http\\Client\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "An advanced async HTTP client library for PHP, enabling efficient, non-blocking, and concurrent requests and responses.", + "homepage": "https://amphp.org/http-client", + "keywords": [ + "async", + "client", + "concurrent", + "http", + "non-blocking", + "rest" + ], + "support": { + "issues": "https://github.com/amphp/http-client/issues", + "source": "https://github.com/amphp/http-client/tree/v5.1.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-21T16:40:36+00:00" + }, + { + "name": "amphp/parser", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parser.git", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", + "parser", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:16:53+00:00" + }, + { + "name": "amphp/pipeline", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/pipeline.git", + "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/f1c2ce35d27ae86ead018adb803eccca7421dd9b", + "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Pipeline\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Asynchronous iterators and operators.", + "homepage": "https://amphp.org/pipeline", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "iterator", + "non-blocking" + ], + "support": { + "issues": "https://github.com/amphp/pipeline/issues", + "source": "https://github.com/amphp/pipeline/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-10T14:48:16+00:00" + }, + { + "name": "amphp/process", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/process.git", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Process\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A fiber-aware process manager based on Amp and Revolt.", + "homepage": "https://amphp.org/process", + "support": { + "issues": "https://github.com/amphp/process/issues", + "source": "https://github.com/amphp/process/tree/v2.0.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:13:44+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Serialization\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/master" + }, + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/socket", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/socket.git", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/dns": "^2", + "ext-openssl": "*", + "kelunik/certificate": "^1.1", + "league/uri": "^6.5 | ^7", + "league/uri-interfaces": "^2.3 | ^7", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "amphp/process": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php", + "src/SocketAddress/functions.php" + ], + "psr-4": { + "Amp\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.", + "homepage": "https://github.com/amphp/socket", + "keywords": [ + "amp", + "async", + "encryption", + "non-blocking", + "sockets", + "tcp", + "tls" + ], + "support": { + "issues": "https://github.com/amphp/socket/issues", + "source": "https://github.com/amphp/socket/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-21T14:33:03+00:00" + }, + { + "name": "amphp/sync", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/375ef5b54a0d12c38e12728dde05a55e30f2fbec", + "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Sync\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], + "support": { + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v2.2.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-12T01:00:01+00:00" + }, + { + "name": "amphp/windows-registry", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/windows-registry.git", + "reference": "0d569e8f256cca974e3842b6e78b4e434bf98306" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/windows-registry/zipball/0d569e8f256cca974e3842b6e78b4e434bf98306", + "reference": "0d569e8f256cca974e3842b6e78b4e434bf98306", + "shasum": "" + }, + "require": { + "amphp/byte-stream": "^2", + "amphp/process": "^2", + "php": ">=8.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\WindowsRegistry\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Windows Registry Reader.", + "support": { + "issues": "https://github.com/amphp/windows-registry/issues", + "source": "https://github.com/amphp/windows-registry/tree/v1.0.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-01-30T23:01:51+00:00" + }, + { + "name": "api-platform/core", + "version": "v3.3.6", + "source": { + "type": "git", + "url": "https://github.com/api-platform/core.git", + "reference": "c7f25dc6c7ca82ade7f8a0e5d63393b25b4a2db5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/core/zipball/c7f25dc6c7ca82ade7f8a0e5d63393b25b4a2db5", + "reference": "c7f25dc6c7ca82ade7f8a0e5d63393b25b4a2db5", "shasum": "" }, "require": { @@ -190,9 +1198,9 @@ ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v3.3.5" + "source": "https://github.com/api-platform/core/tree/v3.3.6" }, - "time": "2024-05-29T05:48:47+00:00" + "time": "2024-06-14T07:14:21+00:00" }, { "name": "beberlei/assert", @@ -533,31 +1541,75 @@ "time": "2022-01-17T14:14:24+00:00" }, { - "name": "doctrine/annotations", - "version": "1.14.3", + "name": "daverandom/libdns", + "version": "v2.1.0", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af" + "url": "https://github.com/DaveRandom/LibDNS.git", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", "shasum": "" }, "require": { - "doctrine/lexer": "^1 || ^2", + "ext-ctype": "*", + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "Required for IDN support" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "LibDNS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DNS protocol implementation written in pure PHP", + "keywords": [ + "dns" + ], + "support": { + "issues": "https://github.com/DaveRandom/LibDNS/issues", + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" + }, + "time": "2024-04-12T12:12:48+00:00" + }, + { + "name": "doctrine/annotations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2 || ^3", "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", + "php": "^7.2 || ^8.0", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/cache": "^5.4 || ^6", "vimeo/psalm": "^4.10" }, "suggest": { @@ -604,9 +1656,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.3" + "source": "https://github.com/doctrine/annotations/tree/2.0.1" }, - "time": "2023-02-01T09:20:38+00:00" + "time": "2023-02-02T22:02:53+00:00" }, { "name": "doctrine/cache", @@ -787,97 +1839,6 @@ ], "time": "2024-04-18T06:56:21+00:00" }, - { - "name": "doctrine/common", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", - "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^2.0 || ^3.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0 || ^10.0", - "doctrine/collections": "^1", - "phpstan/phpstan": "^1.4.1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^6.1", - "vimeo/psalm": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", - "homepage": "https://www.doctrine-project.org/projects/common.html", - "keywords": [ - "common", - "doctrine", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.4.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", - "type": "tidelift" - } - ], - "time": "2024-04-16T13:35:33+00:00" - }, { "name": "doctrine/data-fixtures", "version": "1.7.0", @@ -964,47 +1925,42 @@ }, { "name": "doctrine/dbal", - "version": "3.8.4", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "b05e48a745f722801f55408d0dbd8003b403dbbd" + "reference": "50fda19f80724b55ff770bb4ff352407008e63c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/b05e48a745f722801f55408d0dbd8003b403dbbd", - "reference": "b05e48a745f722801f55408d0dbd8003b403dbbd", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/50fda19f80724b55ff770bb4ff352407008e63c5", + "reference": "50fda19f80724b55ff770bb4ff352407008e63c5", "shasum": "" }, "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1|^2", - "php": "^7.4 || ^8.0", + "php": "^8.1", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, "require-dev": { "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.10.58", - "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.16", - "psalm/plugin-phpunit": "0.18.4", + "jetbrains/phpstorm-stubs": "2023.2", + "phpstan/phpstan": "1.11.5", + "phpstan/phpstan-phpunit": "1.4.0", + "phpstan/phpstan-strict-rules": "^1.6", + "phpunit/phpunit": "10.5.22", + "psalm/plugin-phpunit": "0.19.0", "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.9.0", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0", - "vimeo/psalm": "4.30.0" + "squizlabs/php_codesniffer": "3.10.1", + "symfony/cache": "^6.3.8|^7.0", + "symfony/console": "^5.4|^6.3|^7.0", + "vimeo/psalm": "5.24.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, - "bin": [ - "bin/doctrine-dbal" - ], "type": "library", "autoload": { "psr-4": { @@ -1057,7 +2013,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.4" + "source": "https://github.com/doctrine/dbal/tree/4.0.4" }, "funding": [ { @@ -1073,7 +2029,7 @@ "type": "tidelift" } ], - "time": "2024-04-25T07:04:44+00:00" + "time": "2024-06-19T11:57:23+00:00" }, { "name": "doctrine/deprecations", @@ -1588,28 +2544,27 @@ }, { "name": "doctrine/lexer", - "version": "2.1.1", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.21" + "vimeo/psalm": "^5.21" }, "type": "library", "autoload": { @@ -1646,7 +2601,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.1" + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, "funding": [ { @@ -1662,7 +2617,7 @@ "type": "tidelift" } ], - "time": "2024-02-05T11:35:39+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "doctrine/migrations", @@ -1768,61 +2723,48 @@ }, { "name": "doctrine/orm", - "version": "2.19.5", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "94986af28452da42a46a4489d1c958a2e5d710e5" + "reference": "37946d3a21ddf837c0d84f8156ee60a92102e332" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/94986af28452da42a46a4489d1c958a2e5d710e5", - "reference": "94986af28452da42a46a4489d1c958a2e5d710e5", + "url": "https://api.github.com/repos/doctrine/orm/zipball/37946d3a21ddf837c0d84f8156ee60a92102e332", + "reference": "37946d3a21ddf837c0d84f8156ee60a92102e332", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5 || ^2.1", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/collections": "^2.2", + "doctrine/dbal": "^3.8.2 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2", "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", - "doctrine/lexer": "^2 || ^3", - "doctrine/persistence": "^2.4 || ^3", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.3.1", "ext-ctype": "*", - "php": "^7.1 || ^8.0", + "php": "^8.1", "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 3.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.3.9 || ^7.0" }, "require-dev": { - "doctrine/annotations": "^1.13 || ^2", - "doctrine/coding-standard": "^9.0.2 || ^12.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "~1.4.10 || 1.10.59", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "doctrine/coding-standard": "^12.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "1.11.1", + "phpunit/phpunit": "^10.4.0", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", - "vimeo/psalm": "4.30.0 || 5.22.2" + "symfony/cache": "^5.4 || ^6.2 || ^7.0", + "vimeo/psalm": "5.24.0" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" }, - "bin": [ - "bin/doctrine" - ], "type": "library", "autoload": { "psr-4": { @@ -1863,22 +2805,22 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.19.5" + "source": "https://github.com/doctrine/orm/tree/3.2.0" }, - "time": "2024-04-30T06:49:54+00:00" + "time": "2024-05-23T14:27:52+00:00" }, { "name": "doctrine/persistence", - "version": "3.3.2", + "version": "3.3.3", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "477da35bd0255e032826f440b94b3e37f2d56f42" + "reference": "b337726451f5d530df338fc7f68dee8781b49779" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/477da35bd0255e032826f440b94b3e37f2d56f42", - "reference": "477da35bd0255e032826f440b94b3e37f2d56f42", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/b337726451f5d530df338fc7f68dee8781b49779", + "reference": "b337726451f5d530df338fc7f68dee8781b49779", "shasum": "" }, "require": { @@ -1890,15 +2832,14 @@ "doctrine/common": "<2.10" }, "require-dev": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^12", "doctrine/common": "^3.0", - "phpstan/phpstan": "1.9.4", + "phpstan/phpstan": "1.11.1", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.1", "phpunit/phpunit": "^8.5 || ^9.5", "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.30.0 || 5.3.0" + "vimeo/psalm": "4.30.0 || 5.24.0" }, "type": "library", "autoload": { @@ -1947,7 +2888,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/3.3.2" + "source": "https://github.com/doctrine/persistence/tree/3.3.3" }, "funding": [ { @@ -1963,7 +2904,7 @@ "type": "tidelift" } ], - "time": "2024-03-12T14:54:36+00:00" + "time": "2024-06-20T10:14:30+00:00" }, { "name": "doctrine/sql-formatter", @@ -2634,7 +3575,7 @@ }, { "name": "gregwar/captcha-bundle", - "version": "v2.2.1", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/Gregwar/CaptchaBundle.git", @@ -2695,7 +3636,7 @@ ], "support": { "issues": "https://github.com/Gregwar/CaptchaBundle/issues", - "source": "https://github.com/Gregwar/CaptchaBundle/tree/v2.2.1" + "source": "https://github.com/Gregwar/CaptchaBundle/tree/v2.3.0" }, "time": "2024-06-06T13:14:57+00:00" }, @@ -3500,6 +4441,64 @@ ], "time": "2023-05-21T07:57:08+00:00" }, + { + "name": "kelunik/certificate", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/kelunik/certificate.git", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=7.0" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^6 | 7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Kelunik\\Certificate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Access certificate details and transform between different formats.", + "keywords": [ + "DER", + "certificate", + "certificates", + "openssl", + "pem", + "x509" + ], + "support": { + "issues": "https://github.com/kelunik/certificate/issues", + "source": "https://github.com/kelunik/certificate/tree/v1.1.3" + }, + "time": "2023-02-03T21:26:53+00:00" + }, { "name": "knpuniversity/oauth2-client-bundle", "version": "v2.18.1", @@ -3562,16 +4561,16 @@ }, { "name": "laminas/laminas-code", - "version": "4.13.0", + "version": "4.14.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-code.git", - "reference": "7353d4099ad5388e84737dd16994316a04f48dbf" + "reference": "562e02b7d85cb9142b5116cc76c4c7c162a11a1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/7353d4099ad5388e84737dd16994316a04f48dbf", - "reference": "7353d4099ad5388e84737dd16994316a04f48dbf", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/562e02b7d85cb9142b5116cc76c4c7c162a11a1c", + "reference": "562e02b7d85cb9142b5116cc76c4c7c162a11a1c", "shasum": "" }, "require": { @@ -3583,7 +4582,7 @@ "laminas/laminas-coding-standard": "^2.5.0", "laminas/laminas-stdlib": "^3.17.0", "phpunit/phpunit": "^10.3.3", - "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-phpunit": "^0.19.0", "vimeo/psalm": "^5.15.0" }, "suggest": { @@ -3621,7 +4620,7 @@ "type": "community_bridge" } ], - "time": "2023-10-18T10:00:55+00:00" + "time": "2024-06-17T08:50:25+00:00" }, { "name": "lcobucci/clock", @@ -4004,17 +5003,273 @@ "time": "2023-04-16T18:19:15+00:00" }, { - "name": "liip/imagine-bundle", - "version": "2.12.3", + "name": "league/uri", + "version": "7.4.1", "source": { "type": "git", - "url": "https://github.com/liip/LiipImagineBundle.git", - "reference": "fa2baa6262bb74038f01ac746dc3e2a8bc441e09" + "url": "https://github.com/thephpleague/uri.git", + "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/fa2baa6262bb74038f01ac746dc3e2a8bc441e09", - "reference": "fa2baa6262bb74038f01ac746dc3e2a8bc441e09", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/bedb6e55eff0c933668addaa7efa1e1f2c417cc4", + "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.3", + "php": "^8.1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.4.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-03-23T07:42:40+00:00" + }, + { + "name": "league/uri-components", + "version": "7.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-components.git", + "reference": "b94fe4097885f1b51c4c3fcb78025fbbabbb5d9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/b94fe4097885f1b51c4c3fcb78025fbbabbb5d9d", + "reference": "b94fe4097885f1b51c4c3fcb78025fbbabbb5d9d", + "shasum": "" + }, + "require": { + "league/uri": "^7.3", + "php": "^8.1" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-mbstring": "to use the sorting algorithm of URLSearchParams", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI components manipulation library", + "homepage": "http://uri.thephpleague.com", + "keywords": [ + "authority", + "components", + "fragment", + "host", + "middleware", + "modifier", + "path", + "port", + "query", + "rfc3986", + "scheme", + "uri", + "url", + "userinfo" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-components/tree/7.4.1" + }, + "funding": [ + { + "url": "https://github.com/nyamsprod", + "type": "github" + } + ], + "time": "2024-03-23T07:42:40+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "8d43ef5c841032c87e2de015972c06f3865ef718" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/8d43ef5c841032c87e2de015972c06f3865ef718", + "reference": "8d43ef5c841032c87e2de015972c06f3865ef718", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-factory": "^1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common interfaces and classes for URI representation and interaction", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.4.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-03-23T07:42:40+00:00" + }, + { + "name": "liip/imagine-bundle", + "version": "2.13.0", + "source": { + "type": "git", + "url": "https://github.com/liip/LiipImagineBundle.git", + "reference": "0c95437a7e1a0561004c8f7f7a412884f9f12438" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/0c95437a7e1a0561004c8f7f7a412884f9f12438", + "reference": "0c95437a7e1a0561004c8f7f7a412884f9f12438", "shasum": "" }, "require": { @@ -4040,6 +5295,7 @@ "phpstan/phpstan": "^1.10.0", "psr/cache": "^1.0|^2.0|^3.0", "psr/log": "^1.0", + "symfony/asset": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/browser-kit": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/cache": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/console": "^3.4|^4.4|^5.3|^6.0|^7.0", @@ -4061,10 +5317,12 @@ "ext-gd": "required to use gd driver", "ext-gmagick": "required to use gmagick driver", "ext-imagick": "required to use imagick driver", + "ext-json": "required to read JSON manifest versioning", "ext-mongodb": "required for mongodb components", "league/flysystem": "required to use FlySystem data loader or cache resolver", "monolog/monolog": "A psr/log compatible logger is required to enable logging", "rokka/imagine-vips": "required to use 'vips' driver", + "symfony/asset": "If you want to use asset versioning", "symfony/messenger": "If you like to process images in background", "symfony/templating": "required to use deprecated Templating component instead of Twig" }, @@ -4102,9 +5360,9 @@ ], "support": { "issues": "https://github.com/liip/LiipImagineBundle/issues", - "source": "https://github.com/liip/LiipImagineBundle/tree/2.12.3" + "source": "https://github.com/liip/LiipImagineBundle/tree/2.13.0" }, - "time": "2024-05-16T08:51:30+00:00" + "time": "2024-06-19T09:35:35+00:00" }, { "name": "lorenzo/pinky", @@ -4600,25 +5858,27 @@ }, { "name": "nikic/php-parser", - "version": "v4.19.1", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.1" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -4626,7 +5886,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -4650,9 +5910,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" }, - "time": "2024-03-17T08:10:35+00:00" + "time": "2024-03-05T20:51:40+00:00" }, { "name": "nikolaposa/version", @@ -5506,31 +6766,31 @@ }, { "name": "php-translation/extractor", - "version": "2.1.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/php-translation/extractor.git", - "reference": "09ad2f3654e6badb95a739b0284f5785531f7c8d" + "reference": "44d869006c1131f0ebe1d358fd691621eacc3e45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-translation/extractor/zipball/09ad2f3654e6badb95a739b0284f5785531f7c8d", - "reference": "09ad2f3654e6badb95a739b0284f5785531f7c8d", + "url": "https://api.github.com/repos/php-translation/extractor/zipball/44d869006c1131f0ebe1d358fd691621eacc3e45", + "reference": "44d869006c1131f0ebe1d358fd691621eacc3e45", "shasum": "" }, "require": { "doctrine/annotations": "^1.7 || ^2.0", - "nikic/php-parser": "^3.0 || ^4.0", - "php": "^7.2 || ^8.0", - "symfony/finder": "^3.4 || ^4.4 || ^5.0 || ^6.0", + "nikic/php-parser": "^4.0 || ^5.0", + "php": "^8.1", + "symfony/finder": "^5.4 || ^6.4 || ^7.0", "twig/twig": "^2.0 || ^3.0" }, "require-dev": { "knplabs/knp-menu": "^3.1", - "symfony/phpunit-bridge": "^5.0 || ^6.0", - "symfony/translation": "^3.4 || ^4.4 || ^5.0 || ^6.0", - "symfony/twig-bridge": "^3.4 || ^4.4 || ^5.0 || ^6.0", - "symfony/validator": "^3.4 || ^4.4 || ^5.0 || ^6.0" + "symfony/phpunit-bridge": "^5.4 || ^6.4 || ^7.0", + "symfony/translation": "^5.4 || ^6.4 || ^7.0", + "symfony/twig-bridge": "^5.4 || ^6.4 || ^7.0", + "symfony/validator": "^5.4 || ^6.4 || ^7.0" }, "type": "library", "extra": { @@ -5556,9 +6816,9 @@ "description": "Extract translations form the source code", "support": { "issues": "https://github.com/php-translation/extractor/issues", - "source": "https://github.com/php-translation/extractor/tree/2.1.1" + "source": "https://github.com/php-translation/extractor/tree/2.2.0" }, - "time": "2023-03-28T11:37:22+00:00" + "time": "2024-06-11T21:40:32+00:00" }, { "name": "php-translation/symfony-bundle", @@ -6471,6 +7731,78 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "revolt/event-loop", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "25de49af7223ba039f64da4ae9a28ec2d10d0254" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/25de49af7223ba039f64da4ae9a28ec2d10d0254", + "reference": "25de49af7223ba039f64da4ae9a28ec2d10d0254", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.6" + }, + "time": "2023-11-30T05:34:44+00:00" + }, { "name": "robrichards/xmlseclibs", "version": "3.1.1", @@ -7220,26 +8552,28 @@ }, { "name": "spomky-labs/otphp", - "version": "11.2.2", + "version": "11.3.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/otphp.git", - "reference": "b737d1c6330beae7c0bc225d3e848805b352fe42" + "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/b737d1c6330beae7c0bc225d3e848805b352fe42", - "reference": "b737d1c6330beae7c0bc225d3e848805b352fe42", + "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33", + "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33", "shasum": "" }, "require": { "ext-mbstring": "*", - "paragonie/constant_time_encoding": "^2.0", - "php": "^8.1" + "paragonie/constant_time_encoding": "^2.0 || ^3.0", + "php": ">=8.1", + "psr/clock": "^1.0", + "symfony/deprecation-contracts": "^3.2" }, "require-dev": { "ekino/phpstan-banned-code": "^1.0", - "infection/infection": "^0.26|^0.27|^0.28", + "infection/infection": "^0.26|^0.27|^0.28|^0.29", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/phpstan": "^1.0", "phpstan/phpstan-deprecation-rules": "^1.0", @@ -7247,7 +8581,7 @@ "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5.26|^10.0|^11.0", "qossmic/deptrac-shim": "^1.0", - "rector/rector": "1.0", + "rector/rector": "^1.0", "symfony/phpunit-bridge": "^6.1|^7.0", "symplify/easy-coding-standard": "^12.0" }, @@ -7284,7 +8618,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/otphp/issues", - "source": "https://github.com/Spomky-Labs/otphp/tree/11.2.2" + "source": "https://github.com/Spomky-Labs/otphp/tree/11.3.0" }, "funding": [ { @@ -7296,7 +8630,7 @@ "type": "patreon" } ], - "time": "2024-04-15T07:35:15+00:00" + "time": "2024-06-12T11:22:32+00:00" }, { "name": "spomky-labs/pki-framework", @@ -9960,16 +11294,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { @@ -10019,7 +11353,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" }, "funding": [ { @@ -10035,20 +11369,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", "shasum": "" }, "require": { @@ -10097,7 +11431,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" }, "funding": [ { @@ -10113,20 +11447,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "07094a28851a49107f3ab4f9120ca2975a64b6e1" + "reference": "e76343c631b453088e2260ac41dfebe21954de81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/07094a28851a49107f3ab4f9120ca2975a64b6e1", - "reference": "07094a28851a49107f3ab4f9120ca2975a64b6e1", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/e76343c631b453088e2260ac41dfebe21954de81", + "reference": "e76343c631b453088e2260ac41dfebe21954de81", "shasum": "" }, "require": { @@ -10181,7 +11515,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.30.0" }, "funding": [ { @@ -10197,20 +11531,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:12:16+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "a287ed7475f85bf6f61890146edbc932c0fff919" + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a287ed7475f85bf6f61890146edbc932c0fff919", - "reference": "a287ed7475f85bf6f61890146edbc932c0fff919", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", "shasum": "" }, "require": { @@ -10265,7 +11599,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0" }, "funding": [ { @@ -10281,20 +11615,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", "shasum": "" }, "require": { @@ -10346,7 +11680,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" }, "funding": [ { @@ -10362,20 +11696,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -10426,7 +11760,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -10442,20 +11776,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25" + "reference": "10112722600777e02d2745716b70c5db4ca70442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/861391a8da9a04cbad2d232ddd9e4893220d6e25", - "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442", + "reference": "10112722600777e02d2745716b70c5db4ca70442", "shasum": "" }, "require": { @@ -10499,7 +11833,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0" }, "funding": [ { @@ -10515,20 +11849,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { @@ -10579,7 +11913,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -10595,20 +11929,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-php82", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "559d488c38784112c78b9bf17c5ce8366a265643" + "reference": "77ff49780f56906788a88974867ed68bc49fae5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/559d488c38784112c78b9bf17c5ce8366a265643", - "reference": "559d488c38784112c78b9bf17c5ce8366a265643", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/77ff49780f56906788a88974867ed68bc49fae5b", + "reference": "77ff49780f56906788a88974867ed68bc49fae5b", "shasum": "" }, "require": { @@ -10655,7 +11989,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.30.0" }, "funding": [ { @@ -10671,25 +12005,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "86fcae159633351e5fd145d1c47de6c528f8caff" + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff", - "reference": "86fcae159633351e5fd145d1c47de6c528f8caff", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-php80": "^1.14" + "php": ">=7.1" }, "type": "library", "extra": { @@ -10732,7 +12065,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" }, "funding": [ { @@ -10748,20 +12081,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-19T12:35:24+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853" + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/3abdd21b0ceaa3000ee950097bc3cf9efc137853", - "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", "shasum": "" }, "require": { @@ -10811,7 +12144,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" }, "funding": [ { @@ -10827,7 +12160,7 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/process", @@ -11976,16 +13309,16 @@ }, { "name": "symfony/stimulus-bundle", - "version": "v2.17.0", + "version": "v2.18.1", "source": { "type": "git", "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "b828a32fe9f75500d26b563cc01874657162c413" + "reference": "017b60e036c366c8ce0e77864d5aabab436ad73d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/b828a32fe9f75500d26b563cc01874657162c413", - "reference": "b828a32fe9f75500d26b563cc01874657162c413", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/017b60e036c366c8ce0e77864d5aabab436ad73d", + "reference": "017b60e036c366c8ce0e77864d5aabab436ad73d", "shasum": "" }, "require": { @@ -12025,7 +13358,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.17.0" + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.18.1" }, "funding": [ { @@ -12041,7 +13374,7 @@ "type": "tidelift" } ], - "time": "2024-04-21T10:23:35+00:00" + "time": "2024-06-11T13:21:54+00:00" }, { "name": "symfony/stopwatch", @@ -12633,16 +13966,16 @@ }, { "name": "symfony/ux-translator", - "version": "v2.17.0", + "version": "v2.18.1", "source": { "type": "git", "url": "https://github.com/symfony/ux-translator.git", - "reference": "93ad2ca9725e4eb66d64f909c321cc16fafc7b15" + "reference": "e17e0b5a472b6452bfee17ba38be44c19c03be7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-translator/zipball/93ad2ca9725e4eb66d64f909c321cc16fafc7b15", - "reference": "93ad2ca9725e4eb66d64f909c321cc16fafc7b15", + "url": "https://api.github.com/repos/symfony/ux-translator/zipball/e17e0b5a472b6452bfee17ba38be44c19c03be7a", + "reference": "e17e0b5a472b6452bfee17ba38be44c19c03be7a", "shasum": "" }, "require": { @@ -12689,7 +14022,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/ux-translator/tree/v2.17.0" + "source": "https://github.com/symfony/ux-translator/tree/v2.18.1" }, "funding": [ { @@ -12705,20 +14038,20 @@ "type": "tidelift" } ], - "time": "2024-04-19T06:36:45+00:00" + "time": "2024-06-10T13:33:39+00:00" }, { "name": "symfony/ux-turbo", - "version": "v2.17.0", + "version": "v2.18.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "7093e20d7ca599902a7d1bf4d831849fd78befdb" + "reference": "e447231ddcc09ab68d29047f47d31a524837dc7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/7093e20d7ca599902a7d1bf4d831849fd78befdb", - "reference": "7093e20d7ca599902a7d1bf4d831849fd78befdb", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/e447231ddcc09ab68d29047f47d31a524837dc7a", + "reference": "e447231ddcc09ab68d29047f47d31a524837dc7a", "shasum": "" }, "require": { @@ -12729,6 +14062,7 @@ "symfony/flex": "<1.13" }, "require-dev": { + "dbrekelmans/bdi": "dev-main", "doctrine/doctrine-bundle": "^2.4.3", "doctrine/orm": "^2.8 | 3.0", "phpstan/phpstan": "^1.10", @@ -12785,7 +14119,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.17.0" + "source": "https://github.com/symfony/ux-turbo/tree/v2.18.0" }, "funding": [ { @@ -12801,7 +14135,7 @@ "type": "tidelift" } ], - "time": "2024-04-22T13:58:54+00:00" + "time": "2024-06-01T17:56:14+00:00" }, { "name": "symfony/validator", @@ -14221,16 +15555,16 @@ }, { "name": "web-auth/metadata-service", - "version": "4.8.6", + "version": "4.8.7", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-metadata-service.git", - "reference": "fb7c1f107639285fab90f870aab38360252c82f5" + "reference": "64b65c91c0e4e9a299abab3f83f6f4f20c058f0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/fb7c1f107639285fab90f870aab38360252c82f5", - "reference": "fb7c1f107639285fab90f870aab38360252c82f5", + "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/64b65c91c0e4e9a299abab3f83f6f4f20c058f0a", + "reference": "64b65c91c0e4e9a299abab3f83f6f4f20c058f0a", "shasum": "" }, "require": { @@ -14289,7 +15623,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.8.6" + "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.8.7" }, "funding": [ { @@ -14301,11 +15635,11 @@ "type": "patreon" } ], - "time": "2024-03-13T07:16:02+00:00" + "time": "2024-06-15T07:18:26+00:00" }, { "name": "web-auth/webauthn-lib", - "version": "4.8.6", + "version": "4.8.7", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", @@ -14375,7 +15709,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/4.8.6" + "source": "https://github.com/web-auth/webauthn-lib/tree/4.8.7" }, "funding": [ { @@ -14391,7 +15725,7 @@ }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "4.8.6", + "version": "4.8.7", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", @@ -14460,7 +15794,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.8.6" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.8.7" }, "funding": [ { @@ -14688,384 +16022,6 @@ } ], "packages-dev": [ - { - "name": "amphp/amp", - "version": "v2.6.4", - "source": { - "type": "git", - "url": "https://github.com/amphp/amp.git", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^7 | ^8 | ^9", - "react/promise": "^2", - "vimeo/psalm": "^3.12" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "files": [ - "lib/functions.php", - "lib/Internal/functions.php" - ], - "psr-4": { - "Amp\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" - }, - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Bob Weinand", - "email": "bobwei9@hotmail.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A non-blocking concurrency framework for PHP applications.", - "homepage": "https://amphp.org/amp", - "keywords": [ - "async", - "asynchronous", - "awaitable", - "concurrency", - "event", - "event-loop", - "future", - "non-blocking", - "promise" - ], - "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.4" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2024-03-21T18:52:26+00:00" - }, - { - "name": "amphp/byte-stream", - "version": "v1.8.2", - "source": { - "type": "git", - "url": "https://github.com/amphp/byte-stream.git", - "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", - "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", - "shasum": "" - }, - "require": { - "amphp/amp": "^2", - "php": ">=7.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1.4", - "friendsofphp/php-cs-fixer": "^2.3", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6 || ^7 || ^8", - "psalm/phar": "^3.11.4" - }, - "type": "library", - "autoload": { - "files": [ - "lib/functions.php" - ], - "psr-4": { - "Amp\\ByteStream\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "https://amphp.org/byte-stream", - "keywords": [ - "amp", - "amphp", - "async", - "io", - "non-blocking", - "stream" - ], - "support": { - "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2024-04-13T18:00:56+00:00" - }, - { - "name": "composer/pcre", - "version": "3.1.4", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "04229f163664973f68f38f6f73d917799168ef24" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24", - "reference": "04229f163664973f68f38f6f73d917799168ef24", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.4" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-27T13:40:54+00:00" - }, - { - "name": "composer/semver", - "version": "3.4.0", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-08-31T09:50:34+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-06T16:37:16+00:00" - }, { "name": "dama/doctrine-test-bundle", "version": "v8.2.0", @@ -15133,43 +16089,6 @@ }, "time": "2024-05-28T15:41:06+00:00" }, - { - "name": "dnoegel/php-xdg-base-dir", - "version": "v0.1.1", - "source": { - "type": "git", - "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "XdgBaseDir\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "implementation of xdg base directory specification for php", - "support": { - "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", - "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" - }, - "time": "2019-12-04T15:06:13+00:00" - }, { "name": "doctrine/doctrine-fixtures-bundle", "version": "3.6.1", @@ -15322,180 +16241,18 @@ }, "time": "2021-11-02T08:37:34+00:00" }, - { - "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.1", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "shasum": "" - }, - "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "php": "^7.1 || ^8.0", - "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "AdvancedJsonRpc\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "A more advanced JSONRPC implementation", - "support": { - "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", - "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" - }, - "time": "2021-06-11T22:34:44+00:00" - }, - { - "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpstan/phpstan": "*", - "squizlabs/php_codesniffer": "^3.1", - "vimeo/psalm": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "LanguageServerProtocol\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "PHP classes for the Language Server Protocol", - "keywords": [ - "language", - "microsoft", - "php", - "server" - ], - "support": { - "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" - }, - "time": "2022-03-02T22:36:06+00:00" - }, - { - "name": "fidry/cpu-core-counter", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "fidry/php-cs-fixer-config": "^1.1.2", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^8.5.31 || ^9.5.26", - "webmozarts/strict-phpunit": "^7.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" - } - ], - "description": "Tiny utility to get the number of CPU cores.", - "keywords": [ - "CPU", - "core" - ], - "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" - }, - "funding": [ - { - "url": "https://github.com/theofidry", - "type": "github" - } - ], - "time": "2024-02-07T09:43:46+00:00" - }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -15503,11 +16260,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -15533,7 +16291,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -15541,58 +16299,7 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" - }, - { - "name": "netresearch/jsonmapper", - "version": "v4.4.1", - "source": { - "type": "git", - "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", - "squizlabs/php_codesniffer": "~3.5" - }, - "type": "library", - "autoload": { - "psr-0": { - "JsonMapper": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "OSL-3.0" - ], - "authors": [ - { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" - } - ], - "description": "Map nested JSON structures onto PHP classes", - "support": { - "email": "cweiske@cweiske.de", - "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" - }, - "time": "2024-01-31T06:18:54+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "phar-io/manifest", @@ -15714,16 +16421,16 @@ }, { "name": "phpstan/extension-installer", - "version": "1.3.1", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/phpstan/extension-installer.git", - "reference": "f45734bfb9984c6c56c4486b71230355f066a58a" + "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f45734bfb9984c6c56c4486b71230355f066a58a", - "reference": "f45734bfb9984c6c56c4486b71230355f066a58a", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f6b87faf9fc7978eab2f7919a8760bc9f58f9203", + "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203", "shasum": "" }, "require": { @@ -15752,22 +16459,22 @@ "description": "Composer plugin for automatic installation of PHPStan extensions", "support": { "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.3.1" + "source": "https://github.com/phpstan/extension-installer/tree/1.4.1" }, - "time": "2023-05-24T08:59:17+00:00" + "time": "2024-06-10T08:20:49+00:00" }, { "name": "phpstan/phpstan", - "version": "1.11.4", + "version": "1.11.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "9100a76ce8015b9aa7125b9171ae3a76887b6c82" + "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9100a76ce8015b9aa7125b9171ae3a76887b6c82", - "reference": "9100a76ce8015b9aa7125b9171ae3a76887b6c82", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", + "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", "shasum": "" }, "require": { @@ -15812,20 +16519,20 @@ "type": "github" } ], - "time": "2024-06-06T12:19:22+00:00" + "time": "2024-06-17T15:10:54+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "a223e357c5f153b446b8a5da57dbc1132eb7a88d" + "reference": "dd27a3e83777ba0d9e9cedfaf4ebf95ff67b271f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/a223e357c5f153b446b8a5da57dbc1132eb7a88d", - "reference": "a223e357c5f153b446b8a5da57dbc1132eb7a88d", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/dd27a3e83777ba0d9e9cedfaf4ebf95ff67b271f", + "reference": "dd27a3e83777ba0d9e9cedfaf4ebf95ff67b271f", "shasum": "" }, "require": { @@ -15882,9 +16589,9 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.4.1" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.4.3" }, - "time": "2024-05-28T15:37:29+00:00" + "time": "2024-06-08T05:48:50+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -15937,16 +16644,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.3", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "af6ae0f4b91bc080265e80776af26da3e5befb28" + "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/af6ae0f4b91bc080265e80776af26da3e5befb28", - "reference": "af6ae0f4b91bc080265e80776af26da3e5befb28", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/bca27f1701fc1a297749e6c2a1e3da4462c1a6af", + "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af", "shasum": "" }, "require": { @@ -16003,9 +16710,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.3" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.4" }, - "time": "2024-05-30T15:01:27+00:00" + "time": "2024-06-07T09:43:24+00:00" }, { "name": "phpunit/php-code-coverage", @@ -16429,88 +17136,23 @@ ], "time": "2024-04-05T04:35:58+00:00" }, - { - "name": "psalm/plugin-symfony", - "version": "v5.04", - "source": { - "type": "git", - "url": "https://github.com/psalm/psalm-plugin-symfony.git", - "reference": "2a3f81e62278f488a21b70603df4cfb845af70ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/2a3f81e62278f488a21b70603df4cfb845af70ad", - "reference": "2a3f81e62278f488a21b70603df4cfb845af70ad", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "php": "^7.4 || ^8.0", - "symfony/framework-bundle": "^5.0 || ^6.0", - "vimeo/psalm": "^5.1" - }, - "require-dev": { - "doctrine/annotations": "^1.8|^2", - "doctrine/orm": "^2.9", - "phpunit/phpunit": "~7.5 || ~9.5", - "symfony/cache-contracts": "^1.0 || ^2.0", - "symfony/console": "*", - "symfony/form": "^5.0 || ^6.0", - "symfony/messenger": "^5.0 || ^6.0", - "symfony/security-guard": "*", - "symfony/serializer": "^5.0 || ^6.0", - "symfony/validator": "*", - "twig/twig": "^2.10 || ^3.0", - "weirdan/codeception-psalm-module": "dev-master" - }, - "suggest": { - "weirdan/doctrine-psalm-plugin": "If Doctrine is used, it is recommended install this plugin" - }, - "type": "psalm-plugin", - "extra": { - "psalm": { - "pluginClass": "Psalm\\SymfonyPsalmPlugin\\Plugin" - } - }, - "autoload": { - "psr-4": { - "Psalm\\SymfonyPsalmPlugin\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Farhad Safarov", - "email": "farhad.safarov@gmail.com" - } - ], - "description": "Psalm Plugin for Symfony", - "support": { - "issues": "https://github.com/psalm/psalm-plugin-symfony/issues", - "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.04" - }, - "time": "2023-11-10T07:14:46+00:00" - }, { "name": "rector/rector", - "version": "0.18.13", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "f8011a76d36aa4f839f60f3b4f97707d97176618" + "reference": "c930cdb21294f10955ddfc31b720971e8333943d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/f8011a76d36aa4f839f60f3b4f97707d97176618", - "reference": "f8011a76d36aa4f839f60f3b4f97707d97176618", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/c930cdb21294f10955ddfc31b720971e8333943d", + "reference": "c930cdb21294f10955ddfc31b720971e8333943d", "shasum": "" }, "require": { "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.10.35" + "phpstan/phpstan": "^1.11" }, "conflict": { "rector/rector-doctrine": "*", @@ -16518,6 +17160,9 @@ "rector/rector-phpunit": "*", "rector/rector-symfony": "*" }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, "bin": [ "bin/rector" ], @@ -16540,7 +17185,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.18.13" + "source": "https://github.com/rectorphp/rector/tree/1.1.1" }, "funding": [ { @@ -16548,7 +17193,7 @@ "type": "github" } ], - "time": "2023-12-20T16:08:01+00:00" + "time": "2024-06-21T07:51:17+00:00" }, { "name": "roave/security-advisories", @@ -16556,12 +17201,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "01c19f1a89daf51e8355cc0b7e3113d5274929b5" + "reference": "64eaaecdc0e915ce201f399e4707f83155389e96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/01c19f1a89daf51e8355cc0b7e3113d5274929b5", - "reference": "01c19f1a89daf51e8355cc0b7e3113d5274929b5", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/64eaaecdc0e915ce201f399e4707f83155389e96", + "reference": "64eaaecdc0e915ce201f399e4707f83155389e96", "shasum": "" }, "conflict": { @@ -16621,6 +17266,7 @@ "bmarshall511/wordpress_zero_spam": "<5.2.13", "bolt/bolt": "<3.7.2", "bolt/core": "<=4.2", + "born05/craft-twofactorauthentication": "<3.3.4", "bottelet/flarepoint": "<2.2.1", "bref/bref": "<2.1.17", "brightlocal/phpwhois": "<=4.2.5", @@ -16652,7 +17298,7 @@ "codeigniter4/framework": "<4.4.7", "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", - "composer/composer": "<1.10.27|>=2,<2.2.23|>=2.3,<2.7", + "composer/composer": "<1.10.27|>=2,<2.2.24|>=2.3,<2.7.7", "concrete5/concrete5": "<9.2.8", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", @@ -16772,7 +17418,7 @@ "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", - "getformwork/formwork": "<1.13", + "getformwork/formwork": "<1.13.1|==2.0.0.0-beta1", "getgrav/grav": "<1.7.46", "getkirby/cms": "<4.1.1", "getkirby/kirby": "<=2.5.12", @@ -16786,7 +17432,7 @@ "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", "gree/jose": "<2.2.1", "gregwar/rst": "<1.0.3", - "grumpydictator/firefly-iii": "<6.1.7", + "grumpydictator/firefly-iii": "<6.1.17", "gugoan/economizzer": "<=0.9.0.0-beta1", "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", @@ -16843,6 +17489,7 @@ "jsdecena/laracom": "<2.0.9", "jsmitty12/phpwhois": "<5.1", "juzaweb/cms": "<=3.4", + "jweiland/events2": "<8.3.8|>=9,<9.0.6", "kazist/phpwhois": "<=4.2.6", "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", @@ -16880,7 +17527,7 @@ "lms/routes": "<2.1.1", "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", "luyadev/yii-helpers": "<1.2.1", - "magento/community-edition": "<2.4.3.0-patch3|>=2.4.4,<2.4.5", + "magento/community-edition": "<2.4.5|==2.4.5|>=2.4.5.0-patch1,<2.4.5.0-patch8|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch6|==2.4.7", "magento/core": "<=1.9.4.5", "magento/magento1ce": "<1.9.4.3-dev", "magento/magento1ee": ">=1,<1.14.4.3-dev", @@ -16913,7 +17560,7 @@ "mojo42/jirafeau": "<4.4", "mongodb/mongodb": ">=1,<1.9.2", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.3.4", + "moodle/moodle": "<4.3.5|>=4.4.0.0-beta,<4.4.1", "mos/cimage": "<0.7.19", "movim/moxl": ">=0.8,<=0.10", "movingbytes/social-network": "<=1.2.1", @@ -17106,7 +17753,7 @@ "slim/slim": "<2.6", "slub/slub-events": "<3.0.3", "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<=6.2.2", + "snipe/snipe-it": "<6.4.2", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", "spatie/browsershot": "<3.57.4", @@ -17119,8 +17766,10 @@ "statamic/cms": "<4.46|>=5.3,<5.6.2", "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<2.1.62", + "studiomitte/friendlycaptcha": "<0.1.4", "subhh/libconnect": "<7.0.8|>=8,<8.1", "sukohi/surpass": "<1", + "sulu/form-bundle": ">=2,<2.5.3", "sulu/sulu": "<1.6.44|>=2,<2.4.17|>=2.5,<2.5.13", "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", @@ -17184,7 +17833,7 @@ "thorsten/phpmyfaq": "<3.2.2", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", - "tinymce/tinymce": "<7", + "tinymce/tinymce": "<7.2", "tinymighty/wiki-seo": "<1.2.2", "titon/framework": "<9.9.99", "tobiasbg/tablepress": "<=2.0.0.0-RC1", @@ -17247,7 +17896,7 @@ "winter/wn-dusk-plugin": "<2.1", "winter/wn-system-module": "<1.2.4", "wintercms/winter": "<=1.2.3", - "woocommerce/woocommerce": "<6.6", + "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", "wp-cli/wp-cli": ">=0.12,<2.5", "wp-graphql/wp-graphql": "<=1.14.5", "wp-premium/gravityforms": "<2.4.21", @@ -17290,7 +17939,7 @@ "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", "zendframework/zend-mail": "<2.4.11|>=2.5,<2.7.2", "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-session": ">=2,<2.2.9|>=2.3,<2.3.4", "zendframework/zend-validator": ">=2.3,<2.3.6", "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", @@ -17349,7 +17998,7 @@ "type": "tidelift" } ], - "time": "2024-06-05T14:05:01+00:00" + "time": "2024-06-21T16:04:36+00:00" }, { "name": "sebastian/cli-parser", @@ -18314,74 +18963,6 @@ ], "time": "2020-09-28T06:39:44+00:00" }, - { - "name": "spatie/array-to-xml", - "version": "3.3.0", - "source": { - "type": "git", - "url": "https://github.com/spatie/array-to-xml.git", - "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f56b220fe2db1ade4c88098d83413ebdfc3bf876", - "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "php": "^8.0" - }, - "require-dev": { - "mockery/mockery": "^1.2", - "pestphp/pest": "^1.21", - "spatie/pest-plugin-snapshots": "^1.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Spatie\\ArrayToXml\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://freek.dev", - "role": "Developer" - } - ], - "description": "Convert an array to xml", - "homepage": "https://github.com/spatie/array-to-xml", - "keywords": [ - "array", - "convert", - "xml" - ], - "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.3.0" - }, - "funding": [ - { - "url": "https://spatie.be/open-source/support-us", - "type": "custom" - }, - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2024-05-01T10:20:27+00:00" - }, { "name": "symfony/browser-kit", "version": "v6.4.8", @@ -18593,16 +19174,16 @@ }, { "name": "symfony/maker-bundle", - "version": "v1.59.1", + "version": "v1.60.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "b87b1b25c607a8a50832395bc751c784946a0350" + "reference": "c305a02a22974670f359d4274c9431e1a191f559" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/b87b1b25c607a8a50832395bc751c784946a0350", - "reference": "b87b1b25c607a8a50832395bc751c784946a0350", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/c305a02a22974670f359d4274c9431e1a191f559", + "reference": "c305a02a22974670f359d4274c9431e1a191f559", "shasum": "" }, "require": { @@ -18665,7 +19246,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.59.1" + "source": "https://github.com/symfony/maker-bundle/tree/v1.60.0" }, "funding": [ { @@ -18681,7 +19262,7 @@ "type": "tidelift" } ], - "time": "2024-05-06T03:59:59+00:00" + "time": "2024-06-10T06:03:18+00:00" }, { "name": "symfony/phpunit-bridge", @@ -18849,16 +19430,16 @@ }, { "name": "symplify/easy-coding-standard", - "version": "12.2.1", + "version": "12.3.0", "source": { "type": "git", "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "095fe591b2e51fd84edd21b8c9be74402eadc50e" + "reference": "f919574aa566b4d00fd06700ca61168aafef66e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/095fe591b2e51fd84edd21b8c9be74402eadc50e", - "reference": "095fe591b2e51fd84edd21b8c9be74402eadc50e", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/f919574aa566b4d00fd06700ca61168aafef66e1", + "reference": "f919574aa566b4d00fd06700ca61168aafef66e1", "shasum": "" }, "require": { @@ -18894,7 +19475,7 @@ ], "support": { "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", - "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.2.1" + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.3.0" }, "funding": [ { @@ -18906,7 +19487,7 @@ "type": "github" } ], - "time": "2024-06-02T01:25:21+00:00" + "time": "2024-06-18T07:35:59+00:00" }, { "name": "theseer/tokenizer", @@ -18957,116 +19538,6 @@ } ], "time": "2024-03-03T12:36:25+00:00" - }, - { - "name": "vimeo/psalm", - "version": "5.24.0", - "source": { - "type": "git", - "url": "https://github.com/vimeo/psalm.git", - "reference": "462c80e31c34e58cc4f750c656be3927e80e550e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/462c80e31c34e58cc4f750c656be3927e80e550e", - "reference": "462c80e31c34e58cc4f750c656be3927e80e550e", - "shasum": "" - }, - "require": { - "amphp/amp": "^2.4.2", - "amphp/byte-stream": "^1.5", - "composer-runtime-api": "^2", - "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^2.0 || ^3.0", - "dnoegel/php-xdg-base-dir": "^0.1.1", - "ext-ctype": "*", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-tokenizer": "*", - "felixfbecker/advanced-json-rpc": "^3.1", - "felixfbecker/language-server-protocol": "^1.5.2", - "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.16", - "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", - "sebastian/diff": "^4.0 || ^5.0 || ^6.0", - "spatie/array-to-xml": "^2.17.0 || ^3.0", - "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" - }, - "conflict": { - "nikic/php-parser": "4.17.0" - }, - "provide": { - "psalm/psalm": "self.version" - }, - "require-dev": { - "amphp/phpunit-util": "^2.0", - "bamarni/composer-bin-plugin": "^1.4", - "brianium/paratest": "^6.9", - "ext-curl": "*", - "mockery/mockery": "^1.5", - "nunomaduro/mock-final-classes": "^1.1", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpdoc-parser": "^1.6", - "phpunit/phpunit": "^9.6", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.6", - "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" - }, - "suggest": { - "ext-curl": "In order to send data to shepherd", - "ext-igbinary": "^2.0.5 is required, used to serialize caching data" - }, - "bin": [ - "psalm", - "psalm-language-server", - "psalm-plugin", - "psalm-refactor", - "psalter" - ], - "type": "project", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev", - "dev-4.x": "4.x-dev", - "dev-3.x": "3.x-dev", - "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psalm\\": "src/Psalm/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matthew Brown" - } - ], - "description": "A static analysis tool for finding errors in PHP applications", - "keywords": [ - "code", - "inspection", - "php", - "static analysis" - ], - "support": { - "docs": "https://psalm.dev/docs", - "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm" - }, - "time": "2024-05-01T19:32:08+00:00" } ], "aliases": [ diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index e5e6df3a..b32ddac7 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -33,4 +33,5 @@ api_platform: pagination_client_items_per_page: true # Allow clients to override the default items per page keep_legacy_inflector: false - event_listeners_backward_compatibility_layer: false \ No newline at end of file + # Need to be true, or some tests will fail + use_symfony_listeners: true \ No newline at end of file diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 3a160030..0986565f 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -9,14 +9,25 @@ doctrine: # either here or in the DATABASE_URL env var (see .env file) types: + # UTC datetimes datetime: class: App\Doctrine\Types\UTCDateTimeType date: class: App\Doctrine\Types\UTCDateTimeType + + datetime_immutable: + class: App\Doctrine\Types\UTCDateTimeImmutableType + date_immutable: + class: App\Doctrine\Types\UTCDateTimeImmutableType + big_decimal: class: App\Doctrine\Types\BigDecimalType tinyint: class: App\Doctrine\Types\TinyIntType + + # This was removed in doctrine/orm 4.0 but we need it for the WebauthnKey entity + array: + class: App\Doctrine\Types\ArrayType schema_filter: ~^(?!internal)~ # Only enable this when needed @@ -29,6 +40,8 @@ doctrine: validate_xml_mapping: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: true + controller_resolver: + auto_mapping: true mappings: App: type: attribute @@ -39,10 +52,11 @@ doctrine: dql: string_functions: - regexp: DoctrineExtensions\Query\Mysql\Regexp - ifnull: DoctrineExtensions\Query\Mysql\IfNull + regexp: App\Doctrine\Functions\Regexp field: DoctrineExtensions\Query\Mysql\Field field2: App\Doctrine\Functions\Field2 + natsort: App\Doctrine\Functions\Natsort + array_position: App\Doctrine\Functions\ArrayPosition when@test: doctrine: diff --git a/config/parameters.yaml b/config/parameters.yaml index 4caa5780..3c4b9c0a 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -16,6 +16,8 @@ parameters: partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails + partdb.db.emulate_natural_sort: '%env(bool:DATABASE_EMULATE_NATURAL_SORT)%' # If this is set to true, natural sorting is emulated on platforms that do not support it natively. This can be slow on large datasets. + ###################################################################################################################### # Users and Privacy ###################################################################################################################### @@ -145,3 +147,5 @@ parameters: env(HISTORY_SAVE_NEW_DATA): 1 env(EDA_KICAD_CATEGORY_DEPTH): 0 + + env(DATABASE_EMULATE_NATURAL_SORT): 0 diff --git a/config/services.yaml b/config/services.yaml index 0e30ab14..2d31b483 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -76,18 +76,12 @@ services: # Only the event classes specified here are saved to DB (set to []) to log all events $whitelist: [] - App\EventSubscriber\LogSystem\EventLoggerSubscriber: + App\EventListener\LogSystem\EventLoggerListener: arguments: $save_changed_fields: '%env(bool:HISTORY_SAVE_CHANGED_FIELDS)%' $save_changed_data: '%env(bool:HISTORY_SAVE_CHANGED_DATA)%' $save_removed_data: '%env(bool:HISTORY_SAVE_REMOVED_DATA)%' $save_new_data: '%env(bool:HISTORY_SAVE_NEW_DATA)%' - tags: - - { name: 'doctrine.event_subscriber' } - - App\EventSubscriber\LogSystem\LogDBMigrationSubscriber: - tags: - - { name: 'doctrine.event_subscriber' } App\Form\AttachmentFormType: arguments: diff --git a/docs/configuration.md b/docs/configuration.md index 8990e9a7..c0daa407 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,6 +40,9 @@ options listed, see `.env` file for the full list of possible env variables. * `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates. +* `DATABASE_EMULATE_NATURAL_SORT` (default 0): If set to 1, Part-DB will emulate natural sorting, even if the database + does not support it natively. However this is much slower than the native sorting, and contain bugs or quirks, so use + it only, if you have to. * `DEFAULT_LANG`: The default language to use server-wide (when no language is explicitly specified by a user or via language chooser). Must be something like `en`, `de`, `fr`, etc. * `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has no timezone specified. Must be something diff --git a/docs/index.md b/docs/index.md index 7dafabf5..d732f31d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,7 +41,7 @@ It is installed on a web server and so can be accessed with any browser without * Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions. * Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface. -* MySQL and SQLite supported as database backends +* MySQL, SQLite and PostgreSQL are supported as database backends * Support for rich text descriptions and comments in parts * Support for multiple currencies and automatic update of exchange rates supported * Powerful search and filter function, including parametric search (search for parts according to some specifications) diff --git a/docs/installation/choosing_database.md b/docs/installation/choosing_database.md index 99d5f886..69dc810e 100644 --- a/docs/installation/choosing_database.md +++ b/docs/installation/choosing_database.md @@ -7,10 +7,18 @@ nav_order: 1 # Choosing database: SQLite or MySQL -Part-DB saves its data in a [relational (SQL) database](https://en.wikipedia.org/wiki/Relational_database). Part-DB -supports either the use of [SQLite](https://www.sqlite.org/index.html) -or [MySQL](https://www.mysql.com/) / [MariaDB](https://mariadb.org/) (which are mostly the same, except for some minor -differences). +Part-DB saves its data in a [relational (SQL) database](https://en.wikipedia.org/wiki/Relational_database). + +For this multiple database types are supported, currently these are: + +* [SQLite](https://www.sqlite.org/index.html) +* [MySQL](https://www.mysql.com/) / [MariaDB](https://mariadb.org/) (which are mostly the same, except for some minor + differences) +* [PostgreSQL](https://www.postgresql.org/) + +All these database types allow for the same basic functionality and allow Part-DB to run. However, there are some minor +differences between them, which might be important for you. Therefore the pros and cons of the different database types +are listed here. {: .important } You have to choose between the database types before you start using Part-DB and **you can not change it (easily) after @@ -18,29 +26,157 @@ you have started creating data**. So you should choose the database type for you ## Comparison -**SQLite** is the default database type which is configured out of the box. All data is saved in a single file ( -normally `var/app.db` in the Part-DB folder) and no additional installation or configuration besides Part-DB is needed. -To use **MySQL/MariaDB** as database, you have to install and configure the MySQL server, configure it and create a -database and user for Part-DB, which needs some additional work. When using docker you need an additional docker -container, and volume for the data +### SQLite -When using **SQLite** The database can be backuped easily by just copying the SQLite file to a safe place. Ideally, the * -*MySQL** database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can do this -automatically +#### Pros -However, SQLite does not support certain operations like regex search, which has to be emulated by PHP and therefore is -pretty slow compared to the same operation at MySQL. In the future, there might be features that may only be available, when -using MySQL. Also, SQLite has limitations in comparisons and sorting of Unicode characters, which might lead to unexpected -behavior when using non-ASCII characters in your data. For example `µ` (micro sign) is not seen as equal to `μ(greek minuscule mu), -therefore searching for `µ` (micro sign) will not find parts containing `μ` (mu) and vice versa. In MySQL identical-looking characters are seen as equal, which is more intuitive in most cases. +* **Easy to use**: No additional installation or configuration is needed, just start Part-DB and it will work out of the box +* **Easy backup**: Just copy the SQLite file to a safe place, and you have a backup, which you can restore by copying it + back. No need to work with SQL dumps -In general MySQL might perform better for big Part-DB instances with many entries, lots of users and high activity, than -SQLite. +#### Cons -## Conclusion and Suggestion +* **Performance**: SQLite is not as fast as MySQL or PostgreSQL, especially when using complex queries or many users. +* **Emulated RegEx search**: SQLite does not support RegEx search natively. Part-DB can emulate it, however that is pretty slow. +* **Emualted natural sorting**: SQLite does not support natural sorting natively. Part-DB can emulate it, but it is pretty slow. +* **Limitations with Unicode**: SQLite has limitations in comparisons and sorting of Unicode characters, which might lead to + unexpected behavior when using non-ASCII characters in your data. For example `µ` (micro sign) is not seen as equal to + `μ` (greek minuscule mu), therefore searching for `µ` (micro sign) will not find parts containing `μ` (mu) and vice versa. + The other databases behave more intuitive in this case. +* **No advanced features**: SQLite do no support many of the advanced features of MySQL or PostgreSQL, which might be utilized + in future versions of Part-DB + + +### MySQL/MariaDB + +**If possible, it is recommended to use MariaDB 10.7+ (instead of MySQL), as it supports natural sorting of columns natively.** + +#### Pros + +* **Performance**: Compared to SQLite, MySQL/MariaDB will probably perform better, especially in large databases with many + users and high activity. +* **Natural Sorting**: MariaDB 10.7+ supports natural sorting of columns. On other databases it has to be emulated, which is pretty + slow. +* **Native RegEx search**: MySQL supports RegEx search natively, which is faster than emulating it in PHP. +* **Advanced features**: MySQL/MariaDB supports many advanced features, which might be utilized in future versions of Part-DB. +* **Full Unicode support**: MySQL/MariaDB has better support for Unicode characters, which makes it more intuitive to use + non-ASCII characters in your data. + +#### Cons + +* **Additional installation and configuration**: You have to install and configure the MySQL server, create a database and + user for Part-DB, which needs some additional work compared to SQLite. +* **Backup**: The MySQL database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can automate this. + + +### PostgreSQL + +#### Pros +* **Performance**: PostgreSQL is known for its performance, especially in large databases with many users and high activity. +* **Advanced features**: PostgreSQL supports many advanced features, which might be utilized in future versions of Part-DB. +* **Full Unicode support**: PostgreSQL has better support for Unicode characters, which makes it more intuitive to use + non-ASCII characters in your data. +* **Native RegEx search**: PostgreSQL supports RegEx search natively, which is faster than emulating it in PHP. +* **Native Natural Sorting**: PostgreSQL supports natural sorting of columns natively in all versions and in general the support for it + is better than on MariaDB. +* **Support of transactional DDL**: PostgreSQL supports transactional DDL, which means that if you encounter a problem during a schema change, +the database will automatically rollback the changes. On MySQL/MariaDB you have to manually rollback the changes, by restoring from a database backup. + +#### Cons +* **New backend**: The support of postgresql is new, and it was not tested as much as the other backends. There might be some bugs caused by this. +* **Additional installation and configuration**: You have to install and configure the PostgreSQL server, create a database and + user for Part-DB, which needs some additional work compared to SQLite. +* **Backup**: The PostgreSQL database has to be dumped to a SQL file (using `pg_dump`). The `console partdb:backup` command can automate this. + + +## Recommendation When you are a hobbyist and use Part-DB for your own small inventory management with only you as user (or maybe sometimes -a few other people), then the easy-to-use SQLite database will be fine. +a few other people), then the easy-to-use SQLite database will be fine, as long as you can live with the limitations, stated above. +However using MariaDB (or PostgreSQL), has no disadvantages in that situation (besides the initial setup requirements), so you might +want to use it, to be prepared for future use cases. -When you are planning to have a very big database, with a lot of entries and many users which regularly (and -concurrently) using Part-DB you should maybe use MySQL as this will scale better. \ No newline at end of file +When you are planning to have a very big database, with a lot of entries and many users which regularly using Part-DB, then you should +use MariaDB or PostgreSQL, as they will perform better in that situation and allow for more advanced features. +If you should use MariaDB or PostgreSQL depends on your personal preference and what you already have installed on your servers and +what you are familiar with. + +## Using the different databases + +The only difference in using the different databases, is a different value in the `DATABASE_URL` environment variable in the `.env.local` file +or in the `DATABASE_URL` environment variable in your server or container configuration. It has the shape of a URL, where the scheme (the part before `://`) +is the database type, and the rest is connection information. + +**The env var format below is for the `env.local` file. It might work differently for other env configuration. E.g. in a docker-compose file you have to remove the quotes!** + +### SQLite + +```shell +DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" +``` + +Here you just need to configure the path to the SQLite file, which is created by Part-DB when performing the database migrations. +The `%kernel.project_dir%` is a placeholder for the path to the project directory, which is replaced by the actual path by Symfony, so that you do not +need to specify the path manually. In the example the database will be created as `app.db` in the `var` directory of your Part-DB installation folder. + +### MySQL/MariaDB + +```shell +DATABASE_URL="mysql://user:password@127.0.0.1:3306/database?serverVersion=8.0.37" +``` + +Here you have to replace `user`, `password` and `database` with the credentials of the MySQL/MariaDB user and the database name you want to use. +The host (here 127.0.0.1) and port should also be specified according to your MySQL/MariaDB server configuration. + +In the `serverVersion` parameter you can specify the version of the MySQL/MariaDB server you are using, in the way the server returns it +(e.g. `8.0.37` for MySQL and `10.4.14-MariaDB`). If you do not know it, you can leave the default value. + +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. +```shell +DATABASE_URL="mysql://user:password@localhost/database?serverVersion=8.0.37&unix_socket=/var/run/mysqld/mysqld.sock" +``` + +### PostgreSQL + +```shell +DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=12.19&charset=utf8" +``` + +Here you have to replace `db_user`, `db_password` and `db_name` with the credentials of the PostgreSQL user and the database name you want to use. +The host (here 127.0.0.1) and port should also be specified according to your PostgreSQL server configuration. + +In the `serverVersion` parameter you can specify the version of the PostgreSQL server you are using, in the way the server returns it +(e.g. `12.19 (Debian 12.19-1.pgdg120+1)`). If you do not know it, you can leave the default value. + +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. +```shell +DATABASE_URL="postgresql://db_user:db_password@localhost/db_name?serverVersion=12.19&charset=utf8&unix_socket=/var/run/postgresql/.s.PGSQL.5432" +``` + + +## Natural Sorting + +Natural sorting is the sorting of strings in a way that numbers are sorted by their numerical value, not by their ASCII value. + +For example in the classical binary sorting the string `DIP-4`, `DIP-8`, `DIP-16`, `DIP-28` would be sorted as following: + +* `DIP-16` +* `DIP-28` +* `DIP-4` +* `DIP-8` + +In natural sorting, it would be sorted as: + +* `DIP-4` +* `DIP-8` +* `DIP-16` +* `DIP-28` + +Part-DB can sort names in part tables and tree views naturally. PostgreSQL and MariaDB 10.7+ support natural sorting natively, +and it is automatically used if available. + +For SQLite and MySQL < 10.7 it has to be emulated if wanted, which is pretty slow. Therefore it has to be explicity enabled by setting the +`DATABASE_EMULATE_NATURAL_SORT` environment variable to `1`. If it is 0 the classical binary sorting is used, on these databases. The emulations +might have some quirks and issues, so it is recommended to use a database which supports natural sorting natively, if you want to use it. diff --git a/migrations/Version1.php b/migrations/Version1.php index 59f40c74..fc4c7b2e 100644 --- a/migrations/Version1.php +++ b/migrations/Version1.php @@ -235,4 +235,14 @@ EOD; { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20190902140506.php b/migrations/Version20190902140506.php index 36f184d5..8984cb06 100644 --- a/migrations/Version20190902140506.php +++ b/migrations/Version20190902140506.php @@ -380,4 +380,14 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20190913141126.php b/migrations/Version20190913141126.php index 58093338..31eed64a 100644 --- a/migrations/Version20190913141126.php +++ b/migrations/Version20190913141126.php @@ -88,4 +88,14 @@ final class Version20190913141126 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20190924113252.php b/migrations/Version20190924113252.php index fe7c9c8b..8221c686 100644 --- a/migrations/Version20190924113252.php +++ b/migrations/Version20190924113252.php @@ -179,4 +179,14 @@ final class Version20190924113252 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20191214153125.php b/migrations/Version20191214153125.php index 9a61c10d..83803d13 100644 --- a/migrations/Version20191214153125.php +++ b/migrations/Version20191214153125.php @@ -65,4 +65,14 @@ final class Version20191214153125 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200126191823.php b/migrations/Version20200126191823.php index c093a4d7..cf362ea5 100644 --- a/migrations/Version20200126191823.php +++ b/migrations/Version20200126191823.php @@ -68,4 +68,14 @@ final class Version20200126191823 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200311204104.php b/migrations/Version20200311204104.php index 2a90b62c..daa6fd28 100644 --- a/migrations/Version20200311204104.php +++ b/migrations/Version20200311204104.php @@ -56,4 +56,14 @@ final class Version20200311204104 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200409130946.php b/migrations/Version20200409130946.php index 72bdda9c..ef2dc7ab 100644 --- a/migrations/Version20200409130946.php +++ b/migrations/Version20200409130946.php @@ -42,4 +42,14 @@ final class Version20200409130946 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200502161750.php b/migrations/Version20200502161750.php index 93bb0d00..55117541 100644 --- a/migrations/Version20200502161750.php +++ b/migrations/Version20200502161750.php @@ -163,4 +163,14 @@ EOD; $this->addSql('DROP TABLE u2f_keys'); $this->addSql('DROP TABLE "users"'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20220925162725.php b/migrations/Version20220925162725.php index 0f04f04e..21ac8946 100644 --- a/migrations/Version20220925162725.php +++ b/migrations/Version20220925162725.php @@ -535,4 +535,14 @@ final class Version20220925162725 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); $this->addSql('CREATE INDEX IDX_1483A5E96DEDCEC2 ON "users" (id_preview_attachement)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221003212851.php b/migrations/Version20221003212851.php index 3a7379ca..81e33c0e 100644 --- a/migrations/Version20221003212851.php +++ b/migrations/Version20221003212851.php @@ -47,4 +47,14 @@ final class Version20221003212851 extends AbstractMultiPlatformMigration { $this->addSql('DROP TABLE webauthn_keys'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221114193325.php b/migrations/Version20221114193325.php index 9075dc7a..9766ccf3 100644 --- a/migrations/Version20221114193325.php +++ b/migrations/Version20221114193325.php @@ -4,24 +4,20 @@ declare(strict_types=1); namespace DoctrineMigrations; -use App\Entity\UserSystem\PermissionData; use App\Migration\AbstractMultiPlatformMigration; -use App\Security\Interfaces\HasPermissionsInterface; +use App\Migration\WithPermPresetsTrait; use App\Services\UserSystem\PermissionPresetsHelper; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; -use Doctrine\Migrations\AbstractMigration; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Auto-generated Migration: Please modify to your needs! */ final class Version20221114193325 extends AbstractMultiPlatformMigration implements ContainerAwareInterface { - private ?ContainerInterface $container = null; - private ?PermissionPresetsHelper $permission_presets_helper = null; + use WithPermPresetsTrait; public function __construct(Connection $connection, LoggerInterface $logger) { @@ -33,34 +29,6 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme return 'Update the permission system to the new system. Please note that all permissions will be reset!'; } - private function getJSONPermDataFromPreset(string $preset): string - { - if ($this->permission_presets_helper === null) { - throw new \RuntimeException('PermissionPresetsHelper not set! There seems to be some issue with the dependency injection!'); - } - - //Create a virtual user on which we can apply the preset - $user = new class implements HasPermissionsInterface { - - public PermissionData $perm_data; - - public function __construct() - { - $this->perm_data = new PermissionData(); - } - - public function getPermissions(): PermissionData - { - return $this->perm_data; - } - }; - - //Apply the preset to the virtual user - $this->permission_presets_helper->applyPreset($user, $preset); - - //And return the json data - return json_encode($user->getPermissions()); - } private function addDataMigrationAndWarning(): void { @@ -164,11 +132,15 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); } - public function setContainer(ContainerInterface $container = null) + + + public function postgreSQLUp(Schema $schema): void { - if ($container) { - $this->container = $container; - $this->permission_presets_helper = $container->get(PermissionPresetsHelper::class); - } + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); } } diff --git a/migrations/Version20221204004815.php b/migrations/Version20221204004815.php index 20e151db..c6c7b6ab 100644 --- a/migrations/Version20221204004815.php +++ b/migrations/Version20221204004815.php @@ -55,4 +55,14 @@ final class Version20221204004815 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221216224745.php b/migrations/Version20221216224745.php index 63bb535e..9ac3b8ba 100644 --- a/migrations/Version20221216224745.php +++ b/migrations/Version20221216224745.php @@ -67,6 +67,15 @@ final class Version20221216224745 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX log_idx_type ON log (type)'); $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + } + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); } } diff --git a/migrations/Version20230108165410.php b/migrations/Version20230108165410.php index 4124a95a..90f6314e 100644 --- a/migrations/Version20230108165410.php +++ b/migrations/Version20230108165410.php @@ -320,4 +320,14 @@ final class Version20230108165410 extends AbstractMultiPlatformMigration $this->addSql('ALTER TABLE projects RENAME TO devices'); $this->addSql('ALTER TABLE project_bom_entries RENAME TO device_parts'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230219225340.php b/migrations/Version20230219225340.php index 94e08963..2740c85e 100644 --- a/migrations/Version20230219225340.php +++ b/migrations/Version20230219225340.php @@ -521,4 +521,14 @@ final class Version20230219225340 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + } diff --git a/migrations/Version20230220221024.php b/migrations/Version20230220221024.php index 01d65d51..b2209351 100644 --- a/migrations/Version20230220221024.php +++ b/migrations/Version20230220221024.php @@ -37,4 +37,14 @@ final class Version20230220221024 extends AbstractMultiPlatformMigration { $this->addSql('ALTER TABLE `users` DROP saml_user'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230402170923.php b/migrations/Version20230402170923.php index 016a10d0..3325afb6 100644 --- a/migrations/Version20230402170923.php +++ b/migrations/Version20230402170923.php @@ -297,4 +297,14 @@ final class Version20230402170923 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230408170059.php b/migrations/Version20230408170059.php index 88be7798..e3662e16 100644 --- a/migrations/Version20230408170059.php +++ b/migrations/Version20230408170059.php @@ -49,4 +49,14 @@ final class Version20230408170059 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230408213957.php b/migrations/Version20230408213957.php index 976a79db..47807126 100644 --- a/migrations/Version20230408213957.php +++ b/migrations/Version20230408213957.php @@ -434,4 +434,14 @@ final class Version20230408213957 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230417211732.php b/migrations/Version20230417211732.php index 7fef77eb..5fa3b5f7 100644 --- a/migrations/Version20230417211732.php +++ b/migrations/Version20230417211732.php @@ -45,4 +45,14 @@ final class Version20230417211732 extends AbstractMultiPlatformMigration { //As we done nothing, we don't need to implement this method. } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230528000149.php b/migrations/Version20230528000149.php index 05830937..5e742383 100644 --- a/migrations/Version20230528000149.php +++ b/migrations/Version20230528000149.php @@ -62,4 +62,14 @@ final class Version20230528000149 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230716184033.php b/migrations/Version20230716184033.php index adf3e83c..7c0d8a12 100644 --- a/migrations/Version20230716184033.php +++ b/migrations/Version20230716184033.php @@ -348,4 +348,14 @@ final class Version20230716184033 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230730131708.php b/migrations/Version20230730131708.php index 88b49460..a12c3b8d 100644 --- a/migrations/Version20230730131708.php +++ b/migrations/Version20230730131708.php @@ -99,4 +99,14 @@ final class Version20230730131708 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230816213201.php b/migrations/Version20230816213201.php index 66f17d8a..d776da26 100644 --- a/migrations/Version20230816213201.php +++ b/migrations/Version20230816213201.php @@ -41,4 +41,14 @@ final class Version20230816213201 extends AbstractMultiPlatformMigration { $this->addSql('DROP TABLE api_tokens'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20231114223101.php b/migrations/Version20231114223101.php index 3a8f3b02..c06cb279 100644 --- a/migrations/Version20231114223101.php +++ b/migrations/Version20231114223101.php @@ -68,4 +68,14 @@ final class Version20231114223101 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)'); $this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20231130180903.php b/migrations/Version20231130180903.php index 8d00065c..0eccb5aa 100644 --- a/migrations/Version20231130180903.php +++ b/migrations/Version20231130180903.php @@ -85,4 +85,14 @@ final class Version20231130180903 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20240427222442.php b/migrations/Version20240427222442.php index f9471ff1..92b2733d 100644 --- a/migrations/Version20240427222442.php +++ b/migrations/Version20240427222442.php @@ -62,4 +62,14 @@ final class Version20240427222442 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php new file mode 100644 index 00000000..9692e146 --- /dev/null +++ b/migrations/Version20240606203053.php @@ -0,0 +1,653 @@ +addSql("CREATE COLLATION numeric (provider = icu, locale = 'en-u-kn-true');"); + + $this->addSql('CREATE TABLE api_tokens (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, valid_until TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)'); + $this->addSql('CREATE INDEX IDX_2CAD560EA76ED395 ON api_tokens (user_id)'); + $this->addSql('CREATE TABLE "attachment_types" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, filetype_filter TEXT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON "attachment_types" (parent_id)'); + $this->addSql('CREATE INDEX IDX_EFAED719EA7100A1 ON "attachment_types" (id_preview_attachment)'); + $this->addSql('CREATE INDEX attachment_types_idx_name ON "attachment_types" (name)'); + $this->addSql('CREATE INDEX attachment_types_idx_parent_name ON "attachment_types" (parent_id, name)'); + $this->addSql('CREATE TABLE "attachments" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, path VARCHAR(255) NOT NULL, show_in_table BOOLEAN NOT NULL, type_id INT NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_47C4FAD6C54C8C93 ON "attachments" (type_id)'); + $this->addSql('CREATE INDEX IDX_47C4FAD61F1F2A24 ON "attachments" (element_id)'); + $this->addSql('CREATE INDEX attachments_idx_id_element_id_class_name ON "attachments" (id, element_id, class_name)'); + $this->addSql('CREATE INDEX attachments_idx_class_name_id ON "attachments" (class_name, id)'); + $this->addSql('CREATE INDEX attachment_name_idx ON "attachments" (name)'); + $this->addSql('CREATE INDEX attachment_element_idx ON "attachments" (class_name, element_id)'); + $this->addSql('CREATE TABLE "categories" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, partname_hint TEXT NOT NULL, partname_regex TEXT NOT NULL, disable_footprints BOOLEAN NOT NULL, disable_manufacturers BOOLEAN NOT NULL, disable_autodatasheets BOOLEAN NOT NULL, disable_properties BOOLEAN NOT NULL, default_description TEXT NOT NULL, default_comment TEXT NOT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON "categories" (parent_id)'); + $this->addSql('CREATE INDEX IDX_3AF34668EA7100A1 ON "categories" (id_preview_attachment)'); + $this->addSql('CREATE INDEX category_idx_name ON "categories" (name)'); + $this->addSql('CREATE INDEX category_idx_parent_name ON "categories" (parent_id, name)'); + $this->addSql('CREATE TABLE currencies (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL, iso_code VARCHAR(255) NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); + $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); + $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); + $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); + $this->addSql('CREATE TABLE "footprints" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, id_footprint_3d INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)'); + $this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON "footprints" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)'); + $this->addSql('CREATE INDEX footprint_idx_name ON "footprints" (name)'); + $this->addSql('CREATE INDEX footprint_idx_parent_name ON "footprints" (parent_id, name)'); + $this->addSql('CREATE TABLE "groups" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, permissions_data JSON NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)'); + $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)'); + $this->addSql('CREATE INDEX group_idx_name ON "groups" (name)'); + $this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)'); + $this->addSql('CREATE TABLE label_profiles (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, show_in_dropdown BOOLEAN NOT NULL, options_width DOUBLE PRECISION NOT NULL, options_height DOUBLE PRECISION NOT NULL, options_barcode_type VARCHAR(255) NOT NULL, options_picture_type VARCHAR(255) NOT NULL, options_supported_element VARCHAR(255) NOT NULL, options_additional_css TEXT NOT NULL, options_lines_mode VARCHAR(255) NOT NULL, options_lines TEXT NOT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_C93E9CF5EA7100A1 ON label_profiles (id_preview_attachment)'); + $this->addSql('CREATE TABLE log (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, username VARCHAR(255) NOT NULL, datetime TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, level SMALLINT NOT NULL, target_id INT NOT NULL, target_type SMALLINT NOT NULL, extra JSON NOT NULL, id_user INT DEFAULT NULL, type SMALLINT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); + $this->addSql('CREATE INDEX log_idx_type ON log (type)'); + $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); + $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + $this->addSql('CREATE TABLE "manufacturers" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON "manufacturers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON "manufacturers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX manufacturer_name ON "manufacturers" (name)'); + $this->addSql('CREATE INDEX manufacturer_idx_parent_name ON "manufacturers" (parent_id, name)'); + $this->addSql('CREATE TABLE "measurement_units" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer BOOLEAN NOT NULL, use_si_prefix BOOLEAN NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_F5AF83CF727ACA70 ON "measurement_units" (parent_id)'); + $this->addSql('CREATE INDEX IDX_F5AF83CFEA7100A1 ON "measurement_units" (id_preview_attachment)'); + $this->addSql('CREATE INDEX unit_idx_name ON "measurement_units" (name)'); + $this->addSql('CREATE INDEX unit_idx_parent_name ON "measurement_units" (parent_id, name)'); + $this->addSql('CREATE TABLE oauth_tokens (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, token TEXT DEFAULT NULL, expires_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refresh_token TEXT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); + $this->addSql('CREATE TABLE "orderdetails" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, supplierpartnr VARCHAR(255) NOT NULL, obsolete BOOLEAN NOT NULL, supplier_product_url TEXT NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, part_id INT NOT NULL, id_supplier INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_489AFCDC4CE34BEC ON "orderdetails" (part_id)'); + $this->addSql('CREATE INDEX IDX_489AFCDCCBF180EB ON "orderdetails" (id_supplier)'); + $this->addSql('CREATE INDEX orderdetails_supplier_part_nr ON "orderdetails" (supplierpartnr)'); + $this->addSql('CREATE TABLE parameters (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, symbol VARCHAR(255) NOT NULL, value_min DOUBLE PRECISION DEFAULT NULL, value_typical DOUBLE PRECISION DEFAULT NULL, value_max DOUBLE PRECISION DEFAULT NULL, unit VARCHAR(255) NOT NULL, value_text VARCHAR(255) NOT NULL, param_group VARCHAR(255) NOT NULL, type SMALLINT NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_69348FE1F1F2A24 ON parameters (element_id)'); + $this->addSql('CREATE INDEX parameter_name_idx ON parameters (name)'); + $this->addSql('CREATE INDEX parameter_group_idx ON parameters (param_group)'); + $this->addSql('CREATE INDEX parameter_type_element_idx ON parameters (type, element_id)'); + $this->addSql('CREATE TABLE part_association (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment TEXT DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, owner_id INT NOT NULL, other_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_61B952E07E3C61F9 ON part_association (owner_id)'); + $this->addSql('CREATE INDEX IDX_61B952E0998D9879 ON part_association (other_id)'); + $this->addSql('CREATE TABLE part_lots (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, expiration_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, vendor_barcode VARCHAR(255) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, id_store_location INT DEFAULT NULL, id_part INT NOT NULL, id_owner INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)'); + $this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)'); + $this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)'); + $this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)'); + $this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)'); + $this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)'); + $this->addSql('CREATE TABLE "parts" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags TEXT NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url TEXT NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INT NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, id_category INT NOT NULL, id_footprint INT DEFAULT NULL, id_part_unit INT DEFAULT NULL, id_manufacturer INT DEFAULT NULL, order_orderdetails_id INT DEFAULT NULL, built_project_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)'); + $this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)'); + $this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)'); + $this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)'); + $this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)'); + $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); + $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); + $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); + $this->addSql('CREATE TABLE "pricedetails" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, price NUMERIC(11, 5) NOT NULL, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, id_currency INT DEFAULT NULL, orderdetails_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)'); + $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)'); + $this->addSql('CREATE TABLE project_bom_entries (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames TEXT NOT NULL, name VARCHAR(255) DEFAULT NULL, comment TEXT NOT NULL, price NUMERIC(11, 5) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, id_device INT DEFAULT NULL, id_part INT DEFAULT NULL, price_currency_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); + $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); + $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); + $this->addSql('CREATE TABLE projects (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, order_quantity INT NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description TEXT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)'); + $this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)'); + $this->addSql('CREATE TABLE "storelocations" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, part_owner_must_match BOOLEAN DEFAULT false NOT NULL, parent_id INT DEFAULT NULL, storage_type_id INT DEFAULT NULL, id_owner INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_7517020727ACA70 ON "storelocations" (parent_id)'); + $this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON "storelocations" (storage_type_id)'); + $this->addSql('CREATE INDEX IDX_751702021E5A74C ON "storelocations" (id_owner)'); + $this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON "storelocations" (id_preview_attachment)'); + $this->addSql('CREATE INDEX location_idx_name ON "storelocations" (name)'); + $this->addSql('CREATE INDEX location_idx_parent_name ON "storelocations" (parent_id, name)'); + $this->addSql('CREATE TABLE "suppliers" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, parent_id INT DEFAULT NULL, default_currency_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)'); + $this->addSql('CREATE TABLE u2f_keys (key_handle VARCHAR(128) NOT NULL, public_key VARCHAR(255) NOT NULL, certificate TEXT NOT NULL, counter VARCHAR(255) NOT NULL, id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_4F4ADB4BA76ED395 ON u2f_keys (user_id)'); + $this->addSql('CREATE UNIQUE INDEX user_unique ON u2f_keys (user_id, key_handle)'); + $this->addSql('CREATE TABLE "users" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a TEXT NOT NULL, config_instock_comment_w TEXT NOT NULL, about_me TEXT NOT NULL, trusted_device_cookie_version INT NOT NULL, backup_codes JSON NOT NULL, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT false NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, settings JSON NOT NULL, backup_codes_generation_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, pw_reset_expires TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, saml_user BOOLEAN NOT NULL, name VARCHAR(180) NOT NULL, permissions_data JSON NOT NULL, group_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, currency_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)'); + $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)'); + $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); + $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); + $this->addSql('CREATE TABLE webauthn_keys (public_key_credential_id TEXT NOT NULL, type VARCHAR(255) NOT NULL, transports TEXT NOT NULL, attestation_type VARCHAR(255) NOT NULL, trust_path JSON NOT NULL, aaguid TEXT NOT NULL, credential_public_key TEXT NOT NULL, user_handle VARCHAR(255) NOT NULL, counter INT NOT NULL, other_ui TEXT DEFAULT NULL, backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + $this->addSql('ALTER TABLE api_tokens ADD CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "attachment_types" ADD CONSTRAINT FK_EFAED719727ACA70 FOREIGN KEY (parent_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "attachment_types" ADD CONSTRAINT FK_EFAED719EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "attachments" ADD CONSTRAINT FK_47C4FAD6C54C8C93 FOREIGN KEY (type_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "categories" ADD CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "categories" ADD CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE currencies ADD CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE currencies ADD CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "footprints" ADD CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "footprints" ADD CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "footprints" ADD CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "groups" ADD CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "groups" ADD CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE label_profiles ADD CONSTRAINT FK_C93E9CF5EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE log ADD CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "manufacturers" ADD CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "manufacturers" ADD CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "measurement_units" ADD CONSTRAINT FK_F5AF83CF727ACA70 FOREIGN KEY (parent_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "measurement_units" ADD CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "orderdetails" ADD CONSTRAINT FK_489AFCDC4CE34BEC FOREIGN KEY (part_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "orderdetails" ADD CONSTRAINT FK_489AFCDCCBF180EB FOREIGN KEY (id_supplier) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E07E3C61F9 FOREIGN KEY (owner_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E0998D9879 FOREIGN KEY (other_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "pricedetails" ADD CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "pricedetails" ADD CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE project_bom_entries ADD CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE project_bom_entries ADD CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE project_bom_entries ADD CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE projects ADD CONSTRAINT FK_5C93B3A4727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE projects ADD CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_7517020727ACA70 FOREIGN KEY (parent_id) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_751702021E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_7517020EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "suppliers" ADD CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "suppliers" ADD CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "suppliers" ADD CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE u2f_keys ADD CONSTRAINT FK_4F4ADB4BA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE webauthn_keys ADD CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + + //Create the initial groups and users + //Retrieve the json representations of the presets + $admin = $this->getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_ADMIN); + $editor = $this->getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_EDITOR); + $read_only = $this->getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_READ_ONLY); + + + $sql = <<addSql($sql); + + //Increase the sequence for the groups, to avoid conflicts later + $this->addSql('SELECT setval(\'groups_id_seq\', 4)'); + + + $admin_pw = $this->getInitalAdminPW(); + + $sql = <<addSql($sql); + + //Increase the sequence for the users, to avoid conflicts later + $this->addSql('SELECT setval(\'users_id_seq\', 3)'); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE api_tokens DROP CONSTRAINT FK_2CAD560EA76ED395'); + $this->addSql('ALTER TABLE "attachment_types" DROP CONSTRAINT FK_EFAED719727ACA70'); + $this->addSql('ALTER TABLE "attachment_types" DROP CONSTRAINT FK_EFAED719EA7100A1'); + $this->addSql('ALTER TABLE "attachments" DROP CONSTRAINT FK_47C4FAD6C54C8C93'); + $this->addSql('ALTER TABLE "categories" DROP CONSTRAINT FK_3AF34668727ACA70'); + $this->addSql('ALTER TABLE "categories" DROP CONSTRAINT FK_3AF34668EA7100A1'); + $this->addSql('ALTER TABLE currencies DROP CONSTRAINT FK_37C44693727ACA70'); + $this->addSql('ALTER TABLE currencies DROP CONSTRAINT FK_37C44693EA7100A1'); + $this->addSql('ALTER TABLE "footprints" DROP CONSTRAINT FK_A34D68A2727ACA70'); + $this->addSql('ALTER TABLE "footprints" DROP CONSTRAINT FK_A34D68A2EA7100A1'); + $this->addSql('ALTER TABLE "footprints" DROP CONSTRAINT FK_A34D68A232A38C34'); + $this->addSql('ALTER TABLE "groups" DROP CONSTRAINT FK_F06D3970727ACA70'); + $this->addSql('ALTER TABLE "groups" DROP CONSTRAINT FK_F06D3970EA7100A1'); + $this->addSql('ALTER TABLE label_profiles DROP CONSTRAINT FK_C93E9CF5EA7100A1'); + $this->addSql('ALTER TABLE log DROP CONSTRAINT FK_8F3F68C56B3CA4B'); + $this->addSql('ALTER TABLE "manufacturers" DROP CONSTRAINT FK_94565B12727ACA70'); + $this->addSql('ALTER TABLE "manufacturers" DROP CONSTRAINT FK_94565B12EA7100A1'); + $this->addSql('ALTER TABLE "measurement_units" DROP CONSTRAINT FK_F5AF83CF727ACA70'); + $this->addSql('ALTER TABLE "measurement_units" DROP CONSTRAINT FK_F5AF83CFEA7100A1'); + $this->addSql('ALTER TABLE "orderdetails" DROP CONSTRAINT FK_489AFCDC4CE34BEC'); + $this->addSql('ALTER TABLE "orderdetails" DROP CONSTRAINT FK_489AFCDCCBF180EB'); + $this->addSql('ALTER TABLE part_association DROP CONSTRAINT FK_61B952E07E3C61F9'); + $this->addSql('ALTER TABLE part_association DROP CONSTRAINT FK_61B952E0998D9879'); + $this->addSql('ALTER TABLE part_lots DROP CONSTRAINT FK_EBC8F9435D8F4B37'); + $this->addSql('ALTER TABLE part_lots DROP CONSTRAINT FK_EBC8F943C22F6CC4'); + $this->addSql('ALTER TABLE part_lots DROP CONSTRAINT FK_EBC8F94321E5A74C'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEEA7100A1'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE5697F554'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE7E371A10'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE2626CEF9'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE1ECB93AE'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE81081E9B'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEE8AE70D9'); + $this->addSql('ALTER TABLE "pricedetails" DROP CONSTRAINT FK_C68C4459398D64AA'); + $this->addSql('ALTER TABLE "pricedetails" DROP CONSTRAINT FK_C68C44594A01DDC7'); + $this->addSql('ALTER TABLE project_bom_entries DROP CONSTRAINT FK_1AA2DD312F180363'); + $this->addSql('ALTER TABLE project_bom_entries DROP CONSTRAINT FK_1AA2DD31C22F6CC4'); + $this->addSql('ALTER TABLE project_bom_entries DROP CONSTRAINT FK_1AA2DD313FFDCD60'); + $this->addSql('ALTER TABLE projects DROP CONSTRAINT FK_5C93B3A4727ACA70'); + $this->addSql('ALTER TABLE projects DROP CONSTRAINT FK_5C93B3A4EA7100A1'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_7517020727ACA70'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_7517020B270BFF1'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_751702021E5A74C'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_7517020EA7100A1'); + $this->addSql('ALTER TABLE "suppliers" DROP CONSTRAINT FK_AC28B95C727ACA70'); + $this->addSql('ALTER TABLE "suppliers" DROP CONSTRAINT FK_AC28B95CECD792C0'); + $this->addSql('ALTER TABLE "suppliers" DROP CONSTRAINT FK_AC28B95CEA7100A1'); + $this->addSql('ALTER TABLE u2f_keys DROP CONSTRAINT FK_4F4ADB4BA76ED395'); + $this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E9FE54D947'); + $this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E9EA7100A1'); + $this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E938248176'); + $this->addSql('ALTER TABLE webauthn_keys DROP CONSTRAINT FK_799FD143A76ED395'); + $this->addSql('DROP TABLE api_tokens'); + $this->addSql('DROP TABLE "attachment_types"'); + $this->addSql('DROP TABLE "attachments"'); + $this->addSql('DROP TABLE "categories"'); + $this->addSql('DROP TABLE currencies'); + $this->addSql('DROP TABLE "footprints"'); + $this->addSql('DROP TABLE "groups"'); + $this->addSql('DROP TABLE label_profiles'); + $this->addSql('DROP TABLE log'); + $this->addSql('DROP TABLE "manufacturers"'); + $this->addSql('DROP TABLE "measurement_units"'); + $this->addSql('DROP TABLE oauth_tokens'); + $this->addSql('DROP TABLE "orderdetails"'); + $this->addSql('DROP TABLE parameters'); + $this->addSql('DROP TABLE part_association'); + $this->addSql('DROP TABLE part_lots'); + $this->addSql('DROP TABLE "parts"'); + $this->addSql('DROP TABLE "pricedetails"'); + $this->addSql('DROP TABLE project_bom_entries'); + $this->addSql('DROP TABLE projects'); + $this->addSql('DROP TABLE "storelocations"'); + $this->addSql('DROP TABLE "suppliers"'); + $this->addSql('DROP TABLE u2f_keys'); + $this->addSql('DROP TABLE "users"'); + $this->addSql('DROP TABLE webauthn_keys'); + } + + public function mySQLUp(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE currencies CHANGE exchange_rate exchange_rate NUMERIC(11, 5) DEFAULT NULL'); + //Set empty JSON fields to "{}" to avoid issues with failing JSON validation + $this->addSql('UPDATE `groups` SET permissions_data = "{}" WHERE permissions_data = ""'); + $this->addSql('ALTER TABLE `groups` CHANGE permissions_data permissions_data JSON NOT NULL'); + //Set the empty JSON fields to "{}" to avoid issues with failing JSON validation + $this->addSql('UPDATE `log` SET extra = "{}" WHERE extra = ""'); + $this->addSql('ALTER TABLE `log` CHANGE level level TINYINT NOT NULL, CHANGE extra extra JSON NOT NULL'); + $this->addSql('ALTER TABLE oauth_tokens CHANGE expires_at expires_at DATETIME DEFAULT NULL'); + $this->addSql('ALTER TABLE pricedetails CHANGE price price NUMERIC(11, 5) NOT NULL'); + $this->addSql('ALTER TABLE project_bom_entries CHANGE price price NUMERIC(11, 5) DEFAULT NULL'); + $this->addSql('ALTER TABLE suppliers CHANGE shipping_costs shipping_costs NUMERIC(11, 5) DEFAULT NULL'); + //Set the empty JSON fields to "{}" to avoid issues with failing JSON validation + $this->addSql('UPDATE `users` SET settings = "{}" WHERE settings = ""'); + $this->addSql('UPDATE `users` SET backup_codes = "{}" WHERE backup_codes = ""'); + $this->addSql('UPDATE `users` SET permissions_data = "{}" WHERE permissions_data = ""'); + $this->addSql('ALTER TABLE `users` CHANGE settings settings JSON NOT NULL, CHANGE backup_codes backup_codes JSON NOT NULL, CHANGE permissions_data permissions_data JSON NOT NULL'); + $this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL, CHANGE transports transports LONGTEXT NOT NULL, CHANGE trust_path trust_path JSON NOT NULL, CHANGE aaguid aaguid TINYTEXT NOT NULL, CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL, CHANGE other_ui other_ui LONGTEXT DEFAULT NULL, CHANGE last_time_used last_time_used DATETIME DEFAULT NULL'); + + // Add the natural sort emulation function to the database (based on this stackoverflow: https://stackoverflow.com/questions/153633/natural-sort-in-mysql/58154535#58154535) + $this->addSql(<<0, only the first n numbers in the input string will be converted for nat-sort (so strings that differ only after the first n numbers will not nat-sort amongst themselves). + Total sort-ordering is preserved, i.e. if s1!=s2, then NatSortKey(s1,n)!=NatSortKey(s2,n), for any given n. + Numbers may contain ',' as a thousands separator, and '.' as a decimal point. To reverse these (as appropriate for some European locales), the code would require modification. + Numbers preceded by '+' sort with numbers not preceded with either a '+' or '-' sign. + Negative numbers (preceded with '-') sort before positive numbers, but are sorted in order of ascending absolute value (so -7 sorts BEFORE -1001). + Numbers with leading zeros sort after the same number with no (or fewer) leading zeros. + Decimal-part-only numbers (like .75) are recognised, provided the decimal point is not immediately preceded by either another '.', or by a letter-type character. + Numbers with thousand separators sort after the same number without them. + Thousand separators are only recognised in numbers with no leading zeros that don't immediately follow a ',', and when they format the number correctly. + (When not recognised as a thousand separator, a ',' will instead be treated as separating two distinct numbers). + Version-number-like sequences consisting of 3 or more numbers separated by '.' are treated as distinct entities, and each component number will be nat-sorted. + The entire entity will sort after any number beginning with the first component (so e.g. 10.2.1 sorts after both 10 and 10.995, but before 11) + Note that The first number component in an entity like this is also permitted to contain thousand separators. + + To achieve this, numbers within the input string are prefixed and suffixed according to the following format: + - The number is prefixed by a 2-digit base-36 number representing its length, excluding leading zeros. If there is a decimal point, this length only includes the integer part of the number. + - A 3-character suffix is appended after the number (after the decimals if present). + - The first character is a space, or a '+' sign if the number was preceded by '+'. Any preceding '+' sign is also removed from the front of the number. + - This is followed by a 2-digit base-36 number that encodes the number of leading zeros and whether the number was expressed in comma-separated form (e.g. 1,000,000.25 vs 1000000.25) + - The value of this 2-digit number is: (number of leading zeros)*2 + (1 if comma-separated, 0 otherwise) + - For version number sequences, each component number has the prefix in front of it, and the separating dots are removed. + Then there is a single suffix that consists of a ' ' or '+' character, followed by a pair base-36 digits for each number component in the sequence. + + e.g. here is how some simple sample strings get converted: + 'Foo055' --> 'Foo0255 02' + 'Absolute zero is around -273 centigrade' --> 'Absolute zero is around -03273 00 centigrade' + 'The $1,000,000 prize' --> 'The $071000000 01 prize' + '+99.74 degrees' --> '0299.74+00 degrees' + 'I have 0 apples' --> 'I have 00 02 apples' + '.5 is the same value as 0000.5000' --> '00.5 00 is the same value as 00.5000 08' + 'MariaDB v10.3.0018' --> 'MariaDB v02100130218 000004' + + The restriction to numbers of up to 359 digits comes from the fact that the first character of the base-36 prefix MUST be a decimal digit, and so the highest permitted prefix value is '9Z' or 359 decimal. + The code could be modified to handle longer numbers by increasing the size of (both) the prefix and suffix. + A higher base could also be used (by replacing CONV() with a custom function), provided that the collation you are using sorts the "digits" of the base in the correct order, starting with 0123456789. + However, while the maximum number length may be increased this way, note that the technique this function uses is NOT applicable where strings may contain numbers of unlimited length. + + The function definition does not specify the charset or collation to be used for string-type parameters or variables: The default database charset & collation at the time the function is defined will be used. + This is to make the function code more portable. However, there are some important restrictions: + + - Collation is important here only when comparing (or storing) the output value from this function, but it MUST order the characters " +0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" in that order for the natural sort to work. + This is true for most collations, but not all of them, e.g. in Lithuanian 'Y' comes before 'J' (according to Wikipedia). + To adapt the function to work with such collations, replace CONV() in the function code with a custom function that emits "digits" above 9 that are characters ordered according to the collation in use. + + - For efficiency, the function code uses LENGTH() rather than CHAR_LENGTH() to measure the length of strings that consist only of digits 0-9, '.', and ',' characters. + This works for any single-byte charset, as well as any charset that maps standard ASCII characters to single bytes (such as utf8 or utf8mb4). + If using a charset that maps these characters to multiple bytes (such as, e.g. utf16 or utf32), you MUST replace all instances of LENGTH() in the function definition with CHAR_LENGTH() + + Length of the output: + + Each number converted adds 5 characters (2 prefix + 3 suffix) to the length of the string. n is the maximum count of numbers to convert; + This parameter is provided as a means to limit the maximum output length (to input length + 5*n). + If you do not require the total-ordering property, you could edit the code to use suffixes of 1 character (space or plus) only; this would reduce the maximum output length for any given n. + Since a string of length L has at most ((L+1) DIV 2) individual numbers in it (every 2nd character a digit), for n<=0 the maximum output length is (inputlength + 5*((inputlength+1) DIV 2)) + So for the current input length of 100, the maximum output length is 350. + If changing the input length, the output length must be modified according to the above formula. The DECLARE statements for x,y,r, and suf must also be modified, as the code comments indicate. + ****/ + + DECLARE x,y varchar(1000); # need to be same length as input s + DECLARE r varchar(3500) DEFAULT ''; # return value: needs to be same length as return type + DECLARE suf varchar(101); # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components) + DECLARE i,j,k int UNSIGNED; + IF n<=0 THEN SET n := -1; END IF; # n<=0 means "process all numbers" + LOOP + SET i := REGEXP_INSTR(s,'\\d'); # find position of next digit + IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF; # no more numbers to process -> we're done + SET n := n-1, suf := ' '; + IF i>1 THEN + IF SUBSTRING(s,i-1,1)='.' AND (i=2 OR SUBSTRING(s,i-2,1) RLIKE '[^.\\p{L}\\p{N}\\p{M}\\x{608}\\x{200C}\\x{200D}\\x{2100}-\\x{214F}\\x{24B6}-\\x{24E9}\\x{1F130}-\\x{1F149}\\x{1F150}-\\x{1F169}\\x{1F170}-\\x{1F189}]') AND (SUBSTRING(s,i) NOT RLIKE '^\\d++\\.\\d') THEN SET i:=i-1; END IF; # Allow decimal number (but not version string) to begin with a '.', provided preceding char is neither another '.', nor a member of the unicode character classes: "Alphabetic", "Letter", "Block=Letterlike Symbols" "Number", "Mark", "Join_Control" + IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF; # move any preceding '+' into the suffix, so equal numbers with and without preceding "+" signs sort together + SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i); # add everything before the number to r and strip it from the start of s; preceding '+' is dropped (not included in either r or s) + END IF; + SET x := REGEXP_SUBSTR(s,IF(SUBSTRING(s,1,1) IN ('0','.') OR (SUBSTRING(r,-1)=',' AND suf=' '),'^\\d*+(?:\\.\\d++)*','^(?:[1-9]\\d{0,2}(?:,\\d{3}(?!\\d))++|\\d++)(?:\\.\\d++)*+')); # capture the number + following decimals (including multiple consecutive '.' sequences) + SET s := SUBSTRING(s,CHAR_LENGTH(x)+1); # NOTE: CHAR_LENGTH() can be safely used instead of CHAR_LENGTH() here & below PROVIDED we're using a charset that represents digits, ',' and '.' characters using single bytes (e.g. latin1, utf8) + SET i := INSTR(x,'.'); + IF i=0 THEN SET y := ''; ELSE SET y := SUBSTRING(x,i); SET x := SUBSTRING(x,1,i-1); END IF; # move any following decimals into y + SET i := CHAR_LENGTH(x); + SET x := REPLACE(x,',',''); + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0')); # (j-k)*2 + IF(i=j,0,1) = (count of leading zeros)*2 + (1 if there are thousands-separators, 0 otherwise) Note the first term is bounded to <= base-36 'ZY' as it must fit within 2 characters + SET i := LOCATE('.',y,2); + IF i=0 THEN + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x,y,suf); # k = count of digits in number, bounded to be <= '9Z' base-36 + ELSE # encode a version number (like 3.12.707, etc) + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + WHILE CHAR_LENGTH(y)>0 AND n!=0 DO + IF i=0 THEN SET x := SUBSTRING(y,2); SET y := ''; ELSE SET x := SUBSTRING(y,2,i-2); SET y := SUBSTRING(y,i); SET i := LOCATE('.',y,2); END IF; + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0')); # (j-k)*2 = (count of leading zeros)*2, bounded to fit within 2 base-36 digits + SET n := n-1; + END WHILE; + SET r := CONCAT(r,y,suf); + END IF; + END LOOP; + END + EOD + ); + + } + + public function mySQLDown(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE currencies CHANGE exchange_rate exchange_rate NUMERIC(11, 5) DEFAULT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE `groups` CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE log CHANGE level level TINYINT(1) NOT NULL COMMENT \'(DC2Type:tinyint)\', CHANGE extra extra LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE oauth_tokens CHANGE expires_at expires_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE `pricedetails` CHANGE price price NUMERIC(11, 5) NOT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE project_bom_entries CHANGE price price NUMERIC(11, 5) DEFAULT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE `suppliers` CHANGE shipping_costs shipping_costs NUMERIC(11, 5) DEFAULT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE `users` CHANGE backup_codes backup_codes LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE settings settings LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL COMMENT \'(DC2Type:base64)\', CHANGE transports transports LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', CHANGE trust_path trust_path LONGTEXT NOT NULL COMMENT \'(DC2Type:trust_path)\', CHANGE aaguid aaguid TINYTEXT NOT NULL COMMENT \'(DC2Type:aaguid)\', CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL COMMENT \'(DC2Type:base64)\', CHANGE other_ui other_ui LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:array)\', CHANGE last_time_used last_time_used DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + + //Drop custom function + $this->addSql('DROP FUNCTION IF EXISTS NatSortKey'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM currencies'); + $this->addSql('DROP TABLE currencies'); + $this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__currencies'); + $this->addSql('DROP TABLE __temp__currencies'); + $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); + $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); + $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); + $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data, alternative_names FROM groups'); + $this->addSql('DROP TABLE groups'); + $this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data, alternative_names) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data, alternative_names FROM __temp__groups'); + $this->addSql('DROP TABLE __temp__groups'); + $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON groups (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)'); + $this->addSql('CREATE INDEX group_idx_name ON groups (name)'); + $this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM log'); + $this->addSql('DROP TABLE log'); + $this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, datetime DATETIME NOT NULL, level SMALLINT NOT NULL, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL, type SMALLINT NOT NULL, username VARCHAR(255) NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO log (id, id_user, datetime, level, target_id, target_type, extra, type, username) SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM __temp__log'); + $this->addSql('DROP TABLE __temp__log'); + $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); + $this->addSql('CREATE INDEX log_idx_type ON log (type)'); + $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); + $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__oauth_tokens AS SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM oauth_tokens'); + $this->addSql('DROP TABLE oauth_tokens'); + $this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, token CLOB DEFAULT NULL, expires_at DATETIME DEFAULT NULL, refresh_token CLOB DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL)'); + $this->addSql('INSERT INTO oauth_tokens (id, token, expires_at, refresh_token, name, last_modified, datetime_added) SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM __temp__oauth_tokens'); + $this->addSql('DROP TABLE __temp__oauth_tokens'); + $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM pricedetails'); + $this->addSql('DROP TABLE pricedetails'); + $this->addSql('CREATE TABLE pricedetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO pricedetails (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails'); + $this->addSql('DROP TABLE __temp__pricedetails'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON pricedetails (min_discount_quantity, price_related_quantity)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON pricedetails (min_discount_quantity)'); + $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)'); + $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries'); + $this->addSql('DROP TABLE project_bom_entries'); + $this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AFC547992F180363 FOREIGN KEY (id_device) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AFC54799C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries'); + $this->addSql('DROP TABLE __temp__project_bom_entries'); + $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); + $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); + $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM suppliers'); + $this->addSql('DROP TABLE suppliers'); + $this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__suppliers'); + $this->addSql('DROP TABLE __temp__suppliers'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)'); + $this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM users'); + $this->addSql('DROP TABLE users'); + $this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL, saml_user BOOLEAN NOT NULL, about_me CLOB NOT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM __temp__users'); + $this->addSql('DROP TABLE __temp__users'); + $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON users (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)'); + $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)'); + $this->addSql('CREATE INDEX user_idx_username ON users (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui, backup_eligible, backup_status, uv_initialized, last_time_used FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL, aaguid CLOB NOT NULL, credential_public_key CLOB NOT NULL, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL, backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, last_time_used DATETIME DEFAULT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui, backup_eligible, backup_status, uv_initialized, last_time_used) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui, backup_eligible, backup_status, uv_initialized, last_time_used FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, exchange_rate, iso_code, parent_id, id_preview_attachment FROM currencies'); + $this->addSql('DROP TABLE currencies'); + $this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal) + , iso_code VARCHAR(255) NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO currencies (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, exchange_rate, iso_code, parent_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, exchange_rate, iso_code, parent_id, id_preview_attachment FROM __temp__currencies'); + $this->addSql('DROP TABLE __temp__currencies'); + $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); + $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); + $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); + $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, enforce_2fa, permissions_data, parent_id, id_preview_attachment FROM "groups"'); + $this->addSql('DROP TABLE "groups"'); + $this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json) + , parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "groups" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, enforce_2fa, permissions_data, parent_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, enforce_2fa, permissions_data, parent_id, id_preview_attachment FROM __temp__groups'); + $this->addSql('DROP TABLE __temp__groups'); + $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)'); + $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)'); + $this->addSql('CREATE INDEX group_idx_name ON "groups" (name)'); + $this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, username, datetime, level, target_id, target_type, extra, id_user, type FROM log'); + $this->addSql('DROP TABLE log'); + $this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(255) NOT NULL, datetime DATETIME NOT NULL, level BOOLEAN NOT NULL --(DC2Type:tinyint) + , target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --(DC2Type:json) + , id_user INTEGER DEFAULT NULL, type SMALLINT NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO log (id, username, datetime, level, target_id, target_type, extra, id_user, type) SELECT id, username, datetime, level, target_id, target_type, extra, id_user, type FROM __temp__log'); + $this->addSql('DROP TABLE __temp__log'); + $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); + $this->addSql('CREATE INDEX log_idx_type ON log (type)'); + $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); + $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__oauth_tokens AS SELECT id, name, last_modified, datetime_added, token, expires_at, refresh_token FROM oauth_tokens'); + $this->addSql('DROP TABLE oauth_tokens'); + $this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, token CLOB DEFAULT NULL, expires_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , refresh_token CLOB DEFAULT NULL)'); + $this->addSql('INSERT INTO oauth_tokens (id, name, last_modified, datetime_added, token, expires_at, refresh_token) SELECT id, name, last_modified, datetime_added, token, expires_at, refresh_token FROM __temp__oauth_tokens'); + $this->addSql('DROP TABLE __temp__oauth_tokens'); + $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added, id_currency, orderdetails_id FROM "pricedetails"'); + $this->addSql('DROP TABLE "pricedetails"'); + $this->addSql('CREATE TABLE "pricedetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, price NUMERIC(11, 5) NOT NULL --(DC2Type:big_decimal) + , price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "pricedetails" (id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added, id_currency, orderdetails_id) SELECT id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added, id_currency, orderdetails_id FROM __temp__pricedetails'); + $this->addSql('DROP TABLE __temp__pricedetails'); + $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)'); + $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, quantity, mountnames, name, comment, price, last_modified, datetime_added, id_device, id_part, price_currency_id FROM project_bom_entries'); + $this->addSql('DROP TABLE project_bom_entries'); + $this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal) + , last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO project_bom_entries (id, quantity, mountnames, name, comment, price, last_modified, datetime_added, id_device, id_part, price_currency_id) SELECT id, quantity, mountnames, name, comment, price, last_modified, datetime_added, id_device, id_part, price_currency_id FROM __temp__project_bom_entries'); + $this->addSql('DROP TABLE __temp__project_bom_entries'); + $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); + $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); + $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM "suppliers"'); + $this->addSql('DROP TABLE "suppliers"'); + $this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal) + , parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "suppliers" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM __temp__suppliers'); + $this->addSql('DROP TABLE __temp__suppliers'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, last_modified, datetime_added, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, settings, backup_codes_generation_date, pw_reset_expires, saml_user, name, permissions_data, group_id, id_preview_attachment, currency_id FROM "users"'); + $this->addSql('DROP TABLE "users"'); + $this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, about_me CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json) + , google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, settings CLOB NOT NULL --(DC2Type:json) + , backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, saml_user BOOLEAN NOT NULL, name VARCHAR(180) NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json) + , group_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "users" (id, last_modified, datetime_added, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, settings, backup_codes_generation_date, pw_reset_expires, saml_user, name, permissions_data, group_id, id_preview_attachment, currency_id) SELECT id, last_modified, datetime_added, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, settings, backup_codes_generation_date, pw_reset_expires, saml_user, name, permissions_data, group_id, id_preview_attachment, currency_id FROM __temp__users'); + $this->addSql('DROP TABLE __temp__users'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)'); + $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)'); + $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); + $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, backup_eligible, backup_status, uv_initialized, id, name, last_time_used, last_modified, datetime_added, user_id FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (public_key_credential_id CLOB NOT NULL --(DC2Type:base64) + , type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array) + , attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path) + , aaguid CLOB NOT NULL --(DC2Type:aaguid) + , credential_public_key CLOB NOT NULL --(DC2Type:base64) + , user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array) + , backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_time_used DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INTEGER DEFAULT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, backup_eligible, backup_status, uv_initialized, id, name, last_time_used, last_modified, datetime_added, user_id) SELECT public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, backup_eligible, backup_status, uv_initialized, id, name, last_time_used, last_modified, datetime_added, user_id FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + } +} diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 872169ac..00000000 --- a/psalm.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rector.php b/rector.php index 94ade3df..40eee9f7 100644 --- a/rector.php +++ b/rector.php @@ -2,15 +2,17 @@ declare(strict_types=1); +use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector; use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector; use Rector\Config\RectorConfig; use Rector\Doctrine\Set\DoctrineSetList; -use Rector\PHPUnit\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector; -use Rector\PHPUnit\Set\PHPUnitLevelSetList; +use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector; use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; -use Rector\Symfony\Set\SymfonyLevelSetList; +use Rector\Symfony\CodeQuality\Rector\Class_\EventListenerToEventSubscriberRector; +use Rector\Symfony\CodeQuality\Rector\ClassMethod\ActionSuffixRemoverRector; +use Rector\Symfony\CodeQuality\Rector\MethodCall\LiteralGetToRequestClassConstantRector; use Rector\Symfony\Set\SymfonySetList; use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector; @@ -44,20 +46,36 @@ return static function (RectorConfig $rectorConfig): void { LevelSetList::UP_TO_PHP_81, //Symfony rules - SymfonyLevelSetList::UP_TO_SYMFONY_62, SymfonySetList::SYMFONY_CODE_QUALITY, + SymfonySetList::SYMFONY_64, //Doctrine rules DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES, DoctrineSetList::DOCTRINE_CODE_QUALITY, //PHPUnit rules - PHPUnitLevelSetList::UP_TO_PHPUNIT_90, PHPUnitSetList::PHPUNIT_CODE_QUALITY, + PHPUnitSetList::PHPUNIT_90, ]); $rectorConfig->skip([ - AddDoesNotPerformAssertionToNonAssertingTestRector::class, CountArrayToEmptyArrayComparisonRector::class, + //Leave our !== null checks alone + FlipTypeControlToUseExclusiveTypeRector::class, + //Leave our PartList TableAction alone + ActionSuffixRemoverRector::class, + //We declare event listeners via attributes, therefore no need to migrate them to subscribers + EventListenerToEventSubscriberRector::class, + PreferPHPUnitThisCallRector::class, + //Do not replace 'GET' with class constant, + LiteralGetToRequestClassConstantRector::class, + ]); + + //Do not apply rules to Symfony own files + $rectorConfig->skip([ + __DIR__ . '/public/index.php', + __DIR__ . '/src/Kernel.php', + __DIR__ . '/config/preload.php', + __DIR__ . '/config/bundles.php', ]); }; diff --git a/src/ApiPlatform/Filter/EntityFilterHelper.php b/src/ApiPlatform/Filter/EntityFilterHelper.php index ddb55ae7..42cc567f 100644 --- a/src/ApiPlatform/Filter/EntityFilterHelper.php +++ b/src/ApiPlatform/Filter/EntityFilterHelper.php @@ -81,12 +81,12 @@ class EntityFilterHelper public function getDescription(array $properties): array { - if (!$properties) { + if ($properties === []) { return []; } $description = []; - foreach ($properties as $property => $strategy) { + foreach (array_keys($properties) as $property) { $description[(string)$property] = [ 'property' => $property, 'type' => Type::BUILTIN_TYPE_STRING, diff --git a/src/ApiPlatform/Filter/LikeFilter.php b/src/ApiPlatform/Filter/LikeFilter.php index 90ac8e59..f88c89b1 100644 --- a/src/ApiPlatform/Filter/LikeFilter.php +++ b/src/ApiPlatform/Filter/LikeFilter.php @@ -61,7 +61,7 @@ final class LikeFilter extends AbstractFilter } $description = []; - foreach ($this->properties as $property => $strategy) { + foreach (array_keys($this->properties) as $property) { $description[(string)$property] = [ 'property' => $property, 'type' => Type::BUILTIN_TYPE_STRING, diff --git a/src/ApiPlatform/HandleAttachmentsUploadsProcessor.php b/src/ApiPlatform/HandleAttachmentsUploadsProcessor.php index 58955ff0..b5149442 100644 --- a/src/ApiPlatform/HandleAttachmentsUploadsProcessor.php +++ b/src/ApiPlatform/HandleAttachmentsUploadsProcessor.php @@ -46,7 +46,7 @@ final class HandleAttachmentsUploadsProcessor implements ProcessorInterface } - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed { if ($operation instanceof DeleteOperationInterface) { return $this->removeProcessor->process($data, $operation, $uriVariables, $context); diff --git a/src/Command/BackupCommand.php b/src/Command/BackupCommand.php index ef7d038f..085c552a 100644 --- a/src/Command/BackupCommand.php +++ b/src/Command/BackupCommand.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace App\Command; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Spatie\DbDumper\Databases\PostgreSql; use Symfony\Component\Console\Attribute\AsCommand; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\ORM\EntityManagerInterface; use PhpZip\Constants\ZipCompressionMethod; @@ -50,6 +52,7 @@ class BackupCommand extends Command $backup_attachments = $input->getOption('attachments'); $backup_config = $input->getOption('config'); $backup_full = $input->getOption('full'); + $overwrite = $input->getOption('overwrite'); if ($backup_full) { $backup_database = true; @@ -68,7 +71,9 @@ class BackupCommand extends Command //Check if the file already exists //Then ask the user, if he wants to overwrite the file - if (file_exists($output_filepath) && !$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) { + if (!$overwrite + && file_exists($output_filepath) + && !$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) { $io->error('Backup aborted!'); return Command::FAILURE; } @@ -136,30 +141,42 @@ class BackupCommand extends Command } } + private function runSQLDumper(DbDumper $dumper, ZipFile $zip, array $connectionParams): void + { + $this->configureDumper($connectionParams, $dumper); + + $tmp_file = tempnam(sys_get_temp_dir(), 'partdb_sql_dump'); + + $dumper->dumpToFile($tmp_file); + $zip->addFile($tmp_file, 'database.sql'); + } + protected function backupDatabase(ZipFile $zip, SymfonyStyle $io): void { $io->note('Backup database...'); //Determine if we use MySQL or SQLite $connection = $this->entityManager->getConnection(); - if ($connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) { + $params = $connection->getParams(); + $platform = $connection->getDatabasePlatform(); + if ($platform instanceof AbstractMySQLPlatform) { try { $io->note('MySQL database detected. Dump DB to SQL using mysqldump...'); - $params = $connection->getParams(); - $dumper = MySql::create(); - $this->configureDumper($params, $dumper); - - $tmp_file = tempnam(sys_get_temp_dir(), 'partdb_sql_dump'); - - $dumper->dumpToFile($tmp_file); - $zip->addFile($tmp_file, 'mysql_dump.sql'); + $this->runSQLDumper(MySql::create(), $zip, $params); } catch (\Exception $e) { $io->error('Could not dump database: '.$e->getMessage()); $io->error('This can maybe be fixed by installing the mysqldump binary and adding it to the PATH variable!'); } - } elseif ($connection->getDatabasePlatform() instanceof SqlitePlatform) { + } elseif ($platform instanceof PostgreSQLPlatform) { + try { + $io->note('PostgreSQL database detected. Dump DB to SQL using pg_dump...'); + $this->runSQLDumper(PostgreSql::create(), $zip, $params); + } catch (\Exception $e) { + $io->error('Could not dump database: '.$e->getMessage()); + $io->error('This can maybe be fixed by installing the pg_dump binary and adding it to the PATH variable!'); + } + } elseif ($platform instanceof SQLitePlatform) { $io->note('SQLite database detected. Copy DB file to ZIP...'); - $params = $connection->getParams(); $zip->addFile($params['path'], 'var/app.db'); } else { $io->error('Unknown database platform. Could not backup database!'); diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index 8f3bb902..a7465ca2 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -52,7 +52,6 @@ use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use Omines\DataTablesBundle\DataTableFactory; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -61,6 +60,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Contracts\Translation\TranslatorInterface; use function Symfony\Component\Translation\t; @@ -74,15 +75,10 @@ abstract class BaseAdminController extends AbstractController protected string $attachment_class = ''; protected ?string $parameter_class = ''; - /** - * @var EventDispatcher|EventDispatcherInterface - */ - protected EventDispatcher|EventDispatcherInterface $eventDispatcher; - public function __construct(protected TranslatorInterface $translator, protected UserPasswordHasherInterface $passwordEncoder, protected AttachmentSubmitHandler $attachmentSubmitHandler, protected EventCommentHelper $commentHelper, protected HistoryHelper $historyHelper, protected TimeTravel $timeTravel, - protected DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, protected LabelExampleElementsGenerator $barcodeExampleGenerator, + protected DataTableFactory $dataTableFactory, protected EventDispatcherInterface $eventDispatcher, protected LabelExampleElementsGenerator $barcodeExampleGenerator, protected LabelGenerator $labelGenerator, protected EntityManagerInterface $entityManager) { if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) { @@ -96,7 +92,6 @@ abstract class BaseAdminController extends AbstractController if ('' === $this->parameter_class || ($this->parameter_class && !is_a($this->parameter_class, AbstractParameter::class, true))) { throw new InvalidArgumentException('You have to override the $parameter_class value with a valid Parameter class in your subclass!'); } - $this->eventDispatcher = $eventDispatcher; } protected function revertElementIfNeeded(AbstractDBElement $entity, ?string $timestamp): ?DateTime @@ -192,10 +187,8 @@ abstract class BaseAdminController extends AbstractController } //Ensure that the master picture is still part of the attachments - if ($entity instanceof AttachmentContainingDBElement) { - if ($entity->getMasterPictureAttachment() !== null && !$entity->getAttachments()->contains($entity->getMasterPictureAttachment())) { - $entity->setMasterPictureAttachment(null); - } + if ($entity instanceof AttachmentContainingDBElement && ($entity->getMasterPictureAttachment() !== null && !$entity->getAttachments()->contains($entity->getMasterPictureAttachment()))) { + $entity->setMasterPictureAttachment(null); } $this->commentHelper->setMessage($form['log_comment']->getData()); @@ -283,10 +276,8 @@ abstract class BaseAdminController extends AbstractController } //Ensure that the master picture is still part of the attachments - if ($new_entity instanceof AttachmentContainingDBElement) { - if ($new_entity->getMasterPictureAttachment() !== null && !$new_entity->getAttachments()->contains($new_entity->getMasterPictureAttachment())) { - $new_entity->setMasterPictureAttachment(null); - } + if ($new_entity instanceof AttachmentContainingDBElement && ($new_entity->getMasterPictureAttachment() !== null && !$new_entity->getAttachments()->contains($new_entity->getMasterPictureAttachment()))) { + $new_entity->setMasterPictureAttachment(null); } $this->commentHelper->setMessage($form['log_comment']->getData()); @@ -333,8 +324,8 @@ abstract class BaseAdminController extends AbstractController try { $errors = $importer->importFileAndPersistToDB($file, $options); - foreach ($errors as $name => $error) { - foreach ($error as $violation) { + foreach ($errors as $name => ['violations' => $violations]) { + foreach ($violations as $violation) { $this->addFlash('error', $name.': '.$violation->getMessage()); } } @@ -344,6 +335,7 @@ abstract class BaseAdminController extends AbstractController } } + ret: //Mass creation form $mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]); $mass_creation_form->handleRequest($request); @@ -356,11 +348,14 @@ abstract class BaseAdminController extends AbstractController $results = $importer->massCreation($data['lines'], $this->entity_class, $data['parent'] ?? null, $errors); //Show errors to user: - foreach ($errors as $error) { - if ($error['entity'] instanceof AbstractStructuralDBElement) { - $this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']); - } else { //When we don't have a structural element, we can only show the name - $this->addFlash('error', $error['entity']->getName().':'.$error['violations']); + foreach ($errors as ['entity' => $new_entity, 'violations' => $violations]) { + /** @var ConstraintViolationInterface $violation */ + foreach ($violations as $violation) { + if ($new_entity instanceof AbstractStructuralDBElement) { + $this->addFlash('error', $new_entity->getFullPath().':'.$violation->getMessage()); + } else { //When we don't have a structural element, we can only show the name + $this->addFlash('error', $new_entity->getName().':'.$violation->getMessage()); + } } } @@ -371,11 +366,10 @@ abstract class BaseAdminController extends AbstractController $em->flush(); if (count($results) > 0) { - $this->addFlash('success', t('entity.mass_creation_flash', ['%COUNT%' => count($results)])); + $this->addFlash('success', t('entity.mass_creation_flash', ['%COUNT%' => count($results)])); } } - ret: return $this->render($this->twig_template, [ 'entity' => $new_entity, 'form' => $form, diff --git a/src/Controller/KiCadApiController.php b/src/Controller/KiCadApiController.php index a45e71dd..c28e87a6 100644 --- a/src/Controller/KiCadApiController.php +++ b/src/Controller/KiCadApiController.php @@ -30,6 +30,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +/** + * @see \App\Tests\Controller\KiCadApiControllerTest + */ #[Route('/kicad-api/v1')] class KiCadApiController extends AbstractController { @@ -62,7 +65,7 @@ class KiCadApiController extends AbstractController #[Route('/parts/category/{category}.json', name: 'kicad_api_category')] public function categoryParts(?Category $category): Response { - if ($category) { + if ($category !== null) { $this->denyAccessUnlessGranted('read', $category); } else { $this->denyAccessUnlessGranted('@categories.read'); diff --git a/src/Controller/LogController.php b/src/Controller/LogController.php index c1f81dc7..a849539d 100644 --- a/src/Controller/LogController.php +++ b/src/Controller/LogController.php @@ -151,7 +151,7 @@ class LogController extends AbstractController if (EventUndoMode::UNDO === $mode) { $this->undoLog($log_element); - } elseif (EventUndoMode::REVERT === $mode) { + } else { $this->revertLog($log_element); } diff --git a/src/Controller/OAuthClientController.php b/src/Controller/OAuthClientController.php index 388c7e16..9606a4e4 100644 --- a/src/Controller/OAuthClientController.php +++ b/src/Controller/OAuthClientController.php @@ -51,7 +51,7 @@ class OAuthClientController extends AbstractController } #[Route('/{name}/check', name: 'oauth_client_check')] - public function check(string $name, Request $request): Response + public function check(string $name): Response { $this->denyAccessUnlessGranted('@system.manage_oauth_tokens'); diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 20b07fd9..d6acb36b 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -329,7 +329,7 @@ class PartController extends AbstractController $this->em->flush(); if ($mode === 'new') { $this->addFlash('success', 'part.created_flash'); - } else if ($mode === 'edit') { + } elseif ($mode === 'edit') { $this->addFlash('success', 'part.edited_flash'); } @@ -358,11 +358,11 @@ class PartController extends AbstractController $template = ''; if ($mode === 'new') { $template = 'parts/edit/new_part.html.twig'; - } else if ($mode === 'edit') { + } elseif ($mode === 'edit') { $template = 'parts/edit/edit_part_info.html.twig'; - } else if ($mode === 'merge') { + } elseif ($mode === 'merge') { $template = 'parts/edit/merge_parts.html.twig'; - } else if ($mode === 'update_from_ip') { + } elseif ($mode === 'update_from_ip') { $template = 'parts/edit/update_from_ip.html.twig'; } diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 9068e7c2..761e498c 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -207,6 +207,11 @@ class ProjectController extends AbstractController //Preset the BOM entries with the selected parts, when the form was not submitted yet $preset_data = new ArrayCollection(); foreach (explode(',', (string) $request->get('parts', '')) as $part_id) { + //Skip empty part IDs. Postgres seems to be especially sensitive to empty strings, as it does not allow them in integer columns + if ($part_id === '') { + continue; + } + $part = $entityManager->getRepository(Part::class)->find($part_id); if (null !== $part) { //If there is already a BOM entry for this part, we use this one (we edit it then) @@ -214,7 +219,7 @@ class ProjectController extends AbstractController 'project' => $project, 'part' => $part ]); - if ($bom_entry) { + if ($bom_entry !== null) { $preset_data->add($bom_entry); } else { //Otherwise create an empty one $entry = new ProjectBOMEntry(); diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php index 93fbb822..77183c89 100644 --- a/src/Controller/ScanController.php +++ b/src/Controller/ScanController.php @@ -54,6 +54,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; use Symfony\Component\Routing\Attribute\Route; +/** + * @see \App\Tests\Controller\ScanControllerTest + */ #[Route(path: '/scan')] class ScanController extends AbstractController { diff --git a/src/Controller/ToolsController.php b/src/Controller/ToolsController.php index 984e50eb..8f49d6f7 100644 --- a/src/Controller/ToolsController.php +++ b/src/Controller/ToolsController.php @@ -22,6 +22,7 @@ declare(strict_types=1); */ namespace App\Controller; +use Symfony\Component\Runtime\SymfonyRuntime; use App\Services\Attachments\AttachmentSubmitHandler; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Attachments\BuiltinAttachmentsFinder; @@ -84,7 +85,7 @@ class ToolsController extends AbstractController 'php_post_max_size' => ini_get('post_max_size'), 'kernel_runtime_environment' => $this->getParameter('kernel.runtime_environment'), 'kernel_runtime_mode' => $this->getParameter('kernel.runtime_mode'), - 'kernel_runtime' => $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime', + 'kernel_runtime' => $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? SymfonyRuntime::class, //DB section 'db_type' => $DBInfoHelper->getDatabaseType() ?? 'Unknown', diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index 364cea58..f84547dc 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -38,7 +38,6 @@ use Doctrine\ORM\EntityManagerInterface; use RuntimeException; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\EnumType; @@ -59,11 +58,8 @@ use Symfony\Component\Validator\Constraints\Length; #[Route(path: '/user')] class UserSettingsController extends AbstractController { - protected EventDispatcher|EventDispatcherInterface $eventDispatcher; - - public function __construct(protected bool $demo_mode, EventDispatcherInterface $eventDispatcher) + public function __construct(protected bool $demo_mode, protected EventDispatcherInterface $eventDispatcher) { - $this->eventDispatcher = $eventDispatcher; } #[Route(path: '/2fa_backup_codes', name: 'show_backup_codes')] diff --git a/src/DataFixtures/PartFixtures.php b/src/DataFixtures/PartFixtures.php index a11b11e9..b7193ec0 100644 --- a/src/DataFixtures/PartFixtures.php +++ b/src/DataFixtures/PartFixtures.php @@ -99,7 +99,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $part->addPartLot($partLot1); $partLot2 = new PartLot(); - $partLot2->setExpirationDate(new DateTime()); + $partLot2->setExpirationDate(new \DateTimeImmutable()); $partLot2->setComment('Test'); $partLot2->setNeedsRefill(true); $partLot2->setStorageLocation($manager->find(StorageLocation::class, 3)); diff --git a/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php b/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php index b296c4fa..ff69a69e 100644 --- a/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php +++ b/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php @@ -50,6 +50,6 @@ class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter $paginator = new Paginator($qb_without_group_by); - return $paginator->count() ?? 0; + return $paginator->count(); } } diff --git a/src/DataTables/Adapters/TwoStepORMAdapter.php b/src/DataTables/Adapters/TwoStepORMAdapter.php index 27ccd5a3..2086c907 100644 --- a/src/DataTables/Adapters/TwoStepORMAdapter.php +++ b/src/DataTables/Adapters/TwoStepORMAdapter.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\DataTables\Adapters; +use Doctrine\ORM\Query\Expr\From; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Tools\Pagination\Paginator; @@ -51,12 +52,12 @@ class TwoStepORMAdapter extends ORMAdapter private bool $use_simple_total = false; - private \Closure|null $query_modifier; + private \Closure|null $query_modifier = null; public function __construct(ManagerRegistry $registry = null) { parent::__construct($registry); - $this->detailQueryCallable = static function (QueryBuilder $qb, array $ids) { + $this->detailQueryCallable = static function (QueryBuilder $qb, array $ids): never { throw new \RuntimeException('You need to set the detail_query option to use the TwoStepORMAdapter'); }; } @@ -66,9 +67,7 @@ class TwoStepORMAdapter extends ORMAdapter parent::configureOptions($resolver); $resolver->setRequired('filter_query'); - $resolver->setDefault('query', function (Options $options) { - return $options['filter_query']; - }); + $resolver->setDefault('query', fn(Options $options) => $options['filter_query']); $resolver->setRequired('detail_query'); $resolver->setAllowedTypes('detail_query', \Closure::class); @@ -108,7 +107,7 @@ class TwoStepORMAdapter extends ORMAdapter } } - /** @var Query\Expr\From $fromClause */ + /** @var From $fromClause */ $fromClause = $builder->getDQLPart('from')[0]; $identifier = "{$fromClause->getAlias()}.{$this->metadata->getSingleIdentifierFieldName()}"; @@ -129,6 +128,12 @@ class TwoStepORMAdapter extends ORMAdapter $query->setIdentifierPropertyPath($this->mapFieldToPropertyPath($identifier, $aliases)); } + protected function hasGroupByPart(string $identifier, array $gbList): bool + { + //Always return true, to fix the issue with the count query, when having mutliple group by parts + return true; + } + protected function getCount(QueryBuilder $queryBuilder, $identifier): int { if ($this->query_modifier !== null) { @@ -195,7 +200,7 @@ class TwoStepORMAdapter extends ORMAdapter /** The paginator count queries can be rather slow, so when query for total count (100ms or longer), * just return the entity count. */ - /** @var Query\Expr\From $from_expr */ + /** @var From $from_expr */ $from_expr = $queryBuilder->getDQLPart('from')[0]; return $this->manager->getRepository($from_expr->getFrom())->count([]); diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php index 70995209..da1cef0f 100644 --- a/src/DataTables/AttachmentDataTable.php +++ b/src/DataTables/AttachmentDataTable.php @@ -86,12 +86,13 @@ final class AttachmentDataTable implements DataTableTypeInterface $dataTable->add('name', TextColumn::class, [ 'label' => 'attachment.edit.name', + 'orderField' => 'NATSORT(attachment.name)', 'render' => function ($value, Attachment $context) { //Link to external source if ($context->isExternal()) { return sprintf( '%s', - htmlspecialchars($context->getURL()), + htmlspecialchars((string) $context->getURL()), htmlspecialchars($value) ); } @@ -111,6 +112,7 @@ final class AttachmentDataTable implements DataTableTypeInterface $dataTable->add('attachment_type', TextColumn::class, [ 'label' => 'attachment.table.type', 'field' => 'attachment_type.name', + 'orderField' => 'NATSORT(attachment_type.name)', 'render' => fn($value, Attachment $context): string => sprintf( '%s', $this->entityURLGenerator->editURL($context->getAttachmentType()), diff --git a/src/DataTables/Column/EnumColumn.php b/src/DataTables/Column/EnumColumn.php index e41b79e4..5a5d998d 100644 --- a/src/DataTables/Column/EnumColumn.php +++ b/src/DataTables/Column/EnumColumn.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Column; use Omines\DataTablesBundle\Column\AbstractColumn; diff --git a/src/DataTables/Column/LocaleDateTimeColumn.php b/src/DataTables/Column/LocaleDateTimeColumn.php index d485913c..e60b2867 100644 --- a/src/DataTables/Column/LocaleDateTimeColumn.php +++ b/src/DataTables/Column/LocaleDateTimeColumn.php @@ -47,7 +47,7 @@ class LocaleDateTimeColumn extends AbstractColumn } if (!$value instanceof DateTimeInterface) { - $value = new DateTime((string) $value); + $value = new \DateTimeImmutable((string) $value); } $formatValues = [ diff --git a/src/DataTables/Filters/Constraints/DateTimeConstraint.php b/src/DataTables/Filters/Constraints/DateTimeConstraint.php index 1ded472e..23134de5 100644 --- a/src/DataTables/Filters/Constraints/DateTimeConstraint.php +++ b/src/DataTables/Filters/Constraints/DateTimeConstraint.php @@ -22,9 +22,92 @@ declare(strict_types=1); */ namespace App\DataTables\Filters\Constraints; +use Doctrine\ORM\QueryBuilder; +use RuntimeException; + /** - * An alias of NumberConstraint to use to filter on a DateTime + * Similar to NumberConstraint but for DateTime values */ -class DateTimeConstraint extends NumberConstraint +class DateTimeConstraint extends AbstractConstraint { + protected const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN']; + + public function __construct( + string $property, + string $identifier = null, + /** + * The value1 used for comparison (this is the main one used for all mono-value comparisons) + */ + protected \DateTimeInterface|null $value1 = null, + protected ?string $operator = null, + /** + * The second value used when operator is RANGE; this is the upper bound of the range + */ + protected \DateTimeInterface|null $value2 = null) + { + parent::__construct($property, $identifier); + } + + public function getValue1(): ?\DateTimeInterface + { + return $this->value1; + } + + public function setValue1(\DateTimeInterface|null $value1): void + { + $this->value1 = $value1; + } + + public function getValue2(): ?\DateTimeInterface + { + return $this->value2; + } + + public function setValue2(?\DateTimeInterface $value2): void + { + $this->value2 = $value2; + } + + public function getOperator(): string|null + { + return $this->operator; + } + + /** + * @param string $operator + */ + public function setOperator(?string $operator): void + { + $this->operator = $operator; + } + + public function isEnabled(): bool + { + return $this->value1 !== null + && ($this->operator !== null && $this->operator !== ''); + } + + public function apply(QueryBuilder $queryBuilder): void + { + //If no value is provided then we do not apply a filter + if (!$this->isEnabled()) { + return; + } + + //Ensure we have an valid operator + if(!in_array($this->operator, self::ALLOWED_OPERATOR_VALUES, true)) { + throw new \RuntimeException('Invalid operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES)); + } + + if ($this->operator !== 'BETWEEN') { + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, $this->operator, $this->value1); + } else { + if ($this->value2 === null) { + throw new RuntimeException("Cannot use operator BETWEEN without value2!"); + } + + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '1', '>=', $this->value1); + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '2', '<=', $this->value2); + } + } } diff --git a/src/DataTables/Filters/Constraints/NumberConstraint.php b/src/DataTables/Filters/Constraints/NumberConstraint.php index 9e53b8f3..c872dade 100644 --- a/src/DataTables/Filters/Constraints/NumberConstraint.php +++ b/src/DataTables/Filters/Constraints/NumberConstraint.php @@ -29,12 +29,28 @@ class NumberConstraint extends AbstractConstraint { protected const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN']; - public function getValue1(): float|int|null|\DateTimeInterface + public function __construct( + string $property, + string $identifier = null, + /** + * The value1 used for comparison (this is the main one used for all mono-value comparisons) + */ + protected float|int|null $value1 = null, + protected ?string $operator = null, + /** + * The second value used when operator is RANGE; this is the upper bound of the range + */ + protected float|int|null $value2 = null) + { + parent::__construct($property, $identifier); + } + + public function getValue1(): float|int|null { return $this->value1; } - public function setValue1(float|int|\DateTimeInterface|null $value1): void + public function setValue1(float|int|null $value1): void { $this->value1 = $value1; } @@ -63,22 +79,6 @@ class NumberConstraint extends AbstractConstraint } - public function __construct( - string $property, - string $identifier = null, - /** - * The value1 used for comparison (this is the main one used for all mono-value comparisons) - */ - protected float|int|\DateTimeInterface|null $value1 = null, - protected ?string $operator = null, - /** - * The second value used when operator is RANGE; this is the upper bound of the range - */ - protected float|int|\DateTimeInterface|null $value2 = null) - { - parent::__construct($property, $identifier); - } - public function isEnabled(): bool { return $this->value1 !== null @@ -105,7 +105,13 @@ class NumberConstraint extends AbstractConstraint } $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '1', '>=', $this->value1); - $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '2', '<=', $this->value2); + + //Workaround for the amountSum which we need to add twice on postgres. Replace one of the __ with __2 to make it work + //Otherwise we get an error, that __partLot was already defined + + $property2 = str_replace('__', '__2', $this->property); + + $this->addSimpleAndConstraint($queryBuilder, $property2, $this->identifier . '2', '<=', $this->value2); } } } diff --git a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php index eed37b8b..eb96ad33 100644 --- a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php @@ -23,13 +23,20 @@ declare(strict_types=1); namespace App\DataTables\Filters\Constraints\Part; use App\DataTables\Filters\Constraints\BooleanConstraint; +use App\Entity\Parts\PartLot; use Doctrine\ORM\QueryBuilder; class LessThanDesiredConstraint extends BooleanConstraint { public function __construct(string $property = null, string $identifier = null, ?bool $default_value = null) { - parent::__construct($property ?? 'amountSum', $identifier, $default_value); + parent::__construct($property ?? '( + SELECT COALESCE(SUM(ld_partLot.amount), 0.0) + FROM '.PartLot::class.' ld_partLot + WHERE ld_partLot.part = part.id + AND ld_partLot.instock_unknown = false + AND (ld_partLot.expiration_date IS NULL OR ld_partLot.expiration_date > CURRENT_DATE()) + )', $identifier ?? 'amountSumLessThanDesired', $default_value); } public function apply(QueryBuilder $queryBuilder): void @@ -41,9 +48,9 @@ class LessThanDesiredConstraint extends BooleanConstraint //If value is true, we want to filter for parts with stock < desired stock if ($this->value) { - $queryBuilder->andHaving('amountSum < minamount'); + $queryBuilder->andHaving( $this->property . ' < part.minamount'); } else { - $queryBuilder->andHaving('amountSum >= minamount'); + $queryBuilder->andHaving($this->property . ' >= part.minamount'); } } } diff --git a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php index baae69c7..ae9d14cd 100644 --- a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php @@ -30,16 +30,9 @@ class TagsConstraint extends AbstractConstraint { final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE']; - /** - * @param string $value - */ - public function __construct(string $property, string $identifier = null, /** - * @var string The value to compare to - */ - protected $value = null, /** - * @var string|null The operator to use - */ - protected ?string $operator = '') + public function __construct(string $property, string $identifier = null, + protected ?string $value = null, + protected ?string $operator = '') { parent::__construct($property, $identifier); } @@ -61,12 +54,12 @@ class TagsConstraint extends AbstractConstraint return $this; } - public function getValue(): string + public function getValue(): ?string { return $this->value; } - public function setValue(string $value): self + public function setValue(?string $value): self { $this->value = $value; return $this; diff --git a/src/DataTables/Filters/Constraints/TextConstraint.php b/src/DataTables/Filters/Constraints/TextConstraint.php index 60f83328..89567d83 100644 --- a/src/DataTables/Filters/Constraints/TextConstraint.php +++ b/src/DataTables/Filters/Constraints/TextConstraint.php @@ -33,9 +33,9 @@ class TextConstraint extends AbstractConstraint * @param string $value */ public function __construct(string $property, string $identifier = null, /** - * @var string The value to compare to + * @var string|null The value to compare to */ - protected $value = null, /** + protected ?string $value = null, /** * @var string|null The operator to use */ protected ?string $operator = '') @@ -60,12 +60,12 @@ class TextConstraint extends AbstractConstraint return $this; } - public function getValue(): string + public function getValue(): ?string { return $this->value; } - public function setValue(string $value): self + public function setValue(?string $value): self { $this->value = $value; return $this; @@ -113,7 +113,7 @@ class TextConstraint extends AbstractConstraint //Regex is only supported on MySQL and needs a special function if ($this->operator === 'REGEX') { - $queryBuilder->andWhere(sprintf('REGEXP(%s, :%s) = 1', $this->property, $this->identifier)); + $queryBuilder->andWhere(sprintf('REGEXP(%s, :%s) = TRUE', $this->property, $this->identifier)); $queryBuilder->setParameter($this->identifier, $this->value); } } diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index 177b6dbd..ff98c76f 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -37,6 +37,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\PartLot; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\ProjectSystem\Project; @@ -123,8 +124,13 @@ class PartFilter implements FilterInterface This seems to be related to the fact, that PDO does not have an float parameter type and using string type does not work in this situation (at least in SQLite) TODO: Find a better solution here */ - //We have to use Having here, as we use an alias column which is not supported on the where clause and would result in an error - $this->amountSum = (new IntConstraint('amountSum'))->useHaving(); + $this->amountSum = (new IntConstraint('( + SELECT COALESCE(SUM(__partLot.amount), 0.0) + FROM '.PartLot::class.' __partLot + WHERE __partLot.part = part.id + AND __partLot.instock_unknown = false + AND (__partLot.expiration_date IS NULL OR __partLot.expiration_date > CURRENT_DATE()) + )', identifier: "amountSumWhere")); $this->lotCount = new IntConstraint('COUNT(_partLots)'); $this->lessThanDesired = new LessThanDesiredConstraint(); diff --git a/src/DataTables/Filters/PartSearchFilter.php b/src/DataTables/Filters/PartSearchFilter.php index 34dd2d7a..84dfe9b3 100644 --- a/src/DataTables/Filters/PartSearchFilter.php +++ b/src/DataTables/Filters/PartSearchFilter.php @@ -129,7 +129,7 @@ class PartSearchFilter implements FilterInterface //Convert the fields to search to a list of expressions $expressions = array_map(function (string $field): string { if ($this->regex) { - return sprintf("REGEXP(%s, :search_query) = 1", $field); + return sprintf("REGEXP(%s, :search_query) = TRUE", $field); } return sprintf("%s LIKE :search_query", $field); diff --git a/src/DataTables/Helpers/ColumnSortHelper.php b/src/DataTables/Helpers/ColumnSortHelper.php index c61ad6ce..05bd8182 100644 --- a/src/DataTables/Helpers/ColumnSortHelper.php +++ b/src/DataTables/Helpers/ColumnSortHelper.php @@ -109,7 +109,7 @@ class ColumnSortHelper } //and the remaining non-visible columns - foreach ($this->columns as $col_id => $col_data) { + foreach (array_keys($this->columns) as $col_id) { if (in_array($col_id, $processed_columns, true)) { // column already processed continue; diff --git a/src/DataTables/LogDataTable.php b/src/DataTables/LogDataTable.php index b63fc2d7..f6604279 100644 --- a/src/DataTables/LogDataTable.php +++ b/src/DataTables/LogDataTable.php @@ -154,7 +154,7 @@ class LogDataTable implements DataTableTypeInterface $dataTable->add('user', TextColumn::class, [ 'label' => 'log.user', - 'orderField' => 'user.name', + 'orderField' => 'NATSORT(user.name)', 'render' => function ($value, AbstractLogEntry $context): string { $user = $context->getUser(); @@ -162,7 +162,7 @@ class LogDataTable implements DataTableTypeInterface if (!$user instanceof User) { if ($context->isCLIEntry()) { return sprintf('%s [%s]', - htmlentities($context->getCLIUsername()), + htmlentities((string) $context->getCLIUsername()), $this->translator->trans('log.cli_user') ); } diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 67dfa771..28c564b1 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -108,12 +108,14 @@ final class PartsDataTable implements DataTableTypeInterface ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context), + 'orderField' => 'NATSORT(part.name)' ]) ->add('id', TextColumn::class, [ 'label' => $this->translator->trans('part.table.id'), ]) ->add('ipn', TextColumn::class, [ 'label' => $this->translator->trans('part.table.ipn'), + 'orderField' => 'NATSORT(part.ipn)' ]) ->add('description', MarkdownColumn::class, [ 'label' => $this->translator->trans('part.table.description'), @@ -121,23 +123,24 @@ final class PartsDataTable implements DataTableTypeInterface ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), 'property' => 'category', - 'orderField' => '_category.name' + 'orderField' => 'NATSORT(_category.name)' ]) ->add('footprint', EntityColumn::class, [ 'property' => 'footprint', 'label' => $this->translator->trans('part.table.footprint'), - 'orderField' => '_footprint.name' + 'orderField' => 'NATSORT(_footprint.name)' ]) ->add('manufacturer', EntityColumn::class, [ 'property' => 'manufacturer', 'label' => $this->translator->trans('part.table.manufacturer'), - 'orderField' => '_manufacturer.name' + 'orderField' => 'NATSORT(_manufacturer.name)' ]) ->add('storelocation', TextColumn::class, [ 'label' => $this->translator->trans('part.table.storeLocations'), - 'orderField' => '_storelocations.name', + 'orderField' => 'NATSORT(_storelocations.name)', 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context), ], alias: 'storage_location') + ->add('amount', TextColumn::class, [ 'label' => $this->translator->trans('part.table.amount'), 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderAmount($context), @@ -149,9 +152,21 @@ final class PartsDataTable implements DataTableTypeInterface $context->getPartUnit())), ]) ->add('partUnit', TextColumn::class, [ - 'field' => 'partUnit.name', 'label' => $this->translator->trans('part.table.partUnit'), - 'orderField' => '_partUnit.name' + 'orderField' => 'NATSORT(_partUnit.name)', + 'render' => function($value, Part $context): string { + $partUnit = $context->getPartUnit(); + if ($partUnit === null) { + return ''; + } + + $tmp = htmlspecialchars($partUnit->getName()); + + if ($partUnit->getUnit()) { + $tmp .= ' ('.htmlspecialchars($partUnit->getUnit()).')'; + } + return $tmp; + } ]) ->add('addedDate', LocaleDateTimeColumn::class, [ 'label' => $this->translator->trans('part.table.addedDate'), @@ -169,7 +184,7 @@ final class PartsDataTable implements DataTableTypeInterface 'label' => $this->translator->trans('part.table.manufacturingStatus'), 'class' => ManufacturingStatus::class, 'render' => function (?ManufacturingStatus $status, Part $context): string { - if (!$status) { + if ($status === null) { return ''; } @@ -178,6 +193,7 @@ final class PartsDataTable implements DataTableTypeInterface ]) ->add('manufacturer_product_number', TextColumn::class, [ 'label' => $this->translator->trans('part.table.mpn'), + 'orderField' => 'NATSORT(part.manufacturer_product_number)' ]) ->add('mass', SIUnitNumberColumn::class, [ 'label' => $this->translator->trans('part.table.mass'), @@ -264,8 +280,8 @@ final class PartsDataTable implements DataTableTypeInterface ->addSelect('part.minamount AS HIDDEN minamount') ->from(Part::class, 'part') - //This must be the only group by, or the paginator will not work correctly - ->addGroupBy('part.id'); + //The other group by fields, are dynamically added by the addJoins method + ->addGroupBy('part'); } private function getDetailQuery(QueryBuilder $builder, array $filter_results): void @@ -345,7 +361,7 @@ final class PartsDataTable implements DataTableTypeInterface //Calculate amount sum using a subquery, so we can filter and sort by it $builder->addSelect( '( - SELECT IFNULL(SUM(partLot.amount), 0.0) + SELECT COALESCE(SUM(partLot.amount), 0.0) FROM '.PartLot::class.' partLot WHERE partLot.part = part.id AND partLot.instock_unknown = false @@ -356,35 +372,52 @@ final class PartsDataTable implements DataTableTypeInterface if (str_contains($dql, '_category')) { $builder->leftJoin('part.category', '_category'); + $builder->addGroupBy('_category'); } if (str_contains($dql, '_master_picture_attachment')) { $builder->leftJoin('part.master_picture_attachment', '_master_picture_attachment'); + $builder->addGroupBy('_master_picture_attachment'); } if (str_contains($dql, '_partLots') || str_contains($dql, '_storelocations')) { $builder->leftJoin('part.partLots', '_partLots'); $builder->leftJoin('_partLots.storage_location', '_storelocations'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_partLots'); + //$builder->addGroupBy('_storelocations'); } if (str_contains($dql, '_footprint')) { $builder->leftJoin('part.footprint', '_footprint'); + $builder->addGroupBy('_footprint'); } if (str_contains($dql, '_manufacturer')) { $builder->leftJoin('part.manufacturer', '_manufacturer'); + $builder->addGroupBy('_manufacturer'); } if (str_contains($dql, '_orderdetails') || str_contains($dql, '_suppliers')) { $builder->leftJoin('part.orderdetails', '_orderdetails'); $builder->leftJoin('_orderdetails.supplier', '_suppliers'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_orderdetails'); + //$builder->addGroupBy('_suppliers'); } if (str_contains($dql, '_attachments')) { $builder->leftJoin('part.attachments', '_attachments'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_attachments'); } if (str_contains($dql, '_partUnit')) { $builder->leftJoin('part.partUnit', '_partUnit'); + $builder->addGroupBy('_partUnit'); } if (str_contains($dql, '_parameters')) { $builder->leftJoin('part.parameters', '_parameters'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_parameters'); } if (str_contains($dql, '_projectBomEntries')) { $builder->leftJoin('part.project_bom_entries', '_projectBomEntries'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_projectBomEntries'); } return $builder; diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index ffe166f3..84a89320 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -82,10 +82,10 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), - 'orderField' => 'part.name', + 'orderField' => 'NATSORT(part.name)', 'render' => function ($value, ProjectBOMEntry $context) { if(!$context->getPart() instanceof Part) { - return htmlspecialchars($context->getName()); + return htmlspecialchars((string) $context->getName()); } if($context->getPart() instanceof Part) { $tmp = $this->partDataTableHelper->renderName($context->getPart()); @@ -101,7 +101,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ]) ->add('ipn', TextColumn::class, [ 'label' => $this->translator->trans('part.table.ipn'), - 'orderField' => 'part.ipn', + 'orderField' => 'NATSORT(part.ipn)', 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { if($context->getPart() instanceof Part) { @@ -124,18 +124,18 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), 'property' => 'part.category', - 'orderField' => 'category.name', + 'orderField' => 'NATSORT(category.name)', ]) ->add('footprint', EntityColumn::class, [ 'property' => 'part.footprint', 'label' => $this->translator->trans('part.table.footprint'), - 'orderField' => 'footprint.name', + 'orderField' => 'NATSORT(footprint.name)', ]) ->add('manufacturer', EntityColumn::class, [ 'property' => 'part.manufacturer', 'label' => $this->translator->trans('part.table.manufacturer'), - 'orderField' => 'manufacturer.name', + 'orderField' => 'NATSORT(manufacturer.name)', ]) ->add('mountnames', TextColumn::class, [ @@ -154,7 +154,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'label' => 'project.bom.instockAmount', 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { - if ($context->getPart()) { + if ($context->getPart() !== null) { return $this->partDataTableHelper->renderAmount($context->getPart()); } @@ -165,7 +165,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'label' => 'part.table.storeLocations', 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { - if ($context->getPart()) { + if ($context->getPart() !== null) { return $this->partDataTableHelper->renderStorageLocations($context->getPart()); } diff --git a/src/Doctrine/Functions/ArrayPosition.php b/src/Doctrine/Functions/ArrayPosition.php new file mode 100644 index 00000000..39276912 --- /dev/null +++ b/src/Doctrine/Functions/ArrayPosition.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +class ArrayPosition extends FunctionNode +{ + private ?Node $array = null; + + private ?Node $field = null; + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->array = $parser->InParameter(); + + $parser->match(TokenType::T_COMMA); + + $this->field = $parser->ArithmeticPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $sqlWalker): string + { + return 'ARRAY_POSITION(' . + $this->array->dispatch($sqlWalker) . ', ' . + $this->field->dispatch($sqlWalker) . + ')'; + } +} \ No newline at end of file diff --git a/src/Doctrine/Functions/Field2.php b/src/Doctrine/Functions/Field2.php index 0e65062f..57f55653 100644 --- a/src/Doctrine/Functions/Field2.php +++ b/src/Doctrine/Functions/Field2.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace App\Doctrine\Functions; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\TokenType; @@ -36,7 +38,7 @@ class Field2 extends FunctionNode private $values = []; - public function parse(\Doctrine\ORM\Query\Parser $parser): void + public function parse(Parser $parser): void { $parser->match(TokenType::T_IDENTIFIER); $parser->match(TokenType::T_OPEN_PARENTHESIS); @@ -50,7 +52,7 @@ class Field2 extends FunctionNode $lexer = $parser->getLexer(); while (count($this->values) < 1 || - $lexer->lookahead['type'] != TokenType::T_CLOSE_PARENTHESIS) { + $lexer->lookahead->type !== TokenType::T_CLOSE_PARENTHESIS) { $parser->match(TokenType::T_COMMA); $this->values[] = $parser->ArithmeticPrimary(); } @@ -58,15 +60,16 @@ class Field2 extends FunctionNode $parser->match(TokenType::T_CLOSE_PARENTHESIS); } - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker): string + public function getSql(SqlWalker $sqlWalker): string { $query = 'FIELD2('; $query .= $this->field->dispatch($sqlWalker); $query .= ', '; + $counter = count($this->values); - for ($i = 0; $i < count($this->values); $i++) { + for ($i = 0; $i < $counter; $i++) { if ($i > 0) { $query .= ', '; } @@ -74,8 +77,6 @@ class Field2 extends FunctionNode $query .= $this->values[$i]->dispatch($sqlWalker); } - $query .= ')'; - - return $query; + return $query . ')'; } } \ No newline at end of file diff --git a/src/Doctrine/Functions/Natsort.php b/src/Doctrine/Functions/Natsort.php new file mode 100644 index 00000000..fc60058f --- /dev/null +++ b/src/Doctrine/Functions/Natsort.php @@ -0,0 +1,119 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\MariaDBPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +class Natsort extends FunctionNode +{ + private ?Node $field = null; + + private static ?bool $supportsNaturalSort = null; + + private static bool $allowSlowNaturalSort = false; + + /** + * As we can not inject parameters into the function, we use an event listener, to call the value on the static function. + * This is the only way to inject the value into the function. + * @param bool $allow + * @return void + */ + public static function allowSlowNaturalSort(bool $allow = true): void + { + self::$allowSlowNaturalSort = $allow; + } + + /** + * Check if the MariaDB version which is connected to supports the natural sort (meaning it has a version of 10.7.0 or higher) + * The result is cached in memory. + * @param Connection $connection + * @return bool + * @throws Exception + */ + private function mariaDBSupportsNaturalSort(Connection $connection): bool + { + if (self::$supportsNaturalSort !== null) { + return self::$supportsNaturalSort; + } + + $version = $connection->getServerVersion(); + //Remove the -MariaDB suffix + $version = str_replace('-MariaDB', '', $version); + //We need at least MariaDB 10.7.0 to support the natural sort + self::$supportsNaturalSort = version_compare($version, '10.7.0', '>='); + return self::$supportsNaturalSort; + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->field = $parser->ArithmeticExpression(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $sqlWalker): string + { + assert($this->field !== null, 'Field is not set'); + + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + + if ($platform instanceof PostgreSQLPlatform) { + return $this->field->dispatch($sqlWalker) . ' COLLATE numeric'; + } + + if ($platform instanceof MariaDBPlatform && $this->mariaDBSupportsNaturalSort($sqlWalker->getConnection())) { + return 'NATURAL_SORT_KEY(' . $this->field->dispatch($sqlWalker) . ')'; + } + + //Do the following operations only if we allow slow natural sort + if (self::$allowSlowNaturalSort) { + if ($platform instanceof SQLitePlatform) { + return $this->field->dispatch($sqlWalker).' COLLATE NATURAL_CMP'; + } + + if ($platform instanceof AbstractMySQLPlatform) { + return 'NatSortKey(' . $this->field->dispatch($sqlWalker) . ', 0)'; + } + } + + //For every other platform, return the field as is + return $this->field->dispatch($sqlWalker); + } + + +} \ No newline at end of file diff --git a/src/Doctrine/Functions/Regexp.php b/src/Doctrine/Functions/Regexp.php new file mode 100644 index 00000000..d7c6f1e7 --- /dev/null +++ b/src/Doctrine/Functions/Regexp.php @@ -0,0 +1,52 @@ +. + */ + +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\SqlWalker; + +/** + * Similar to the regexp function, but with support for multi platform. + */ +class Regexp extends \DoctrineExtensions\Query\Mysql\Regexp +{ + public function getSql(SqlWalker $sqlWalker): string + { + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + + // + if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { + $operator = 'REGEXP'; + } elseif ($platform instanceof PostgreSQLPlatform) { + //Use the case-insensitive operator, to have the same behavior as MySQL + $operator = '~*'; + } else { + throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support regular expressions.'); + } + + return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->regexp->dispatch($sqlWalker) . ')'; + } +} \ No newline at end of file diff --git a/src/Doctrine/Helpers/FieldHelper.php b/src/Doctrine/Helpers/FieldHelper.php index 49dc1475..11300db3 100644 --- a/src/Doctrine/Helpers/FieldHelper.php +++ b/src/Doctrine/Helpers/FieldHelper.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace App\Doctrine\Helpers; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\ORM\QueryBuilder; /** @@ -45,10 +46,10 @@ final class FieldHelper $db_platform = $qb->getEntityManager()->getConnection()->getDatabasePlatform(); //If we are on MySQL, we can just use the FIELD function - if ($db_platform instanceof AbstractMySQLPlatform) { - $param = (is_numeric($bound_param) ? '?' : ":") . (string) $bound_param; + if ($db_platform instanceof AbstractMySQLPlatform ) { + $param = (is_numeric($bound_param) ? '?' : ":").(string)$bound_param; $qb->orderBy("FIELD($field_expr, $param)", $order); - } else { + } else { //Use the sqlite/portable version or postgresql //Retrieve the values from the bound parameter $param = $qb->getParameter($bound_param); if ($param === null) { @@ -57,12 +58,31 @@ final class FieldHelper //Generate a unique key from the field_expr $key = 'field2_' . (string) $bound_param; - self::addSqliteOrderBy($qb, $field_expr, $key, $param->getValue(), $order); + + if ($db_platform instanceof PostgreSQLPlatform) { + self::addPostgresOrderBy($qb, $field_expr, $key, $param->getValue(), $order); + } else { + self::addSqliteOrderBy($qb, $field_expr, $key, $param->getValue(), $order); + } } return $qb; } + + private static function addPostgresOrderBy(QueryBuilder $qb, string $field_expr, string $key, array $values, ?string $order = null): void + { + //Use postgres native array_position function, to get the index of the value in the array + //In the end it gives a similar result as the FIELD function + $qb->orderBy("array_position(:$key, $field_expr)", $order); + + //Convert the values to a literal array, to overcome the problem of passing more than 100 parameters + $values = array_map(fn($value) => is_string($value) ? "'$value'" : $value, $values); + $literalArray = '{' . implode(',', $values) . '}'; + + $qb->setParameter($key, $literalArray); + } + private static function addSqliteOrderBy(QueryBuilder $qb, string $field_expr, string $key, array $values, ?string $order = null): void { //Otherwise we emulate it using @@ -88,11 +108,12 @@ final class FieldHelper //If we are on MySQL, we can just use the FIELD function if ($db_platform instanceof AbstractMySQLPlatform) { - $qb->orderBy("FIELD($field_expr, :field_arr)", $order); + $qb->orderBy("FIELD2($field_expr, :field_arr)", $order); + } elseif ($db_platform instanceof PostgreSQLPlatform) { + //Use the postgres native array_position function + self::addPostgresOrderBy($qb, $field_expr, $key, $values, $order); } else { - //Generate a unique key from the field_expr - - //Otherwise we have to it using the FIELD2 function + //Otherwise use the portable version using string concatenation self::addSqliteOrderBy($qb, $field_expr, $key, $values, $order); } diff --git a/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php index 2f6d39dd..2a707e1f 100644 --- a/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php +++ b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php @@ -27,7 +27,6 @@ use Composer\CaBundle\CaBundle; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; -use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; /** * This middleware sets SSL options for MySQL connections @@ -42,7 +41,7 @@ class MySQLSSLConnectionMiddlewareDriver extends AbstractDriverMiddleware public function connect(array $params): Connection { //Only set this on MySQL connections, as other databases don't support this parameter - if($this->enabled && $this->getDatabasePlatform() instanceof AbstractMySQLPlatform) { + if($this->enabled && $params['driver'] === 'pdo_mysql') { $params['driverOptions'][\PDO::MYSQL_ATTR_SSL_CA] = CaBundle::getSystemCaRootBundlePath(); $params['driverOptions'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->verify; } diff --git a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php index ee851052..80a81612 100644 --- a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php +++ b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php @@ -26,7 +26,6 @@ namespace App\Doctrine\Middleware; use App\Exceptions\InvalidRegexException; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; -use Doctrine\DBAL\Platforms\SqlitePlatform; /** * This middleware is used to add the regexp operator to the SQLite platform. @@ -41,7 +40,7 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware $connection = parent::connect($params); // TODO: Change the autogenerated stub //Then add the functions if we are on SQLite - if ($this->getDatabasePlatform() instanceof SqlitePlatform) { + if ($params['driver'] === 'pdo_sqlite') { $native_connection = $connection->getNativeConnection(); //Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation @@ -49,6 +48,11 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware $native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC); + + //Create a new collation for natural sorting + if (method_exists($native_connection, 'sqliteCreateCollation')) { + $native_connection->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...)); + } } } @@ -94,10 +98,9 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware * This function returns the index (position) of the first argument in the subsequent arguments. * If the first argument is not found or is NULL, 0 is returned. * @param string|int|null $value - * @param mixed ...$array * @return int */ - final public static function field(string|int|null $value, ...$array): int + final public static function field(string|int|null $value, mixed ...$array): int { if ($value === null) { return 0; diff --git a/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php index e581b5c1..d05b6b9c 100644 --- a/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php +++ b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php @@ -24,7 +24,6 @@ namespace App\Doctrine\Middleware; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; -use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; /** * This command sets the initial command parameter for MySQL connections, so we can set the SQL mode @@ -35,7 +34,7 @@ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware public function connect(array $params): Connection { //Only set this on MySQL connections, as other databases don't support this parameter - if($this->getDatabasePlatform() instanceof AbstractMySQLPlatform) { + if($params['driver'] === 'pdo_mysql') { //1002 is \PDO::MYSQL_ATTR_INIT_COMMAND constant value $params['driverOptions'][\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))'; } diff --git a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php index e596a8b6..0fbf6cdb 100644 --- a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php +++ b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php @@ -31,8 +31,6 @@ use Doctrine\DBAL\Schema\Identifier; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; - use function array_reverse; use function assert; use function count; @@ -207,6 +205,8 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface return 'ALTER TABLE '.$tableIdentifier->getQuotedName($platform).' AUTO_INCREMENT = 1;'; } + throw new \RuntimeException("Resetting autoincrement is not supported on this platform!"); + //This seems to cause problems somehow /*if ($platform instanceof SqlitePlatform) { return 'DELETE FROM `sqlite_sequence` WHERE name = \''.$tableIdentifier->getQuotedName($platform).'\';'; @@ -278,7 +278,7 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface foreach ($classes as $class) { foreach ($class->associationMappings as $assoc) { - if (! $assoc['isOwningSide'] || $assoc['type'] !== ClassMetadataInfo::MANY_TO_MANY) { + if (! $assoc['isOwningSide'] || $assoc['type'] !== ClassMetadata::MANY_TO_MANY) { continue; } diff --git a/src/Doctrine/Types/ArrayType.php b/src/Doctrine/Types/ArrayType.php new file mode 100644 index 00000000..daab9b75 --- /dev/null +++ b/src/Doctrine/Types/ArrayType.php @@ -0,0 +1,116 @@ +. + */ + +declare(strict_types=1); + +namespace App\Doctrine\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\Exception\SerializationFailed; +use Doctrine\DBAL\Types\Type; +use Doctrine\Deprecations\Deprecation; + +use function is_resource; +use function restore_error_handler; +use function serialize; +use function set_error_handler; +use function stream_get_contents; +use function unserialize; + +use const E_DEPRECATED; +use const E_USER_DEPRECATED; + +/** + * This class is taken from doctrine ORM 3.8. https://github.com/doctrine/dbal/blob/3.8.x/src/Types/ArrayType.php + * + * It was removed in doctrine ORM 4.0. However, we require it for backward compatibility with WebauthnKey. + * Therefore, we manually added it here as a custom type as a forward compatibility layer. + */ +class ArrayType extends Type +{ + /** + * {@inheritDoc} + */ + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string + { + return $platform->getClobTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): string + { + return serialize($value); + } + + /** + * {@inheritDoc} + */ + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed + { + if ($value === null) { + return null; + } + + $value = is_resource($value) ? stream_get_contents($value) : $value; + + set_error_handler(function (int $code, string $message): bool { + if ($code === E_DEPRECATED || $code === E_USER_DEPRECATED) { + return false; + } + + //Change to original code. Use SerializationFailed instead of ConversionException. + throw new SerializationFailed("Serialization failed (Code $code): " . $message); + }); + + try { + //Change to original code. Use false for allowed_classes, to avoid unsafe unserialization of objects. + return unserialize($value, ['allowed_classes' => false]); + } finally { + restore_error_handler(); + } + } + + /** + * {@inheritDoc} + */ + public function getName(): string + { + return "array"; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function requiresSQLCommentHint(AbstractPlatform $platform): bool + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/5509', + '%s is deprecated.', + __METHOD__, + ); + + return true; + } +} \ No newline at end of file diff --git a/src/Doctrine/Types/TinyIntType.php b/src/Doctrine/Types/TinyIntType.php index e9a75daa..c2daeeca 100644 --- a/src/Doctrine/Types/TinyIntType.php +++ b/src/Doctrine/Types/TinyIntType.php @@ -22,7 +22,9 @@ declare(strict_types=1); */ namespace App\Doctrine\Types; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Types\Type; /** @@ -33,7 +35,15 @@ class TinyIntType extends Type public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return 'TINYINT'; + //MySQL knows the TINYINT type directly + //We do not use the TINYINT for sqlite, as it will be resolved to a BOOL type and bring problems with migrations + if ($platform instanceof AbstractMySQLPlatform ) { + //Use TINYINT(1) to allow for proper migration diffs + return 'TINYINT(1)'; + } + + //For other platforms, we use the smallest integer type available + return $platform->getSmallIntTypeDeclarationSQL($column); } public function getName(): string diff --git a/src/Doctrine/Types/UTCDateTimeImmutableType.php b/src/Doctrine/Types/UTCDateTimeImmutableType.php new file mode 100644 index 00000000..c0d6659d --- /dev/null +++ b/src/Doctrine/Types/UTCDateTimeImmutableType.php @@ -0,0 +1,97 @@ +. + */ + +declare(strict_types=1); + +namespace App\Doctrine\Types; + +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\DateTimeImmutableType; +use Doctrine\DBAL\Types\DateTimeType; +use Doctrine\DBAL\Types\Exception\InvalidFormat; + +/** + * This DateTimeImmutableType all dates to UTC, so it can be later used with the timezones. + * Taken (and adapted) from here: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/working-with-datetime.html. + */ +class UTCDateTimeImmutableType extends DateTimeImmutableType +{ + private static ?DateTimeZone $utc_timezone = null; + + /** + * {@inheritdoc} + * + * @param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + if (!self::$utc_timezone instanceof \DateTimeZone) { + self::$utc_timezone = new DateTimeZone('UTC'); + } + + if ($value instanceof \DateTimeImmutable) { + $value = $value->setTimezone(self::$utc_timezone); + } + + return parent::convertToDatabaseValue($value, $platform); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform): ?\DateTimeImmutable + { + if (!self::$utc_timezone instanceof \DateTimeZone) { + self::$utc_timezone = new DateTimeZone('UTC'); + } + + if (null === $value || $value instanceof \DateTimeImmutable) { + return $value; + } + + $converted = \DateTimeImmutable::createFromFormat( + $platform->getDateTimeFormatString(), + $value, + self::$utc_timezone + ); + + if (!$converted) { + throw InvalidFormat::new( + $value, + static::class, + $platform->getDateTimeFormatString(), + ); + } + + return $converted; + } +} diff --git a/src/Doctrine/Types/UTCDateTimeType.php b/src/Doctrine/Types/UTCDateTimeType.php index c7140252..a6fda747 100644 --- a/src/Doctrine/Types/UTCDateTimeType.php +++ b/src/Doctrine/Types/UTCDateTimeType.php @@ -28,6 +28,7 @@ use DateTimeZone; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\DateTimeType; +use Doctrine\DBAL\Types\Exception\InvalidFormat; /** * This DateTimeType all dates to UTC, so it can be later used with the timezones. @@ -64,11 +65,9 @@ class UTCDateTimeType extends DateTimeType * * @param T $value * - * @return (T is null ? null : DateTimeInterface) - * * @template T */ - public function convertToPHPValue($value, AbstractPlatform $platform): ?\DateTimeInterface + public function convertToPHPValue($value, AbstractPlatform $platform): ?DateTime { if (!self::$utc_timezone instanceof \DateTimeZone) { self::$utc_timezone = new DateTimeZone('UTC'); @@ -85,7 +84,11 @@ class UTCDateTimeType extends DateTimeType ); if (!$converted) { - throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateTimeFormatString()); + throw InvalidFormat::new( + $value, + static::class, + $platform->getDateTimeFormatString(), + ); } return $converted; diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index c940bba8..c2b56d69 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -185,9 +185,9 @@ abstract class Attachment extends AbstractNamedDBElement protected ?AttachmentType $attachment_type = null; #[Groups(['attachment:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['attachment:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; public function __construct() @@ -385,7 +385,7 @@ abstract class Attachment extends AbstractNamedDBElement return null; } - return parse_url($this->getURL(), PHP_URL_HOST); + return parse_url((string) $this->getURL(), PHP_URL_HOST); } /** @@ -477,7 +477,8 @@ abstract class Attachment extends AbstractNamedDBElement */ public function setElement(AttachmentContainingDBElement $element): self { - if (!is_a($element, static::ALLOWED_ELEMENT_CLASS)) { + //Do not allow Rector to replace this check with a instanceof. It will not work!! + if (!is_a($element, static::ALLOWED_ELEMENT_CLASS, true)) { throw new InvalidArgumentException(sprintf('The element associated with a %s must be a %s!', static::class, static::ALLOWED_ELEMENT_CLASS)); } diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index 58546e6a..fdf42de5 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -86,7 +87,7 @@ use Symfony\Component\Validator\Constraints as Assert; class AttachmentType extends AbstractStructuralDBElement { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: AttachmentType::class, cascade: ['persist'])] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'children')] @@ -110,7 +111,7 @@ class AttachmentType extends AbstractStructuralDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: AttachmentTypeAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['attachment_type:read', 'attachment_type:write'])] protected Collection $attachments; @@ -123,7 +124,7 @@ class AttachmentType extends AbstractStructuralDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: AttachmentTypeParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['attachment_type:read', 'attachment_type:write'])] protected Collection $parameters; @@ -134,9 +135,9 @@ class AttachmentType extends AbstractStructuralDBElement protected Collection $attachments_with_type; #[Groups(['attachment_type:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['attachment_type:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; public function __construct() diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index 0d5b3579..a8735479 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -41,9 +41,9 @@ use Symfony\Component\Validator\Constraints as Assert; abstract class AbstractCompany extends AbstractPartsContainingDBElement { #[Groups(['company:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['company:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; /** * @var string The address of the company diff --git a/src/Entity/Base/PartsContainingRepositoryInterface.php b/src/Entity/Base/PartsContainingRepositoryInterface.php index f852bc35..89e7e5f6 100644 --- a/src/Entity/Base/PartsContainingRepositoryInterface.php +++ b/src/Entity/Base/PartsContainingRepositoryInterface.php @@ -30,11 +30,11 @@ interface PartsContainingRepositoryInterface * Returns all parts associated with this element. * * @param object $element the element for which the parts should be determined - * @param array $order_by The order of the parts. Format ['name' => 'ASC'] + * @param string $nameOrderDirection the direction in which the parts should be ordered by name, either ASC or DESC * * @return Part[] */ - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array; + public function getParts(object $element, string $nameOrderDirection = "ASC"): array; /** * Gets the count of the parts associated with this element. diff --git a/src/Entity/Base/TimestampTrait.php b/src/Entity/Base/TimestampTrait.php index e6fac441..77506b18 100644 --- a/src/Entity/Base/TimestampTrait.php +++ b/src/Entity/Base/TimestampTrait.php @@ -34,28 +34,28 @@ use Symfony\Component\Serializer\Annotation\Groups; trait TimestampTrait { /** - * @var \DateTimeInterface|null the date when this element was modified the last time + * @var \DateTimeImmutable|null the date when this element was modified the last time */ #[Groups(['extended', 'full'])] #[ApiProperty(writable: false)] - #[ORM\Column(name: 'last_modified', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] - protected ?\DateTimeInterface $lastModified = null; + #[ORM\Column(name: 'last_modified', type: Types::DATETIME_IMMUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] + protected ?\DateTimeImmutable $lastModified = null; /** - * @var \DateTimeInterface|null the date when this element was created + * @var \DateTimeImmutable|null the date when this element was created */ #[Groups(['extended', 'full'])] #[ApiProperty(writable: false)] - #[ORM\Column(name: 'datetime_added', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] - protected ?\DateTimeInterface $addedDate = null; + #[ORM\Column(name: 'datetime_added', type: Types::DATETIME_IMMUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] + protected ?\DateTimeImmutable $addedDate = null; /** * Returns the last time when the element was modified. * Returns null if the element was not yet saved to DB yet. * - * @return \DateTimeInterface|null the time of the last edit + * @return \DateTimeImmutable|null the time of the last edit */ - public function getLastModified(): ?\DateTimeInterface + public function getLastModified(): ?\DateTimeImmutable { return $this->lastModified; } @@ -64,9 +64,9 @@ trait TimestampTrait * Returns the date/time when the element was created. * Returns null if the element was not yet saved to DB yet. * - * @return \DateTimeInterface|null the creation time of the part + * @return \DateTimeImmutable|null the creation time of the part */ - public function getAddedDate(): ?\DateTimeInterface + public function getAddedDate(): ?\DateTimeImmutable { return $this->addedDate; } @@ -78,9 +78,9 @@ trait TimestampTrait #[ORM\PreUpdate] public function updateTimestamps(): void { - $this->lastModified = new DateTime('now'); + $this->lastModified = new \DateTimeImmutable('now'); if (null === $this->addedDate) { - $this->addedDate = new DateTime('now'); + $this->addedDate = new \DateTimeImmutable('now'); } } } diff --git a/src/Entity/LabelSystem/BarcodeType.php b/src/Entity/LabelSystem/BarcodeType.php index 0794b606..daf7d401 100644 --- a/src/Entity/LabelSystem/BarcodeType.php +++ b/src/Entity/LabelSystem/BarcodeType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; enum BarcodeType: string diff --git a/src/Entity/LabelSystem/LabelPictureType.php b/src/Entity/LabelSystem/LabelPictureType.php index c9183ca6..c1f90fe2 100644 --- a/src/Entity/LabelSystem/LabelPictureType.php +++ b/src/Entity/LabelSystem/LabelPictureType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; enum LabelPictureType: string @@ -34,4 +36,4 @@ enum LabelPictureType: string * Show the main attachment of the element on the label */ case MAIN_ATTACHMENT = 'main_attachment'; -} \ No newline at end of file +} diff --git a/src/Entity/LabelSystem/LabelProcessMode.php b/src/Entity/LabelSystem/LabelProcessMode.php index 76bf175f..d5967b49 100644 --- a/src/Entity/LabelSystem/LabelProcessMode.php +++ b/src/Entity/LabelSystem/LabelProcessMode.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; enum LabelProcessMode: string @@ -26,4 +28,4 @@ enum LabelProcessMode: string case PLACEHOLDER = 'html'; /** Interpret the given lines as twig template */ case TWIG = 'twig'; -} \ No newline at end of file +} diff --git a/src/Entity/LabelSystem/LabelProfile.php b/src/Entity/LabelSystem/LabelProfile.php index 0a5150e7..a59d9292 100644 --- a/src/Entity/LabelSystem/LabelProfile.php +++ b/src/Entity/LabelSystem/LabelProfile.php @@ -41,6 +41,7 @@ declare(strict_types=1); namespace App\Entity\LabelSystem; +use Doctrine\Common\Collections\Criteria; use App\Entity\Attachments\Attachment; use App\Repository\LabelProfileRepository; use App\EntityListeners\TreeCacheInvalidationListener; @@ -66,7 +67,7 @@ class LabelProfile extends AttachmentContainingDBElement * @var Collection */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: LabelAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: LabelAttachment::class)] diff --git a/src/Entity/LabelSystem/LabelSupportedElement.php b/src/Entity/LabelSystem/LabelSupportedElement.php index 5007de7f..2f9afa85 100644 --- a/src/Entity/LabelSystem/LabelSupportedElement.php +++ b/src/Entity/LabelSystem/LabelSupportedElement.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; use App\Entity\Parts\Part; @@ -42,4 +44,4 @@ enum LabelSupportedElement: string self::STORELOCATION => StorageLocation::class, }; } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/AbstractLogEntry.php b/src/Entity/LogSystem/AbstractLogEntry.php index f1c163c5..aa795613 100644 --- a/src/Entity/LogSystem/AbstractLogEntry.php +++ b/src/Entity/LogSystem/AbstractLogEntry.php @@ -25,7 +25,7 @@ namespace App\Entity\LogSystem; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\UserSystem\User; -use DateTime; + use Doctrine\ORM\Mapping as ORM; use App\Repository\LogEntryRepository; @@ -55,10 +55,11 @@ abstract class AbstractLogEntry extends AbstractDBElement #[ORM\Column(type: Types::STRING)] protected string $username = ''; - /** @var \DateTimeInterface The datetime the event associated with this log entry has occured + /** + * @var \DateTimeImmutable The datetime the event associated with this log entry has occured */ - #[ORM\Column(name: 'datetime', type: Types::DATETIME_MUTABLE)] - protected \DateTimeInterface $timestamp; + #[ORM\Column(name: 'datetime', type: Types::DATETIME_IMMUTABLE)] + protected \DateTimeImmutable $timestamp; /** * @var LogLevel The priority level of the associated level. 0 is highest, 7 lowest @@ -89,7 +90,7 @@ abstract class AbstractLogEntry extends AbstractDBElement public function __construct() { - $this->timestamp = new DateTime(); + $this->timestamp = new \DateTimeImmutable(); } /** @@ -164,7 +165,7 @@ abstract class AbstractLogEntry extends AbstractDBElement /** * Returns the timestamp when the event that caused this log entry happened. */ - public function getTimestamp(): \DateTimeInterface + public function getTimestamp(): \DateTimeImmutable { return $this->timestamp; } @@ -174,7 +175,7 @@ abstract class AbstractLogEntry extends AbstractDBElement * * @return $this */ - public function setTimestamp(\DateTimeInterface $timestamp): self + public function setTimestamp(\DateTimeImmutable $timestamp): self { $this->timestamp = $timestamp; diff --git a/src/Entity/LogSystem/LogLevel.php b/src/Entity/LogSystem/LogLevel.php index fd0c678b..435c5468 100644 --- a/src/Entity/LogSystem/LogLevel.php +++ b/src/Entity/LogSystem/LogLevel.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; use Psr\Log\LogLevel as PSRLogLevel; diff --git a/src/Entity/LogSystem/LogTargetType.php b/src/Entity/LogSystem/LogTargetType.php index 6e413079..1c6e4f8c 100644 --- a/src/Entity/LogSystem/LogTargetType.php +++ b/src/Entity/LogSystem/LogTargetType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; use App\Entity\Attachments\Attachment; @@ -120,7 +122,7 @@ enum LogTargetType: int } } - $elementClass = is_object($element) ? get_class($element) : $element; + $elementClass = is_object($element) ? $element::class : $element; //If no matching type was found, throw an exception throw new \InvalidArgumentException("The given class $elementClass is not a valid log target type."); } diff --git a/src/Entity/LogSystem/LogWithEventUndoTrait.php b/src/Entity/LogSystem/LogWithEventUndoTrait.php index 16568241..ed8629dc 100644 --- a/src/Entity/LogSystem/LogWithEventUndoTrait.php +++ b/src/Entity/LogSystem/LogWithEventUndoTrait.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; use App\Entity\Contracts\LogWithEventUndoInterface; @@ -48,4 +50,4 @@ trait LogWithEventUndoTrait $mode_int = $this->extra['um'] ?? 1; return EventUndoMode::fromExtraInt($mode_int); } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/PartStockChangeType.php b/src/Entity/LogSystem/PartStockChangeType.php index 92c5073f..f69fe95f 100644 --- a/src/Entity/LogSystem/PartStockChangeType.php +++ b/src/Entity/LogSystem/PartStockChangeType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; enum PartStockChangeType: string @@ -53,4 +55,4 @@ enum PartStockChangeType: string default => throw new \InvalidArgumentException("Invalid short type: $value"), }; } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/PartStockChangedLogEntry.php b/src/Entity/LogSystem/PartStockChangedLogEntry.php index dd7b5234..1bac9e9f 100644 --- a/src/Entity/LogSystem/PartStockChangedLogEntry.php +++ b/src/Entity/LogSystem/PartStockChangedLogEntry.php @@ -52,8 +52,6 @@ class PartStockChangedLogEntry extends AbstractLogEntry $this->level = LogLevel::INFO; $this->setTargetElement($lot); - - $this->typeString = 'part_stock_changed'; $this->extra = array_merge($this->extra, [ 't' => $type->toExtraShortType(), 'o' => $old_stock, diff --git a/src/Entity/OAuthToken.php b/src/Entity/OAuthToken.php index 84b3279c..0073aeed 100644 --- a/src/Entity/OAuthToken.php +++ b/src/Entity/OAuthToken.php @@ -38,15 +38,15 @@ use League\OAuth2\Client\Token\AccessTokenInterface; class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface { /** @var string|null The short-term usable OAuth2 token */ - #[ORM\Column(type: 'text', nullable: true)] + #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $token = null; - /** @var \DateTimeInterface The date when the token expires */ + /** @var \DateTimeImmutable|null The date when the token expires */ #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] - private ?\DateTimeInterface $expires_at = null; + private ?\DateTimeImmutable $expires_at = null; /** @var string|null The refresh token for the OAuth2 auth */ - #[ORM\Column(type: 'text', nullable: true)] + #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $refresh_token = null; /** @@ -54,7 +54,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface */ private const DEFAULT_EXPIRATION_TIME = 3600; - public function __construct(string $name, ?string $refresh_token, ?string $token = null, \DateTimeInterface $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 !== null && $expires_at === null) { @@ -82,7 +82,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface ); } - private static function unixTimestampToDatetime(int $timestamp): \DateTimeInterface + private static function unixTimestampToDatetime(int $timestamp): \DateTimeImmutable { return \DateTimeImmutable::createFromFormat('U', (string)$timestamp); } @@ -92,7 +92,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface return $this->token; } - public function getExpirationDate(): ?\DateTimeInterface + public function getExpirationDate(): ?\DateTimeImmutable { return $this->expires_at; } diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index ed62618c..99ed3c6d 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -91,7 +92,7 @@ use Symfony\Component\Validator\Constraints as Assert; class Category extends AbstractPartsContainingDBElement { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -165,7 +166,7 @@ class Category extends AbstractPartsContainingDBElement #[Assert\Valid] #[Groups(['full', 'category:read', 'category:write'])] #[ORM\OneToMany(mappedBy: 'element', targetEntity: CategoryAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: CategoryAttachment::class)] @@ -178,13 +179,13 @@ class Category extends AbstractPartsContainingDBElement #[Assert\Valid] #[Groups(['full', 'category:read', 'category:write'])] #[ORM\OneToMany(mappedBy: 'element', targetEntity: CategoryParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] protected Collection $parameters; #[Groups(['category:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['category:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; #[Assert\Valid] #[ORM\Embedded(class: EDACategoryInfo::class)] diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index 10d3c9db..6b043562 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -96,7 +97,7 @@ class Footprint extends AbstractPartsContainingDBElement protected ?AbstractStructuralDBElement $parent = null; #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[Groups(['footprint:read', 'footprint:write'])] @@ -107,7 +108,7 @@ class Footprint extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: FootprintAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['footprint:read', 'footprint:write'])] protected Collection $attachments; @@ -128,14 +129,14 @@ class Footprint extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: FootprintParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['footprint:read', 'footprint:write'])] protected Collection $parameters; #[Groups(['footprint:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['footprint:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; #[Assert\Valid] #[ORM\Embedded(class: EDAFootprintInfo::class)] diff --git a/src/Entity/Parts/InfoProviderReference.php b/src/Entity/Parts/InfoProviderReference.php index ea0fae7f..c6888b2c 100644 --- a/src/Entity/Parts/InfoProviderReference.php +++ b/src/Entity/Parts/InfoProviderReference.php @@ -31,31 +31,32 @@ use Symfony\Component\Serializer\Annotation\Groups; /** * This class represents a reference to a info provider inside a part. + * @see \App\Tests\Entity\Parts\InfoProviderReferenceTest */ #[Embeddable] class InfoProviderReference { /** @var string|null The key referencing the provider used to get this part, or null if it was not provided by a data provider */ - #[Column(type: 'string', nullable: true)] + #[Column(type: Types::STRING, nullable: true)] #[Groups(['provider_reference:read'])] private ?string $provider_key = null; /** @var string|null The id of this part inside the provider system or null if the part was not provided by a data provider */ - #[Column(type: 'string', nullable: true)] + #[Column(type: Types::STRING, nullable: true)] #[Groups(['provider_reference:read'])] private ?string $provider_id = null; /** * @var string|null The url of this part inside the provider system or null if this info is not existing */ - #[Column(type: 'string', nullable: true)] + #[Column(type: Types::STRING, nullable: true)] #[Groups(['provider_reference:read'])] private ?string $provider_url = null; - #[Column(type: Types::DATETIME_MUTABLE, nullable: true, options: ['default' => null])] + #[Column(type: Types::DATETIME_IMMUTABLE, nullable: true, options: ['default' => null])] #[Groups(['provider_reference:read'])] - private ?\DateTimeInterface $last_updated = null; + private ?\DateTimeImmutable $last_updated = null; /** * Constructing is forbidden from outside. @@ -94,9 +95,8 @@ class InfoProviderReference /** * Gets the time, when the part was last time updated by the provider. - * @return \DateTimeInterface|null */ - public function getLastUpdated(): ?\DateTimeInterface + public function getLastUpdated(): ?\DateTimeImmutable { return $this->last_updated; } diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php index 94f83e68..0edf8232 100644 --- a/src/Entity/Parts/Manufacturer.php +++ b/src/Entity/Parts/Manufacturer.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -95,7 +96,7 @@ class Manufacturer extends AbstractCompany protected ?AbstractStructuralDBElement $parent = null; #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; /** @@ -103,7 +104,7 @@ class Manufacturer extends AbstractCompany */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: ManufacturerAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['manufacturer:read', 'manufacturer:write'])] #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $attachments; @@ -118,7 +119,7 @@ class Manufacturer extends AbstractCompany */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: ManufacturerParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['manufacturer:read', 'manufacturer:write'])] #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $parameters; diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index 3ff1427b..79281e9f 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -123,7 +124,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement protected bool $use_si_prefix = false; #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class, cascade: ['persist'])] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -137,7 +138,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: MeasurementUnitAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected Collection $attachments; @@ -150,14 +151,14 @@ class MeasurementUnit extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: MeasurementUnitParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected Collection $parameters; #[Groups(['measurement_unit:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['measurement_unit:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; /** diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 325b143a..4addf503 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; @@ -119,7 +120,7 @@ class Part extends AttachmentContainingDBElement #[Assert\Valid] #[Groups(['full', 'part:read', 'part:write'])] #[ORM\OneToMany(mappedBy: 'element', targetEntity: PartParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[UniqueObjectCollection(fields: ['name', 'group', 'element'])] protected Collection $parameters; @@ -140,7 +141,7 @@ class Part extends AttachmentContainingDBElement #[Assert\Valid] #[Groups(['full', 'part:read', 'part:write'])] #[ORM\OneToMany(mappedBy: 'element', targetEntity: PartAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; /** @@ -153,9 +154,9 @@ class Part extends AttachmentContainingDBElement protected ?Attachment $master_picture_attachment = null; #[Groups(['part:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['part:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; public function __construct() diff --git a/src/Entity/Parts/PartAssociation.php b/src/Entity/Parts/PartAssociation.php index 843a63ee..32017488 100644 --- a/src/Entity/Parts/PartAssociation.php +++ b/src/Entity/Parts/PartAssociation.php @@ -49,6 +49,7 @@ use Symfony\Component\Validator\Constraints\Length; /** * This entity describes a part association, which is a semantic connection between two parts. * For example, a part association can be used to describe that a part is a replacement for another part. + * @see \App\Tests\Entity\Parts\PartAssociationTest */ #[ORM\Entity(repositoryClass: DBElementRepository::class)] #[ORM\HasLifecycleCallbacks] diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index 5a5ecb80..70475efd 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -105,13 +105,13 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named protected string $comment = ''; /** - * @var \DateTimeInterface|null Set a time until when the lot must be used. + * @var \DateTimeImmutable|null Set a time until when the lot must be used. * Set to null, if the lot can be used indefinitely. */ #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] - #[ORM\Column(name: 'expiration_date', type: Types::DATETIME_MUTABLE, nullable: true)] + #[ORM\Column(name: 'expiration_date', type: Types::DATETIME_IMMUTABLE, nullable: true)] #[Year2038BugWorkaround] - protected ?\DateTimeInterface $expiration_date = null; + protected ?\DateTimeImmutable $expiration_date = null; /** * @var StorageLocation|null The storelocation of this lot @@ -194,7 +194,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named } //Check if the expiration date is bigger then current time - return $this->expiration_date < new DateTime('now'); + return $this->expiration_date < new \DateTimeImmutable('now'); } /** @@ -236,7 +236,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Gets the expiration date for the part lot. Returns null, if no expiration date was set. */ - public function getExpirationDate(): ?\DateTimeInterface + public function getExpirationDate(): ?\DateTimeImmutable { return $this->expiration_date; } @@ -246,7 +246,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * * */ - public function setExpirationDate(?\DateTimeInterface $expiration_date): self + public function setExpirationDate(?\DateTimeImmutable $expiration_date): self { $this->expiration_date = $expiration_date; diff --git a/src/Entity/Parts/PartTraits/InstockTrait.php b/src/Entity/Parts/PartTraits/InstockTrait.php index 5f73243d..08b070f3 100644 --- a/src/Entity/Parts/PartTraits/InstockTrait.php +++ b/src/Entity/Parts/PartTraits/InstockTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\PartLot; @@ -42,7 +43,7 @@ trait InstockTrait #[Assert\Valid] #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\OneToMany(mappedBy: 'part', targetEntity: PartLot::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['amount' => 'DESC'])] + #[ORM\OrderBy(['amount' => Criteria::DESC])] protected Collection $partLots; /** diff --git a/src/Entity/Parts/PartTraits/OrderTrait.php b/src/Entity/Parts/PartTraits/OrderTrait.php index 0a914e24..2c142016 100644 --- a/src/Entity/Parts/PartTraits/OrderTrait.php +++ b/src/Entity/Parts/PartTraits/OrderTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\Types\Types; use App\Entity\PriceInformations\Orderdetail; use Symfony\Component\Serializer\Annotation\Groups; @@ -41,7 +42,7 @@ trait OrderTrait #[Assert\Valid] #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\OneToMany(mappedBy: 'part', targetEntity: Orderdetail::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['supplierpartnr' => 'ASC'])] + #[ORM\OrderBy(['supplierpartnr' => Criteria::ASC])] protected Collection $orderdetails; /** diff --git a/src/Entity/Parts/StorageLocation.php b/src/Entity/Parts/StorageLocation.php index 8dd22a8c..6c455ae5 100644 --- a/src/Entity/Parts/StorageLocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -90,7 +91,7 @@ use Symfony\Component\Validator\Constraints as Assert; class StorageLocation extends AbstractPartsContainingDBElement { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -114,7 +115,7 @@ class StorageLocation extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: StorageLocationParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['location:read', 'location:write'])] protected Collection $parameters; @@ -169,9 +170,9 @@ class StorageLocation extends AbstractPartsContainingDBElement protected ?Attachment $master_picture_attachment = null; #[Groups(['location:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['location:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; /******************************************************************************** diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php index 12373593..6b01a92c 100644 --- a/src/Entity/Parts/Supplier.php +++ b/src/Entity/Parts/Supplier.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -92,7 +93,7 @@ use Symfony\Component\Validator\Constraints as Assert; class Supplier extends AbstractCompany { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -129,7 +130,7 @@ class Supplier extends AbstractCompany */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: SupplierAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['supplier:read', 'supplier:write'])] #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $attachments; @@ -144,7 +145,7 @@ class Supplier extends AbstractCompany */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: SupplierParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['supplier:read', 'supplier:write'])] #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $parameters; diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index 4df27d14..8a63046a 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -118,7 +119,7 @@ class Currency extends AbstractStructuralDBElement protected string $iso_code = ""; #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class, cascade: ['persist'])] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -132,7 +133,7 @@ class Currency extends AbstractStructuralDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: CurrencyAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['currency:read', 'currency:write'])] protected Collection $attachments; @@ -145,7 +146,7 @@ class Currency extends AbstractStructuralDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: CurrencyParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['currency:read', 'currency:write'])] protected Collection $parameters; @@ -155,9 +156,9 @@ class Currency extends AbstractStructuralDBElement protected Collection $pricedetails; #[Groups(['currency:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['currency:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; public function __construct() diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index 811df111..3709b37d 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; @@ -45,7 +46,7 @@ use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\TimeStampableInterface; use App\Entity\Parts\Part; use App\Entity\Parts\Supplier; -use DateTime; +use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -96,10 +97,13 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N { use TimestampTrait; + /** + * @var Collection + */ #[Assert\Valid] #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\OneToMany(mappedBy: 'orderdetail', targetEntity: Pricedetail::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['min_discount_quantity' => 'ASC'])] + #[ORM\OrderBy(['min_discount_quantity' => Criteria::ASC])] protected Collection $pricedetails; /** @@ -169,9 +173,9 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N #[ORM\PreUpdate] public function updateTimestamps(): void { - $this->lastModified = new DateTime('now'); + $this->lastModified = new DateTimeImmutable('now'); if (!$this->addedDate instanceof \DateTimeInterface) { - $this->addedDate = new DateTime('now'); + $this->addedDate = new DateTimeImmutable('now'); } if ($this->part instanceof Part) { diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 98d4a491..86a7bcd5 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -38,7 +38,7 @@ use App\Validator\Constraints\BigDecimal\BigDecimalPositive; use App\Validator\Constraints\Selectable; use Brick\Math\BigDecimal; use Brick\Math\RoundingMode; -use DateTime; +use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; @@ -141,9 +141,9 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface #[ORM\PreUpdate] public function updateTimestamps(): void { - $this->lastModified = new DateTime('now'); + $this->lastModified = new DateTimeImmutable('now'); if (!$this->addedDate instanceof \DateTimeInterface) { - $this->addedDate = new DateTime('now'); + $this->addedDate = new DateTimeImmutable('now'); } if ($this->orderdetail instanceof Orderdetail) { diff --git a/src/Entity/ProjectSystem/Project.php b/src/Entity/ProjectSystem/Project.php index d3a4b65c..849aa236 100644 --- a/src/Entity/ProjectSystem/Project.php +++ b/src/Entity/ProjectSystem/Project.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\ProjectSystem; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; @@ -88,7 +89,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class Project extends AbstractStructuralDBElement { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -100,6 +101,9 @@ class Project extends AbstractStructuralDBElement #[Groups(['project:read', 'project:write'])] protected string $comment = ''; + /** + * @var Collection + */ #[Assert\Valid] #[Groups(['extended', 'full'])] #[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectBOMEntry::class, cascade: ['persist', 'remove'], orphanRemoval: true)] @@ -137,7 +141,7 @@ class Project extends AbstractStructuralDBElement * @var Collection */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: ProjectAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['project:read', 'project:write'])] protected Collection $attachments; @@ -149,14 +153,14 @@ class Project extends AbstractStructuralDBElement /** @var Collection */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: ProjectParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['project:read', 'project:write'])] protected Collection $parameters; #[Groups(['project:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['project:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; /******************************************************************************** diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php index cd73de90..f5cbf541 100644 --- a/src/Entity/UserSystem/ApiToken.php +++ b/src/Entity/UserSystem/ApiToken.php @@ -75,10 +75,10 @@ class ApiToken implements TimeStampableInterface #[Groups('token:read')] private ?User $user = null; - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] #[Groups('token:read')] #[Year2038BugWorkaround] - private ?\DateTimeInterface $valid_until; + private ?\DateTimeImmutable $valid_until; #[ORM\Column(length: 68, unique: true)] private string $token; @@ -87,9 +87,9 @@ class ApiToken implements TimeStampableInterface #[Groups('token:read')] private ApiTokenLevel $level = ApiTokenLevel::READ_ONLY; - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] #[Groups('token:read')] - private ?\DateTimeInterface $last_time_used = null; + private ?\DateTimeImmutable $last_time_used = null; public function __construct(ApiTokenType $tokenType = ApiTokenType::PERSONAL_ACCESS_TOKEN) { @@ -97,7 +97,7 @@ class ApiToken implements TimeStampableInterface $this->token = $tokenType->getTokenPrefix() . bin2hex(random_bytes(32)); //By default, tokens are valid for 1 year. - $this->valid_until = new \DateTime('+1 year'); + $this->valid_until = new \DateTimeImmutable('+1 year'); } public function getTokenType(): ApiTokenType @@ -116,7 +116,7 @@ class ApiToken implements TimeStampableInterface return $this; } - public function getValidUntil(): ?\DateTimeInterface + public function getValidUntil(): ?\DateTimeImmutable { return $this->valid_until; } @@ -127,10 +127,10 @@ class ApiToken implements TimeStampableInterface */ public function isValid(): bool { - return $this->valid_until === null || $this->valid_until > new \DateTime(); + return $this->valid_until === null || $this->valid_until > new \DateTimeImmutable(); } - public function setValidUntil(?\DateTimeInterface $valid_until): ApiToken + public function setValidUntil(?\DateTimeImmutable $valid_until): ApiToken { $this->valid_until = $valid_until; return $this; @@ -159,19 +159,17 @@ class ApiToken implements TimeStampableInterface /** * Gets the last time the token was used to authenticate or null if it was never used. - * @return \DateTimeInterface|null */ - public function getLastTimeUsed(): ?\DateTimeInterface + public function getLastTimeUsed(): ?\DateTimeImmutable { return $this->last_time_used; } /** * Sets the last time the token was used to authenticate. - * @param \DateTimeInterface|null $last_time_used * @return ApiToken */ - public function setLastTimeUsed(?\DateTimeInterface $last_time_used): ApiToken + public function setLastTimeUsed(?\DateTimeImmutable $last_time_used): ApiToken { $this->last_time_used = $last_time_used; return $this; diff --git a/src/Entity/UserSystem/Group.php b/src/Entity/UserSystem/Group.php index 32056cc9..6da9d35f 100644 --- a/src/Entity/UserSystem/Group.php +++ b/src/Entity/UserSystem/Group.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use Doctrine\Common\Collections\Criteria; use App\Entity\Attachments\Attachment; use App\Validator\Constraints\NoLockout; use Doctrine\DBAL\Types\Types; @@ -49,7 +50,7 @@ use Symfony\Component\Validator\Constraints as Assert; class Group extends AbstractStructuralDBElement implements HasPermissionsInterface { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -74,7 +75,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: GroupAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: GroupAttachment::class)] @@ -91,7 +92,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: GroupParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] protected Collection $parameters; public function __construct() diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 6f2a8f25..cc218eeb 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -116,10 +117,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected ?int $id = null; #[Groups(['user:read'])] - protected ?\DateTimeInterface $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; #[Groups(['user:read'])] - protected ?\DateTimeInterface $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; /** * @var bool Determines if the user is disabled (user can not log in) @@ -267,7 +268,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var Collection */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: UserAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['user:read', 'user:write'])] protected Collection $attachments; @@ -276,11 +277,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[Groups(['user:read', 'user:write'])] protected ?Attachment $master_picture_attachment = null; - /** @var \DateTimeInterface|null The time when the backup codes were generated + /** @var \DateTimeImmutable|null The time when the backup codes were generated */ #[Groups(['full'])] - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] - protected ?\DateTimeInterface $backupCodesGenerationDate = null; + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + protected ?\DateTimeImmutable $backupCodesGenerationDate = null; /** @var Collection */ @@ -317,10 +318,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected ?PermissionData $permissions = null; /** - * @var \DateTimeInterface|null the time until the password reset token is valid + * @var \DateTimeImmutable|null the time until the password reset token is valid */ - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] - protected ?\DateTimeInterface $pw_reset_expires = null; + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + protected ?\DateTimeImmutable $pw_reset_expires = null; /** * @var bool True if the user was created by a SAML provider (and therefore cannot change its password) @@ -527,7 +528,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Gets the datetime when the password reset token expires. */ - public function getPwResetExpires(): \DateTimeInterface|null + public function getPwResetExpires(): \DateTimeImmutable|null { return $this->pw_reset_expires; } @@ -535,7 +536,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Sets the datetime when the password reset token expires. */ - public function setPwResetExpires(\DateTimeInterface $pw_reset_expires): self + public function setPwResetExpires(\DateTimeImmutable $pw_reset_expires): self { $this->pw_reset_expires = $pw_reset_expires; @@ -896,7 +897,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe public function setBackupCodes(array $codes): self { $this->backupCodes = $codes; - $this->backupCodesGenerationDate = $codes === [] ? null : new DateTime(); + $this->backupCodesGenerationDate = $codes === [] ? null : new \DateTimeImmutable(); return $this; } @@ -904,7 +905,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Return the date when the backup codes were generated. */ - public function getBackupCodesGenerationDate(): ?\DateTimeInterface + public function getBackupCodesGenerationDate(): ?\DateTimeImmutable { return $this->backupCodesGenerationDate; } diff --git a/src/Entity/UserSystem/WebauthnKey.php b/src/Entity/UserSystem/WebauthnKey.php index abb77a96..b2716e07 100644 --- a/src/Entity/UserSystem/WebauthnKey.php +++ b/src/Entity/UserSystem/WebauthnKey.php @@ -51,7 +51,7 @@ class WebauthnKey extends BasePublicKeyCredentialSource implements TimeStampable protected ?User $user = null; #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] - protected ?\DateTimeInterface $last_time_used = null; + protected ?\DateTimeImmutable $last_time_used = null; public function getName(): string { @@ -82,9 +82,8 @@ class WebauthnKey extends BasePublicKeyCredentialSource implements TimeStampable /** * Retrieve the last time when the key was used. - * @return \DateTimeInterface|null */ - public function getLastTimeUsed(): ?\DateTimeInterface + public function getLastTimeUsed(): ?\DateTimeImmutable { return $this->last_time_used; } diff --git a/src/EventListener/AllowSlowNaturalSortListener.php b/src/EventListener/AllowSlowNaturalSortListener.php new file mode 100644 index 00000000..02ec6144 --- /dev/null +++ b/src/EventListener/AllowSlowNaturalSortListener.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EventListener; + +use App\Doctrine\Functions\Natsort; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * This is a workaround to the fact that we can not inject parameters into doctrine custom functions. + * Therefore we use this event listener to call the static function on the custom function, to inject the value, before + * any NATSORT function is called. + */ +#[AsEventListener] +class AllowSlowNaturalSortListener +{ + public function __construct( + #[Autowire(param: 'partdb.db.emulate_natural_sort')] + private readonly bool $allowNaturalSort) + { + } + + public function __invoke(RequestEvent $event) + { + Natsort::allowSlowNaturalSort($this->allowNaturalSort); + } +} \ No newline at end of file diff --git a/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php b/src/EventListener/LogSystem/EventLoggerListener.php similarity index 97% rename from src/EventSubscriber/LogSystem/EventLoggerSubscriber.php rename to src/EventListener/LogSystem/EventLoggerListener.php index b3f07a9b..6fe3d8dc 100644 --- a/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php +++ b/src/EventListener/LogSystem/EventLoggerListener.php @@ -20,7 +20,7 @@ declare(strict_types=1); -namespace App\EventSubscriber\LogSystem; +namespace App\EventListener\LogSystem; use App\Entity\Attachments\Attachment; use App\Entity\Base\AbstractDBElement; @@ -38,7 +38,7 @@ use App\Entity\UserSystem\User; use App\Services\LogSystem\EventCommentHelper; use App\Services\LogSystem\EventLogger; use App\Services\LogSystem\EventUndoHelper; -use Doctrine\Common\EventSubscriber; +use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Event\PostFlushEventArgs; @@ -50,7 +50,10 @@ use Symfony\Component\Serializer\SerializerInterface; /** * This event subscriber writes to the event log when entities are changed, removed, created. */ -class EventLoggerSubscriber implements EventSubscriber +#[AsDoctrineListener(event: Events::onFlush)] +#[AsDoctrineListener(event: Events::postPersist)] +#[AsDoctrineListener(event: Events::postFlush)] +class EventLoggerListener { /** * @var array The given fields will not be saved, because they contain sensitive information @@ -187,15 +190,6 @@ class EventLoggerSubscriber implements EventSubscriber return true; } - public function getSubscribedEvents(): array - { - return[ - Events::onFlush, - Events::postPersist, - Events::postFlush, - ]; - } - protected function logElementDeleted(AbstractDBElement $entity, EntityManagerInterface $em): void { $log = new ElementDeletedLogEntry($entity); diff --git a/src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php b/src/EventListener/LogSystem/LogDBMigrationListener.php similarity index 92% rename from src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php rename to src/EventListener/LogSystem/LogDBMigrationListener.php index 07ec8ea4..c8b60e18 100644 --- a/src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php +++ b/src/EventListener/LogSystem/LogDBMigrationListener.php @@ -20,11 +20,11 @@ declare(strict_types=1); -namespace App\EventSubscriber\LogSystem; +namespace App\EventListener\LogSystem; use App\Entity\LogSystem\DatabaseUpdatedLogEntry; use App\Services\LogSystem\EventLogger; -use Doctrine\Common\EventSubscriber; +use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Event\MigrationsEventArgs; use Doctrine\Migrations\Events; @@ -32,7 +32,9 @@ use Doctrine\Migrations\Events; /** * This subscriber logs databaseMigrations to Event log. */ -class LogDBMigrationSubscriber implements EventSubscriber +#[AsDoctrineListener(event: Events::onMigrationsMigrated)] +#[AsDoctrineListener(event: Events::onMigrationsMigrating)] +class LogDBMigrationListener { protected ?string $old_version = null; protected ?string $new_version = null; diff --git a/src/Form/AttachmentFormType.php b/src/Form/AttachmentFormType.php index 109c6602..957d692b 100644 --- a/src/Form/AttachmentFormType.php +++ b/src/Form/AttachmentFormType.php @@ -142,7 +142,7 @@ class AttachmentFormType extends AbstractType if (!$file instanceof UploadedFile) { //When no file was uploaded, but a URL was entered, try to determine the attachment name from the URL - if (empty($attachment->getName()) && !empty($attachment->getURL())) { + if ((trim($attachment->getName()) === '') && ($attachment->getURL() !== null && $attachment->getURL() !== '')) { $name = basename(parse_url($attachment->getURL(), PHP_URL_PATH)); $attachment->setName($name); } diff --git a/src/Form/PasswordTypeExtension.php b/src/Form/PasswordTypeExtension.php index a911bf90..64711c53 100644 --- a/src/Form/PasswordTypeExtension.php +++ b/src/Form/PasswordTypeExtension.php @@ -1,4 +1,7 @@ . */ - namespace App\Form; use Symfony\Component\Form\AbstractTypeExtension; @@ -51,4 +53,4 @@ class PasswordTypeExtension extends AbstractTypeExtension $view->vars['password_estimator'] = $options['password_estimator']; } -} \ No newline at end of file +} diff --git a/src/Form/Permissions/PermissionsType.php b/src/Form/Permissions/PermissionsType.php index c0fbb4e8..86fdbc2c 100644 --- a/src/Form/Permissions/PermissionsType.php +++ b/src/Form/Permissions/PermissionsType.php @@ -45,9 +45,7 @@ class PermissionsType extends AbstractType $resolver->setDefaults([ 'show_legend' => true, 'show_presets' => false, - 'show_dependency_notice' => static function (Options $options) { - return !$options['disabled']; - }, + 'show_dependency_notice' => static fn(Options $options) => !$options['disabled'], 'constraints' => static function (Options $options) { if (!$options['disabled']) { return [new NoLockout()]; diff --git a/src/Form/ProjectSystem/ProjectAddPartsType.php b/src/Form/ProjectSystem/ProjectAddPartsType.php index ea51764a..61f72c41 100644 --- a/src/Form/ProjectSystem/ProjectAddPartsType.php +++ b/src/Form/ProjectSystem/ProjectAddPartsType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\ProjectSystem; use App\Entity\ProjectSystem\Project; @@ -83,4 +85,4 @@ class ProjectAddPartsType extends AbstractType $resolver->setAllowedTypes('project', ['null', Project::class]); } -} \ No newline at end of file +} diff --git a/src/Form/Type/Helper/StructuralEntityChoiceHelper.php b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php index 0c596f83..e8e19ad6 100644 --- a/src/Form/Type/Helper/StructuralEntityChoiceHelper.php +++ b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php @@ -100,7 +100,7 @@ class StructuralEntityChoiceHelper public function generateChoiceAttrCurrency(Currency $choice, Options|array $options): array { $tmp = $this->generateChoiceAttr($choice, $options); - $symbol = empty($choice->getIsoCode()) ? null : Currencies::getSymbol($choice->getIsoCode()); + $symbol = $choice->getIsoCode() === '' ? null : Currencies::getSymbol($choice->getIsoCode()); $tmp['data-short'] = $options['short'] ? $symbol : $choice->getName(); //Show entities that are not added to DB yet separately from other entities diff --git a/src/Form/Type/Helper/StructuralEntityChoiceLoader.php b/src/Form/Type/Helper/StructuralEntityChoiceLoader.php index a527a44f..b79fad1b 100644 --- a/src/Form/Type/Helper/StructuralEntityChoiceLoader.php +++ b/src/Form/Type/Helper/StructuralEntityChoiceLoader.php @@ -52,11 +52,7 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader protected function loadChoices(): iterable { //If the starting_element is set and not persisted yet, add it to the list - if ($this->starting_element !== null && $this->starting_element->getID() === null) { - $tmp = [$this->starting_element]; - } else { - $tmp = []; - } + $tmp = $this->starting_element !== null && $this->starting_element->getID() === null ? [$this->starting_element] : []; if ($this->additional_element) { $tmp = $this->createNewEntitiesFromValue($this->additional_element); @@ -163,7 +159,7 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader // the same as the value that is generated for the same entity after it is persisted. // Otherwise, errors occurs that the element could not be found. foreach ($values as &$data) { - $data = trim($data); + $data = trim((string) $data); $data = preg_replace('/\s*->\s*/', '->', $data); } unset ($data); diff --git a/src/Form/Type/StructuralEntityType.php b/src/Form/Type/StructuralEntityType.php index 41ad1c97..1018eeeb 100644 --- a/src/Form/Type/StructuralEntityType.php +++ b/src/Form/Type/StructuralEntityType.php @@ -108,12 +108,8 @@ class StructuralEntityType extends AbstractType $resolver->setDefault('dto_value', null); $resolver->setAllowedTypes('dto_value', ['null', 'string']); //If no help text is explicitly set, we use the dto value as help text and show it as html - $resolver->setDefault('help', function (Options $options) { - return $this->dtoText($options['dto_value']); - }); - $resolver->setDefault('help_html', function (Options $options) { - return $options['dto_value'] !== null; - }); + $resolver->setDefault('help', fn(Options $options) => $this->dtoText($options['dto_value'])); + $resolver->setDefault('help_html', fn(Options $options) => $options['dto_value'] !== null); $resolver->setDefault('attr', function (Options $options) { $tmp = [ diff --git a/src/Helpers/FilenameSanatizer.php b/src/Helpers/FilenameSanatizer.php index 3dca5995..0ca07dfc 100644 --- a/src/Helpers/FilenameSanatizer.php +++ b/src/Helpers/FilenameSanatizer.php @@ -47,9 +47,9 @@ class FilenameSanatizer '-', $filename); // avoids ".", ".." or ".hiddenFiles" - $filename = ltrim($filename, '.-'); + $filename = ltrim((string) $filename, '.-'); //Limit filename length to 255 bytes $ext = pathinfo($filename, PATHINFO_EXTENSION); - return mb_strcut(pathinfo($filename, PATHINFO_FILENAME), 0, 255 - ($ext ? strlen($ext) + 1 : 0), mb_detect_encoding($filename)) . ($ext ? '.' . $ext : ''); + return mb_strcut(pathinfo($filename, PATHINFO_FILENAME), 0, 255 - ($ext !== '' && $ext !== '0' ? strlen($ext) + 1 : 0), mb_detect_encoding($filename)) . ($ext !== '' && $ext !== '0' ? '.' . $ext : ''); } } \ No newline at end of file diff --git a/src/Helpers/LabelResponse.php b/src/Helpers/LabelResponse.php index 98451e2f..2973eb7e 100644 --- a/src/Helpers/LabelResponse.php +++ b/src/Helpers/LabelResponse.php @@ -84,7 +84,7 @@ class LabelResponse extends Response */ public function setAutoLastModified(): LabelResponse { - $this->setLastModified(new DateTime()); + $this->setLastModified(new \DateTimeImmutable()); return $this; } diff --git a/src/Helpers/Projects/ProjectBuildRequest.php b/src/Helpers/Projects/ProjectBuildRequest.php index c2c2ad90..430d37b5 100644 --- a/src/Helpers/Projects/ProjectBuildRequest.php +++ b/src/Helpers/Projects/ProjectBuildRequest.php @@ -174,11 +174,7 @@ final class ProjectBuildRequest */ public function getLotWithdrawAmount(PartLot|int $lot): float { - if ($lot instanceof PartLot) { - $lot_id = $lot->getID(); - } else { // Then it must be an int - $lot_id = $lot; - } + $lot_id = $lot instanceof PartLot ? $lot->getID() : $lot; if (! array_key_exists($lot_id, $this->withdraw_amounts)) { throw new \InvalidArgumentException('The given lot is not in the withdraw amounts array!'); diff --git a/src/Helpers/TrinaryLogicHelper.php b/src/Helpers/TrinaryLogicHelper.php index 54ab9bf8..f4b460de 100644 --- a/src/Helpers/TrinaryLogicHelper.php +++ b/src/Helpers/TrinaryLogicHelper.php @@ -26,6 +26,7 @@ namespace App\Helpers; /** * Helper functions for logic operations with trinary logic. * True and false are represented as classical boolean values, undefined is represented as null. + * @see \App\Tests\Helpers\TrinaryLogicHelperTest */ class TrinaryLogicHelper { diff --git a/src/Migration/AbstractMultiPlatformMigration.php b/src/Migration/AbstractMultiPlatformMigration.php index 82b0fa73..bc2b3f19 100644 --- a/src/Migration/AbstractMultiPlatformMigration.php +++ b/src/Migration/AbstractMultiPlatformMigration.php @@ -25,7 +25,8 @@ namespace App\Migration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; use Psr\Log\LoggerInterface; @@ -50,6 +51,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration match ($db_type) { 'mysql' => $this->mySQLUp($schema), 'sqlite' => $this->sqLiteUp($schema), + 'postgresql' => $this->postgreSQLUp($schema), default => $this->abortIf(true, "Database type '$db_type' is not supported!"), }; } @@ -61,6 +63,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration match ($db_type) { 'mysql' => $this->mySQLDown($schema), 'sqlite' => $this->sqLiteDown($schema), + 'postgresql' => $this->postgreSQLDown($schema), default => $this->abortIf(true, "Database type is not supported!"), }; } @@ -163,10 +166,14 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration return 'mysql'; } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return 'sqlite'; } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return 'postgresql'; + } + return null; } @@ -177,4 +184,8 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration abstract public function sqLiteUp(Schema $schema): void; abstract public function sqLiteDown(Schema $schema): void; + + abstract public function postgreSQLUp(Schema $schema): void; + + abstract public function postgreSQLDown(Schema $schema): void; } diff --git a/src/Migration/WithPermPresetsTrait.php b/src/Migration/WithPermPresetsTrait.php new file mode 100644 index 00000000..5182f3b6 --- /dev/null +++ b/src/Migration/WithPermPresetsTrait.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Migration; + +use App\Entity\UserSystem\PermissionData; +use App\Security\Interfaces\HasPermissionsInterface; +use App\Services\UserSystem\PermissionPresetsHelper; +use Symfony\Component\DependencyInjection\ContainerInterface; + +trait WithPermPresetsTrait +{ + private ?ContainerInterface $container = null; + private ?PermissionPresetsHelper $permission_presets_helper = null; + + private function getJSONPermDataFromPreset(string $preset): string + { + if ($this->permission_presets_helper === null) { + throw new \RuntimeException('PermissionPresetsHelper not set! There seems to be some issue with the dependency injection!'); + } + + //Create a virtual user on which we can apply the preset + $user = new class implements HasPermissionsInterface { + + public PermissionData $perm_data; + + public function __construct() + { + $this->perm_data = new PermissionData(); + } + + public function getPermissions(): PermissionData + { + return $this->perm_data; + } + }; + + //Apply the preset to the virtual user + $this->permission_presets_helper->applyPreset($user, $preset); + + //And return the json data + return json_encode($user->getPermissions()); + } + + public function setContainer(ContainerInterface $container = null): void + { + if ($container !== null) { + $this->container = $container; + $this->permission_presets_helper = $container->get(PermissionPresetsHelper::class); + } + } +} \ No newline at end of file diff --git a/src/Repository/AbstractPartsContainingRepository.php b/src/Repository/AbstractPartsContainingRepository.php index e3c7f610..4fd0bbff 100644 --- a/src/Repository/AbstractPartsContainingRepository.php +++ b/src/Repository/AbstractPartsContainingRepository.php @@ -40,11 +40,11 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo * Returns all parts associated with this element. * * @param object $element the element for which the parts should be determined - * @param array $order_by The order of the parts. Format ['name' => 'ASC'] + * @param string $nameOrderDirection the direction in which the parts should be ordered by name, either ASC or DESC * * @return Part[] */ - abstract public function getParts(object $element, array $order_by = ['name' => 'ASC']): array; + abstract public function getParts(object $element, string $nameOrderDirection = "ASC"): array; /** * Gets the count of the parts associated with this element. @@ -113,7 +113,7 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo return $parts; } - protected function getPartsByField(object $element, array $order_by, string $field_name): array + protected function getPartsByField(object $element, string $nameOrderDirection, string $field_name): array { if (!$element instanceof AbstractPartsContainingDBElement) { throw new InvalidArgumentException('$element must be an instance of AbstractPartContainingDBElement!'); @@ -121,7 +121,14 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo $repo = $this->getEntityManager()->getRepository(Part::class); - return $repo->findBy([$field_name => $element], $order_by); + //Build a query builder to get the parts with a custom order by + + $qb = $repo->createQueryBuilder('part') + ->where('part.'.$field_name.' = :element') + ->setParameter('element', $element) + ->orderBy('NATSORT(part.name)', $nameOrderDirection); + + return $qb->getQuery()->getResult(); } protected function getPartsCountByField(object $element, string $field_name): int diff --git a/src/Repository/AttachmentContainingDBElementRepository.php b/src/Repository/AttachmentContainingDBElementRepository.php index 7f00f87f..40869662 100644 --- a/src/Repository/AttachmentContainingDBElementRepository.php +++ b/src/Repository/AttachmentContainingDBElementRepository.php @@ -25,11 +25,12 @@ namespace App\Repository; use App\Doctrine\Helpers\FieldHelper; use App\Entity\Attachments\AttachmentContainingDBElement; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; /** * @template TEntityClass of AttachmentContainingDBElement * @extends NamedDBElementRepository + * @see \App\Tests\Repository\AttachmentContainingDBElementRepositoryTest */ class AttachmentContainingDBElementRepository extends NamedDBElementRepository { @@ -70,7 +71,7 @@ class AttachmentContainingDBElementRepository extends NamedDBElementRepository $q = $qb->getQuery(); - $q->setFetchMode($this->getEntityName(), 'master_picture_attachment', ClassMetadataInfo::FETCH_EAGER); + $q->setFetchMode($this->getEntityName(), 'master_picture_attachment', ClassMetadata::FETCH_EAGER); $result = $q->getResult(); diff --git a/src/Repository/DBElementRepository.php b/src/Repository/DBElementRepository.php index e2e60b07..2437e848 100644 --- a/src/Repository/DBElementRepository.php +++ b/src/Repository/DBElementRepository.php @@ -49,6 +49,7 @@ use ReflectionClass; /** * @template TEntityClass of AbstractDBElement * @extends EntityRepository + * @see \App\Tests\Repository\DBElementRepositoryTest */ class DBElementRepository extends EntityRepository { @@ -79,7 +80,7 @@ class DBElementRepository extends EntityRepository /** * Find all elements that match a list of IDs. - * + * They are ordered by IDs in an ascending order. * @return AbstractDBElement[] * @phpstan-return list */ @@ -89,6 +90,7 @@ class DBElementRepository extends EntityRepository $q = $qb->select('element') ->where('element.id IN (?1)') ->setParameter(1, $ids) + ->orderBy('element.id', 'ASC') ->getQuery(); return $q->getResult(); @@ -142,9 +144,7 @@ class DBElementRepository extends EntityRepository */ protected function sortResultArrayByIDArray(array &$result_array, array $ids): void { - usort($result_array, static function (AbstractDBElement $a, AbstractDBElement $b) use ($ids) { - return array_search($a->getID(), $ids, true) <=> array_search($b->getID(), $ids, true); - }); + usort($result_array, static fn(AbstractDBElement $a, AbstractDBElement $b) => array_search($a->getID(), $ids, true) <=> array_search($b->getID(), $ids, true)); } protected function setField(AbstractDBElement $element, string $field, int $new_value): void diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index 82e4bdd8..40ff3ec2 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -85,10 +85,8 @@ class LogEntryRepository extends DBElementRepository ->orderBy('log.timestamp', 'DESC') ->setMaxResults(1); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($class), - 'target_id' => $id, - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($class)); + $qb->setParameter('target_id', $id); $query = $qb->getQuery(); @@ -119,13 +117,13 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') ->andWhere('log.timestamp >= :until') - ->orderBy('log.timestamp', 'DESC'); + ->orderBy('log.timestamp', 'DESC') + ; + + $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); + $qb->setParameter('target_id', $element->getID()); + $qb->setParameter('until', $until); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($element), - 'target_id' => $element->getID(), - 'until' => $until, - ]); $query = $qb->getQuery(); @@ -145,13 +143,12 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') ->andWhere('log.timestamp >= :until') - ->orderBy('log.timestamp', 'DESC'); + ->orderBy('log.timestamp', 'DESC') + ->groupBy('log.id'); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($element), - 'target_id' => $element->getID(), - 'until' => $timestamp, - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); + $qb->setParameter('target_id', $element->getID()); + $qb->setParameter('until', $timestamp); $query = $qb->getQuery(); $count = $query->getSingleScalarResult(); @@ -232,10 +229,8 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.target_id = :target_id') ->orderBy('log.timestamp', 'DESC'); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($element), - 'target_id' => $element->getID(), - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); + $qb->setParameter('target_id', $element->getID()); $query = $qb->getQuery(); $query->setMaxResults(1); diff --git a/src/Repository/NamedDBElementRepository.php b/src/Repository/NamedDBElementRepository.php index ae8d4d8f..8c78cb5e 100644 --- a/src/Repository/NamedDBElementRepository.php +++ b/src/Repository/NamedDBElementRepository.php @@ -29,6 +29,7 @@ use App\Helpers\Trees\TreeViewNode; /** * @template TEntityClass of AbstractNamedDBElement * @extends DBElementRepository + * @see \App\Tests\Repository\NamedDBElementRepositoryTest */ class NamedDBElementRepository extends DBElementRepository { @@ -42,7 +43,7 @@ class NamedDBElementRepository extends DBElementRepository { $result = []; - $entities = $this->findBy([], ['name' => 'ASC']); + $entities = $this->getFlatList(); foreach ($entities as $entity) { /** @var AbstractNamedDBElement $entity */ $node = new TreeViewNode($entity->getName(), null, null); @@ -65,13 +66,17 @@ class NamedDBElementRepository extends DBElementRepository } /** - * Returns a flattened list of all nodes. + * Returns a flattened list of all nodes, sorted by name in natural order. * @return AbstractNamedDBElement[] * @phpstan-return array */ public function getFlatList(): array { - //All nodes are sorted by name - return $this->findBy([], ['name' => 'ASC']); + $qb = $this->createQueryBuilder('e'); + $q = $qb->select('e') + ->orderBy('NATSORT(e.name)', 'ASC') + ->getQuery(); + + return $q->getResult(); } } diff --git a/src/Repository/PartRepository.php b/src/Repository/PartRepository.php index 3ab3ed31..84357b72 100644 --- a/src/Repository/PartRepository.php +++ b/src/Repository/PartRepository.php @@ -90,7 +90,7 @@ class PartRepository extends NamedDBElementRepository $qb->setParameter('query', '%'.$query.'%'); $qb->setMaxResults($max_limits); - $qb->orderBy('part.name', 'ASC'); + $qb->orderBy('NATSORT(part.name)', 'ASC'); return $qb->getQuery()->getResult(); } diff --git a/src/Repository/Parts/CategoryRepository.php b/src/Repository/Parts/CategoryRepository.php index 8e270047..9bbb1e37 100644 --- a/src/Repository/Parts/CategoryRepository.php +++ b/src/Repository/Parts/CategoryRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class CategoryRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Category) { throw new InvalidArgumentException('$element must be an Category!'); } - return $this->getPartsByField($element, $order_by, 'category'); + return $this->getPartsByField($element, $nameOrderDirection, 'category'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/FootprintRepository.php b/src/Repository/Parts/FootprintRepository.php index 355cb1bb..8934831a 100644 --- a/src/Repository/Parts/FootprintRepository.php +++ b/src/Repository/Parts/FootprintRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class FootprintRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Footprint) { throw new InvalidArgumentException('$element must be an Footprint!'); } - return $this->getPartsByField($element, $order_by, 'footprint'); + return $this->getPartsByField($element, $nameOrderDirection, 'footprint'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/ManufacturerRepository.php b/src/Repository/Parts/ManufacturerRepository.php index a47142d4..1a838710 100644 --- a/src/Repository/Parts/ManufacturerRepository.php +++ b/src/Repository/Parts/ManufacturerRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class ManufacturerRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Manufacturer) { throw new InvalidArgumentException('$element must be an Manufacturer!'); } - return $this->getPartsByField($element, $order_by, 'manufacturer'); + return $this->getPartsByField($element, $nameOrderDirection, 'manufacturer'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/MeasurementUnitRepository.php b/src/Repository/Parts/MeasurementUnitRepository.php index 1c9b106b..c581f751 100644 --- a/src/Repository/Parts/MeasurementUnitRepository.php +++ b/src/Repository/Parts/MeasurementUnitRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class MeasurementUnitRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof MeasurementUnit) { throw new InvalidArgumentException('$element must be an MeasurementUnit!'); } - return $this->getPartsByField($element, $order_by, 'partUnit'); + return $this->getPartsByField($element, $nameOrderDirection, 'partUnit'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/StorelocationRepository.php b/src/Repository/Parts/StorelocationRepository.php index 192d4f6d..82317868 100644 --- a/src/Repository/Parts/StorelocationRepository.php +++ b/src/Repository/Parts/StorelocationRepository.php @@ -30,12 +30,7 @@ use InvalidArgumentException; class StorelocationRepository extends AbstractPartsContainingRepository { - /** - * @param object $element - * @param array $order_by - * @return array - */ - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof StorageLocation) { throw new InvalidArgumentException('$element must be an Storelocation!'); @@ -47,11 +42,9 @@ class StorelocationRepository extends AbstractPartsContainingRepository ->from(Part::class, 'part') ->leftJoin('part.partLots', 'lots') ->where('lots.storage_location = ?1') - ->setParameter(1, $element); - - foreach ($order_by as $field => $order) { - $qb->addOrderBy('part.'.$field, $order); - } + ->setParameter(1, $element) + ->orderBy('NATSORT(part.name)', $nameOrderDirection) + ; return $qb->getQuery()->getResult(); } diff --git a/src/Repository/Parts/SupplierRepository.php b/src/Repository/Parts/SupplierRepository.php index 6dc995f1..393ae593 100644 --- a/src/Repository/Parts/SupplierRepository.php +++ b/src/Repository/Parts/SupplierRepository.php @@ -30,7 +30,7 @@ use InvalidArgumentException; class SupplierRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Supplier) { throw new InvalidArgumentException('$element must be an Supplier!'); @@ -42,11 +42,9 @@ class SupplierRepository extends AbstractPartsContainingRepository ->from(Part::class, 'part') ->leftJoin('part.orderdetails', 'orderdetail') ->where('orderdetail.supplier = ?1') - ->setParameter(1, $element); - - foreach ($order_by as $field => $order) { - $qb->addOrderBy('part.'.$field, $order); - } + ->setParameter(1, $element) + ->orderBy('NATSORT(part.name)', $nameOrderDirection) + ; return $qb->getQuery()->getResult(); } diff --git a/src/Repository/StructuralDBElementRepository.php b/src/Repository/StructuralDBElementRepository.php index 978cee20..47c85db3 100644 --- a/src/Repository/StructuralDBElementRepository.php +++ b/src/Repository/StructuralDBElementRepository.php @@ -40,6 +40,28 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit */ private array $new_entity_cache = []; + /** + * Finds all nodes for the given parent node, ordered by name in a natural sort way + * @param AbstractStructuralDBElement|null $parent + * @param string $nameOrdering The ordering of the names. Either ASC or DESC + * @return array + */ + public function findNodesForParent(?AbstractStructuralDBElement $parent, string $nameOrdering = "ASC"): array + { + $qb = $this->createQueryBuilder('e'); + $qb->select('e') + ->orderBy('NATSORT(e.name)', $nameOrdering); + + if ($parent !== null) { + $qb->where('e.parent = :parent') + ->setParameter('parent', $parent); + } else { + $qb->where('e.parent IS NULL'); + } + //@phpstan-ignore-next-line [parent is only defined by the sub classes] + return $qb->getQuery()->getResult(); + } + /** * Finds all nodes without a parent node. They are our root nodes. * @@ -47,7 +69,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit */ public function findRootNodes(): array { - return $this->findBy(['parent' => null], ['name' => 'ASC']); + return $this->findNodesForParent(null); } /** @@ -63,7 +85,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit { $result = []; - $entities = $this->findBy(['parent' => $parent], ['name' => 'ASC']); + $entities = $this->findNodesForParent($parent); foreach ($entities as $entity) { /** @var AbstractStructuralDBElement $entity */ //Make a recursive call to find all children nodes @@ -89,7 +111,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit { $result = []; - $entities = $this->findBy(['parent' => $parent], ['name' => 'ASC']); + $entities = $this->findNodesForParent($parent); $elementIterator = new StructuralDBElementIterator($entities); $recursiveIterator = new RecursiveIteratorIterator($elementIterator, RecursiveIteratorIterator::SELF_FIRST); @@ -238,7 +260,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit //Try to find if we already have an element cached for this name $entity = $this->getNewEntityFromCache($name, null); - if ($entity) { + if ($entity !== null) { return $entity; } diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index fa95e83d..bbaa2b39 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace App\Repository; use App\Entity\UserSystem\User; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\Query\Parameter; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -34,6 +36,7 @@ use Symfony\Component\Security\Core\User\UserInterface; * @method User[] findAll() * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) * @extends NamedDBElementRepository + * @see \App\Tests\Repository\UserRepositoryTest */ final class UserRepository extends NamedDBElementRepository implements PasswordUpgraderInterface { @@ -97,10 +100,8 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU ->where('u.name = (:name)') ->orWhere('u.email = (:email)'); - $qb->setParameters([ - 'email' => $name_or_password, - 'name' => $name_or_password, - ]); + $qb->setParameter('email', $name_or_password); + $qb->setParameter('name', $name_or_password); try { return $qb->getQuery()->getOneOrNullResult(); diff --git a/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php b/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php index 5d67e36f..9bfa691d 100644 --- a/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php +++ b/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php @@ -44,12 +44,12 @@ class WebauthnKeyLastUseTwoFactorProvider implements TwoFactorProviderInterface public function __construct( #[AutowireDecorated] - private TwoFactorProviderInterface $decorated, - private EntityManagerInterface $entityManager, + private readonly TwoFactorProviderInterface $decorated, + private readonly EntityManagerInterface $entityManager, #[Autowire(service: 'jbtronics_webauthn_tfa.user_public_key_source_repo')] - private UserPublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, + private readonly UserPublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, #[Autowire(service: 'jbtronics_webauthn_tfa.webauthn_provider')] - private WebauthnProvider $webauthnProvider, + private readonly WebauthnProvider $webauthnProvider, ) { } diff --git a/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php b/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php index 863fbee6..8283dbbe 100644 --- a/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php +++ b/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Serializer\APIPlatform; +use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException; use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Post; @@ -59,7 +60,7 @@ class DetermineTypeFromElementIRIDenormalizer implements DenormalizerInterface, * @param array $input * @param Operation $operation * @return array - * @throws \ApiPlatform\Metadata\Exception\ResourceClassNotFoundException + * @throws ResourceClassNotFoundException */ private function addTypeDiscriminatorIfNecessary(array $input, Operation $operation): array { diff --git a/src/Serializer/APIPlatform/SkippableItemNormalizer.php b/src/Serializer/APIPlatform/SkippableItemNormalizer.php index 20dc4c01..5568c4cb 100644 --- a/src/Serializer/APIPlatform/SkippableItemNormalizer.php +++ b/src/Serializer/APIPlatform/SkippableItemNormalizer.php @@ -37,7 +37,7 @@ use Symfony\Component\Serializer\SerializerInterface; * Platform subsystem should not be used. */ #[AsDecorator("api_platform.serializer.normalizer.item")] -class SkippableItemNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface +class SkippableItemNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface { public const DISABLE_ITEM_NORMALIZER = 'DISABLE_ITEM_NORMALIZER'; @@ -47,11 +47,6 @@ class SkippableItemNormalizer implements NormalizerInterface, DenormalizerInterf } - public function hasCacheableSupportsMethod(): bool - { - return $this->inner->hasCacheableSupportsMethod(); - } - public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { return $this->inner->denormalize($data, $type, $format, $context); diff --git a/src/Serializer/PartNormalizer.php b/src/Serializer/PartNormalizer.php index a4890104..650b0214 100644 --- a/src/Serializer/PartNormalizer.php +++ b/src/Serializer/PartNormalizer.php @@ -161,7 +161,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Norm if (isset($data['supplier']) && $data['supplier'] !== "") { $supplier = $this->locationDenormalizer->denormalize($data['supplier'], Supplier::class, $format, $context); - if ($supplier) { + if ($supplier !== null) { $orderdetail = new Orderdetail(); $orderdetail->setSupplier($supplier); diff --git a/src/Serializer/StructuralElementDenormalizer.php b/src/Serializer/StructuralElementDenormalizer.php index 93bcf0b2..17b3d81e 100644 --- a/src/Serializer/StructuralElementDenormalizer.php +++ b/src/Serializer/StructuralElementDenormalizer.php @@ -24,23 +24,28 @@ namespace App\Serializer; use App\Entity\Base\AbstractStructuralDBElement; use App\Repository\StructuralDBElementRepository; +use App\Serializer\APIPlatform\SkippableItemNormalizer; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; /** * @see \App\Tests\Serializer\StructuralElementDenormalizerTest */ -class StructuralElementDenormalizer implements DenormalizerInterface +class StructuralElementDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface { + use DenormalizerAwareTrait; + + private const ALREADY_CALLED = 'STRUCTURAL_DENORMALIZER_ALREADY_CALLED'; + private array $object_cache = []; public function __construct( - private readonly EntityManagerInterface $entityManager, - #[Autowire(service: ObjectNormalizer::class)] - private readonly DenormalizerInterface $denormalizer) + private readonly EntityManagerInterface $entityManager) { } @@ -51,6 +56,13 @@ class StructuralElementDenormalizer implements DenormalizerInterface return false; } + //If we already handled this object, skip it + if (isset($context[self::ALREADY_CALLED]) + && is_array($context[self::ALREADY_CALLED]) + && in_array($data, $context[self::ALREADY_CALLED], true)) { + return false; + } + return is_array($data) && is_subclass_of($type, AbstractStructuralDBElement::class) //Only denormalize if we are doing a file import operation @@ -59,6 +71,16 @@ class StructuralElementDenormalizer implements DenormalizerInterface public function denormalize($data, string $type, string $format = null, array $context = []): ?AbstractStructuralDBElement { + //Do not use API Platform's denormalizer + $context[SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER] = true; + + if (!isset($context[self::ALREADY_CALLED])) { + $context[self::ALREADY_CALLED] = []; + } + + $context[self::ALREADY_CALLED][] = $data; + + /** @var AbstractStructuralDBElement $deserialized_entity */ $deserialized_entity = $this->denormalizer->denormalize($data, $type, $format, $context); diff --git a/src/Services/Attachments/AttachmentPathResolver.php b/src/Services/Attachments/AttachmentPathResolver.php index e6015b3a..e3e7a3ca 100644 --- a/src/Services/Attachments/AttachmentPathResolver.php +++ b/src/Services/Attachments/AttachmentPathResolver.php @@ -139,12 +139,12 @@ class AttachmentPathResolver } //If we have now have a placeholder left, the string is invalid: - if (preg_match('#%\w+%#', $placeholder_path)) { + if (preg_match('#%\w+%#', (string) $placeholder_path)) { return null; } //Path is invalid if path is directory traversal - if (str_contains($placeholder_path, '..')) { + if (str_contains((string) $placeholder_path, '..')) { return null; } @@ -183,7 +183,7 @@ class AttachmentPathResolver } //If the new string does not begin with a placeholder, it is invalid - if (!preg_match('#^%\w+%#', $real_path)) { + if (!preg_match('#^%\w+%#', (string) $real_path)) { return null; } diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php index d5fe9370..d9b2a380 100644 --- a/src/Services/Attachments/AttachmentSubmitHandler.php +++ b/src/Services/Attachments/AttachmentSubmitHandler.php @@ -300,7 +300,7 @@ class AttachmentSubmitHandler return $attachment; } - $filename = basename($old_path); + $filename = basename((string) $old_path); //If the basename is not one of the new unique on, we have to save the old filename if (!preg_match('#\w+-\w{13}\.#', $filename)) { //Save filename to attachment field @@ -378,7 +378,7 @@ class AttachmentSubmitHandler //If we don't know filename yet, try to determine it out of url if ('' === $filename) { - $filename = basename(parse_url($url, PHP_URL_PATH)); + $filename = basename(parse_url((string) $url, PHP_URL_PATH)); } //Set original file diff --git a/src/Services/Cache/ElementCacheTagGenerator.php b/src/Services/Cache/ElementCacheTagGenerator.php index 382c9317..88fca09f 100644 --- a/src/Services/Cache/ElementCacheTagGenerator.php +++ b/src/Services/Cache/ElementCacheTagGenerator.php @@ -46,11 +46,10 @@ class ElementCacheTagGenerator { //Ensure that the given element is a class name if (is_object($element)) { - $element = get_class($element); - } else { //And that the class exists - if (!class_exists($element)) { - throw new \InvalidArgumentException("The given class '$element' does not exist!"); - } + $element = $element::class; + } elseif (!class_exists($element)) { + //And that the class exists + throw new \InvalidArgumentException("The given class '$element' does not exist!"); } //Check if the tag is already cached diff --git a/src/Services/EDA/KiCadHelper.php b/src/Services/EDA/KiCadHelper.php index 05630d16..13de01e8 100644 --- a/src/Services/EDA/KiCadHelper.php +++ b/src/Services/EDA/KiCadHelper.php @@ -134,7 +134,7 @@ class KiCadHelper if ($this->category_depth >= 0) { //Ensure that the category is set - if (!$category) { + if ($category === null) { throw new NotFoundHttpException('Category must be set, if category_depth is greater than 1!'); } @@ -196,25 +196,25 @@ class KiCadHelper //Add basic fields $result["fields"]["description"] = $this->createField($part->getDescription()); - if ($part->getCategory()) { + if ($part->getCategory() !== null) { $result["fields"]["Category"] = $this->createField($part->getCategory()->getFullPath('/')); } - if ($part->getManufacturer()) { + if ($part->getManufacturer() !== null) { $result["fields"]["Manufacturer"] = $this->createField($part->getManufacturer()->getName()); } if ($part->getManufacturerProductNumber() !== "") { $result['fields']["MPN"] = $this->createField($part->getManufacturerProductNumber()); } - if ($part->getManufacturingStatus()) { + if ($part->getManufacturingStatus() !== null) { $result["fields"]["Manufacturing Status"] = $this->createField( //Always use the english translation $this->translator->trans($part->getManufacturingStatus()->toTranslationKey(), locale: 'en') ); } - if ($part->getFootprint()) { + if ($part->getFootprint() !== null) { $result["fields"]["Part-DB Footprint"] = $this->createField($part->getFootprint()->getName()); } - if ($part->getPartUnit()) { + if ($part->getPartUnit() !== null) { $unit = $part->getPartUnit()->getName(); if ($part->getPartUnit()->getUnit() !== "") { $unit .= ' ('.$part->getPartUnit()->getUnit().')'; @@ -225,7 +225,7 @@ class KiCadHelper $result["fields"]["Mass"] = $this->createField($part->getMass() . ' g'); } $result["fields"]["Part-DB ID"] = $this->createField($part->getId()); - if (!empty($part->getIpn())) { + if ($part->getIpn() !== null && $part->getIpn() !== '' && $part->getIpn() !== '0') { $result["fields"]["Part-DB IPN"] = $this->createField($part->getIpn()); } @@ -308,14 +308,9 @@ class KiCadHelper )) { return true; } - //And on the footprint - if ($part->getFootprint() && $part->getFootprint()->getEdaInfo()->getKicadFootprint() !== null) { - return true; - } - //Otherwise the part should be not visible - return false; + return $part->getFootprint() && $part->getFootprint()->getEdaInfo()->getKicadFootprint() !== null; } /** diff --git a/src/Services/EntityMergers/EntityMerger.php b/src/Services/EntityMergers/EntityMerger.php index 0f1355ea..c0be84ee 100644 --- a/src/Services/EntityMergers/EntityMerger.php +++ b/src/Services/EntityMergers/EntityMerger.php @@ -69,7 +69,7 @@ class EntityMerger { $merger = $this->findMergerForObject($target, $other, $context); if ($merger === null) { - throw new \RuntimeException('No merger found for merging '.get_class($other).' into '.get_class($target)); + throw new \RuntimeException('No merger found for merging '.$other::class.' into '.$target::class); } return $merger->merge($target, $other, $context); } diff --git a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php index dcd0984a..a5c9a5fa 100644 --- a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php +++ b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php @@ -85,9 +85,7 @@ trait EntityMergerHelperTrait protected function useOtherValueIfNotNull(object $target, object $other, string $field): object { return $this->useCallback( - function ($target_value, $other_value) { - return $target_value ?? $other_value; - }, + fn($target_value, $other_value) => $target_value ?? $other_value, $target, $other, $field @@ -106,9 +104,7 @@ trait EntityMergerHelperTrait protected function useOtherValueIfNotEmtpy(object $target, object $other, string $field): object { return $this->useCallback( - function ($target_value, $other_value) { - return empty($target_value) ? $other_value : $target_value; - }, + fn($target_value, $other_value) => empty($target_value) ? $other_value : $target_value, $target, $other, $field @@ -126,9 +122,7 @@ trait EntityMergerHelperTrait protected function useLargerValue(object $target, object $other, string $field): object { return $this->useCallback( - function ($target_value, $other_value) { - return max($target_value, $other_value); - }, + fn($target_value, $other_value) => max($target_value, $other_value), $target, $other, $field @@ -146,9 +140,7 @@ trait EntityMergerHelperTrait protected function useSmallerValue(object $target, object $other, string $field): object { return $this->useCallback( - function ($target_value, $other_value) { - return min($target_value, $other_value); - }, + fn($target_value, $other_value) => min($target_value, $other_value), $target, $other, $field @@ -166,9 +158,7 @@ trait EntityMergerHelperTrait protected function useTrueValue(object $target, object $other, string $field): object { return $this->useCallback( - function (bool $target_value, bool $other_value): bool { - return $target_value || $other_value; - }, + fn(bool $target_value, bool $other_value): bool => $target_value || $other_value, $target, $other, $field @@ -232,10 +222,8 @@ trait EntityMergerHelperTrait continue 2; } } - } else { - if ($target_collection->contains($item)) { - continue; - } + } elseif ($target_collection->contains($item)) { + continue; } $clones[] = clone $item; @@ -257,11 +245,9 @@ trait EntityMergerHelperTrait */ protected function mergeAttachments(AttachmentContainingDBElement $target, AttachmentContainingDBElement $other): object { - return $this->mergeCollections($target, $other, 'attachments', function (Attachment $t, Attachment $o): bool { - return $t->getName() === $o->getName() - && $t->getAttachmentType() === $o->getAttachmentType() - && $t->getPath() === $o->getPath(); - }); + return $this->mergeCollections($target, $other, 'attachments', fn(Attachment $t, Attachment $o): bool => $t->getName() === $o->getName() + && $t->getAttachmentType() === $o->getAttachmentType() + && $t->getPath() === $o->getPath()); } /** @@ -272,16 +258,14 @@ trait EntityMergerHelperTrait */ protected function mergeParameters(AbstractStructuralDBElement|Part $target, AbstractStructuralDBElement|Part $other): object { - return $this->mergeCollections($target, $other, 'parameters', function (AbstractParameter $t, AbstractParameter $o): bool { - return $t->getName() === $o->getName() - && $t->getSymbol() === $o->getSymbol() - && $t->getUnit() === $o->getUnit() - && $t->getValueMax() === $o->getValueMax() - && $t->getValueMin() === $o->getValueMin() - && $t->getValueTypical() === $o->getValueTypical() - && $t->getValueText() === $o->getValueText() - && $t->getGroup() === $o->getGroup(); - }); + return $this->mergeCollections($target, $other, 'parameters', fn(AbstractParameter $t, AbstractParameter $o): bool => $t->getName() === $o->getName() + && $t->getSymbol() === $o->getSymbol() + && $t->getUnit() === $o->getUnit() + && $t->getValueMax() === $o->getValueMax() + && $t->getValueMin() === $o->getValueMin() + && $t->getValueTypical() === $o->getValueTypical() + && $t->getValueText() === $o->getValueText() + && $t->getGroup() === $o->getGroup()); } /** diff --git a/src/Services/EntityMergers/Mergers/PartMerger.php b/src/Services/EntityMergers/Mergers/PartMerger.php index 29d4fe5c..4ce779e8 100644 --- a/src/Services/EntityMergers/Mergers/PartMerger.php +++ b/src/Services/EntityMergers/Mergers/PartMerger.php @@ -33,6 +33,7 @@ use App\Entity\PriceInformations\Orderdetail; * This class merges two parts together. * * @implements EntityMergerInterface + * @see \App\Tests\Services\EntityMergers\Mergers\PartMergerTest */ class PartMerger implements EntityMergerInterface { @@ -99,7 +100,7 @@ class PartMerger implements EntityMergerInterface return $target; } - private static function comparePartAssociations(PartAssociation $t, PartAssociation $o): bool { + private function comparePartAssociations(PartAssociation $t, PartAssociation $o): bool { //We compare the translation keys, as it contains info about the type and other type info return $t->getOther() === $o->getOther() && $t->getTypeTranslationKey() === $o->getTypeTranslationKey(); @@ -117,7 +118,7 @@ class PartMerger implements EntityMergerInterface $this->mergeParameters($target, $other); //Merge the associations - $this->mergeCollections($target, $other, 'associated_parts_as_owner', self::comparePartAssociations(...)); + $this->mergeCollections($target, $other, 'associated_parts_as_owner', $this->comparePartAssociations(...)); //We have to recreate the associations towards the other part, as they are not created by the merger foreach ($other->getAssociatedPartsAsOther() as $association) { @@ -131,7 +132,7 @@ class PartMerger implements EntityMergerInterface } //Ensure that the association is not already present foreach ($owner->getAssociatedPartsAsOwner() as $existing_association) { - if (self::comparePartAssociations($existing_association, $clone)) { + if ($this->comparePartAssociations($existing_association, $clone)) { continue 2; } } diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 16aadb0e..5718daec 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -360,11 +360,7 @@ class EntityURLGenerator */ protected function mapToController(array $map, string|AbstractDBElement $entity): string { - if (is_string($entity)) { //If a class name was already passed, then use it directly - $class = $entity; - } else { //Otherwise get the class name from the entity - $class = $entity::class; - } + $class = is_string($entity) ? $entity : $entity::class; //Check if we have an direct mapping for the given class if (!array_key_exists($class, $map)) { diff --git a/src/Services/ImportExportSystem/EntityImporter.php b/src/Services/ImportExportSystem/EntityImporter.php index 5f73ae92..4c60a71b 100644 --- a/src/Services/ImportExportSystem/EntityImporter.php +++ b/src/Services/ImportExportSystem/EntityImporter.php @@ -29,6 +29,7 @@ use App\Entity\Parts\Part; use App\Repository\StructuralDBElementRepository; use App\Serializer\APIPlatform\SkippableItemNormalizer; use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\ConstraintViolationListInterface; use function count; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; @@ -57,6 +58,7 @@ class EntityImporter * @phpstan-param class-string $class_name * @param AbstractStructuralDBElement|null $parent the element which will be used as parent element for new elements * @param array $errors an associative array containing all validation errors + * @param-out array $errors * * @return AbstractNamedDBElement[] An array containing all valid imported entities (with the type $class_name) * @return T[] @@ -152,6 +154,7 @@ class EntityImporter * @param string $data The serialized data which should be imported * @param array $options The options for the import process * @param array $errors An array which will be filled with the validation errors, if any occurs during import + * @param-out array $errors * @return array An array containing all valid imported entities */ public function importString(string $data, array $options = [], array &$errors = []): array @@ -218,6 +221,10 @@ class EntityImporter if (count($tmp) > 0) { //Log validation errors to global log. $name = $entity instanceof AbstractStructuralDBElement ? $entity->getFullPath() : $entity->getName(); + if (trim($name) === '') { + $name = 'Row ' . (string) $key; + } + $errors[$name] = [ 'violations' => $tmp, 'entity' => $entity, @@ -264,7 +271,7 @@ class EntityImporter * @param array $options options for the import process * @param AbstractNamedDBElement[] $entities The imported entities are returned in this array * - * @return array An associative array containing an ConstraintViolationList and the entity name as key are returned, + * @return array An associative array containing an ConstraintViolationList and the entity name as key are returned, * if an error happened during validation. When everything was successfully, the array should be empty. */ public function importFileAndPersistToDB(File $file, array $options = [], array &$entities = []): array @@ -296,6 +303,7 @@ class EntityImporter * * @param File $file the file that should be used for importing * @param array $options options for the import process + * @param-out array $errors * * @return array an array containing the deserialized elements */ diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php index 681a1371..574bc32b 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php @@ -30,7 +30,7 @@ use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Contracts\TimeStampableInterface; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; /** @@ -212,7 +212,7 @@ trait PKImportHelperTrait $id = (int) $id; $metadata = $this->em->getClassMetadata($element::class); - $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); + $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdentifierValues($element, ['id' => $id]); } @@ -225,7 +225,7 @@ trait PKImportHelperTrait protected function setCreationDate(TimeStampableInterface $entity, ?string $datetime_str): void { if ($datetime_str !== null && $datetime_str !== '' && $datetime_str !== '0000-00-00 00:00:00') { - $date = new \DateTime($datetime_str); + $date = new \DateTimeImmutable($datetime_str); } else { $date = null; //Null means "now" at persist time } diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php index b8e8272e..fafde29a 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php @@ -116,7 +116,7 @@ class PKOptionalImporter //All imported users get assigned to the "PartKeepr Users" group $group_users = $this->em->find(Group::class, 3); $group = $this->em->getRepository(Group::class)->findOneBy(['name' => 'PartKeepr Users', 'parent' => $group_users]); - if (!$group) { + if ($group === null) { $group = new Group(); $group->setName('PartKeepr Users'); $group->setParent($group_users); diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php index 767f36b4..9dd67233 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php @@ -218,7 +218,7 @@ class PKPartImporter 'iso_code' => $currency_iso_code, ]); - if (!$currency) { + if ($currency === null) { $currency = new Currency(); $currency->setIsoCode($currency_iso_code); $currency->setName(Currencies::getName($currency_iso_code)); @@ -265,7 +265,7 @@ class PKPartImporter ]); //When no orderdetail exists, create one - if (!$orderdetail) { + if ($orderdetail === null) { $orderdetail = new Orderdetail(); $orderdetail->setSupplier($supplier); $orderdetail->setSupplierpartnr($spn); diff --git a/src/Services/InfoProviderSystem/DTOs/FileDTO.php b/src/Services/InfoProviderSystem/DTOs/FileDTO.php index 295cf78a..0d1db76a 100644 --- a/src/Services/InfoProviderSystem/DTOs/FileDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/FileDTO.php @@ -26,6 +26,7 @@ namespace App\Services\InfoProviderSystem\DTOs; /** * This DTO represents a file that can be downloaded from a URL. * This could be a datasheet, a 3D model, a picture or similar. + * @see \App\Tests\Services\InfoProviderSystem\DTOs\FileDTOTest */ class FileDTO { diff --git a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php index d9a0596c..3332700b 100644 --- a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php @@ -26,6 +26,7 @@ namespace App\Services\InfoProviderSystem\DTOs; /** * This DTO represents a parameter of a part (similar to the AbstractParameter entity). * This could be a voltage, a current, a temperature or similar. + * @see \App\Tests\Services\InfoProviderSystem\DTOs\ParameterDTOTest */ class ParameterDTO { @@ -76,7 +77,7 @@ class ParameterDTO $parts = preg_split('/\s*(\.{3}|~)\s*/', $value); if (count($parts) === 2) { //Try to extract number and unit from value (allow leading +) - if (empty($unit)) { + if ($unit === null || trim($unit) === '') { [$number, $unit] = self::splitIntoValueAndUnit(ltrim($parts[0], " +")) ?? [$parts[0], null]; } else { $number = $parts[0]; diff --git a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php index 6073cc5f..bcd8be43 100644 --- a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php @@ -25,6 +25,7 @@ namespace App\Services\InfoProviderSystem\DTOs; /** * This DTO represents a purchase information for a part (supplier name, order number and prices). + * @see \App\Tests\Services\InfoProviderSystem\DTOs\PurchaseInfoDTOTest */ class PurchaseInfoDTO { diff --git a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php index c12b54f8..28943702 100644 --- a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php @@ -27,6 +27,7 @@ use App\Entity\Parts\ManufacturingStatus; /** * This DTO represents a search result for a part. + * @see \App\Tests\Services\InfoProviderSystem\DTOs\SearchResultDTOTest */ class SearchResultDTO { diff --git a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php index a0d1baf0..2c2b4076 100644 --- a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php +++ b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php @@ -45,6 +45,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * This class converts DTOs to entities which can be persisted in the DB + * @see \App\Tests\Services\InfoProviderSystem\DTOtoEntityConverterTest */ final class DTOtoEntityConverter { @@ -127,7 +128,7 @@ final class DTOtoEntityConverter $entity->setAttachmentType($type); //If no name is given, try to extract the name from the URL - if (empty($dto->name)) { + if ($dto->name === null || $dto->name === '' || $dto->name === '0') { $entity->setName($this->getAttachmentNameFromURL($dto->url)); } else { $entity->setName($dto->name); diff --git a/src/Services/InfoProviderSystem/ProviderRegistry.php b/src/Services/InfoProviderSystem/ProviderRegistry.php index acfbdc1e..f6c398d2 100644 --- a/src/Services/InfoProviderSystem/ProviderRegistry.php +++ b/src/Services/InfoProviderSystem/ProviderRegistry.php @@ -27,6 +27,7 @@ use App\Services\InfoProviderSystem\Providers\InfoProviderInterface; /** * This class keeps track of all registered info providers and allows to find them by their key + * @see \App\Tests\Services\InfoProviderSystem\ProviderRegistryTest */ final class ProviderRegistry { diff --git a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php index 1ffbd836..e707290a 100644 --- a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php +++ b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php @@ -93,7 +93,7 @@ class DigikeyProvider implements InfoProviderInterface public function isActive(): bool { //The client ID has to be set and a token has to be available (user clicked connect) - return !empty($this->clientId) && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); + return $this->clientId !== '' && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); } public function searchByKeyword(string $keyword): array @@ -210,7 +210,7 @@ class DigikeyProvider implements InfoProviderInterface $footprint_name = $parameter['Value']; } - if (in_array(trim($parameter['Value']), array('', '-'), true)) { + if (in_array(trim((string) $parameter['Value']), ['', '-'], true)) { continue; } diff --git a/src/Services/InfoProviderSystem/Providers/Element14Provider.php b/src/Services/InfoProviderSystem/Providers/Element14Provider.php index 085c9e50..ad70f9d9 100644 --- a/src/Services/InfoProviderSystem/Providers/Element14Provider.php +++ b/src/Services/InfoProviderSystem/Providers/Element14Provider.php @@ -65,7 +65,7 @@ class Element14Provider implements InfoProviderInterface public function isActive(): bool { - return !empty($this->api_key); + return $this->api_key !== ''; } /** diff --git a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php index b065bed4..cdd85d07 100755 --- a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php +++ b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php @@ -96,7 +96,7 @@ class LCSCProvider implements InfoProviderInterface */ private function getRealDatasheetUrl(?string $url): string { - if (!empty($url) && preg_match("/^https:\/\/(datasheet\.lcsc\.com|www\.lcsc\.com\/datasheet)\/.*(C\d+)\.pdf$/", $url, $matches) > 0) { + if ($url !== null && trim($url) !== '' && preg_match("/^https:\/\/(datasheet\.lcsc\.com|www\.lcsc\.com\/datasheet)\/.*(C\d+)\.pdf$/", $url, $matches) > 0) { $response = $this->lcscClient->request('GET', $url, [ 'headers' => [ 'Referer' => 'https://www.lcsc.com/product-detail/_' . $matches[2] . '.html' @@ -139,7 +139,7 @@ class LCSCProvider implements InfoProviderInterface // LCSC does not display LCSC codes in the search, instead taking you directly to the // detailed product listing. It does so utilizing a product tip field. // If product tip exists and there are no products in the product list try a detail query - if (count($products) === 0 && !($tipProductCode === null)) { + if (count($products) === 0 && $tipProductCode !== null) { $result[] = $this->queryDetail($tipProductCode); } @@ -174,11 +174,11 @@ class LCSCProvider implements InfoProviderInterface { // Get product images in advance $product_images = $this->getProductImages($product['productImages'] ?? null); - $product['productImageUrl'] = $product['productImageUrl'] ?? null; + $product['productImageUrl'] ??= null; // If the product does not have a product image but otherwise has attached images, use the first one. if (count($product_images) > 0) { - $product['productImageUrl'] = $product['productImageUrl'] ?? $product_images[0]->url; + $product['productImageUrl'] ??= $product_images[0]->url; } // LCSC puts HTML in footprints and descriptions sometimes randomly @@ -321,7 +321,7 @@ class LCSCProvider implements InfoProviderInterface foreach ($attributes as $attribute) { //Skip this attribute if it's empty - if (in_array(trim($attribute['paramValueEn']), array('', '-'), true)) { + if (in_array(trim((string) $attribute['paramValueEn']), ['', '-'], true)) { continue; } diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index 1e58285c..e5f937da 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -74,7 +74,7 @@ class MouserProvider implements InfoProviderInterface public function isActive(): bool { - return !empty($this->api_key); + return $this->api_key !== ''; } public function searchByKeyword(string $keyword): array @@ -247,7 +247,7 @@ class MouserProvider implements InfoProviderInterface private function parseDataSheets(?string $sheetUrl, ?string $sheetName): ?array { - if (empty($sheetUrl)) { + if ($sheetUrl === null || $sheetUrl === '' || $sheetUrl === '0') { return null; } $result = []; diff --git a/src/Services/InfoProviderSystem/Providers/OctopartProvider.php b/src/Services/InfoProviderSystem/Providers/OctopartProvider.php index f261e225..e28162ba 100644 --- a/src/Services/InfoProviderSystem/Providers/OctopartProvider.php +++ b/src/Services/InfoProviderSystem/Providers/OctopartProvider.php @@ -183,7 +183,7 @@ class OctopartProvider implements InfoProviderInterface { //The client ID has to be set and a token has to be available (user clicked connect) //return /*!empty($this->clientId) && */ $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); - return !empty($this->clientId) && !empty($this->secret); + return $this->clientId !== '' && $this->secret !== ''; } private function mapLifeCycleStatus(?string $value): ?ManufacturingStatus @@ -243,11 +243,14 @@ class OctopartProvider implements InfoProviderInterface //If we encounter the mass spec, we save it for later if ($spec['attribute']['shortname'] === "weight") { $mass = (float) $spec['siValue']; - } else if ($spec['attribute']['shortname'] === "case_package") { //Package + } elseif ($spec['attribute']['shortname'] === "case_package") { + //Package $package = $spec['value']; - } else if ($spec['attribute']['shortname'] === "numberofpins") { //Pin Count + } elseif ($spec['attribute']['shortname'] === "numberofpins") { + //Pin Count $pinCount = $spec['value']; - } else if ($spec['attribute']['shortname'] === "lifecyclestatus") { //LifeCycleStatus + } elseif ($spec['attribute']['shortname'] === "lifecyclestatus") { + //LifeCycleStatus $mStatus = $this->mapLifeCycleStatus($spec['value']); } @@ -295,7 +298,7 @@ class OctopartProvider implements InfoProviderInterface $category = null; if (!empty($part['category']['name'])) { $category = implode(' -> ', array_map(static fn($c) => $c['name'], $part['category']['ancestors'] ?? [])); - if (!empty($category)) { + if ($category !== '' && $category !== '0') { $category .= ' -> '; } $category .= $part['category']['name']; diff --git a/src/Services/InfoProviderSystem/Providers/TMEClient.php b/src/Services/InfoProviderSystem/Providers/TMEClient.php index df2c7202..0e32e9a6 100644 --- a/src/Services/InfoProviderSystem/Providers/TMEClient.php +++ b/src/Services/InfoProviderSystem/Providers/TMEClient.php @@ -47,7 +47,7 @@ class TMEClient public function isUsable(): bool { - return !($this->token === '' || $this->secret === ''); + return $this->token !== '' && $this->secret !== ''; } diff --git a/src/Services/InfoProviderSystem/Providers/TMEProvider.php b/src/Services/InfoProviderSystem/Providers/TMEProvider.php index 892294f3..61944b7d 100644 --- a/src/Services/InfoProviderSystem/Providers/TMEProvider.php +++ b/src/Services/InfoProviderSystem/Providers/TMEProvider.php @@ -80,7 +80,7 @@ class TMEProvider implements InfoProviderInterface $result[] = new SearchResultDTO( provider_key: $this->getProviderKey(), provider_id: $product['Symbol'], - name: !empty($product['OriginalSymbol']) ? $product['OriginalSymbol'] : $product['Symbol'], + name: empty($product['OriginalSymbol']) ? $product['Symbol'] : $product['OriginalSymbol'], description: $product['Description'], category: $product['Category'], manufacturer: $product['Producer'], @@ -116,7 +116,7 @@ class TMEProvider implements InfoProviderInterface return new PartDetailDTO( provider_key: $this->getProviderKey(), provider_id: $product['Symbol'], - name: !empty($product['OriginalSymbol']) ? $product['OriginalSymbol'] : $product['Symbol'], + name: empty($product['OriginalSymbol']) ? $product['Symbol'] : $product['OriginalSymbol'], description: $product['Description'], category: $product['Category'], manufacturer: $product['Producer'], diff --git a/src/Services/LabelSystem/Barcodes/BarcodeHelper.php b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php index d13da589..c9fe64f3 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeHelper.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php @@ -28,6 +28,7 @@ use Com\Tecnick\Barcode\Barcode; /** * This function is used to generate barcodes of various types using arbitrary (text) content. + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeHelperTest */ class BarcodeHelper { @@ -66,7 +67,7 @@ class BarcodeHelper { $svg = $this->barcodeAsSVG($content, $type); $base64 = $this->dataUri($svg, 'image/svg+xml'); - $alt_text = $alt_text ?? $content; + $alt_text ??= $content; return ''.$alt_text.''; } diff --git a/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php b/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php index b53d3f02..c9750ea3 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php @@ -48,7 +48,7 @@ use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; /** - * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeNormalizerTest + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeScanHelperTest */ final class BarcodeScanHelper { diff --git a/src/Services/LabelSystem/DompdfFactory.php b/src/Services/LabelSystem/DompdfFactory.php index ff30c480..a2c8c3cd 100644 --- a/src/Services/LabelSystem/DompdfFactory.php +++ b/src/Services/LabelSystem/DompdfFactory.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LabelSystem; use Dompdf\Dompdf; @@ -36,10 +38,8 @@ class DompdfFactory implements DompdfFactoryInterface private function createDirectoryIfNotExisting(string $path): void { - if (!is_dir($path)) { - if (!mkdir($concurrentDirectory = $path, 0777, true) && !is_dir($concurrentDirectory)) { - throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); - } + if (!is_dir($path) && (!mkdir($concurrentDirectory = $path, 0777, true) && !is_dir($concurrentDirectory))) { + throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); } } @@ -51,4 +51,4 @@ class DompdfFactory implements DompdfFactoryInterface 'tempDir' => $this->tmpDirectory, ]); } -} \ No newline at end of file +} diff --git a/src/Services/LabelSystem/LabelBarcodeGenerator.php b/src/Services/LabelSystem/LabelBarcodeGenerator.php index f5d950b4..66f74e58 100644 --- a/src/Services/LabelSystem/LabelBarcodeGenerator.php +++ b/src/Services/LabelSystem/LabelBarcodeGenerator.php @@ -49,7 +49,7 @@ use App\Services\LabelSystem\Barcodes\BarcodeHelper; use InvalidArgumentException; /** - * @see \App\Tests\Services\LabelSystem\BarcodeGeneratorTest + * @see \App\Tests\Services\LabelSystem\LabelBarcodeGeneratorTest */ final class LabelBarcodeGenerator { diff --git a/src/Services/LabelSystem/LabelExampleElementsGenerator.php b/src/Services/LabelSystem/LabelExampleElementsGenerator.php index c9cdbb04..199d5f46 100644 --- a/src/Services/LabelSystem/LabelExampleElementsGenerator.php +++ b/src/Services/LabelSystem/LabelExampleElementsGenerator.php @@ -97,7 +97,7 @@ final class LabelExampleElementsGenerator $lot->setDescription('Example Lot'); $lot->setComment('Lot comment'); - $lot->setExpirationDate(new DateTime('+1 days')); + $lot->setExpirationDate(new \DateTimeImmutable('+1 day')); $lot->setStorageLocation($this->getStructuralData(StorageLocation::class)); $lot->setAmount(123); $lot->setOwner($this->getUser()); diff --git a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php index 5a9b2294..ddd4dbf1 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php +++ b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php @@ -81,7 +81,7 @@ final class GlobalProviders implements PlaceholderProviderInterface return 'anonymous'; } - $now = new DateTime(); + $now = new \DateTimeImmutable(); if ('[[DATETIME]]' === $placeholder) { $formatter = IntlDateFormatter::create( diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php index 9d9b3416..0df4d3d7 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php @@ -119,7 +119,7 @@ final class PartProvider implements PlaceholderProviderInterface } if ('[[DESCRIPTION_T]]' === $placeholder) { - return strip_tags($parsedown->line($part->getDescription())); + return strip_tags((string) $parsedown->line($part->getDescription())); } if ('[[COMMENT]]' === $placeholder) { @@ -127,7 +127,7 @@ final class PartProvider implements PlaceholderProviderInterface } if ('[[COMMENT_T]]' === $placeholder) { - return strip_tags($parsedown->line($part->getComment())); + return strip_tags((string) $parsedown->line($part->getComment())); } return null; diff --git a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php index ca8088da..f37f5901 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php @@ -52,7 +52,7 @@ final class StructuralDBElementProvider implements PlaceholderProviderInterface return $label_target->getComment(); } if ('[[COMMENT_T]]' === $placeholder) { - return strip_tags($label_target->getComment()); + return strip_tags((string) $label_target->getComment()); } if ('[[FULL_PATH]]' === $placeholder) { return $label_target->getFullPath(); diff --git a/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php index 8588e133..b316abf2 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php @@ -42,7 +42,6 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; use App\Entity\Contracts\TimeStampableInterface; -use DateTime; use IntlDateFormatter; use Locale; @@ -57,11 +56,11 @@ final class TimestampableElementProvider implements PlaceholderProviderInterface $formatter = new IntlDateFormatter(Locale::getDefault(), IntlDateFormatter::SHORT, IntlDateFormatter::SHORT); if ('[[LAST_MODIFIED]]' === $placeholder) { - return $formatter->format($label_target->getLastModified() ?? new DateTime()); + return $formatter->format($label_target->getLastModified() ?? new \DateTimeImmutable()); } if ('[[CREATION_DATE]]' === $placeholder) { - return $formatter->format($label_target->getAddedDate() ?? new DateTime()); + return $formatter->format($label_target->getAddedDate() ?? new \DateTimeImmutable()); } } diff --git a/src/Services/LogSystem/EventUndoMode.php b/src/Services/LogSystem/EventUndoMode.php index 51ad664e..de30dcfd 100644 --- a/src/Services/LogSystem/EventUndoMode.php +++ b/src/Services/LogSystem/EventUndoMode.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LogSystem; use InvalidArgumentException; diff --git a/src/Services/LogSystem/LogDataFormatter.php b/src/Services/LogSystem/LogDataFormatter.php index c5e82a47..af54c60c 100644 --- a/src/Services/LogSystem/LogDataFormatter.php +++ b/src/Services/LogSystem/LogDataFormatter.php @@ -136,7 +136,7 @@ class LogDataFormatter } try { - $dateTime = new \DateTime($date, new \DateTimeZone($timezone)); + $dateTime = new \DateTimeImmutable($date, new \DateTimeZone($timezone)); } catch (\Exception) { return 'unknown DateTime format'; } diff --git a/src/Services/LogSystem/LogEntryExtraFormatter.php b/src/Services/LogSystem/LogEntryExtraFormatter.php index fdfd72c8..ae2a5eba 100644 --- a/src/Services/LogSystem/LogEntryExtraFormatter.php +++ b/src/Services/LogSystem/LogEntryExtraFormatter.php @@ -135,7 +135,7 @@ class LogEntryExtraFormatter } if ($context instanceof LogWithCommentInterface && $context->hasComment()) { - $array[] = htmlspecialchars($context->getComment()); + $array[] = htmlspecialchars((string) $context->getComment()); } if ($context instanceof ElementCreatedLogEntry && $context->hasCreationInstockValue()) { @@ -193,7 +193,7 @@ class LogEntryExtraFormatter htmlspecialchars($this->elementTypeNameGenerator->getLocalizedTypeLabel(PartLot::class)) .' ' . $context->getMoveToTargetID(); } - if ($context->getActionTimestamp()) { + if ($context->getActionTimestamp() !== null) { $formatter = new \IntlDateFormatter($this->translator->getLocale(), \IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT); $array['log.part_stock_changed.timestamp'] = $formatter->format($context->getActionTimestamp()); } diff --git a/src/Services/LogSystem/TimeTravel.php b/src/Services/LogSystem/TimeTravel.php index 400e85f5..37209415 100644 --- a/src/Services/LogSystem/TimeTravel.php +++ b/src/Services/LogSystem/TimeTravel.php @@ -34,11 +34,13 @@ use App\Repository\LogEntryRepository; use Brick\Math\BigDecimal; use DateTime; use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Exception; use InvalidArgumentException; +use PHPUnit\Util\Type; use ReflectionClass; class TimeTravel @@ -136,16 +138,16 @@ class TimeTravel //Revert many-to-one association (one element in property) if ( - ClassMetadataInfo::MANY_TO_ONE === $mapping['type'] - || ClassMetadataInfo::ONE_TO_ONE === $mapping['type'] + ClassMetadata::MANY_TO_ONE === $mapping['type'] + || ClassMetadata::ONE_TO_ONE === $mapping['type'] ) { $target_element = $this->getField($element, $field); if (null !== $target_element && $element->getLastModified() > $timestamp) { $this->revertEntityToTimestamp($target_element, $timestamp, $reverted_elements); } } elseif ( //Revert *_TO_MANY associations (collection properties) - (ClassMetadataInfo::MANY_TO_MANY === $mapping['type'] - || ClassMetadataInfo::ONE_TO_MANY === $mapping['type']) + (ClassMetadata::MANY_TO_MANY === $mapping['type'] + || ClassMetadata::ONE_TO_MANY === $mapping['type']) && !$mapping['isOwningSide'] ) { $target_elements = $this->getField($element, $field); @@ -171,17 +173,26 @@ class TimeTravel /** * This function decodes the array which is created during the json_encode of a datetime object and returns a DateTime object. * @param array $input - * @return DateTime + * @return \DateTimeInterface * @throws Exception */ - private function dateTimeDecode(?array $input): ?\DateTime + private function dateTimeDecode(?array $input, string $doctrineType): ?\DateTimeInterface { //Allow null values if ($input === null) { return null; } - return new \DateTime($input['date'], new \DateTimeZone($input['timezone'])); + //Mutable types + if (in_array($doctrineType, [Types::DATETIME_MUTABLE, Types::DATE_MUTABLE], true)) { + return new \DateTime($input['date'], new \DateTimeZone($input['timezone'])); + } + //Immutable types + if (in_array($doctrineType, [Types::DATETIME_IMMUTABLE, Types::DATE_IMMUTABLE], true)) { + return new \DateTimeImmutable($input['date'], new \DateTimeZone($input['timezone'])); + } + + throw new InvalidArgumentException('The given doctrine type is not a datetime type!'); } /** @@ -208,8 +219,10 @@ class TimeTravel $data = BigDecimal::of($data); } - if (!$data instanceof DateTime && ('datetime' === $metadata->getFieldMapping($field)['type'])) { - $data = $this->dateTimeDecode($data); + if (!$data instanceof \DateTimeInterface + && (in_array($metadata->getFieldMapping($field)['type'], + [Types::DATETIME_IMMUTABLE, Types::DATETIME_IMMUTABLE, Types::DATE_MUTABLE, Types::DATETIME_IMMUTABLE], true))) { + $data = $this->dateTimeDecode($data, $metadata->getFieldMapping($field)['type']); } $this->setField($element, $field, $data); @@ -219,7 +232,7 @@ class TimeTravel $target_class = $mapping['targetEntity']; //Try to extract the old ID: if (is_array($data) && isset($data['@id'])) { - $entity = $this->em->getPartialReference($target_class, $data['@id']); + $entity = $this->em->getReference($target_class, $data['@id']); $this->setField($element, $field, $entity); } } diff --git a/src/Services/Misc/DBInfoHelper.php b/src/Services/Misc/DBInfoHelper.php index 65596b29..620da702 100644 --- a/src/Services/Misc/DBInfoHelper.php +++ b/src/Services/Misc/DBInfoHelper.php @@ -25,7 +25,8 @@ namespace App\Services\Misc; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\ORM\EntityManagerInterface; /** @@ -50,10 +51,14 @@ class DBInfoHelper return 'mysql'; } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return 'sqlite'; } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return 'postgresql'; + } + return null; } @@ -67,10 +72,14 @@ class DBInfoHelper return $this->connection->fetchOne('SELECT VERSION()'); } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return $this->connection->fetchOne('SELECT sqlite_version()'); } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return $this->connection->fetchOne('SELECT version()'); + } + return null; } @@ -89,7 +98,7 @@ class DBInfoHelper } } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { try { return (int) $this->connection->fetchOne('SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();'); } catch (Exception) { @@ -97,6 +106,14 @@ class DBInfoHelper } } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + try { + return (int) $this->connection->fetchOne('SELECT pg_database_size(current_database())'); + } catch (Exception) { + return null; + } + } + return null; } @@ -121,9 +138,17 @@ class DBInfoHelper } } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return 'sqlite'; } + + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + try { + return $this->connection->fetchOne('SELECT current_user'); + } catch (Exception) { + return null; + } + } return null; } diff --git a/src/Services/OAuth/OAuthTokenManager.php b/src/Services/OAuth/OAuthTokenManager.php index f67e9c6b..9c22503b 100644 --- a/src/Services/OAuth/OAuthTokenManager.php +++ b/src/Services/OAuth/OAuthTokenManager.php @@ -47,11 +47,10 @@ final class OAuthTokenManager $tokenEntity = $this->entityManager->getRepository(OAuthToken::class)->findOneBy(['name' => $app_name]); //If the token was already existing, we just replace it with the new one - if ($tokenEntity) { + if ($tokenEntity !== null) { $tokenEntity->replaceWithNewToken($token); - //@phpstan-ignore-next-line - $this->entityManager->flush($tokenEntity); + $this->entityManager->flush(); //We are done return $tokenEntity; @@ -60,8 +59,8 @@ final class OAuthTokenManager //If the token was not existing, we create a new one $tokenEntity = OAuthToken::fromAccessToken($token, $app_name); $this->entityManager->persist($tokenEntity); - //@phpstan-ignore-next-line - $this->entityManager->flush($tokenEntity); + + $this->entityManager->flush(); return $tokenEntity; } @@ -97,7 +96,7 @@ final class OAuthTokenManager { $token = $this->getToken($app_name); - if (!$token) { + if ($token === null) { throw new \RuntimeException('No token was saved yet for '.$app_name); } @@ -113,9 +112,7 @@ final class OAuthTokenManager //Persist the token $token->replaceWithNewToken($new_token); - - //@phpstan-ignore-next-line - $this->entityManager->flush($token); + $this->entityManager->flush(); return $token; } @@ -131,7 +128,7 @@ final class OAuthTokenManager $token = $this->getToken($app_name); //If the token is not existing, we return null - if (!$token) { + if ($token === null) { return null; } diff --git a/src/Services/System/BannerHelper.php b/src/Services/System/BannerHelper.php index c0dbf600..1b6da52a 100644 --- a/src/Services/System/BannerHelper.php +++ b/src/Services/System/BannerHelper.php @@ -43,7 +43,7 @@ class BannerHelper if (!is_string($banner)) { throw new \RuntimeException('The parameter "partdb.banner" must be a string.'); } - if (empty($banner)) { + if ($banner === '') { $banner_path = $this->project_dir .DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md'; diff --git a/src/Services/Trees/SidebarTreeUpdater.php b/src/Services/Trees/SidebarTreeUpdater.php index 396410db..c0f93b1f 100644 --- a/src/Services/Trees/SidebarTreeUpdater.php +++ b/src/Services/Trees/SidebarTreeUpdater.php @@ -49,7 +49,7 @@ final class SidebarTreeUpdater //This tag and therfore this whole cache gets cleared by TreeCacheInvalidationListener when a structural element is changed $item->tag('sidebar_tree_update'); - return new \DateTime(); + return new \DateTimeImmutable(); }); } } diff --git a/src/Services/UserSystem/PasswordResetManager.php b/src/Services/UserSystem/PasswordResetManager.php index 31dbdf90..1a78cb60 100644 --- a/src/Services/UserSystem/PasswordResetManager.php +++ b/src/Services/UserSystem/PasswordResetManager.php @@ -59,8 +59,7 @@ class PasswordResetManager $user->setPwResetToken($this->passwordEncoder->hash($unencrypted_token)); //Determine the expiration datetime of - $expiration_date = new DateTime(); - $expiration_date->add(date_interval_create_from_date_string('1 day')); + $expiration_date = new \DateTimeImmutable("+1 day"); $user->setPwResetExpires($expiration_date); if ($user->getEmail() !== null && $user->getEmail() !== '') { @@ -105,7 +104,7 @@ class PasswordResetManager } //Check if token is expired yet - if ($user->getPwResetExpires() < new DateTime()) { + if ($user->getPwResetExpires() < new \DateTimeImmutable()) { return false; } @@ -119,7 +118,7 @@ class PasswordResetManager //Remove token $user->setPwResetToken(null); - $user->setPwResetExpires(new DateTime()); + $user->setPwResetExpires(new \DateTimeImmutable()); //Save to DB $this->em->flush(); diff --git a/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php b/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php index 8ec411f1..05e5ed4c 100644 --- a/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php +++ b/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\UserSystem\TFA; use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; @@ -67,4 +69,4 @@ class DecoratedGoogleAuthenticator implements GoogleAuthenticatorInterface { return $this->inner->generateSecret(); } -} \ No newline at end of file +} diff --git a/src/Services/UserSystem/VoterHelper.php b/src/Services/UserSystem/VoterHelper.php index c13cf22a..644351f4 100644 --- a/src/Services/UserSystem/VoterHelper.php +++ b/src/Services/UserSystem/VoterHelper.php @@ -29,6 +29,9 @@ use App\Security\ApiTokenAuthenticatedToken; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +/** + * @see \App\Tests\Services\UserSystem\VoterHelperTest + */ final class VoterHelper { private readonly UserRepository $userRepository; diff --git a/src/State/PartDBInfoProvider.php b/src/State/PartDBInfoProvider.php index 7d09d721..c6760ede 100644 --- a/src/State/PartDBInfoProvider.php +++ b/src/State/PartDBInfoProvider.php @@ -1,5 +1,7 @@ providerRegistry->getProviderByKey($key); - } catch (\InvalidArgumentException $exception) { + } catch (\InvalidArgumentException) { return null; } } @@ -65,7 +65,7 @@ class InfoProviderExtension extends AbstractExtension { try { return $this->providerRegistry->getProviderByKey($key)->getProviderInfo()['name']; - } catch (\InvalidArgumentException $exception) { + } catch (\InvalidArgumentException) { return null; } } diff --git a/src/Twig/Sandbox/SandboxedLabelExtension.php b/src/Twig/Sandbox/SandboxedLabelExtension.php index 540f204f..59fb0af0 100644 --- a/src/Twig/Sandbox/SandboxedLabelExtension.php +++ b/src/Twig/Sandbox/SandboxedLabelExtension.php @@ -35,7 +35,7 @@ class SandboxedLabelExtension extends AbstractExtension } - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('placeholder', fn(string $text, object $label_target) => $this->labelTextReplacer->handlePlaceholderOrReturnNull($text, $label_target)), diff --git a/src/Twig/TwigCoreExtension.php b/src/Twig/TwigCoreExtension.php index 94b067d5..352e09d3 100644 --- a/src/Twig/TwigCoreExtension.php +++ b/src/Twig/TwigCoreExtension.php @@ -42,7 +42,7 @@ final class TwigCoreExtension extends AbstractExtension { return [ /* Returns the enum cases as values */ - new TwigFunction('enum_cases', [$this, 'getEnumCases']), + new TwigFunction('enum_cases', $this->getEnumCases(...)), ]; } diff --git a/src/Twig/UserExtension.php b/src/Twig/UserExtension.php index 93ea57be..5045257a 100644 --- a/src/Twig/UserExtension.php +++ b/src/Twig/UserExtension.php @@ -127,7 +127,7 @@ final class UserExtension extends AbstractExtension public function removeLocaleFromPath(string $path): string { //Ensure the path has the correct format - if (!preg_match('/^\/\w{2}\//', $path)) { + if (!preg_match('/^\/\w{2}(?:_\w{2})?\//', $path)) { throw new \InvalidArgumentException('The given path is not a localized path!'); } diff --git a/src/Validator/Constraints/NoneOfItsChildrenValidator.php b/src/Validator/Constraints/NoneOfItsChildrenValidator.php index 2220e664..2be5f16b 100644 --- a/src/Validator/Constraints/NoneOfItsChildrenValidator.php +++ b/src/Validator/Constraints/NoneOfItsChildrenValidator.php @@ -30,6 +30,7 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; /** * The validator for the NoneOfItsChildren annotation. + * @see \App\Tests\Validator\Constraints\NoneOfItsChildrenValidatorTest */ class NoneOfItsChildrenValidator extends ConstraintValidator { diff --git a/src/Validator/Constraints/SelectableValidator.php b/src/Validator/Constraints/SelectableValidator.php index dfe8eba8..013a3964 100644 --- a/src/Validator/Constraints/SelectableValidator.php +++ b/src/Validator/Constraints/SelectableValidator.php @@ -30,6 +30,7 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; /** * The validator for the Selectable constraint. + * @see \App\Tests\Validator\Constraints\SelectableValidatorTest */ class SelectableValidator extends ConstraintValidator { diff --git a/src/Validator/Constraints/UniqueObjectCollection.php b/src/Validator/Constraints/UniqueObjectCollection.php index d432892c..c71fcc5d 100644 --- a/src/Validator/Constraints/UniqueObjectCollection.php +++ b/src/Validator/Constraints/UniqueObjectCollection.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints; use InvalidArgumentException; @@ -59,4 +61,4 @@ class UniqueObjectCollection extends Constraint throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); } } -} \ No newline at end of file +} diff --git a/src/Validator/Constraints/UniqueObjectCollectionValidator.php b/src/Validator/Constraints/UniqueObjectCollectionValidator.php index 244b6c9c..b80889a4 100644 --- a/src/Validator/Constraints/UniqueObjectCollectionValidator.php +++ b/src/Validator/Constraints/UniqueObjectCollectionValidator.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints; use App\Validator\UniqueValidatableInterface; @@ -26,6 +28,9 @@ use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; +/** + * @see \App\Tests\Validator\Constraints\UniqueObjectCollectionValidatorTest + */ class UniqueObjectCollectionValidator extends ConstraintValidator { @@ -106,4 +111,4 @@ class UniqueObjectCollectionValidator extends ConstraintValidator return $output; } -} \ No newline at end of file +} diff --git a/src/Validator/Constraints/UrlOrBuiltinValidator.php b/src/Validator/Constraints/UrlOrBuiltinValidator.php index af498d2a..71407a6a 100644 --- a/src/Validator/Constraints/UrlOrBuiltinValidator.php +++ b/src/Validator/Constraints/UrlOrBuiltinValidator.php @@ -34,6 +34,7 @@ use function is_object; * The validator for UrlOrBuiltin. * It checks if the value is either a builtin ressource or a valid url. * In both cases it is not checked, if the ressource is really existing. + * @see \App\Tests\Validator\Constraints\UrlOrBuiltinValidatorTest */ class UrlOrBuiltinValidator extends UrlValidator { diff --git a/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php b/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php index f7f9e26e..25afe57b 100644 --- a/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php +++ b/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php @@ -33,6 +33,9 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; use function is_string; use function strlen; +/** + * @see \App\Tests\Validator\Constraints\ValidGoogleAuthCodeValidatorTest + */ class ValidGoogleAuthCodeValidator extends ConstraintValidator { public function __construct(private readonly GoogleAuthenticatorInterface $googleAuthenticator, private readonly Security $security) diff --git a/src/Validator/Constraints/ValidPermissionValidator.php b/src/Validator/Constraints/ValidPermissionValidator.php index b1a5b623..afb7721b 100644 --- a/src/Validator/Constraints/ValidPermissionValidator.php +++ b/src/Validator/Constraints/ValidPermissionValidator.php @@ -63,11 +63,11 @@ class ValidPermissionValidator extends ConstraintValidator if ($changed) { //Check if this was called in context of UserController $request = $this->requestStack->getMainRequest(); - if (!$request) { + if ($request === null) { return; } //Determine the controller class (the part before the ::) - $controller_class = explode('::', $request->attributes->get('_controller'))[0]; + $controller_class = explode('::', (string) $request->attributes->get('_controller'))[0]; if (in_array($controller_class, [UserController::class, GroupController::class], true)) { /** @var Session $session */ diff --git a/src/Validator/Constraints/ValidThemeValidator.php b/src/Validator/Constraints/ValidThemeValidator.php index 5d222934..713be9a5 100644 --- a/src/Validator/Constraints/ValidThemeValidator.php +++ b/src/Validator/Constraints/ValidThemeValidator.php @@ -26,6 +26,9 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; +/** + * @see \App\Tests\Validator\Constraints\ValidThemeValidatorTest + */ class ValidThemeValidator extends ConstraintValidator { public function __construct(private readonly array $available_themes) diff --git a/src/Validator/UniqueValidatableInterface.php b/src/Validator/UniqueValidatableInterface.php index 97e3a0b9..3d954490 100644 --- a/src/Validator/UniqueValidatableInterface.php +++ b/src/Validator/UniqueValidatableInterface.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator; interface UniqueValidatableInterface @@ -29,4 +31,4 @@ interface UniqueValidatableInterface * @return array An array of the form ['field1' => 'value1', 'field2' => 'value2', ...] */ public function getComparableFields(): array; -} \ No newline at end of file +} diff --git a/symfony.lock b/symfony.lock index 1e04eedd..25ef4331 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,10 +1,4 @@ { - "amphp/amp": { - "version": "v2.2.1" - }, - "amphp/byte-stream": { - "version": "v1.6.1" - }, "api-platform/core": { "version": "3.2", "recipe": { @@ -34,15 +28,6 @@ "composer/package-versions-deprecated": { "version": "1.11.99.4" }, - "composer/pcre": { - "version": "1.0.0" - }, - "composer/semver": { - "version": "1.5.0" - }, - "composer/xdebug-handler": { - "version": "1.3.3" - }, "dama/doctrine-test-bundle": { "version": "8.0", "recipe": { @@ -55,9 +40,6 @@ "./config/packages/dama_doctrine_test_bundle.yaml" ] }, - "dnoegel/php-xdg-base-dir": { - "version": "v0.1.1" - }, "doctrine/annotations": { "version": "1.14", "recipe": { @@ -74,9 +56,6 @@ "doctrine/collections": { "version": "v1.5.0" }, - "doctrine/common": { - "version": "v2.10.0" - }, "doctrine/data-fixtures": { "version": "v1.3.2" }, @@ -161,12 +140,6 @@ "erusev/parsedown": { "version": "1.7.4" }, - "felixfbecker/advanced-json-rpc": { - "version": "v3.0.4" - }, - "felixfbecker/language-server-protocol": { - "version": "v1.4.0" - }, "florianv/exchanger": { "version": "1.4.1" }, @@ -258,9 +231,6 @@ "./config/packages/nelmio_security.yaml" ] }, - "netresearch/jsonmapper": { - "version": "v1.6.0" - }, "nikic/php-parser": { "version": "v4.2.1" }, @@ -389,9 +359,6 @@ "./tests/bootstrap.php" ] }, - "psalm/plugin-symfony": { - "version": "v1.2.1" - }, "psr/cache": { "version": "1.0.1" }, @@ -900,9 +867,6 @@ "ua-parser/uap-php": { "version": "v3.9.8" }, - "vimeo/psalm": { - "version": "3.5.1" - }, "web-auth/webauthn-symfony-bundle": { "version": "4.7", "recipe": { diff --git a/templates/base.html.twig b/templates/base.html.twig index 425594c0..020047df 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -1,5 +1,7 @@ - + diff --git a/tests/API/APIDocsAvailabilityTest.php b/tests/API/APIDocsAvailabilityTest.php index bc3b6119..d2ed7fa4 100644 --- a/tests/API/APIDocsAvailabilityTest.php +++ b/tests/API/APIDocsAvailabilityTest.php @@ -56,13 +56,11 @@ class APIDocsAvailabilityTest extends WebTestCase self::assertResponseStatusCodeSame(403); } - public static function urlProvider(): array + public static function urlProvider(): \Iterator { - return [ - ['/api'], - ['/api/docs.html'], - ['/api/docs.json'], - ['/api/docs.jsonld'], - ]; + yield ['/api']; + yield ['/api/docs.html']; + yield ['/api/docs.json']; + yield ['/api/docs.jsonld']; } } \ No newline at end of file diff --git a/tests/API/APITokenAuthenticationTest.php b/tests/API/APITokenAuthenticationTest.php index fa251839..a78b0594 100644 --- a/tests/API/APITokenAuthenticationTest.php +++ b/tests/API/APITokenAuthenticationTest.php @@ -34,7 +34,7 @@ class APITokenAuthenticationTest extends ApiTestCase self::ensureKernelShutdown(); $client = static::createClient(); $client->request('GET', '/api/parts'); - self::assertResponseStatusCodeSame(401); + $this->assertResponseStatusCodeSame(401); } public function testExpiredToken(): void @@ -42,7 +42,7 @@ class APITokenAuthenticationTest extends ApiTestCase self::ensureKernelShutdown(); $client = $this->createClientWithCredentials(APITokenFixtures::TOKEN_EXPIRED); $client->request('GET', '/api/parts'); - self::assertResponseStatusCodeSame(401); + $this->assertResponseStatusCodeSame(401); } public function testReadOnlyToken(): void @@ -52,14 +52,14 @@ class APITokenAuthenticationTest extends ApiTestCase //Read should be possible $client->request('GET', '/api/parts'); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); //Trying to list all users and create a new footprint should fail $client->request('GET', '/api/users'); - self::assertResponseStatusCodeSame(403); + $this->assertResponseStatusCodeSame(403); $client->request('POST', '/api/footprints', ['json' => ['name' => 'post test']]); - self::assertResponseStatusCodeSame(403); + $this->assertResponseStatusCodeSame(403); } public function testEditToken(): void @@ -69,14 +69,14 @@ class APITokenAuthenticationTest extends ApiTestCase //Read should be possible $client->request('GET', '/api/parts'); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); //Trying to list all users $client->request('GET', '/api/users'); - self::assertResponseStatusCodeSame(403); + $this->assertResponseStatusCodeSame(403); $client->request('POST', '/api/footprints', ['json' => ['name' => 'post test']]); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); } public function testAdminToken(): void @@ -86,14 +86,14 @@ class APITokenAuthenticationTest extends ApiTestCase //Read should be possible $client->request('GET', '/api/parts'); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); //Trying to list all users $client->request('GET', '/api/users'); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); $client->request('POST', '/api/footprints', ['json' => ['name' => 'post test']]); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); } public function testWithAuthorizationToken(): void @@ -104,14 +104,14 @@ class APITokenAuthenticationTest extends ApiTestCase //Read should be possible $client->request('GET', '/api/parts'); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); //Trying to list all users $client->request('GET', '/api/users'); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); $client->request('POST', '/api/footprints', ['json' => ['name' => 'post test']]); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); } protected function createClientWithCredentials(string $token): Client diff --git a/tests/API/Endpoints/ApiTokenEnpointTest.php b/tests/API/Endpoints/ApiTokenEnpointTest.php index 1050eb1c..99340182 100644 --- a/tests/API/Endpoints/ApiTokenEnpointTest.php +++ b/tests/API/Endpoints/ApiTokenEnpointTest.php @@ -30,9 +30,9 @@ class ApiTokenEnpointTest extends AuthenticatedApiTestCase public function testGetCurrentToken(): void { $response = self::createAuthenticatedClient()->request('GET', '/api/tokens/current'); - self::assertResponseIsSuccessful(); + $this->assertResponseIsSuccessful(); - self::assertJsonContains([ + $this->assertJsonContains([ 'name' => 'admin', 'level' => 3, ]); diff --git a/tests/Controller/AdminPages/AbstractAdminControllerTest.php b/tests/Controller/AdminPages/AbstractAdminControllerTest.php index ef0195c6..af3aefee 100644 --- a/tests/Controller/AdminPages/AbstractAdminControllerTest.php +++ b/tests/Controller/AdminPages/AbstractAdminControllerTest.php @@ -34,14 +34,12 @@ abstract class AbstractAdminControllerTest extends WebTestCase protected static string $base_path = 'not_valid'; protected static string $entity_class = 'not valid'; - public function readDataProvider(): array + public function readDataProvider(): \Iterator { - return [ - ['noread', false], - ['anonymous', true], - ['user', true], - ['admin', true], - ]; + yield ['noread', false]; + yield ['anonymous', true]; + yield ['user', true]; + yield ['admin', true]; } /** @@ -98,14 +96,12 @@ abstract class AbstractAdminControllerTest extends WebTestCase $this->assertSame($read, !$client->getResponse()->isForbidden(), 'Permission Checking not working!'); } - public function deleteDataProvider(): array + public function deleteDataProvider(): \Iterator { - return [ - ['noread', false], - ['anonymous', false], - ['user', true], - ['admin', true], - ]; + yield ['noread', false]; + yield ['anonymous', false]; + yield ['user', true]; + yield ['admin', true]; } /** diff --git a/tests/Controller/RedirectControllerTest.php b/tests/Controller/RedirectControllerTest.php index d7df73cc..029c93f5 100644 --- a/tests/Controller/RedirectControllerTest.php +++ b/tests/Controller/RedirectControllerTest.php @@ -50,18 +50,16 @@ class RedirectControllerTest extends WebTestCase $this->userRepo = $this->em->getRepository(User::class); } - public function urlMatchDataProvider(): array + public function urlMatchDataProvider(): \Iterator { - return [ - ['/', true], - ['/part/2/info', true], - ['/part/de/2', true], - ['/part/en/', true], - ['/de/', false], - ['/de_DE/', false], - ['/en/', false], - ['/en_US/', false], - ]; + yield ['/', true]; + yield ['/part/2/info', true]; + yield ['/part/de/2', true]; + yield ['/part/en/', true]; + yield ['/de/', false]; + yield ['/de_DE/', false]; + yield ['/en/', false]; + yield ['/en_US/', false]; } /** @@ -81,22 +79,20 @@ class RedirectControllerTest extends WebTestCase $this->assertSame($expect_redirect, $response->isRedirect()); } - public function urlAddLocaleDataProvider(): array + public function urlAddLocaleDataProvider(): \Iterator { - return [ - //User locale, original target, redirect target - ['de', '/', '/de/'], - ['de', '/part/3', '/de/part/3'], - ['en', '/', '/en/'], - ['en', '/category/new', '/en/category/new'], - ['en_US', '/part/3', '/en_US/part/3'], - //Without an explicit set value, the user should be redirected to english version - [null, '/', '/en/'], - ['en_US', '/part/3', '/en_US/part/3'], - //Test that query parameters work - ['de', '/dialog?target_id=133&target_type=part', '/de/dialog?target_id=133&target_type=part'], - ['en', '/dialog?storelocation=1', '/en/dialog?storelocation=1'], - ]; + //User locale, original target, redirect target + yield ['de', '/', '/de/']; + yield ['de', '/part/3', '/de/part/3']; + yield ['en', '/', '/en/']; + yield ['en', '/category/new', '/en/category/new']; + yield ['en_US', '/part/3', '/en_US/part/3']; + //Without an explicit set value, the user should be redirected to english version + yield [null, '/', '/en/']; + yield ['en_US', '/part/3', '/en_US/part/3']; + //Test that query parameters work + yield ['de', '/dialog?target_id=133&target_type=part', '/de/dialog?target_id=133&target_type=part']; + yield ['en', '/dialog?storelocation=1', '/en/dialog?storelocation=1']; } /** diff --git a/tests/Controller/ScanControllerTest.php b/tests/Controller/ScanControllerTest.php index 5226e69b..98992e09 100644 --- a/tests/Controller/ScanControllerTest.php +++ b/tests/Controller/ScanControllerTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Controller; use Symfony\Bundle\FrameworkBundle\KernelBrowser; @@ -49,4 +51,4 @@ class ScanControllerTest extends WebTestCase $this->client->request('GET', '/scan/part/1'); $this->assertResponseRedirects('/en/part/1'); } -} \ No newline at end of file +} diff --git a/tests/DatatablesAvailabilityTest.php b/tests/DatatablesAvailabilityTest.php index 3cb34d9b..5af04627 100644 --- a/tests/DatatablesAvailabilityTest.php +++ b/tests/DatatablesAvailabilityTest.php @@ -48,7 +48,7 @@ class DatatablesAvailabilityTest extends WebTestCase /** * @dataProvider urlProvider */ - public function testDataTable(string $url): void + public function testDataTable(string $url, ?array $ordering = null): void { //We have localized routes $url = '/en'.$url; @@ -68,7 +68,14 @@ class DatatablesAvailabilityTest extends WebTestCase 'PHP_AUTH_PW' => 'test', ]); $client->catchExceptions(false); - $client->request('POST', $url, ['_dt' => 'dt']); + + $post = ['_dt' => 'dt']; + + if ($ordering) { + $post['order'] = $ordering; + } + + $client->request('POST', $url, $post); $this->assertTrue($client->getResponse()->isSuccessful()); $this->assertJson($client->getResponse()->getContent()); } @@ -92,5 +99,18 @@ class DatatablesAvailabilityTest extends WebTestCase //Test using filters yield ['/category/1/parts?part_filter%5Bname%5D%5Boperator%5D=%3D&part_filter%5Bname%5D%5Bvalue%5D=BC547&part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Btags%5D%5Boperator%5D=ANY&part_filter%5Btags%5D%5Bvalue%5D=Test&part_filter%5Bsubmit%5D=']; yield ['/category/1/parts?part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Bstorelocation%5D%5Boperator%5D=%3D&part_filter%5Bstorelocation%5D%5Bvalue%5D=1&part_filter%5BattachmentsCount%5D%5Boperator%5D=%3D&part_filter%5BattachmentsCount%5D%5Bvalue1%5D=3&part_filter%5Bsubmit%5D=']; + //Filter over total amount + yield ['/parts?part_filter%5BamountSum%5D%5Boperator%5D=>&part_filter%5BamountSum%5D%5Bvalue1%5D=1&part_filter%5Bsubmit%5D=']; + //Less than desired filter: + yield ['/parts?part_filter%5BlessThanDesired%5D%5Bvalue%5D=true&part_filter%5Bsubmit%5D=']; + + //Test regex search + yield ['/parts/search?category=1&comment=1&description=1&ipn=1&keyword=test&mpn=1&name=1&ordernr=1®ex=1&storelocation=1&tags=1']; + } + + public function testOrdering(): void + { + //Amount ordering (which uses the dynamic amount calculation) + $this->testDataTable('/parts', [['column' => 9, 'dir' => 'asc']]); } } diff --git a/tests/Doctrine/SQLiteRegexMiddlewareTest.php b/tests/Doctrine/SQLiteRegexMiddlewareTest.php index 25358705..01abb16e 100644 --- a/tests/Doctrine/SQLiteRegexMiddlewareTest.php +++ b/tests/Doctrine/SQLiteRegexMiddlewareTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Doctrine; use App\Doctrine\Middleware\SQLiteRegexExtensionMiddlewareDriver; diff --git a/tests/Entity/Attachments/AttachmentTest.php b/tests/Entity/Attachments/AttachmentTest.php index e775f32f..a2179e53 100644 --- a/tests/Entity/Attachments/AttachmentTest.php +++ b/tests/Entity/Attachments/AttachmentTest.php @@ -72,22 +72,20 @@ class AttachmentTest extends TestCase $this->assertEmpty($attachment->getFilename()); } - public function subClassesDataProvider(): array + public function subClassesDataProvider(): \Iterator { - return [ - [AttachmentTypeAttachment::class, AttachmentType::class], - [CategoryAttachment::class, Category::class], - [CurrencyAttachment::class, Currency::class], - [ProjectAttachment::class, Project::class], - [FootprintAttachment::class, Footprint::class], - [GroupAttachment::class, Group::class], - [ManufacturerAttachment::class, Manufacturer::class], - [MeasurementUnitAttachment::class, MeasurementUnit::class], - [PartAttachment::class, Part::class], - [StorageLocationAttachment::class, StorageLocation::class], - [SupplierAttachment::class, Supplier::class], - [UserAttachment::class, User::class], - ]; + yield [AttachmentTypeAttachment::class, AttachmentType::class]; + yield [CategoryAttachment::class, Category::class]; + yield [CurrencyAttachment::class, Currency::class]; + yield [ProjectAttachment::class, Project::class]; + yield [FootprintAttachment::class, Footprint::class]; + yield [GroupAttachment::class, Group::class]; + yield [ManufacturerAttachment::class, Manufacturer::class]; + yield [MeasurementUnitAttachment::class, MeasurementUnit::class]; + yield [PartAttachment::class, Part::class]; + yield [StorageLocationAttachment::class, StorageLocation::class]; + yield [SupplierAttachment::class, Supplier::class]; + yield [UserAttachment::class, User::class]; } /** @@ -117,27 +115,21 @@ class AttachmentTest extends TestCase /** @var Attachment $attachment */ $attachment = new $attachment_class(); - if (Project::class !== $allowed_class) { - $element = new Project(); - } else { - $element = new Category(); - } + $element = Project::class !== $allowed_class ? new Project() : new Category(); $attachment->setElement($element); } - public function externalDataProvider(): array + public function externalDataProvider(): \Iterator { - return [ - ['', false], - ['%MEDIA%/foo/bar.txt', false], - ['%BASE%/foo/bar.jpg', false], - ['%FOOTPRINTS%/foo/bar.jpg', false], - ['%FOOTPRINTS3D%/foo/bar.jpg', false], - ['%SECURE%/test.txt', false], - ['%test%/foo/bar.ghp', true], - ['foo%MEDIA%/foo.jpg', true], - ['foo%MEDIA%/%BASE%foo.jpg', true], - ]; + yield ['', false]; + yield ['%MEDIA%/foo/bar.txt', false]; + yield ['%BASE%/foo/bar.jpg', false]; + yield ['%FOOTPRINTS%/foo/bar.jpg', false]; + yield ['%FOOTPRINTS3D%/foo/bar.jpg', false]; + yield ['%SECURE%/test.txt', false]; + yield ['%test%/foo/bar.ghp', true]; + yield ['foo%MEDIA%/foo.jpg', true]; + yield ['foo%MEDIA%/%BASE%foo.jpg', true]; } /** @@ -150,20 +142,18 @@ class AttachmentTest extends TestCase $this->assertSame($expected, $attachment->isExternal()); } - public function extensionDataProvider(): array + public function extensionDataProvider(): \Iterator { - return [ - ['%MEDIA%/foo/bar.txt', null, 'txt'], - ['%MEDIA%/foo/bar.JPeg', null, 'jpeg'], - ['%MEDIA%/foo/bar.JPeg', 'test.txt', 'txt'], - ['%MEDIA%/foo/bar', null, ''], - ['%MEDIA%/foo.bar', 'bar', ''], - ['http://google.de', null, null], - ['https://foo.bar', null, null], - ['https://foo.bar/test.jpeg', null, null], - ['test', null, null], - ['test.txt', null, null], - ]; + yield ['%MEDIA%/foo/bar.txt', null, 'txt']; + yield ['%MEDIA%/foo/bar.JPeg', null, 'jpeg']; + yield ['%MEDIA%/foo/bar.JPeg', 'test.txt', 'txt']; + yield ['%MEDIA%/foo/bar', null, '']; + yield ['%MEDIA%/foo.bar', 'bar', '']; + yield ['http://google.de', null, null]; + yield ['https://foo.bar', null, null]; + yield ['https://foo.bar/test.jpeg', null, null]; + yield ['test', null, null]; + yield ['test.txt', null, null]; } /** @@ -177,21 +167,19 @@ class AttachmentTest extends TestCase $this->assertSame($expected, $attachment->getExtension()); } - public function pictureDataProvider(): array + public function pictureDataProvider(): \Iterator { - return [ - ['%MEDIA%/foo/bar.txt', false], - ['https://test.de/picture.jpeg', true], - ['https://test.de/picture.png?test=fdsj&width=34', true], - ['https://invalid.invalid/file.txt', false], - ['http://infsf.inda/file.zip?test', false], - ['https://test.de', true], - ['https://invalid.com/invalid/pic', true], - ['%MEDIA%/foo/bar.jpeg', true], - ['%MEDIA%/foo/bar.webp', true], - ['%MEDIA%/foo', false], - ['%SECURE%/foo.txt/test', false], - ]; + yield ['%MEDIA%/foo/bar.txt', false]; + yield ['https://test.de/picture.jpeg', true]; + yield ['https://test.de/picture.png?test=fdsj&width=34', true]; + yield ['https://invalid.invalid/file.txt', false]; + yield ['http://infsf.inda/file.zip?test', false]; + yield ['https://test.de', true]; + yield ['https://invalid.com/invalid/pic', true]; + yield ['%MEDIA%/foo/bar.jpeg', true]; + yield ['%MEDIA%/foo/bar.webp', true]; + yield ['%MEDIA%/foo', false]; + yield ['%SECURE%/foo.txt/test', false]; } /** @@ -204,16 +192,14 @@ class AttachmentTest extends TestCase $this->assertSame($expected, $attachment->isPicture()); } - public function builtinDataProvider(): array + public function builtinDataProvider(): \Iterator { - return [ - ['', false], - ['%MEDIA%/foo/bar.txt', false], - ['%BASE%/foo/bar.txt', false], - ['/', false], - ['https://google.de', false], - ['%FOOTPRINTS%/foo/bar.txt', true], - ]; + yield ['', false]; + yield ['%MEDIA%/foo/bar.txt', false]; + yield ['%BASE%/foo/bar.txt', false]; + yield ['/', false]; + yield ['https://google.de', false]; + yield ['%FOOTPRINTS%/foo/bar.txt', true]; } /** @@ -226,13 +212,11 @@ class AttachmentTest extends TestCase $this->assertSame($expected, $attachment->isBuiltIn()); } - public function hostDataProvider(): array + public function hostDataProvider(): \Iterator { - return [ - ['%MEDIA%/foo/bar.txt', null], - ['https://www.google.de/test.txt', 'www.google.de'], - ['https://foo.bar/test?txt=test', 'foo.bar'], - ]; + yield ['%MEDIA%/foo/bar.txt', null]; + yield ['https://www.google.de/test.txt', 'www.google.de']; + yield ['https://foo.bar/test?txt=test', 'foo.bar']; } /** @@ -245,13 +229,11 @@ class AttachmentTest extends TestCase $this->assertSame($expected, $attachment->getHost()); } - public function filenameProvider(): array + public function filenameProvider(): \Iterator { - return [ - ['%MEDIA%/foo/bar.txt', null, 'bar.txt'], - ['%MEDIA%/foo/bar.JPeg', 'test.txt', 'test.txt'], - ['https://www.google.de/test.txt', null, null], - ]; + yield ['%MEDIA%/foo/bar.txt', null, 'bar.txt']; + yield ['%MEDIA%/foo/bar.JPeg', 'test.txt', 'test.txt']; + yield ['https://www.google.de/test.txt', null, null]; } /** diff --git a/tests/Entity/LogSystem/LogLevelTest.php b/tests/Entity/LogSystem/LogLevelTest.php index 634d5dd5..402942e1 100644 --- a/tests/Entity/LogSystem/LogLevelTest.php +++ b/tests/Entity/LogSystem/LogLevelTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Entity\LogSystem; use App\Entity\LogSystem\LogLevel; diff --git a/tests/Entity/LogSystem/LogTargetTypeTest.php b/tests/Entity/LogSystem/LogTargetTypeTest.php index 2d7675da..46682496 100644 --- a/tests/Entity/LogSystem/LogTargetTypeTest.php +++ b/tests/Entity/LogSystem/LogTargetTypeTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Entity\LogSystem; use App\Entity\Attachments\Attachment; diff --git a/tests/Entity/Parameters/PartParameterTest.php b/tests/Entity/Parameters/PartParameterTest.php index c8005d6c..c42b3cbe 100644 --- a/tests/Entity/Parameters/PartParameterTest.php +++ b/tests/Entity/Parameters/PartParameterTest.php @@ -46,29 +46,25 @@ use PHPUnit\Framework\TestCase; class PartParameterTest extends TestCase { - public function valueWithUnitDataProvider(): array + public function valueWithUnitDataProvider(): \Iterator { - return [ - ['1', 1.0, ''], - ['1 V', 1.0, 'V'], - ['1.23', 1.23, ''], - ['1.23 V', 1.23, 'V'], - ]; + yield ['1', 1.0, '']; + yield ['1 V', 1.0, 'V']; + yield ['1.23', 1.23, '']; + yield ['1.23 V', 1.23, 'V']; } - public function formattedValueDataProvider(): array + public function formattedValueDataProvider(): \Iterator { - return [ - ['Text Test', null, null, null, 'V', 'Text Test'], - ['10.23 V', null, 10.23, null, 'V', ''], - ['10.23 V [Text]', null, 10.23, null, 'V', 'Text'], - ['max. 10.23 V', null, null, 10.23, 'V', ''], - ['max. 10.23 [Text]', null, null, 10.23, '', 'Text'], - ['min. 10.23 V', 10.23, null, null, 'V', ''], - ['10.23 V ... 11 V', 10.23, null, 11, 'V', ''], - ['10.23 V (9 V ... 11 V)', 9, 10.23, 11, 'V', ''], - ['10.23 V (9 V ... 11 V) [Test]', 9, 10.23, 11, 'V', 'Test'], - ]; + yield ['Text Test', null, null, null, 'V', 'Text Test']; + yield ['10.23 V', null, 10.23, null, 'V', '']; + yield ['10.23 V [Text]', null, 10.23, null, 'V', 'Text']; + yield ['max. 10.23 V', null, null, 10.23, 'V', '']; + yield ['max. 10.23 [Text]', null, null, 10.23, '', 'Text']; + yield ['min. 10.23 V', 10.23, null, null, 'V', '']; + yield ['10.23 V ... 11 V', 10.23, null, 11, 'V', '']; + yield ['10.23 V (9 V ... 11 V)', 9, 10.23, 11, 'V', '']; + yield ['10.23 V (9 V ... 11 V) [Test]', 9, 10.23, 11, 'V', 'Test']; } /** diff --git a/tests/Entity/Parts/InfoProviderReferenceTest.php b/tests/Entity/Parts/InfoProviderReferenceTest.php index 365eb68c..a1a8d5de 100644 --- a/tests/Entity/Parts/InfoProviderReferenceTest.php +++ b/tests/Entity/Parts/InfoProviderReferenceTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Entity\Parts; use App\Entity\Parts\InfoProviderReference; @@ -46,9 +48,9 @@ class InfoProviderReferenceTest extends TestCase //The provider reference instance should return true for the providerCreated method $this->assertTrue($provider->isProviderCreated()); //And the correct values for all other methods - $this->assertEquals('test', $provider->getProviderKey()); - $this->assertEquals('id', $provider->getProviderId()); - $this->assertEquals('url', $provider->getProviderUrl()); + $this->assertSame('test', $provider->getProviderKey()); + $this->assertSame('id', $provider->getProviderId()); + $this->assertSame('url', $provider->getProviderUrl()); $this->assertNotNull($provider->getLastUpdated()); } @@ -60,9 +62,9 @@ class InfoProviderReferenceTest extends TestCase //The provider reference instance should return true for the providerCreated method $this->assertTrue($reference->isProviderCreated()); //And the correct values for all other methods - $this->assertEquals('test', $reference->getProviderKey()); - $this->assertEquals('id', $reference->getProviderId()); - $this->assertEquals('url', $reference->getProviderUrl()); + $this->assertSame('test', $reference->getProviderKey()); + $this->assertSame('id', $reference->getProviderId()); + $this->assertSame('url', $reference->getProviderUrl()); $this->assertNotNull($reference->getLastUpdated()); } } diff --git a/tests/Entity/Parts/PartAssociationTest.php b/tests/Entity/Parts/PartAssociationTest.php index d541c468..e002846e 100644 --- a/tests/Entity/Parts/PartAssociationTest.php +++ b/tests/Entity/Parts/PartAssociationTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Entity\Parts; use App\Entity\Parts\AssociationType; @@ -34,7 +36,7 @@ class PartAssociationTest extends TestCase $assoc->setOtherType('Custom Type'); //If the type is not OTHER the translation key should be the same as the type - $this->assertEquals($assoc->getType()->getTranslationKey(), $assoc->getTypeTranslationKey()); + $this->assertSame($assoc->getType()->getTranslationKey(), $assoc->getTypeTranslationKey()); //If the type is OTHER the translation key should be the other type $assoc->setType(AssociationType::OTHER); diff --git a/tests/Entity/Parts/PartLotTest.php b/tests/Entity/Parts/PartLotTest.php index dee09aae..30687b72 100644 --- a/tests/Entity/Parts/PartLotTest.php +++ b/tests/Entity/Parts/PartLotTest.php @@ -33,12 +33,11 @@ class PartLotTest extends TestCase $lot = new PartLot(); $this->assertNull($lot->isExpired(), 'Lot must be return null when no Expiration date is set!'); - $datetime = new DateTime(); - $lot->setExpirationDate($datetime->setTimestamp(strtotime('now +1 hour'))); + $lot->setExpirationDate(new \DateTimeImmutable('+1 hour')); $this->assertFalse($lot->isExpired(), 'Lot with expiration date in the future must not be expired!'); - $lot->setExpirationDate($datetime->setTimestamp(strtotime('now -1 hour'))); + $lot->setExpirationDate(new \DateTimeImmutable('-1 hour')); $this->assertTrue($lot->isExpired(), 'Lot with expiration date in the past must be expired!'); } } diff --git a/tests/Entity/Parts/PartTest.php b/tests/Entity/Parts/PartTest.php index 275d39d2..3e9233c1 100644 --- a/tests/Entity/Parts/PartTest.php +++ b/tests/Entity/Parts/PartTest.php @@ -55,15 +55,15 @@ class PartTest extends TestCase //Without a set measurement unit the part must return an int $part->setMinAmount(1.345); - $this->assertSame(1.0, $part->getMinAmount()); + $this->assertEqualsWithDelta(1.0, $part->getMinAmount(), PHP_FLOAT_EPSILON); //If a non-int-based unit is assigned, a float is returned $part->setPartUnit($measurement_unit); - $this->assertSame(1.345, $part->getMinAmount()); + $this->assertEqualsWithDelta(1.345, $part->getMinAmount(), PHP_FLOAT_EPSILON); //If an int-based unit is assigned an int is returned $measurement_unit->setIsInteger(true); - $this->assertSame(1.0, $part->getMinAmount()); + $this->assertEqualsWithDelta(1.0, $part->getMinAmount(), PHP_FLOAT_EPSILON); } public function testUseFloatAmount(): void @@ -87,7 +87,7 @@ class PartTest extends TestCase $measurement_unit = new MeasurementUnit(); $datetime = new DateTime(); - $this->assertSame(0.0, $part->getAmountSum()); + $this->assertEqualsWithDelta(0.0, $part->getAmountSum(), PHP_FLOAT_EPSILON); $part->addPartLot((new PartLot())->setAmount(3.141)); $part->addPartLot((new PartLot())->setAmount(10.0)); @@ -95,18 +95,18 @@ class PartTest extends TestCase $part->addPartLot( (new PartLot()) ->setAmount(6) - ->setExpirationDate($datetime->setTimestamp(strtotime('now -1 hour'))) + ->setExpirationDate(new \DateTimeImmutable('-1 hour')) ); - $this->assertSame(13.0, $part->getAmountSum()); + $this->assertEqualsWithDelta(13.0, $part->getAmountSum(), PHP_FLOAT_EPSILON); $part->setPartUnit($measurement_unit); - $this->assertSame(13.141, $part->getAmountSum()); + $this->assertEqualsWithDelta(13.141, $part->getAmountSum(), PHP_FLOAT_EPSILON); //1 billion part lot $part->addPartLot((new PartLot())->setAmount(1_000_000_000)); - $this->assertSame(1_000_000_013.141, $part->getAmountSum()); + $this->assertEqualsWithDelta(1_000_000_013.141, $part->getAmountSum(), PHP_FLOAT_EPSILON); $measurement_unit->setIsInteger(true); - $this->assertSame(1_000_000_013.0, $part->getAmountSum()); + $this->assertEqualsWithDelta(1_000_000_013.0, $part->getAmountSum(), PHP_FLOAT_EPSILON); } } diff --git a/tests/Entity/PriceSystem/PricedetailTest.php b/tests/Entity/PriceSystem/PricedetailTest.php index dd5abb25..8a3cf328 100644 --- a/tests/Entity/PriceSystem/PricedetailTest.php +++ b/tests/Entity/PriceSystem/PricedetailTest.php @@ -60,18 +60,18 @@ class PricedetailTest extends TestCase $orderdetail2->method('getPart')->willReturn($part2); //By default a price detail returns 1 - $this->assertSame(1.0, $pricedetail->getPriceRelatedQuantity()); + $this->assertEqualsWithDelta(1.0, $pricedetail->getPriceRelatedQuantity(), PHP_FLOAT_EPSILON); $pricedetail->setOrderdetail($orderdetail); $pricedetail->setPriceRelatedQuantity(10.23); - $this->assertSame(10.0, $pricedetail->getPriceRelatedQuantity()); + $this->assertEqualsWithDelta(10.0, $pricedetail->getPriceRelatedQuantity(), PHP_FLOAT_EPSILON); //Price related quantity must not be zero! $pricedetail->setPriceRelatedQuantity(0.23); - $this->assertSame(1.0, $pricedetail->getPriceRelatedQuantity()); + $this->assertEqualsWithDelta(1.0, $pricedetail->getPriceRelatedQuantity(), PHP_FLOAT_EPSILON); //With a part that has a float amount unit, also values like 0.23 can be returned $pricedetail->setOrderdetail($orderdetail2); - $this->assertSame(0.23, $pricedetail->getPriceRelatedQuantity()); + $this->assertEqualsWithDelta(0.23, $pricedetail->getPriceRelatedQuantity(), PHP_FLOAT_EPSILON); } public function testGetMinDiscountQuantity(): void @@ -88,17 +88,17 @@ class PricedetailTest extends TestCase $orderdetail2->method('getPart')->willReturn($part2); //By default a price detail returns 1 - $this->assertSame(1.0, $pricedetail->getMinDiscountQuantity()); + $this->assertEqualsWithDelta(1.0, $pricedetail->getMinDiscountQuantity(), PHP_FLOAT_EPSILON); $pricedetail->setOrderdetail($orderdetail); $pricedetail->setMinDiscountQuantity(10.23); - $this->assertSame(10.0, $pricedetail->getMinDiscountQuantity()); + $this->assertEqualsWithDelta(10.0, $pricedetail->getMinDiscountQuantity(), PHP_FLOAT_EPSILON); //Price related quantity must not be zero! $pricedetail->setMinDiscountQuantity(0.23); - $this->assertSame(1.0, $pricedetail->getMinDiscountQuantity()); + $this->assertEqualsWithDelta(1.0, $pricedetail->getMinDiscountQuantity(), PHP_FLOAT_EPSILON); //With a part that has a float amount unit, also values like 0.23 can be returned $pricedetail->setOrderdetail($orderdetail2); - $this->assertSame(0.23, $pricedetail->getMinDiscountQuantity()); + $this->assertEqualsWithDelta(0.23, $pricedetail->getMinDiscountQuantity(), PHP_FLOAT_EPSILON); } } diff --git a/tests/Entity/UserSystem/ApiTokenTypeTest.php b/tests/Entity/UserSystem/ApiTokenTypeTest.php index d504f39d..21d14b69 100644 --- a/tests/Entity/UserSystem/ApiTokenTypeTest.php +++ b/tests/Entity/UserSystem/ApiTokenTypeTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Entity\UserSystem; use App\Entity\UserSystem\ApiTokenType; @@ -28,7 +30,7 @@ class ApiTokenTypeTest extends TestCase public function testGetTokenPrefix(): void { - $this->assertEquals('tcp_', ApiTokenType::PERSONAL_ACCESS_TOKEN->getTokenPrefix()); + $this->assertSame('tcp_', ApiTokenType::PERSONAL_ACCESS_TOKEN->getTokenPrefix()); } public function testGetTypeFromToken(): void diff --git a/tests/Entity/UserSystem/UserTest.php b/tests/Entity/UserSystem/UserTest.php index 11837b77..f11eec0f 100644 --- a/tests/Entity/UserSystem/UserTest.php +++ b/tests/Entity/UserSystem/UserTest.php @@ -45,13 +45,11 @@ class UserTest extends TestCase $this->assertSame('John (@username)', $user->getFullName(true)); } - public function googleAuthenticatorEnabledDataProvider(): array + public function googleAuthenticatorEnabledDataProvider(): \Iterator { - return [ - [null, false], - ['', false], - ['SSSk38498', true], - ]; + yield [null, false]; + yield ['', false]; + yield ['SSSk38498', true]; } /** @@ -75,7 +73,7 @@ class UserTest extends TestCase $codes = ['test', 'invalid', 'test']; $user->setBackupCodes($codes); // Backup Codes generation date must be changed! - $this->assertInstanceOf(\DateTime::class, $user->getBackupCodesGenerationDate()); + $this->assertNotNull($user->getBackupCodesGenerationDate()); $this->assertSame($codes, $user->getBackupCodes()); //Test what happens if we delete the backup keys diff --git a/tests/Helpers/Projects/ProjectBuildRequestTest.php b/tests/Helpers/Projects/ProjectBuildRequestTest.php index c561613d..baccfcd1 100644 --- a/tests/Helpers/Projects/ProjectBuildRequestTest.php +++ b/tests/Helpers/Projects/ProjectBuildRequestTest.php @@ -120,11 +120,11 @@ class ProjectBuildRequestTest extends TestCase //The values should be already prefilled correctly $request = new ProjectBuildRequest($this->project1, 10); //We need totally 20: Take 10 from the first (maximum 10) and 10 from the second (maximum 20) - $this->assertSame(10.0, $request->getLotWithdrawAmount($this->lot1a)); - $this->assertSame(10.0, $request->getLotWithdrawAmount($this->lot1b)); + $this->assertEqualsWithDelta(10.0, $request->getLotWithdrawAmount($this->lot1a), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(10.0, $request->getLotWithdrawAmount($this->lot1b), PHP_FLOAT_EPSILON); //If the needed amount is higher than the maximum, we should get the maximum - $this->assertSame(2.5, $request->getLotWithdrawAmount($this->lot2)); + $this->assertEqualsWithDelta(2.5, $request->getLotWithdrawAmount($this->lot2), PHP_FLOAT_EPSILON); } public function testGetNumberOfBuilds(): void @@ -142,9 +142,9 @@ class ProjectBuildRequestTest extends TestCase public function testGetNeededAmountForBOMEntry(): void { $build_request = new ProjectBuildRequest($this->project1, 5); - $this->assertSame(10.0, $build_request->getNeededAmountForBOMEntry($this->bom_entry1a)); - $this->assertSame(7.5, $build_request->getNeededAmountForBOMEntry($this->bom_entry1b)); - $this->assertSame(20.0, $build_request->getNeededAmountForBOMEntry($this->bom_entry1c)); + $this->assertEqualsWithDelta(10.0, $build_request->getNeededAmountForBOMEntry($this->bom_entry1a), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(7.5, $build_request->getNeededAmountForBOMEntry($this->bom_entry1b), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(20.0, $build_request->getNeededAmountForBOMEntry($this->bom_entry1c), PHP_FLOAT_EPSILON); } public function testGetSetLotWithdrawAmount(): void @@ -156,8 +156,8 @@ class ProjectBuildRequestTest extends TestCase $build_request->setLotWithdrawAmount($this->lot1b->getID(), 3); //And it should be possible to get the amount via the lot object or via the ID - $this->assertSame(2.0, $build_request->getLotWithdrawAmount($this->lot1a->getID())); - $this->assertSame(3.0, $build_request->getLotWithdrawAmount($this->lot1b)); + $this->assertEqualsWithDelta(2.0, $build_request->getLotWithdrawAmount($this->lot1a->getID()), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(3.0, $build_request->getLotWithdrawAmount($this->lot1b), PHP_FLOAT_EPSILON); } public function testGetWithdrawAmountSum(): void @@ -168,9 +168,9 @@ class ProjectBuildRequestTest extends TestCase $build_request->setLotWithdrawAmount($this->lot1a, 2); $build_request->setLotWithdrawAmount($this->lot1b, 3); - $this->assertSame(5.0, $build_request->getWithdrawAmountSum($this->bom_entry1a)); + $this->assertEqualsWithDelta(5.0, $build_request->getWithdrawAmountSum($this->bom_entry1a), PHP_FLOAT_EPSILON); $build_request->setLotWithdrawAmount($this->lot2, 1.5); - $this->assertSame(1.5, $build_request->getWithdrawAmountSum($this->bom_entry1b)); + $this->assertEqualsWithDelta(1.5, $build_request->getWithdrawAmountSum($this->bom_entry1b), PHP_FLOAT_EPSILON); } diff --git a/tests/Helpers/TrinaryLogicHelperTest.php b/tests/Helpers/TrinaryLogicHelperTest.php index f8ec35b1..3082571b 100644 --- a/tests/Helpers/TrinaryLogicHelperTest.php +++ b/tests/Helpers/TrinaryLogicHelperTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Helpers; use App\Helpers\TrinaryLogicHelper; diff --git a/tests/Repository/AttachmentContainingDBElementRepositoryTest.php b/tests/Repository/AttachmentContainingDBElementRepositoryTest.php index e11bd138..f61750d9 100644 --- a/tests/Repository/AttachmentContainingDBElementRepositoryTest.php +++ b/tests/Repository/AttachmentContainingDBElementRepositoryTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Repository; use App\Entity\Parts\Category; diff --git a/tests/Repository/DBElementRepositoryTest.php b/tests/Repository/DBElementRepositoryTest.php index c803302b..05ede7e2 100644 --- a/tests/Repository/DBElementRepositoryTest.php +++ b/tests/Repository/DBElementRepositoryTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Repository; use App\Entity\Attachments\Attachment; diff --git a/tests/Repository/UserRepositoryTest.php b/tests/Repository/UserRepositoryTest.php index dea4a294..0e6f3c2d 100644 --- a/tests/Repository/UserRepositoryTest.php +++ b/tests/Repository/UserRepositoryTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Repository; use App\Entity\UserSystem\User; diff --git a/tests/Serializer/PartNormalizerTest.php b/tests/Serializer/PartNormalizerTest.php index de5ac2db..cd93d93c 100644 --- a/tests/Serializer/PartNormalizerTest.php +++ b/tests/Serializer/PartNormalizerTest.php @@ -110,7 +110,7 @@ class PartNormalizerTest extends WebTestCase $this->assertCount(1, $part->getPartLots()); /** @var PartLot $partLot */ $partLot = $part->getPartLots()->first(); - $this->assertSame(5.0, $partLot->getAmount()); + $this->assertEqualsWithDelta(5.0, $partLot->getAmount(), PHP_FLOAT_EPSILON); $this->assertNotNull($partLot->getStorageLocation()); $this->assertSame('Test Storage Location', $partLot->getStorageLocation()->getName()); @@ -130,7 +130,7 @@ class PartNormalizerTest extends WebTestCase //Must be in base currency $this->assertNull($priceDetail->getCurrency()); //Must be for 1 part and 1 minimum order quantity - $this->assertSame(1.0, $priceDetail->getPriceRelatedQuantity()); - $this->assertSame(1.0, $priceDetail->getMinDiscountQuantity()); + $this->assertEqualsWithDelta(1.0, $priceDetail->getPriceRelatedQuantity(), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(1.0, $priceDetail->getMinDiscountQuantity(), PHP_FLOAT_EPSILON); } } diff --git a/tests/Serializer/StructuralElementDenormalizerTest.php b/tests/Serializer/StructuralElementDenormalizerTest.php index fc1c0f1d..2543c4f3 100644 --- a/tests/Serializer/StructuralElementDenormalizerTest.php +++ b/tests/Serializer/StructuralElementDenormalizerTest.php @@ -37,6 +37,10 @@ class StructuralElementDenormalizerTest extends WebTestCase //Get a service instance. self::bootKernel(); $this->service = self::getContainer()->get(StructuralElementDenormalizer::class); + + //We need to inject the serializer into the normalizer, as we use it directly + $serializer = self::getContainer()->get('serializer'); + $this->service->setDenormalizer($serializer); } public function testSupportsDenormalization(): void diff --git a/tests/Services/Attachments/AttachmentPathResolverTest.php b/tests/Services/Attachments/AttachmentPathResolverTest.php index 22809390..3c432f48 100644 --- a/tests/Services/Attachments/AttachmentPathResolverTest.php +++ b/tests/Services/Attachments/AttachmentPathResolverTest.php @@ -69,7 +69,7 @@ class AttachmentPathResolverTest extends WebTestCase $this->assertNull($this->service->parameterToAbsolutePath('/./this/one/too')); } - public function placeholderDataProvider(): array + public function placeholderDataProvider(): \Iterator { //We need to do initialization (again), as dataprovider is called before setUp() self::bootKernel(); @@ -77,28 +77,28 @@ class AttachmentPathResolverTest extends WebTestCase $this->projectDir = str_replace('\\', '/', $this->projectDir_orig); $this->media_path = $this->projectDir.'/public/media'; $this->footprint_path = $this->projectDir.'/public/img/footprints'; - - return [ - ['%FOOTPRINTS%/test/test.jpg', $this->footprint_path.'/test/test.jpg'], - ['%FOOTPRINTS%/test/', $this->footprint_path.'/test/'], - ['%MEDIA%/test', $this->media_path.'/test'], - ['%MEDIA%', $this->media_path], - ['%FOOTPRINTS%', $this->footprint_path], - //Footprints 3D are disabled - ['%FOOTPRINTS_3D%', null], - //Check that invalid pathes return null - ['/no/placeholder', null], - ['%INVALID_PLACEHOLDER%', null], - ['%FOOTPRINTS/test/', null], //Malformed placeholder - ['/wrong/%FOOTRPINTS%/', null], //Placeholder not at beginning - ['%FOOTPRINTS%/%MEDIA%', null], //No more than one placholder - ['%FOOTPRINTS%/%FOOTPRINTS%', null], - ['%FOOTPRINTS%/../../etc/passwd', null], - ['%FOOTPRINTS%/0\..\test', null], - ]; + yield ['%FOOTPRINTS%/test/test.jpg', $this->footprint_path.'/test/test.jpg']; + yield ['%FOOTPRINTS%/test/', $this->footprint_path.'/test/']; + yield ['%MEDIA%/test', $this->media_path.'/test']; + yield ['%MEDIA%', $this->media_path]; + yield ['%FOOTPRINTS%', $this->footprint_path]; + //Footprints 3D are disabled + yield ['%FOOTPRINTS_3D%', null]; + //Check that invalid pathes return null + yield ['/no/placeholder', null]; + yield ['%INVALID_PLACEHOLDER%', null]; + yield ['%FOOTPRINTS/test/', null]; + //Malformed placeholder + yield ['/wrong/%FOOTRPINTS%/', null]; + //Placeholder not at beginning + yield ['%FOOTPRINTS%/%MEDIA%', null]; + //No more than one placholder + yield ['%FOOTPRINTS%/%FOOTPRINTS%', null]; + yield ['%FOOTPRINTS%/../../etc/passwd', null]; + yield ['%FOOTPRINTS%/0\..\test', null]; } - public function realPathDataProvider(): array + public function realPathDataProvider(): \Iterator { //We need to do initialization (again), as dataprovider is called before setUp() self::bootKernel(); @@ -106,20 +106,17 @@ class AttachmentPathResolverTest extends WebTestCase $this->projectDir = str_replace('\\', '/', $this->projectDir_orig); $this->media_path = $this->projectDir.'/public/media'; $this->footprint_path = $this->projectDir.'/public/img/footprints'; - - return [ - [$this->media_path.'/test/img.jpg', '%MEDIA%/test/img.jpg'], - [$this->media_path.'/test/img.jpg', '%BASE%/data/media/test/img.jpg', true], - [$this->footprint_path.'/foo.jpg', '%FOOTPRINTS%/foo.jpg'], - [$this->footprint_path.'/foo.jpg', '%FOOTPRINTS%/foo.jpg', true], - //Every kind of absolute path, that is not based with our placeholder dirs must be invald - ['/etc/passwd', null], - ['C:\\not\\existing.txt', null], - //More than one placeholder is not allowed - [$this->footprint_path.'/test/'.$this->footprint_path, null], - //Path must begin with path - ['/not/root'.$this->footprint_path, null], - ]; + yield [$this->media_path.'/test/img.jpg', '%MEDIA%/test/img.jpg']; + yield [$this->media_path.'/test/img.jpg', '%BASE%/data/media/test/img.jpg', true]; + yield [$this->footprint_path.'/foo.jpg', '%FOOTPRINTS%/foo.jpg']; + yield [$this->footprint_path.'/foo.jpg', '%FOOTPRINTS%/foo.jpg', true]; + //Every kind of absolute path, that is not based with our placeholder dirs must be invald + yield ['/etc/passwd', null]; + yield ['C:\\not\\existing.txt', null]; + //More than one placeholder is not allowed + yield [$this->footprint_path.'/test/'.$this->footprint_path, null]; + //Path must begin with path + yield ['/not/root'.$this->footprint_path, null]; } /** diff --git a/tests/Services/Attachments/FileTypeFilterToolsTest.php b/tests/Services/Attachments/FileTypeFilterToolsTest.php index 63aa8bde..a6032a4b 100644 --- a/tests/Services/Attachments/FileTypeFilterToolsTest.php +++ b/tests/Services/Attachments/FileTypeFilterToolsTest.php @@ -35,56 +35,52 @@ class FileTypeFilterToolsTest extends WebTestCase self::$service = self::getContainer()->get(FileTypeFilterTools::class); } - public function validateDataProvider(): array + public function validateDataProvider(): \Iterator { - return [ - ['', true], //Empty string is valid - ['.jpeg,.png, .gif', true], //Only extensions are valid - ['image/*, video/*, .mp4, video/x-msvideo, application/vnd.amazon.ebook', true], - ['application/vnd.amazon.ebook, audio/opus', true], - - ['*.notvalid, .png', false], //No stars in extension - ['test.png', false], //No full filename - ['application/*', false], //Only certain placeholders are allowed - ['.png;.png,.jpg', false], //Wrong separator - ['.png .jpg .gif', false], - ]; + yield ['', true]; + //Empty string is valid + yield ['.jpeg,.png, .gif', true]; + //Only extensions are valid + yield ['image/*, video/*, .mp4, video/x-msvideo, application/vnd.amazon.ebook', true]; + yield ['application/vnd.amazon.ebook, audio/opus', true]; + yield ['*.notvalid, .png', false]; + //No stars in extension + yield ['test.png', false]; + //No full filename + yield ['application/*', false]; + //Only certain placeholders are allowed + yield ['.png;.png,.jpg', false]; + //Wrong separator + yield ['.png .jpg .gif', false]; } - public function normalizeDataProvider(): array + public function normalizeDataProvider(): \Iterator { - return [ - ['', ''], - ['.jpeg,.png,.gif', '.jpeg,.png,.gif'], - ['.jpeg, .png, .gif,', '.jpeg,.png,.gif'], - ['jpg, *.gif', '.jpg,.gif'], - ['video, image/', 'video/*,image/*'], - ['video/*', 'video/*'], - ['video/x-msvideo,.jpeg', 'video/x-msvideo,.jpeg'], - ['.video', '.video'], - //Remove duplicate entries - ['png, .gif, .png,', '.png,.gif'], - ]; + yield ['', '']; + yield ['.jpeg,.png,.gif', '.jpeg,.png,.gif']; + yield ['.jpeg, .png, .gif,', '.jpeg,.png,.gif']; + yield ['jpg, *.gif', '.jpg,.gif']; + yield ['video, image/', 'video/*,image/*']; + yield ['video/*', 'video/*']; + yield ['video/x-msvideo,.jpeg', 'video/x-msvideo,.jpeg']; + yield ['.video', '.video']; + //Remove duplicate entries + yield ['png, .gif, .png,', '.png,.gif']; } - public function extensionAllowedDataProvider(): array + public function extensionAllowedDataProvider(): \Iterator { - return [ - ['', 'txt', true], - ['', 'everything_should_match', true], - - ['.jpg,.png', 'jpg', true], - ['.jpg,.png', 'png', true], - ['.jpg,.png', 'txt', false], - - ['image/*', 'jpeg', true], - ['image/*', 'png', true], - ['image/*', 'txt', false], - - ['application/pdf,.txt', 'pdf', true], - ['application/pdf,.txt', 'txt', true], - ['application/pdf,.txt', 'jpg', false], - ]; + yield ['', 'txt', true]; + yield ['', 'everything_should_match', true]; + yield ['.jpg,.png', 'jpg', true]; + yield ['.jpg,.png', 'png', true]; + yield ['.jpg,.png', 'txt', false]; + yield ['image/*', 'jpeg', true]; + yield ['image/*', 'png', true]; + yield ['image/*', 'txt', false]; + yield ['application/pdf,.txt', 'pdf', true]; + yield ['application/pdf,.txt', 'txt', true]; + yield ['application/pdf,.txt', 'jpg', false]; } /** diff --git a/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php b/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php index 7daedef2..22fa220b 100644 --- a/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php +++ b/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\EntityMergers\Mergers; use App\Entity\Parts\Part; diff --git a/tests/Services/EntityMergers/Mergers/MergeTestClass.php b/tests/Services/EntityMergers/Mergers/MergeTestClass.php index da7ad67c..73fa9314 100644 --- a/tests/Services/EntityMergers/Mergers/MergeTestClass.php +++ b/tests/Services/EntityMergers/Mergers/MergeTestClass.php @@ -37,7 +37,7 @@ class MergeTestClass public Collection $collection; - public ?Category $category; + public ?Category $category = null; public function __construct() { diff --git a/tests/Services/EntityMergers/Mergers/PartMergerTest.php b/tests/Services/EntityMergers/Mergers/PartMergerTest.php index d607ee72..fc60ca23 100644 --- a/tests/Services/EntityMergers/Mergers/PartMergerTest.php +++ b/tests/Services/EntityMergers/Mergers/PartMergerTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\EntityMergers\Mergers; use App\Entity\Parts\AssociationType; diff --git a/tests/Services/ImportExportSystem/BOMImporterTest.php b/tests/Services/ImportExportSystem/BOMImporterTest.php index b7379537..b9aba1d4 100644 --- a/tests/Services/ImportExportSystem/BOMImporterTest.php +++ b/tests/Services/ImportExportSystem/BOMImporterTest.php @@ -84,7 +84,7 @@ class BOMImporterTest extends WebTestCase $this->assertCount(4, $bom); $this->assertSame('R19,R17', $bom[0]->getMountnames()); - $this->assertSame(2.0, $bom[0]->getQuantity()); + $this->assertEqualsWithDelta(2.0, $bom[0]->getQuantity(), PHP_FLOAT_EPSILON); $this->assertSame('4.7k (R_0805_2012Metric_Pad1.20x1.40mm_HandSolder)', $bom[0]->getName()); $this->assertSame('Test', $bom[0]->getComment()); @@ -103,7 +103,7 @@ class BOMImporterTest extends WebTestCase $this->assertCount(4, $bom); $this->assertSame('R19,R17', $bom[0]->getMountnames()); - $this->assertSame(2.0, $bom[0]->getQuantity()); + $this->assertEqualsWithDelta(2.0, $bom[0]->getQuantity(), PHP_FLOAT_EPSILON); $this->assertSame('4.7k (R_0805_2012Metric_Pad1.20x1.40mm_HandSolder)', $bom[0]->getName()); $this->assertSame('Test', $bom[0]->getComment()); } diff --git a/tests/Services/ImportExportSystem/EntityImporterTest.php b/tests/Services/ImportExportSystem/EntityImporterTest.php index 07ac0017..31859b6a 100644 --- a/tests/Services/ImportExportSystem/EntityImporterTest.php +++ b/tests/Services/ImportExportSystem/EntityImporterTest.php @@ -27,11 +27,13 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; use App\Entity\Parts\Part; +use App\Entity\ProjectSystem\Project; use App\Entity\UserSystem\User; use App\Services\ImportExportSystem\EntityImporter; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationListInterface; /** * @group DB @@ -172,16 +174,14 @@ EOT; $this->assertSame($longName, $errors[0]['entity']->getName()); } - public function formatDataProvider(): array + public function formatDataProvider(): \Iterator { - return [ - ['csv', 'csv'], - ['csv', 'CSV'], - ['xml', 'Xml'], - ['json', 'json'], - ['yaml', 'yml'], - ['yaml', 'YAML'], - ]; + yield ['csv', 'csv']; + yield ['csv', 'CSV']; + yield ['xml', 'Xml']; + yield ['json', 'json']; + yield ['yaml', 'yml']; + yield ['yaml', 'YAML']; } /** @@ -192,6 +192,63 @@ EOT; $this->assertSame($expected, $this->service->determineFormat($extension)); } + public function testImportStringProjects(): void + { + $input = <<service->importString($input, [ + 'class' => Project::class, + 'format' => 'csv', + 'csv_delimiter' => ';', + ], $errors); + + $this->assertCount(2, $results); + + //No errors must be present + $this->assertEmpty($errors); + + + $this->assertContainsOnlyInstancesOf(Project::class, $results); + + $this->assertSame('Test 1', $results[0]->getName()); + $this->assertSame('Test 1 notes', $results[0]->getComment()); + } + + public function testImportStringProjectWithErrors(): void + { + $input = <<service->importString($input, [ + 'class' => Project::class, + 'format' => 'csv', + 'csv_delimiter' => ';', + ], $errors); + + $this->assertCount(1, $results); + $this->assertCount(1, $errors); + + //Validate shape of error output + + $this->assertArrayHasKey('Row 0', $errors); + $this->assertArrayHasKey('entity', $errors['Row 0']); + $this->assertArrayHasKey('violations', $errors['Row 0']); + + $this->assertInstanceOf(ConstraintViolationListInterface::class, $errors['Row 0']['violations']); + $this->assertInstanceOf(Project::class, $errors['Row 0']['entity']); + } + public function testImportStringParts(): void { $input = <<assertSame('', $error['entity']->getName()); $this->assertContainsOnlyInstancesOf(ConstraintViolation::class, $error['violations']); //Element name must be element name - $this->assertArrayHasKey('', $errors); + $this->assertArrayHasKey('Row 1', $errors); //Check the valid element $this->assertSame('Test 1', $results[0]->getName()); diff --git a/tests/Services/InfoProviderSystem/DTOs/FileDTOTest.php b/tests/Services/InfoProviderSystem/DTOs/FileDTOTest.php index 8ec454e7..f6e145f9 100644 --- a/tests/Services/InfoProviderSystem/DTOs/FileDTOTest.php +++ b/tests/Services/InfoProviderSystem/DTOs/FileDTOTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\InfoProviderSystem\DTOs; use App\Services\InfoProviderSystem\DTOs\FileDTO; @@ -27,18 +29,15 @@ class FileDTOTest extends TestCase { - public static function escapingDataProvider(): array + public static function escapingDataProvider(): \Iterator { - return [ - //Normal URLs must be unchanged, even if they contain special characters - ["https://localhost:8000/en/part/1335/edit#attachments", "https://localhost:8000/en/part/1335/edit#attachments"], - ["https://localhost:8000/en/part/1335/edit?test=%20%20&sfee_aswer=test-223!*()", "https://localhost:8000/en/part/1335/edit?test=%20%20&sfee_aswer=test-223!*()"], - - //Remaining URL unsafe characters must be escaped - ["test%5Ese", "test^se"], - ["test%20se", "test se"], - ["test%7Cse", "test|se"], - ]; + //Normal URLs must be unchanged, even if they contain special characters + yield ["https://localhost:8000/en/part/1335/edit#attachments", "https://localhost:8000/en/part/1335/edit#attachments"]; + yield ["https://localhost:8000/en/part/1335/edit?test=%20%20&sfee_aswer=test-223!*()", "https://localhost:8000/en/part/1335/edit?test=%20%20&sfee_aswer=test-223!*()"]; + //Remaining URL unsafe characters must be escaped + yield ["test%5Ese", "test^se"]; + yield ["test%20se", "test se"]; + yield ["test%7Cse", "test|se"]; } /** diff --git a/tests/Services/InfoProviderSystem/DTOs/ParameterDTOTest.php b/tests/Services/InfoProviderSystem/DTOs/ParameterDTOTest.php index 105cbfcf..7bbebf0b 100644 --- a/tests/Services/InfoProviderSystem/DTOs/ParameterDTOTest.php +++ b/tests/Services/InfoProviderSystem/DTOs/ParameterDTOTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\InfoProviderSystem\DTOs; use App\Services\InfoProviderSystem\DTOs\ParameterDTO; @@ -241,18 +243,18 @@ class ParameterDTOTest extends TestCase public function testSplitIntoValueAndUnit(): void { - $this->assertEquals(['1.0', 'kg'], ParameterDTO::splitIntoValueAndUnit('1.0 kg')); - $this->assertEquals(['1.0', 'kg'], ParameterDTO::splitIntoValueAndUnit('1.0kg')); - $this->assertEquals(['1', 'kg'], ParameterDTO::splitIntoValueAndUnit('1 kg')); + $this->assertSame(['1.0', 'kg'], ParameterDTO::splitIntoValueAndUnit('1.0 kg')); + $this->assertSame(['1.0', 'kg'], ParameterDTO::splitIntoValueAndUnit('1.0kg')); + $this->assertSame(['1', 'kg'], ParameterDTO::splitIntoValueAndUnit('1 kg')); - $this->assertEquals(['1.0', '°C'], ParameterDTO::splitIntoValueAndUnit('1.0°C')); - $this->assertEquals(['1.0', '°C'], ParameterDTO::splitIntoValueAndUnit('1.0 °C')); + $this->assertSame(['1.0', '°C'], ParameterDTO::splitIntoValueAndUnit('1.0°C')); + $this->assertSame(['1.0', '°C'], ParameterDTO::splitIntoValueAndUnit('1.0 °C')); - $this->assertEquals(['1.0', 'C_m'], ParameterDTO::splitIntoValueAndUnit('1.0C_m')); - $this->assertEquals(["70", "℃"], ParameterDTO::splitIntoValueAndUnit("70℃")); + $this->assertSame(['1.0', 'C_m'], ParameterDTO::splitIntoValueAndUnit('1.0C_m')); + $this->assertSame(["70", "℃"], ParameterDTO::splitIntoValueAndUnit("70℃")); - $this->assertEquals(["-5.0", "kg"], ParameterDTO::splitIntoValueAndUnit("-5.0 kg")); - $this->assertEquals(["-5.0", "µg"], ParameterDTO::splitIntoValueAndUnit("-5.0 µg")); + $this->assertSame(["-5.0", "kg"], ParameterDTO::splitIntoValueAndUnit("-5.0 kg")); + $this->assertSame(["-5.0", "µg"], ParameterDTO::splitIntoValueAndUnit("-5.0 µg")); $this->assertNull(ParameterDTO::splitIntoValueAndUnit('kg')); $this->assertNull(ParameterDTO::splitIntoValueAndUnit('Test')); diff --git a/tests/Services/InfoProviderSystem/DTOs/PurchaseInfoDTOTest.php b/tests/Services/InfoProviderSystem/DTOs/PurchaseInfoDTOTest.php index 0442a873..14a3c03f 100644 --- a/tests/Services/InfoProviderSystem/DTOs/PurchaseInfoDTOTest.php +++ b/tests/Services/InfoProviderSystem/DTOs/PurchaseInfoDTOTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\InfoProviderSystem\DTOs; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; diff --git a/tests/Services/InfoProviderSystem/DTOs/SearchResultDTOTest.php b/tests/Services/InfoProviderSystem/DTOs/SearchResultDTOTest.php index f23439f8..dd516c8d 100644 --- a/tests/Services/InfoProviderSystem/DTOs/SearchResultDTOTest.php +++ b/tests/Services/InfoProviderSystem/DTOs/SearchResultDTOTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\InfoProviderSystem\DTOs; use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; @@ -45,8 +47,8 @@ class SearchResultDTOTest extends TestCase 'description', preview_image_url: 'https://invalid.com/preview_image_url.jpg' ); - $this->assertEquals('https://invalid.com/preview_image_url.jpg', $searchResultDTO->preview_image_url); - $this->assertEquals('https://invalid.com/preview_image_url.jpg', $searchResultDTO->preview_image_file->url); + $this->assertSame('https://invalid.com/preview_image_url.jpg', $searchResultDTO->preview_image_url); + $this->assertSame('https://invalid.com/preview_image_url.jpg', $searchResultDTO->preview_image_file->url); //Invalid url characters should be replaced with their URL encoded version (similar to FileDTO) $searchResultDTO = new SearchResultDTO( @@ -56,7 +58,7 @@ class SearchResultDTOTest extends TestCase 'description', preview_image_url: 'https://invalid.com/preview_image^url.jpg?param1=1¶m2=2' ); - $this->assertEquals('https://invalid.com/preview_image%5Eurl.jpg?param1=1¶m2=2', $searchResultDTO->preview_image_url); - $this->assertEquals('https://invalid.com/preview_image%5Eurl.jpg?param1=1¶m2=2', $searchResultDTO->preview_image_file->url); + $this->assertSame('https://invalid.com/preview_image%5Eurl.jpg?param1=1¶m2=2', $searchResultDTO->preview_image_url); + $this->assertSame('https://invalid.com/preview_image%5Eurl.jpg?param1=1¶m2=2', $searchResultDTO->preview_image_file->url); } } diff --git a/tests/Services/InfoProviderSystem/DTOtoEntityConverterTest.php b/tests/Services/InfoProviderSystem/DTOtoEntityConverterTest.php index 658f5135..6c6637c3 100644 --- a/tests/Services/InfoProviderSystem/DTOtoEntityConverterTest.php +++ b/tests/Services/InfoProviderSystem/DTOtoEntityConverterTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\InfoProviderSystem; use App\Entity\Attachments\AttachmentType; @@ -53,7 +55,7 @@ class DTOtoEntityConverterTest extends WebTestCase $entity = $this->service->convertParameter($dto); - $this->assertEquals($dto->name, $entity->getName()); + $this->assertSame($dto->name, $entity->getName()); $this->assertEquals($dto->value_text, $entity->getValueText()); $this->assertEquals($dto->value_typ, $entity->getValueTypical()); $this->assertEquals($dto->value_min, $entity->getValueMin()); @@ -74,8 +76,8 @@ class DTOtoEntityConverterTest extends WebTestCase ); $entity = $this->service->convertPrice($dto); - $this->assertEquals($dto->minimum_discount_amount, $entity->getMinDiscountQuantity()); - $this->assertEquals((float) $dto->price, (float) (string) $entity->getPrice()); + $this->assertSame($dto->minimum_discount_amount, $entity->getMinDiscountQuantity()); + $this->assertSame((float) $dto->price, (float) (string) $entity->getPrice()); $this->assertEquals($dto->price_related_quantity, $entity->getPriceRelatedQuantity()); //For non-base currencies, a new currency entity is created @@ -114,8 +116,8 @@ class DTOtoEntityConverterTest extends WebTestCase $entity = $this->service->convertPurchaseInfo($dto); - $this->assertEquals($dto->distributor_name, $entity->getSupplier()->getName()); - $this->assertEquals($dto->order_number, $entity->getSupplierPartNr()); + $this->assertSame($dto->distributor_name, $entity->getSupplier()->getName()); + $this->assertSame($dto->order_number, $entity->getSupplierPartNr()); $this->assertEquals($dto->product_url, $entity->getSupplierProductUrl()); } @@ -128,7 +130,7 @@ class DTOtoEntityConverterTest extends WebTestCase $entity = $this->service->convertFile($dto, $type); $this->assertEquals($dto->name, $entity->getName()); - $this->assertEquals($dto->url, $entity->getUrl()); + $this->assertSame($dto->url, $entity->getUrl()); $this->assertEquals($type, $entity->getAttachmentType()); } @@ -141,8 +143,8 @@ class DTOtoEntityConverterTest extends WebTestCase $entity = $this->service->convertFile($dto, $type); //If no name is given, the name is derived from the url - $this->assertEquals('file.pdf', $entity->getName()); - $this->assertEquals($dto->url, $entity->getUrl()); + $this->assertSame('file.pdf', $entity->getName()); + $this->assertSame($dto->url, $entity->getUrl()); $this->assertEquals($type, $entity->getAttachmentType()); } @@ -184,11 +186,11 @@ class DTOtoEntityConverterTest extends WebTestCase $this->assertCount(3, $entity->getAttachments()); //The attachments should have the name of the named duplicate file $image1 = $entity->getAttachments()[0]; - $this->assertEquals('Main image', $image1->getName()); + $this->assertSame('Main image', $image1->getName()); $image1 = $entity->getAttachments()[1]; $datasheet = $entity->getAttachments()[2]; - $this->assertEquals('TestFile', $datasheet->getName()); + $this->assertSame('TestFile', $datasheet->getName()); } } diff --git a/tests/Services/InfoProviderSystem/ProviderRegistryTest.php b/tests/Services/InfoProviderSystem/ProviderRegistryTest.php index 5e6bec28..9026c5bf 100644 --- a/tests/Services/InfoProviderSystem/ProviderRegistryTest.php +++ b/tests/Services/InfoProviderSystem/ProviderRegistryTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\InfoProviderSystem; use App\Services\InfoProviderSystem\ProviderRegistry; diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php index e14d9bc5..9d0ed7e2 100644 --- a/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php +++ b/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php @@ -57,22 +57,18 @@ class BarcodeContentGeneratorTest extends KernelTestCase $this->service = self::getContainer()->get(BarcodeContentGenerator::class); } - public function Barcode1DDataProvider(): array + public function Barcode1DDataProvider(): \Iterator { - return [ - ['P0000', Part::class], - ['L0000', PartLot::class], - ['S0000', StorageLocation::class], - ]; + yield ['P0000', Part::class]; + yield ['L0000', PartLot::class]; + yield ['S0000', StorageLocation::class]; } - public function Barcode2DDataProvider(): array + public function Barcode2DDataProvider(): \Iterator { - return [ - ['/scan/part/0', Part::class], - ['/scan/lot/0', PartLot::class], - ['/scan/location/0', StorageLocation::class], - ]; + yield ['/scan/part/0', Part::class]; + yield ['/scan/lot/0', PartLot::class]; + yield ['/scan/location/0', StorageLocation::class]; } /** diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeHelperTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeHelperTest.php index e0639427..d681b3b9 100644 --- a/tests/Services/LabelSystem/Barcodes/BarcodeHelperTest.php +++ b/tests/Services/LabelSystem/Barcodes/BarcodeHelperTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\LabelSystem\Barcodes; use App\Entity\LabelSystem\BarcodeType; diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php index 08390896..b2b94bab 100644 --- a/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php +++ b/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php @@ -58,14 +58,12 @@ final class BarcodeRedirectorTest extends KernelTestCase $this->service = self::getContainer()->get(BarcodeRedirector::class); } - public static function urlDataProvider(): array + public static function urlDataProvider(): \Iterator { - return [ - [new BarcodeScanResult(LabelSupportedElement::PART, 1, BarcodeSourceType::INTERNAL), '/en/part/1'], - //Part lot redirects to Part info page (Part lot 1 is associated with part 3) - [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL), '/en/part/3'], - [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 1, BarcodeSourceType::INTERNAL), '/en/store_location/1/parts'], - ]; + yield [new BarcodeScanResult(LabelSupportedElement::PART, 1, BarcodeSourceType::INTERNAL), '/en/part/1']; + //Part lot redirects to Part info page (Part lot 1 is associated with part 3) + yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL), '/en/part/3']; + yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 1, BarcodeSourceType::INTERNAL), '/en/store_location/1/parts']; } /** diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php index 60a0a3f1..65cb02d4 100644 --- a/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php +++ b/tests/Services/LabelSystem/Barcodes/BarcodeScanHelperTest.php @@ -113,16 +113,19 @@ class BarcodeScanHelperTest extends WebTestCase 'lot2_vendor_barcode']; } - public static function invalidDataProvider(): array + public static function invalidDataProvider(): \Iterator { - return [ - ['https://localhost/part/1'], //Without scan - ['L-'], //Without number - ['L-123'], //Too short - ['X-123456'], //Unknown prefix - ['XXXWADSDF sdf'], //Garbage - [''], //Empty - ]; + yield ['https://localhost/part/1']; + //Without scan + yield ['L-']; + //Without number + yield ['L-123']; + //Too short + yield ['X-123456']; + //Unknown prefix + yield ['XXXWADSDF sdf']; + //Garbage + yield ['']; } /** diff --git a/tests/Services/LabelSystem/LabelGeneratorTest.php b/tests/Services/LabelSystem/LabelGeneratorTest.php index ff347a93..88ab97c5 100644 --- a/tests/Services/LabelSystem/LabelGeneratorTest.php +++ b/tests/Services/LabelSystem/LabelGeneratorTest.php @@ -63,13 +63,11 @@ class LabelGeneratorTest extends WebTestCase $this->service = self::getContainer()->get(LabelGenerator::class); } - public static function supportsDataProvider(): array + public static function supportsDataProvider(): \Iterator { - return [ - [LabelSupportedElement::PART, Part::class], - [LabelSupportedElement::PART_LOT, PartLot::class], - [LabelSupportedElement::STORELOCATION, StorageLocation::class], - ]; + yield [LabelSupportedElement::PART, Part::class]; + yield [LabelSupportedElement::PART_LOT, PartLot::class]; + yield [LabelSupportedElement::STORELOCATION, StorageLocation::class]; } /** diff --git a/tests/Services/LabelSystem/LabelTextReplacerTest.php b/tests/Services/LabelSystem/LabelTextReplacerTest.php index 0d9aa3ae..e5c7c26e 100644 --- a/tests/Services/LabelSystem/LabelTextReplacerTest.php +++ b/tests/Services/LabelSystem/LabelTextReplacerTest.php @@ -70,32 +70,28 @@ class LabelTextReplacerTest extends WebTestCase $this->target->setComment('P Comment'); } - public function handlePlaceholderDataProvider(): array + public function handlePlaceholderDataProvider(): \Iterator { - return [ - ['Part 1', '[[NAME]]'], - ['P Description', '[[DESCRIPTION]]'], - ['[[UNKNOWN]]', '[[UNKNOWN]]', '[[UNKNOWN]]'], - ['[[INVALID', '[[INVALID'], - ['[[', '[['], - ['NAME', 'NAME'], - ['[[NAME', '[[NAME'], - ['Test [[NAME]]', 'Test [[NAME]]', 'Test [[NAME]]'], - ]; + yield ['Part 1', '[[NAME]]']; + yield ['P Description', '[[DESCRIPTION]]']; + yield ['[[UNKNOWN]]', '[[UNKNOWN]]', '[[UNKNOWN]]']; + yield ['[[INVALID', '[[INVALID']; + yield ['[[', '[[']; + yield ['NAME', 'NAME']; + yield ['[[NAME', '[[NAME']; + yield ['Test [[NAME]]', 'Test [[NAME]]', 'Test [[NAME]]']; } - public function replaceDataProvider(): array + public function replaceDataProvider(): \Iterator { - return [ - ['Part 1', '[[NAME]]'], - ['TestPart 1', 'Test[[NAME]]'], - ["P Description\nPart 1", "[[DESCRIPTION_T]]\n[[NAME]]"], - ['Part 1 Part 1', '[[NAME]] [[NAME]]'], - ['[[UNKNOWN]] Test', '[[UNKNOWN]] Test'], - ["[[NAME\n]] [[NAME ]]", "[[NAME\n]] [[NAME ]]"], - ['[[]]', '[[]]'], - ['TEST[[ ]]TEST', 'TEST[[ ]]TEST'], - ]; + yield ['Part 1', '[[NAME]]']; + yield ['TestPart 1', 'Test[[NAME]]']; + yield ["P Description\nPart 1", "[[DESCRIPTION_T]]\n[[NAME]]"]; + yield ['Part 1 Part 1', '[[NAME]] [[NAME]]']; + yield ['[[UNKNOWN]] Test', '[[UNKNOWN]] Test']; + yield ["[[NAME\n]] [[NAME ]]", "[[NAME\n]] [[NAME ]]"]; + yield ['[[]]', '[[]]']; + yield ['TEST[[ ]]TEST', 'TEST[[ ]]TEST']; } /** diff --git a/tests/Services/LabelSystem/PlaceholderProviders/PartLotProviderTest.php b/tests/Services/LabelSystem/PlaceholderProviders/PartLotProviderTest.php index bfcf4c43..98285aa5 100644 --- a/tests/Services/LabelSystem/PlaceholderProviders/PartLotProviderTest.php +++ b/tests/Services/LabelSystem/PlaceholderProviders/PartLotProviderTest.php @@ -65,7 +65,7 @@ class PartLotProviderTest extends WebTestCase $this->target = new PartLot(); $this->target->setDescription('Lot description'); $this->target->setComment('Lot comment'); - $this->target->setExpirationDate(new \DateTime('1999-04-13')); + $this->target->setExpirationDate(new \DateTimeImmutable('1999-04-13')); $this->target->setInstockUnknown(true); $location = new StorageLocation(); diff --git a/tests/Services/LabelSystem/SandboxedTwigFactoryTest.php b/tests/Services/LabelSystem/SandboxedTwigFactoryTest.php index 7d43db7a..13a06b54 100644 --- a/tests/Services/LabelSystem/SandboxedTwigFactoryTest.php +++ b/tests/Services/LabelSystem/SandboxedTwigFactoryTest.php @@ -61,50 +61,46 @@ class SandboxedTwigFactoryTest extends WebTestCase $this->service = self::getContainer()->get(SandboxedTwigFactory::class); } - public function twigDataProvider(): array + public function twigDataProvider(): \Iterator { - return [ - [' {% for i in range(1, 3) %} + yield [' {% for i in range(1, 3) %} {{ part.id }} {{ part.name }} {{ part.lastModified | format_datetime }} {% endfor %} - '], - [' {% if part.category %} + ']; + yield [' {% if part.category %} {{ part.category }} {% endif %} - '], - [' {% set a = random(1, 3) %} + ']; + yield [' {% set a = random(1, 3) %} {{ 1 + 2 | abs }} {{ "test" | capitalize | escape | lower | raw }} {{ "\n" | nl2br | trim | title | url_encode | reverse }} - '], - [' + ']; + yield [' {{ location.isRoot}} {{ location.isChildOf(location) }} {{ location.comment }} {{ location.level }} {{ location.fullPath }} {% set arr = location.pathArray %} {% set child = location.children %} {{location.childrenNotSelectable}} - '], - [' + ']; + yield [' {{ part.reviewNeeded }} {{ part.tags }} {{ part.mass }} - '], - [' + ']; + yield [' {{ entity_type(part) is object }} - '], - [' + ']; + yield [' {% apply placeholders(part) %}[[NAME]]{% endapply %}
{{ placeholder("[[NAME]]", part) }} - '] - ]; + ']; } - public function twigNotAllowedDataProvider(): array + public function twigNotAllowedDataProvider(): \Iterator { - return [ - ['{% block test %} {% endblock %}'], - ['{% deprecated test %}'], - ['{% flush %}'], - ["{{ part.setName('test') }}"], - ['{{ part.setCategory(null) }}'], - ]; + yield ['{% block test %} {% endblock %}']; + yield ['{% deprecated test %}']; + yield ['{% flush %}']; + yield ["{{ part.setName('test') }}"]; + yield ['{{ part.setCategory(null) }}']; } /** diff --git a/tests/Services/Misc/FAIconGeneratorTest.php b/tests/Services/Misc/FAIconGeneratorTest.php index 4ca318d0..1bfc740c 100644 --- a/tests/Services/Misc/FAIconGeneratorTest.php +++ b/tests/Services/Misc/FAIconGeneratorTest.php @@ -41,60 +41,58 @@ class FAIconGeneratorTest extends WebTestCase $this->service = self::getContainer()->get(FAIconGenerator::class); } - public function fileExtensionDataProvider(): array + public function fileExtensionDataProvider(): \Iterator { - return [ - ['pdf', 'fa-file-pdf'], - ['jpeg','fa-file-image'], - ['txt', 'fa-file-lines'], - ['doc', 'fa-file-word'], - ['zip', 'fa-file-zipper'], - ['png', 'fa-file-image'], - ['jpg', 'fa-file-image'], - ['gif', 'fa-file-image'], - ['svg', 'fa-file-image'], - ['xls', 'fa-file-excel'], - ['xlsx', 'fa-file-excel'], - ['ppt', 'fa-file-powerpoint'], - ['pptx', 'fa-file-powerpoint'], - ['docx', 'fa-file-word'], - ['odt', 'fa-file-word'], - ['ods', 'fa-file-excel'], - ['odp', 'fa-file-powerpoint'], - ['py', 'fa-file-code'], - ['js', 'fa-file-code'], - ['html', 'fa-file-code'], - ['css', 'fa-file-code'], - ['xml', 'fa-file-code'], - ['json', 'fa-file-code'], - ['yml', 'fa-file-code'], - ['yaml', 'fa-file-code'], - ['csv', 'fa-file-csv'], - ['sql', 'fa-file-code'], - ['sh', 'fa-file-code'], - ['bat', 'fa-file-code'], - ['exe', 'fa-file-code'], - ['dll', 'fa-file-code'], - ['lib', 'fa-file-code'], - ['so', 'fa-file-code'], - ['a', 'fa-file-code'], - ['o', 'fa-file-code'], - ['class', 'fa-file-code'], - ['jar', 'fa-file-code'], - ['rar', 'fa-file-zipper'], - ['7z', 'fa-file-zipper'], - ['tar', 'fa-file-zipper'], - ['gz', 'fa-file-zipper'], - ['tgz', 'fa-file-zipper'], - ['bz2', 'fa-file-zipper'], - ['tbz', 'fa-file-zipper'], - ['xz', 'fa-file-zipper'], - ['txz', 'fa-file-zipper'], - ['zip', 'fa-file-zipper'], - ['php', 'fa-file-code'], - ['tmp', 'fa-file'], - ['fgd', 'fa-file'], - ]; + yield ['pdf', 'fa-file-pdf']; + yield ['jpeg','fa-file-image']; + yield ['txt', 'fa-file-lines']; + yield ['doc', 'fa-file-word']; + yield ['zip', 'fa-file-zipper']; + yield ['png', 'fa-file-image']; + yield ['jpg', 'fa-file-image']; + yield ['gif', 'fa-file-image']; + yield ['svg', 'fa-file-image']; + yield ['xls', 'fa-file-excel']; + yield ['xlsx', 'fa-file-excel']; + yield ['ppt', 'fa-file-powerpoint']; + yield ['pptx', 'fa-file-powerpoint']; + yield ['docx', 'fa-file-word']; + yield ['odt', 'fa-file-word']; + yield ['ods', 'fa-file-excel']; + yield ['odp', 'fa-file-powerpoint']; + yield ['py', 'fa-file-code']; + yield ['js', 'fa-file-code']; + yield ['html', 'fa-file-code']; + yield ['css', 'fa-file-code']; + yield ['xml', 'fa-file-code']; + yield ['json', 'fa-file-code']; + yield ['yml', 'fa-file-code']; + yield ['yaml', 'fa-file-code']; + yield ['csv', 'fa-file-csv']; + yield ['sql', 'fa-file-code']; + yield ['sh', 'fa-file-code']; + yield ['bat', 'fa-file-code']; + yield ['exe', 'fa-file-code']; + yield ['dll', 'fa-file-code']; + yield ['lib', 'fa-file-code']; + yield ['so', 'fa-file-code']; + yield ['a', 'fa-file-code']; + yield ['o', 'fa-file-code']; + yield ['class', 'fa-file-code']; + yield ['jar', 'fa-file-code']; + yield ['rar', 'fa-file-zipper']; + yield ['7z', 'fa-file-zipper']; + yield ['tar', 'fa-file-zipper']; + yield ['gz', 'fa-file-zipper']; + yield ['tgz', 'fa-file-zipper']; + yield ['bz2', 'fa-file-zipper']; + yield ['tbz', 'fa-file-zipper']; + yield ['xz', 'fa-file-zipper']; + yield ['txz', 'fa-file-zipper']; + yield ['zip', 'fa-file-zipper']; + yield ['php', 'fa-file-code']; + yield ['tmp', 'fa-file']; + yield ['fgd', 'fa-file']; } /** diff --git a/tests/Services/Misc/RangeParserTest.php b/tests/Services/Misc/RangeParserTest.php index 27f45e98..1a9b2146 100644 --- a/tests/Services/Misc/RangeParserTest.php +++ b/tests/Services/Misc/RangeParserTest.php @@ -81,21 +81,19 @@ class RangeParserTest extends WebTestCase yield [[], '1, 2, test', true]; } - public function validDataProvider(): array + public function validDataProvider(): \Iterator { - return [ - [true, ''], - [true, ' '], - [true, '1, 2, 3'], - [true, '1-2,3, 4- 5'], - [true, '1 -2, 3- 4, 6'], - [true, '1--2'], - [true, '1- -2'], - [true, ',,12,33'], - [false, 'test'], - [false, '1-2-3'], - [false, '1, 2 test'], - ]; + yield [true, '']; + yield [true, ' ']; + yield [true, '1, 2, 3']; + yield [true, '1-2,3, 4- 5']; + yield [true, '1 -2, 3- 4, 6']; + yield [true, '1--2']; + yield [true, '1- -2']; + yield [true, ',,12,33']; + yield [false, 'test']; + yield [false, '1-2-3']; + yield [false, '1, 2 test']; } /** diff --git a/tests/Services/Parameters/ParameterExtractorTest.php b/tests/Services/Parameters/ParameterExtractorTest.php index 842f8b80..148470d1 100644 --- a/tests/Services/Parameters/ParameterExtractorTest.php +++ b/tests/Services/Parameters/ParameterExtractorTest.php @@ -56,19 +56,17 @@ class ParameterExtractorTest extends WebTestCase $this->service = self::getContainer()->get(ParameterExtractor::class); } - public function emptyDataProvider(): array + public function emptyDataProvider(): \Iterator { - return [ - [''], - [' '], - ["\t\n"], - [':;'], - ['NPN Transistor'], - ['=BC547 rewr'], - ['For good, [b]bad[/b], evil'], - ['Param:; Test'], - ['A [link](https://demo.part-db.de) should not be matched'] - ]; + yield ['']; + yield [' ']; + yield ["\t\n"]; + yield [':;']; + yield ['NPN Transistor']; + yield ['=BC547 rewr']; + yield ['For good, [b]bad[/b], evil']; + yield ['Param:; Test']; + yield ['A [link](https://demo.part-db.de) should not be matched']; } /** diff --git a/tests/Services/Parts/PartLotWithdrawAddHelperTest.php b/tests/Services/Parts/PartLotWithdrawAddHelperTest.php index 05a88f49..697d3983 100644 --- a/tests/Services/Parts/PartLotWithdrawAddHelperTest.php +++ b/tests/Services/Parts/PartLotWithdrawAddHelperTest.php @@ -119,15 +119,15 @@ class PartLotWithdrawAddHelperTest extends WebTestCase { //Add 5 to lot 1 $this->service->add($this->partLot1, 5, "Test"); - $this->assertSame(15.0, $this->partLot1->getAmount()); + $this->assertEqualsWithDelta(15.0, $this->partLot1->getAmount(), PHP_FLOAT_EPSILON); //Add 3.2 to lot 2 $this->service->add($this->partLot2, 3.2, "Test"); - $this->assertSame(5.0, $this->partLot2->getAmount()); + $this->assertEqualsWithDelta(5.0, $this->partLot2->getAmount(), PHP_FLOAT_EPSILON); //Add 1.5 to lot 3 $this->service->add($this->partLot3, 1.5, "Test"); - $this->assertSame(2.0, $this->partLot3->getAmount()); + $this->assertEqualsWithDelta(2.0, $this->partLot3->getAmount(), PHP_FLOAT_EPSILON); } @@ -135,23 +135,23 @@ class PartLotWithdrawAddHelperTest extends WebTestCase { //Withdraw 5 from lot 1 $this->service->withdraw($this->partLot1, 5, "Test"); - $this->assertSame(5.0, $this->partLot1->getAmount()); + $this->assertEqualsWithDelta(5.0, $this->partLot1->getAmount(), PHP_FLOAT_EPSILON); //Withdraw 2.2 from lot 2 $this->service->withdraw($this->partLot2, 2.2, "Test"); - $this->assertSame(0.0, $this->partLot2->getAmount()); + $this->assertEqualsWithDelta(0.0, $this->partLot2->getAmount(), PHP_FLOAT_EPSILON); } public function testMove(): void { //Move 5 from lot 1 to lot 2 $this->service->move($this->partLot1, $this->partLot2, 5, "Test"); - $this->assertSame(5.0, $this->partLot1->getAmount()); - $this->assertSame(7.0, $this->partLot2->getAmount()); + $this->assertEqualsWithDelta(5.0, $this->partLot1->getAmount(), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(7.0, $this->partLot2->getAmount(), PHP_FLOAT_EPSILON); //Move 2.2 from lot 2 to lot 3 $this->service->move($this->partLot2, $this->partLot3, 2.2, "Test"); - $this->assertSame(5.0, $this->partLot2->getAmount()); - $this->assertSame(2.0, $this->partLot3->getAmount()); + $this->assertEqualsWithDelta(5.0, $this->partLot2->getAmount(), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(2.0, $this->partLot3->getAmount(), PHP_FLOAT_EPSILON); } } diff --git a/tests/Services/UserSystem/PermissionManagerTest.php b/tests/Services/UserSystem/PermissionManagerTest.php index 9d4aa11b..cfe74ce7 100644 --- a/tests/Services/UserSystem/PermissionManagerTest.php +++ b/tests/Services/UserSystem/PermissionManagerTest.php @@ -91,16 +91,14 @@ class PermissionManagerTest extends WebTestCase $this->group->method('getParent')->willReturn($parent_group); } - public function getPermissionNames(): array + public function getPermissionNames(): \Iterator { //List some permission names - return [ - ['parts'], - ['system'], - ['footprints'], - ['suppliers'], - ['tools'] - ]; + yield ['parts']; + yield ['system']; + yield ['footprints']; + yield ['suppliers']; + yield ['tools']; } /** diff --git a/tests/Services/UserSystem/TFA/BackupCodeGeneratorTest.php b/tests/Services/UserSystem/TFA/BackupCodeGeneratorTest.php index b731ec87..b489eec2 100644 --- a/tests/Services/UserSystem/TFA/BackupCodeGeneratorTest.php +++ b/tests/Services/UserSystem/TFA/BackupCodeGeneratorTest.php @@ -46,9 +46,12 @@ class BackupCodeGeneratorTest extends TestCase new BackupCodeGenerator(4, 10); } - public function codeLengthDataProvider(): array + public function codeLengthDataProvider(): \Iterator { - return [[6], [8], [10], [16]]; + yield [6]; + yield [8]; + yield [10]; + yield [16]; } /** @@ -60,9 +63,11 @@ class BackupCodeGeneratorTest extends TestCase $this->assertMatchesRegularExpression("/^([a-f0-9]){{$code_length}}\$/", $generator->generateSingleCode()); } - public function codeCountDataProvider(): array + public function codeCountDataProvider(): \Iterator { - return [[2], [8], [10]]; + yield [2]; + yield [8]; + yield [10]; } /** diff --git a/tests/Services/UserSystem/VoterHelperTest.php b/tests/Services/UserSystem/VoterHelperTest.php index 28bb9ba8..53d7ee82 100644 --- a/tests/Services/UserSystem/VoterHelperTest.php +++ b/tests/Services/UserSystem/VoterHelperTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Services\UserSystem; use App\Entity\UserSystem\ApiToken; diff --git a/tests/Twig/UserExtensionTest.php b/tests/Twig/UserExtensionTest.php index 1344bbc5..ea024bda 100644 --- a/tests/Twig/UserExtensionTest.php +++ b/tests/Twig/UserExtensionTest.php @@ -36,16 +36,18 @@ class UserExtensionTest extends WebTestCase $this->service = self::getContainer()->get(UserExtension::class); } - public function removeeLocaleFromPathDataSet(): ?\Generator + public function removeLocaleFromPathDataSet(): ?\Generator { yield ['/', '/de/']; yield ['/test', '/de/test']; yield ['/test/foo', '/en/test/foo']; yield ['/test/foo/bar?param1=val1¶m2=val2', '/en/test/foo/bar?param1=val1¶m2=val2']; + //Allow de_DE + yield ['/test/foo/bar?param1=val1¶m2=val2', '/de_DE/test/foo/bar?param1=val1¶m2=val2']; } /** - * @dataProvider removeeLocaleFromPathDataSet + * @dataProvider removeLocaleFromPathDataSet */ public function testRemoveLocaleFromPath(string $expected, string $input): void { diff --git a/tests/Validator/Constraints/NoneOfItsChildrenValidatorTest.php b/tests/Validator/Constraints/NoneOfItsChildrenValidatorTest.php index 39eb9336..0efcd5de 100644 --- a/tests/Validator/Constraints/NoneOfItsChildrenValidatorTest.php +++ b/tests/Validator/Constraints/NoneOfItsChildrenValidatorTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Validator\Constraints; use App\Entity\Attachments\AttachmentType; @@ -57,10 +59,6 @@ class NoneOfItsChildrenValidatorTest extends ConstraintValidatorTestCase $this->child1_2->setName('child1_2')->setParent($this->child1); } - private function getDummyObjects(): array - { - $obj1 = new AttachmentType(); - } protected function createValidator(): NoneOfItsChildrenValidator { diff --git a/tests/Validator/Constraints/SelectableValidatorTest.php b/tests/Validator/Constraints/SelectableValidatorTest.php index bb6f7bcc..bc520621 100644 --- a/tests/Validator/Constraints/SelectableValidatorTest.php +++ b/tests/Validator/Constraints/SelectableValidatorTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Validator\Constraints; use App\Entity\Attachments\AttachmentType; diff --git a/tests/Validator/Constraints/UniqueObjectCollectionValidatorTest.php b/tests/Validator/Constraints/UniqueObjectCollectionValidatorTest.php index bddd3d49..d9fab6cf 100644 --- a/tests/Validator/Constraints/UniqueObjectCollectionValidatorTest.php +++ b/tests/Validator/Constraints/UniqueObjectCollectionValidatorTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Validator\Constraints; use App\Tests\Validator\DummyUniqueValidatableObject; diff --git a/tests/Validator/Constraints/UrlOrBuiltinValidatorTest.php b/tests/Validator/Constraints/UrlOrBuiltinValidatorTest.php index de7e47d5..c75754df 100644 --- a/tests/Validator/Constraints/UrlOrBuiltinValidatorTest.php +++ b/tests/Validator/Constraints/UrlOrBuiltinValidatorTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Validator\Constraints; use App\Validator\Constraints\UrlOrBuiltin; diff --git a/tests/Validator/Constraints/ValidGoogleAuthCodeValidatorTest.php b/tests/Validator/Constraints/ValidGoogleAuthCodeValidatorTest.php index 5ba98a47..a1bc1a74 100644 --- a/tests/Validator/Constraints/ValidGoogleAuthCodeValidatorTest.php +++ b/tests/Validator/Constraints/ValidGoogleAuthCodeValidatorTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Validator\Constraints; use App\Entity\UserSystem\User; @@ -61,7 +63,7 @@ class ValidGoogleAuthCodeValidatorTest extends ConstraintValidatorTestCase { //Leave empty } - public function getUser(): ?\Symfony\Component\Security\Core\User\UserInterface + public function getUser(): ?UserInterface { return new class implements TwoFactorInterface, UserInterface { diff --git a/tests/Validator/Constraints/ValidThemeValidatorTest.php b/tests/Validator/Constraints/ValidThemeValidatorTest.php index 8ad95e7a..9db8f33b 100644 --- a/tests/Validator/Constraints/ValidThemeValidatorTest.php +++ b/tests/Validator/Constraints/ValidThemeValidatorTest.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Validator\Constraints; use App\Validator\Constraints\ValidTheme; diff --git a/tests/Validator/DummyUniqueValidatableObject.php b/tests/Validator/DummyUniqueValidatableObject.php index 7ca2565d..a7dd9d7a 100644 --- a/tests/Validator/DummyUniqueValidatableObject.php +++ b/tests/Validator/DummyUniqueValidatableObject.php @@ -1,4 +1,7 @@ . */ - namespace App\Tests\Validator; use App\Validator\UniqueValidatableInterface; @@ -38,4 +40,4 @@ class DummyUniqueValidatableObject implements UniqueValidatableInterface, \Strin { return 'objectString'; } -} \ No newline at end of file +} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index ea6923ac..9bbe0a34 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -7,7 +7,7 @@ Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:4 templates\AdminPages\AttachmentTypeAdmin.html.twig:4 - + attachment_type.caption File types for attachments @@ -17,7 +17,7 @@ Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:12 new - + attachment_type.edit Edit file type @@ -27,7 +27,7 @@ Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:16 new - + attachment_type.new New file type @@ -46,7 +46,7 @@ templates\base.html.twig:197 templates\base.html.twig:225 - + category.labelp Categories @@ -59,7 +59,7 @@ Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:11 templates\AdminPages\CategoryAdmin.html.twig:8 - + admin.options Options @@ -72,7 +72,7 @@ Part-DB1\templates\AdminPages\CompanyAdminBase.html.twig:15 templates\AdminPages\CategoryAdmin.html.twig:9 - + admin.advanced Advanced @@ -82,7 +82,7 @@ Part-DB1\templates\AdminPages\CategoryAdmin.html.twig:13 new - + category.edit Edit category @@ -92,7 +92,7 @@ Part-DB1\templates\AdminPages\CategoryAdmin.html.twig:17 new - + category.new New category @@ -102,7 +102,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:4 Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:4 - + currency.caption Currency @@ -112,7 +112,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:12 Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:12 - + currency.iso_code.caption ISO code @@ -122,7 +122,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:15 Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:15 - + currency.symbol.caption Currency symbol @@ -132,7 +132,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:29 new - + currency.edit Edit currency @@ -142,7 +142,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:33 new - + currency.new New currency @@ -153,7 +153,7 @@ Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:4 templates\AdminPages\DeviceAdmin.html.twig:4 - + project.caption Project @@ -163,7 +163,7 @@ Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:8 new - + project.edit Edit project @@ -173,7 +173,7 @@ Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:12 new - + project.new New project @@ -196,7 +196,7 @@ templates\base.html.twig:206 templates\base.html.twig:237 - + search.placeholder Search @@ -212,7 +212,7 @@ templates\base.html.twig:193 templates\base.html.twig:221 - + expandAll Expand All @@ -228,7 +228,7 @@ templates\base.html.twig:194 templates\base.html.twig:222 - + reduceAll Reduce All @@ -240,9 +240,9 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:54 Part-DB1\templates\Parts\info\_sidebar.html.twig:4 - + part.info.timetravel_hint - This is how the part appeared before %timestamp%. <i>Please note that this feature is experimental, so the info may not be correct.</i> + Please note that this feature is experimental, so the info may not be correct.]]> @@ -251,7 +251,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:60 templates\AdminPages\EntityAdminBase.html.twig:42 - + standard.label Properties @@ -262,7 +262,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:61 templates\AdminPages\EntityAdminBase.html.twig:43 - + infos.label Info @@ -273,7 +273,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:63 new - + history.label History @@ -284,7 +284,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:66 templates\AdminPages\EntityAdminBase.html.twig:45 - + export.label Export @@ -295,7 +295,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:68 templates\AdminPages\EntityAdminBase.html.twig:47 - + import_export.label Import / Export @@ -305,7 +305,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:69 Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:69 - + mass_creation.label Mass creation @@ -316,7 +316,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:82 templates\AdminPages\EntityAdminBase.html.twig:59 - + admin.common Common @@ -326,7 +326,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:86 Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:86 - + admin.attachments Attachments @@ -335,7 +335,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:90 - + admin.parameters Parameters @@ -346,7 +346,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:167 templates\AdminPages\EntityAdminBase.html.twig:142 - + export_all.label Export all elements @@ -356,7 +356,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:185 Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:173 - + mass_creation.help Each line will be interpreted as a name of an element, which will be created. You can create nested structures by indentations. @@ -367,7 +367,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:45 templates\AdminPages\EntityAdminBase.html.twig:35 - + edit.caption Edit element "%name" @@ -378,7 +378,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:50 templates\AdminPages\EntityAdminBase.html.twig:37 - + new.caption New element @@ -393,7 +393,7 @@ templates\base.html.twig:199 templates\base.html.twig:227 - + footprint.labelp Footprints @@ -403,7 +403,7 @@ Part-DB1\templates\AdminPages\FootprintAdmin.html.twig:13 new - + footprint.edit Edit footprint @@ -413,7 +413,7 @@ Part-DB1\templates\AdminPages\FootprintAdmin.html.twig:17 new - + footprint.new New footprint @@ -423,7 +423,7 @@ Part-DB1\templates\AdminPages\GroupAdmin.html.twig:4 Part-DB1\templates\AdminPages\GroupAdmin.html.twig:4 - + group.edit.caption Groups @@ -435,7 +435,7 @@ Part-DB1\templates\AdminPages\GroupAdmin.html.twig:9 Part-DB1\templates\AdminPages\UserAdmin.html.twig:16 - + user.edit.permissions Permissions @@ -445,7 +445,7 @@ Part-DB1\templates\AdminPages\GroupAdmin.html.twig:24 new - + group.edit Edit group @@ -455,7 +455,7 @@ Part-DB1\templates\AdminPages\GroupAdmin.html.twig:28 new - + group.new New group @@ -464,7 +464,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:4 - + label_profile.caption Label profiles @@ -473,7 +473,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:8 - + label_profile.advanced Advanced @@ -482,7 +482,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:9 - + label_profile.comment Notes @@ -492,7 +492,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:55 new - + label_profile.edit Edit label profile @@ -502,7 +502,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:59 new - + label_profile.new New label profile @@ -513,7 +513,7 @@ Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:4 templates\AdminPages\ManufacturerAdmin.html.twig:4 - + manufacturer.caption Manufacturers @@ -523,7 +523,7 @@ Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:8 new - + manufacturer.edit Edit manufacturer @@ -533,7 +533,7 @@ Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:12 new - + manufacturer.new New manufacturer @@ -543,7 +543,7 @@ Part-DB1\templates\AdminPages\MeasurementUnitAdmin.html.twig:4 Part-DB1\templates\AdminPages\MeasurementUnitAdmin.html.twig:4 - + measurement_unit.caption Measurement Unit @@ -558,7 +558,7 @@ templates\base.html.twig:198 templates\base.html.twig:226 - + storelocation.labelp Storage locations @@ -568,7 +568,7 @@ Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:32 new - + storelocation.edit Edit storage location @@ -578,7 +578,7 @@ Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:36 new - + storelocation.new New storage location @@ -589,7 +589,7 @@ Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:4 templates\AdminPages\SupplierAdmin.html.twig:4 - + supplier.caption Suppliers @@ -599,7 +599,7 @@ Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:16 new - + supplier.edit Edit supplier @@ -609,7 +609,7 @@ Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:20 new - + supplier.new New supplier @@ -619,7 +619,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:8 Part-DB1\templates\AdminPages\UserAdmin.html.twig:8 - + user.edit.caption Users @@ -629,7 +629,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:14 Part-DB1\templates\AdminPages\UserAdmin.html.twig:14 - + user.edit.configuration Configuration @@ -639,7 +639,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:15 Part-DB1\templates\AdminPages\UserAdmin.html.twig:15 - + user.edit.password Password @@ -649,7 +649,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:45 Part-DB1\templates\AdminPages\UserAdmin.html.twig:45 - + user.edit.tfa.caption Two-factor authentication @@ -659,7 +659,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:47 Part-DB1\templates\AdminPages\UserAdmin.html.twig:47 - + user.edit.tfa.google_active Authenticator app active @@ -673,7 +673,7 @@ Part-DB1\templates\Users\backup_codes.html.twig:15 Part-DB1\templates\Users\_2fa_settings.html.twig:95 - + tfa_backup.remaining_tokens Remaining backup codes count @@ -687,7 +687,7 @@ Part-DB1\templates\Users\backup_codes.html.twig:17 Part-DB1\templates\Users\_2fa_settings.html.twig:96 - + tfa_backup.generation_date Generation date of the backup codes @@ -699,7 +699,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:53 Part-DB1\templates\AdminPages\UserAdmin.html.twig:60 - + user.edit.tfa.disabled Method not enabled @@ -709,7 +709,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:56 Part-DB1\templates\AdminPages\UserAdmin.html.twig:56 - + user.edit.tfa.u2f_keys_count Active security keys @@ -719,7 +719,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:72 Part-DB1\templates\AdminPages\UserAdmin.html.twig:72 - + user.edit.tfa.disable_tfa_title Do you really want to proceed? @@ -729,12 +729,12 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:72 Part-DB1\templates\AdminPages\UserAdmin.html.twig:72 - + user.edit.tfa.disable_tfa_message - This will disable <b>all active two-factor authentication methods of the user</b> and delete the <b>backup codes</b>! -<br> -The user will have to set up all two-factor authentication methods again and print new backup codes! <br><br> -<b>Only do this if you are absolutely sure about the identity of the user (seeking help), otherwise the account could be compromised by an attacker!</b> + all active two-factor authentication methods of the user and delete the backup codes! +
+The user will have to set up all two-factor authentication methods again and print new backup codes!

+Only do this if you are absolutely sure about the identity of the user (seeking help), otherwise the account could be compromised by an attacker!]]>
@@ -742,7 +742,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\UserAdmin.html.twig:73 Part-DB1\templates\AdminPages\UserAdmin.html.twig:73 - + user.edit.tfa.disable_tfa.btn Disable all two-factor authentication methods @@ -752,7 +752,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\UserAdmin.html.twig:85 new - + user.edit Edit user @@ -762,7 +762,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\UserAdmin.html.twig:89 new - + user.new New user @@ -775,7 +775,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\edit\_attachments.html.twig:4 Part-DB1\templates\Parts\info\_attachments_info.html.twig:63 - + attachment.delete Delete @@ -789,7 +789,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\edit\_attachments.html.twig:38 Part-DB1\src\DataTables\AttachmentDataTable.php:159 - + attachment.external External @@ -801,7 +801,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\_attachments.html.twig:47 Part-DB1\templates\Parts\edit\_attachments.html.twig:45 - + attachment.preview.alt Attachment thumbnail @@ -815,7 +815,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\edit\_attachments.html.twig:48 Part-DB1\templates\Parts\info\_attachments_info.html.twig:45 - + attachment.view View @@ -831,7 +831,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\info\_attachments_info.html.twig:38 Part-DB1\src\DataTables\AttachmentDataTable.php:166 - + attachment.file_not_found File not found @@ -843,7 +843,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\info\_attachments_info.html.twig:48 Part-DB1\templates\Parts\edit\_attachments.html.twig:62 - + attachment.secure Private attachment @@ -855,7 +855,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\_attachments.html.twig:77 Part-DB1\templates\Parts\edit\_attachments.html.twig:75 - + attachment.create Add attachment @@ -869,7 +869,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\edit\_attachments.html.twig:80 Part-DB1\templates\Parts\edit\_lots.html.twig:33 - + part_lot.edit.delete.confirm Do you really want to delete this stock? This can not be undone! @@ -880,7 +880,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\_delete_form.html.twig:2 templates\AdminPages\_delete_form.html.twig:2 - + entity.delete.confirm_title You really want to delete %name%? @@ -891,11 +891,11 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\_delete_form.html.twig:3 templates\AdminPages\_delete_form.html.twig:3 - + entity.delete.message - This can not be undone! -<br> -Sub elements will be moved upwards. + +Sub elements will be moved upwards.]]> @@ -904,7 +904,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_delete_form.html.twig:11 templates\AdminPages\_delete_form.html.twig:9 - + entity.delete Delete element @@ -919,7 +919,7 @@ Sub elements will be moved upwards. Part-DB1\src\Form\Part\PartBaseType.php:267 new - + edit.log_comment Change comment @@ -930,7 +930,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_delete_form.html.twig:24 templates\AdminPages\_delete_form.html.twig:12 - + entity.delete.recursive Delete recursive (all sub elements) @@ -939,7 +939,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_duplicate.html.twig:3 - + entity.duplicate Duplicate element @@ -953,7 +953,7 @@ Sub elements will be moved upwards. templates\AdminPages\_export_form.html.twig:4 src\Form\ImportType.php:67 - + export.format File format @@ -964,7 +964,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:16 templates\AdminPages\_export_form.html.twig:16 - + export.level Verbosity level @@ -975,7 +975,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:19 templates\AdminPages\_export_form.html.twig:19 - + export.level.simple Simple @@ -986,7 +986,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:20 templates\AdminPages\_export_form.html.twig:20 - + export.level.extended Extended @@ -997,7 +997,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:21 templates\AdminPages\_export_form.html.twig:21 - + export.level.full Full @@ -1008,7 +1008,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:31 templates\AdminPages\_export_form.html.twig:31 - + export.include_children Include children elements in export @@ -1019,7 +1019,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:39 templates\AdminPages\_export_form.html.twig:39 - + export.btn Export @@ -1038,7 +1038,7 @@ Sub elements will be moved upwards. templates\Parts\edit_part_info.html.twig:12 templates\Parts\show_part_info.html.twig:11 - + id.label ID @@ -1062,7 +1062,7 @@ Sub elements will be moved upwards. templates\AdminPages\EntityAdminBase.html.twig:101 templates\Parts\show_part_info.html.twig:248 - + createdAt Created At @@ -1080,7 +1080,7 @@ Sub elements will be moved upwards. templates\AdminPages\EntityAdminBase.html.twig:114 templates\Parts\show_part_info.html.twig:263 - + lastModified Last modified @@ -1090,7 +1090,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_info.html.twig:38 Part-DB1\templates\AdminPages\_info.html.twig:38 - + entity.info.parts_count Number of parts with this element @@ -1101,7 +1101,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:125 Part-DB1\templates\Parts\edit\_specifications.html.twig:6 - + specifications.property Parameter @@ -1111,7 +1111,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:7 Part-DB1\templates\Parts\edit\_specifications.html.twig:7 - + specifications.symbol Symbol @@ -1121,7 +1121,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:8 Part-DB1\templates\Parts\edit\_specifications.html.twig:8 - + specifications.value_min Min. @@ -1131,7 +1131,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:9 Part-DB1\templates\Parts\edit\_specifications.html.twig:9 - + specifications.value_typ Typ. @@ -1141,7 +1141,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:10 Part-DB1\templates\Parts\edit\_specifications.html.twig:10 - + specifications.value_max Max. @@ -1151,7 +1151,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:11 Part-DB1\templates\Parts\edit\_specifications.html.twig:11 - + specifications.unit Unit @@ -1161,7 +1161,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:12 Part-DB1\templates\Parts\edit\_specifications.html.twig:12 - + specifications.text Text @@ -1171,7 +1171,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:13 Part-DB1\templates\Parts\edit\_specifications.html.twig:13 - + specifications.group Group @@ -1181,7 +1181,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:26 Part-DB1\templates\Parts\edit\_specifications.html.twig:26 - + specification.create New Parameter @@ -1191,7 +1191,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:31 Part-DB1\templates\Parts\edit\_specifications.html.twig:31 - + parameter.delete.confirm Do you really want to delete this parameter? @@ -1201,7 +1201,7 @@ Sub elements will be moved upwards. Part-DB1\templates\attachment_list.html.twig:3 Part-DB1\templates\attachment_list.html.twig:3 - + attachment.list.title Attachments list @@ -1215,7 +1215,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\_log_table.html.twig:8 Part-DB1\templates\Parts\lists\_parts_list.html.twig:6 - + part_list.loading.caption Loading @@ -1229,7 +1229,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\_log_table.html.twig:9 Part-DB1\templates\Parts\lists\_parts_list.html.twig:7 - + part_list.loading.message This can take a moment. If this message do not disappear, try to reload the page. @@ -1240,7 +1240,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:68 templates\base.html.twig:246 - + vendor.base.javascript_hint Please activate Javascript to use all features! @@ -1250,7 +1250,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:73 Part-DB1\templates\base.html.twig:73 - + sidebar.big.toggle Show/Hide sidebar @@ -1261,7 +1261,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:95 templates\base.html.twig:271 - + loading.caption Loading: @@ -1272,7 +1272,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:96 templates\base.html.twig:272 - + loading.message This can take a while. If this messages stays for a long time, try to reload the page. @@ -1283,7 +1283,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:101 templates\base.html.twig:277 - + loading.bar Loading... @@ -1294,7 +1294,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:112 templates\base.html.twig:288 - + back_to_top Back to page's top @@ -1304,7 +1304,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:35 Part-DB1\templates\Form\permissionLayout.html.twig:35 - + permission.edit.permission Permissions @@ -1314,7 +1314,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:36 Part-DB1\templates\Form\permissionLayout.html.twig:36 - + permission.edit.value Value @@ -1324,7 +1324,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:53 Part-DB1\templates\Form\permissionLayout.html.twig:53 - + permission.legend.title Explanation of the states @@ -1334,7 +1334,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:57 Part-DB1\templates\Form\permissionLayout.html.twig:57 - + permission.legend.disallow Forbidden @@ -1344,7 +1344,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:61 Part-DB1\templates\Form\permissionLayout.html.twig:61 - + permission.legend.allow Allowed @@ -1354,7 +1354,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:65 Part-DB1\templates\Form\permissionLayout.html.twig:65 - + permission.legend.inherit Inherit from (parent) group @@ -1364,7 +1364,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:3 Part-DB1\templates\helper.twig:3 - + bool.true True @@ -1374,7 +1374,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:5 Part-DB1\templates\helper.twig:5 - + bool.false False @@ -1384,7 +1384,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:92 Part-DB1\templates\helper.twig:87 - + Yes Yes @@ -1394,7 +1394,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:94 Part-DB1\templates\helper.twig:89 - + No No @@ -1403,7 +1403,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:126 - + specifications.value Value @@ -1414,7 +1414,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:7 templates\homepage.html.twig:7 - + version.caption Version @@ -1425,7 +1425,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:22 templates\homepage.html.twig:19 - + homepage.license License information @@ -1436,7 +1436,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:31 templates\homepage.html.twig:28 - + homepage.github.caption Project page @@ -1447,9 +1447,9 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:31 templates\homepage.html.twig:28 - + homepage.github.text - Source, downloads, bug reports, to-do-list etc. can be found on <a href="%href%" class="link-external" target="_blank">GitHub project page</a> + GitHub project page]]> @@ -1458,7 +1458,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:32 templates\homepage.html.twig:29 - + homepage.help.caption Help @@ -1469,9 +1469,9 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:32 templates\homepage.html.twig:29 - + homepage.help.text - Help and tips can be found in Wiki the <a href="%href%" class="link-external" target="_blank">GitHub page</a> + GitHub page]]> @@ -1480,7 +1480,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:33 templates\homepage.html.twig:30 - + homepage.forum.caption Forum @@ -1491,7 +1491,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:45 new - + homepage.last_activity Last activity @@ -1501,7 +1501,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:3 Part-DB1\templates\LabelSystem\dialog.html.twig:6 - + label_generator.title Label generator @@ -1510,7 +1510,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:16 - + label_generator.common Common @@ -1519,7 +1519,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:20 - + label_generator.advanced Advanced @@ -1528,7 +1528,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:24 - + label_generator.profiles Profiles @@ -1537,7 +1537,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:58 - + label_generator.selected_profile Currently selected profile @@ -1546,7 +1546,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:62 - + label_generator.edit_profile Edit profile @@ -1555,7 +1555,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:75 - + label_generator.load_profile Load profile @@ -1564,7 +1564,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:102 - + label_generator.download Download @@ -1574,7 +1574,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dropdown_macro.html.twig:3 Part-DB1\templates\LabelSystem\dropdown_macro.html.twig:5 - + label_generator.label_btn Generate label @@ -1583,7 +1583,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dropdown_macro.html.twig:20 - + label_generator.label_empty New empty label @@ -1592,7 +1592,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:3 - + label_scanner.title Label scanner @@ -1601,7 +1601,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:7 - + label_scanner.no_cam_found.title No webcam found @@ -1610,7 +1610,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:7 - + label_scanner.no_cam_found.text You need a webcam and give permission to use the scanner function. You can input the barcode code manually below. @@ -1619,7 +1619,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:27 - + label_scanner.source_select Select source @@ -1629,7 +1629,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\log_list.html.twig:3 Part-DB1\templates\LogSystem\log_list.html.twig:3 - + log.list.title System log @@ -1640,7 +1640,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\_log_table.html.twig:1 new - + log.undo.confirm_title Really undo change / revert to timestamp? @@ -1651,7 +1651,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\_log_table.html.twig:2 new - + log.undo.confirm_message Do you really want to undo the given change / reset the element to the given timestamp? @@ -1661,7 +1661,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\base.html.twig:24 Part-DB1\templates\mail\base.html.twig:24 - + mail.footer.email_sent_by This email was sent automatically by @@ -1671,7 +1671,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\base.html.twig:24 Part-DB1\templates\mail\base.html.twig:24 - + mail.footer.dont_reply Do not answer to this email. @@ -1681,7 +1681,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:6 Part-DB1\templates\mail\pw_reset.html.twig:6 - + email.hi %name% Hi %name% @@ -1691,7 +1691,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:7 Part-DB1\templates\mail\pw_reset.html.twig:7 - + email.pw_reset.message somebody (hopefully you) requested a reset of your password. If this request was not made by you, ignore this mail. @@ -1701,7 +1701,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:9 Part-DB1\templates\mail\pw_reset.html.twig:9 - + email.pw_reset.button Click here to reset password @@ -1711,9 +1711,9 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:11 Part-DB1\templates\mail\pw_reset.html.twig:11 - + email.pw_reset.fallback - If this does not work for you, go to <a href="%url%">%url%</a> and enter the following info + %url% and enter the following info]]> @@ -1721,7 +1721,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:16 Part-DB1\templates\mail\pw_reset.html.twig:16 - + email.pw_reset.username Username @@ -1731,7 +1731,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:19 Part-DB1\templates\mail\pw_reset.html.twig:19 - + email.pw_reset.token Token @@ -1741,9 +1741,9 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:24 Part-DB1\templates\mail\pw_reset.html.twig:24 - + email.pw_reset.valid_unit %date% - The reset token will be valid until <i>%date%</i>. + %date%.]]> @@ -1753,7 +1753,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:78 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:58 - + orderdetail.delete Delete @@ -1763,7 +1763,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:39 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:39 - + pricedetails.edit.min_qty Minimum discount quantity @@ -1773,7 +1773,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:40 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:40 - + pricedetails.edit.price Price @@ -1783,7 +1783,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:41 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:41 - + pricedetails.edit.price_qty for amount @@ -1793,7 +1793,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:54 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:54 - + pricedetail.create Add price @@ -1804,7 +1804,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:4 templates\Parts\edit_part_info.html.twig:4 - + part.edit.title Edit part @@ -1815,7 +1815,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:9 templates\Parts\edit_part_info.html.twig:9 - + part.edit.card_title Edit part @@ -1825,7 +1825,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:22 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:22 - + part.edit.tab.common Common @@ -1835,7 +1835,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:28 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:28 - + part.edit.tab.manufacturer Manufacturer @@ -1845,7 +1845,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:34 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:34 - + part.edit.tab.advanced Advanced @@ -1855,7 +1855,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40 - + part.edit.tab.part_lots Stocks @@ -1865,7 +1865,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:46 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:46 - + part.edit.tab.attachments Attachments @@ -1875,7 +1875,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:52 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:52 - + part.edit.tab.orderdetails Purchase information @@ -1884,7 +1884,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:58 - + part.edit.tab.specifications Parameters @@ -1894,7 +1894,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:64 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:58 - + part.edit.tab.comment Notes @@ -1905,7 +1905,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\new_part.html.twig:8 templates\Parts\new_part.html.twig:8 - + part.new.card_title Create new part @@ -1915,7 +1915,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_lots.html.twig:5 Part-DB1\templates\Parts\edit\_lots.html.twig:5 - + part_lot.delete Delete @@ -1925,7 +1925,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_lots.html.twig:28 Part-DB1\templates\Parts\edit\_lots.html.twig:28 - + part_lot.create Add stock @@ -1935,7 +1935,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_orderdetails.html.twig:13 Part-DB1\templates\Parts\edit\_orderdetails.html.twig:13 - + orderdetail.create Add distributor @@ -1945,7 +1945,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_orderdetails.html.twig:18 Part-DB1\templates\Parts\edit\_orderdetails.html.twig:18 - + pricedetails.edit.delete.confirm Do you really want to delete this price? This can not be undone. @@ -1955,7 +1955,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_orderdetails.html.twig:62 Part-DB1\templates\Parts\edit\_orderdetails.html.twig:61 - + orderdetails.edit.delete.confirm Do you really want to delete this distributor info? This can not be undone! @@ -1969,7 +1969,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:4 templates\Parts\show_part_info.html.twig:9 - + part.info.title Detail info for part @@ -1979,7 +1979,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:47 Part-DB1\templates\Parts\info\show_part_info.html.twig:47 - + part.part_lots.label Stocks @@ -1994,7 +1994,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:74 src\Form\PartType.php:86 - + comment.label Notes @@ -2003,7 +2003,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:64 - + part.info.specifications Parameters @@ -2014,7 +2014,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:64 templates\Parts\show_part_info.html.twig:82 - + attachment.labelp Attachments @@ -2025,7 +2025,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:71 templates\Parts\show_part_info.html.twig:88 - + vendor.partinfo.shopping_infos Shopping information @@ -2036,7 +2036,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:78 templates\Parts\show_part_info.html.twig:94 - + vendor.partinfo.history History @@ -2055,7 +2055,7 @@ Sub elements will be moved upwards. templates\base.html.twig:231 templates\Parts\show_part_info.html.twig:100 - + tools.label Tools @@ -2065,7 +2065,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:103 Part-DB1\templates\Parts\info\show_part_info.html.twig:90 - + extended_info.label Extended info @@ -2075,7 +2075,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:7 Part-DB1\templates\Parts\info\_attachments_info.html.twig:7 - + attachment.name Name @@ -2085,7 +2085,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:8 Part-DB1\templates\Parts\info\_attachments_info.html.twig:8 - + attachment.attachment_type Attachment Type @@ -2095,7 +2095,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:9 Part-DB1\templates\Parts\info\_attachments_info.html.twig:9 - + attachment.file_name File name @@ -2105,7 +2105,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:10 Part-DB1\templates\Parts\info\_attachments_info.html.twig:10 - + attachment.file_size File size @@ -2114,7 +2114,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:54 - + attachment.preview Preview picture @@ -2124,7 +2124,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:67 Part-DB1\templates\Parts\info\_attachments_info.html.twig:50 - + attachment.download Download @@ -2135,7 +2135,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:11 new - + user.creating_user User who created this part @@ -2149,7 +2149,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:28 Part-DB1\templates\Parts\info\_extended_infos.html.twig:50 - + Unknown Unknown @@ -2162,7 +2162,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:30 new - + accessDenied Access Denied @@ -2173,7 +2173,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:26 new - + user.last_editing_user User who edited this part last @@ -2183,7 +2183,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:41 Part-DB1\templates\Parts\info\_extended_infos.html.twig:41 - + part.isFavorite Favorite @@ -2193,7 +2193,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:46 Part-DB1\templates\Parts\info\_extended_infos.html.twig:46 - + part.minOrderAmount Minimum order amount @@ -2210,7 +2210,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:24 src\Form\PartType.php:80 - + manufacturer.label Manufacturer @@ -2222,7 +2222,7 @@ Sub elements will be moved upwards. templates\base.html.twig:54 src\Form\PartType.php:62 - + name.label Name @@ -2233,7 +2233,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_main_infos.html.twig:27 new - + part.back_to_info Back to current version @@ -2248,7 +2248,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:31 src\Form\PartType.php:65 - + description.label Description @@ -2265,7 +2265,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:32 src\Form\PartType.php:74 - + category.label Category @@ -2277,7 +2277,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:42 src\Form\PartType.php:69 - + instock.label Instock @@ -2289,7 +2289,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:44 src\Form\PartType.php:72 - + mininstock.label Minimum Instock @@ -2305,7 +2305,7 @@ Sub elements will be moved upwards. templates\base.html.twig:73 templates\Parts\show_part_info.html.twig:47 - + footprint.label Footprint @@ -2318,7 +2318,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_main_infos.html.twig:60 templates\Parts\show_part_info.html.twig:51 - + part.avg_price.label Average Price @@ -2328,7 +2328,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:5 Part-DB1\templates\Parts\info\_order_infos.html.twig:5 - + part.supplier.name Name @@ -2338,7 +2338,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:6 Part-DB1\templates\Parts\info\_order_infos.html.twig:6 - + part.supplier.partnr Partnr. @@ -2348,7 +2348,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:28 Part-DB1\templates\Parts\info\_order_infos.html.twig:28 - + part.order.minamount Minimum amount @@ -2358,7 +2358,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:29 Part-DB1\templates\Parts\info\_order_infos.html.twig:29 - + part.order.price Price @@ -2368,7 +2368,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:31 Part-DB1\templates\Parts\info\_order_infos.html.twig:31 - + part.order.single_price Unit Price @@ -2378,7 +2378,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:71 Part-DB1\templates\Parts\info\_order_infos.html.twig:71 - + edit.caption_short Edit @@ -2388,7 +2388,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:72 Part-DB1\templates\Parts\info\_order_infos.html.twig:72 - + delete.caption Delete @@ -2398,7 +2398,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:7 Part-DB1\templates\Parts\info\_part_lots.html.twig:6 - + part_lots.description Description @@ -2408,7 +2408,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:8 Part-DB1\templates\Parts\info\_part_lots.html.twig:7 - + part_lots.storage_location Storage location @@ -2418,7 +2418,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:9 Part-DB1\templates\Parts\info\_part_lots.html.twig:8 - + part_lots.amount Amount @@ -2428,7 +2428,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:24 Part-DB1\templates\Parts\info\_part_lots.html.twig:22 - + part_lots.location_unknown Storage location unknown @@ -2438,7 +2438,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:31 Part-DB1\templates\Parts\info\_part_lots.html.twig:29 - + part_lots.instock_unknown Amount unknown @@ -2448,7 +2448,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:40 Part-DB1\templates\Parts\info\_part_lots.html.twig:38 - + part_lots.expiration_date Expiration date @@ -2458,7 +2458,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:48 Part-DB1\templates\Parts\info\_part_lots.html.twig:46 - + part_lots.is_expired Expired @@ -2468,7 +2468,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:55 Part-DB1\templates\Parts\info\_part_lots.html.twig:53 - + part_lots.need_refill Needs refill @@ -2478,7 +2478,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_picture.html.twig:15 Part-DB1\templates\Parts\info\_picture.html.twig:15 - + part.info.prev_picture Previous picture @@ -2488,7 +2488,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_picture.html.twig:19 Part-DB1\templates\Parts\info\_picture.html.twig:19 - + part.info.next_picture Next picture @@ -2498,7 +2498,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_sidebar.html.twig:21 Part-DB1\templates\Parts\info\_sidebar.html.twig:21 - + part.mass.tooltip Mass @@ -2508,7 +2508,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_sidebar.html.twig:30 Part-DB1\templates\Parts\info\_sidebar.html.twig:30 - + part.needs_review.badge Needs review @@ -2518,7 +2518,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_sidebar.html.twig:39 Part-DB1\templates\Parts\info\_sidebar.html.twig:39 - + part.favorite.badge Favorite @@ -2528,7 +2528,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_sidebar.html.twig:47 Part-DB1\templates\Parts\info\_sidebar.html.twig:47 - + part.obsolete.badge No longer available @@ -2537,7 +2537,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_specifications.html.twig:10 - + parameters.extracted_from_description Automatically extracted from description @@ -2546,7 +2546,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_specifications.html.twig:15 - + parameters.auto_extracted_from_comment Automatically extracted from notes @@ -2557,7 +2557,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:4 templates\Parts\show_part_info.html.twig:125 - + part.edit.btn Edit part @@ -2568,7 +2568,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:14 templates\Parts\show_part_info.html.twig:135 - + part.clone.btn Clone part @@ -2579,7 +2579,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_action_bar.html.twig:4 templates\Parts\show_part_info.html.twig:143 - + part.create.btn Create new part @@ -2589,7 +2589,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:31 Part-DB1\templates\Parts\info\_tools.html.twig:29 - + part.delete.confirm_title Do you really want to delete this part? @@ -2599,7 +2599,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:32 Part-DB1\templates\Parts\info\_tools.html.twig:30 - + part.delete.message This part and any associated information (like attachments, price information, etc.) will be deleted. This can not be undone! @@ -2609,7 +2609,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:39 Part-DB1\templates\Parts\info\_tools.html.twig:37 - + part.delete Delete part @@ -2619,7 +2619,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\all_list.html.twig:4 Part-DB1\templates\Parts\lists\all_list.html.twig:4 - + parts_list.all.title All parts @@ -2629,7 +2629,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\category_list.html.twig:4 Part-DB1\templates\Parts\lists\category_list.html.twig:4 - + parts_list.category.title Parts with category @@ -2639,7 +2639,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\footprint_list.html.twig:4 Part-DB1\templates\Parts\lists\footprint_list.html.twig:4 - + parts_list.footprint.title Parts with footprint @@ -2649,7 +2649,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\manufacturer_list.html.twig:4 Part-DB1\templates\Parts\lists\manufacturer_list.html.twig:4 - + parts_list.manufacturer.title Parts with manufacturer @@ -2659,7 +2659,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\search_list.html.twig:4 Part-DB1\templates\Parts\lists\search_list.html.twig:4 - + parts_list.search.title Search Parts @@ -2669,7 +2669,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\store_location_list.html.twig:4 Part-DB1\templates\Parts\lists\store_location_list.html.twig:4 - + parts_list.storelocation.title Parts with storage locations @@ -2679,7 +2679,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\supplier_list.html.twig:4 Part-DB1\templates\Parts\lists\supplier_list.html.twig:4 - + parts_list.supplier.title Parts with supplier @@ -2689,7 +2689,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\tags_list.html.twig:4 Part-DB1\templates\Parts\lists\tags_list.html.twig:4 - + parts_list.tags.title Parts with tag @@ -2699,7 +2699,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:22 Part-DB1\templates\Parts\lists\_info_card.html.twig:17 - + entity.info.common.tab Common @@ -2709,7 +2709,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:26 Part-DB1\templates\Parts\lists\_info_card.html.twig:20 - + entity.info.statistics.tab Statistics @@ -2718,7 +2718,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:31 - + entity.info.attachments.tab Attachments @@ -2727,7 +2727,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:37 - + entity.info.parameters.tab Parameters @@ -2737,7 +2737,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:54 Part-DB1\templates\Parts\lists\_info_card.html.twig:30 - + entity.info.name Name @@ -2749,7 +2749,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:34 Part-DB1\templates\Parts\lists\_info_card.html.twig:67 - + entity.info.parent Parent @@ -2759,7 +2759,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:70 Part-DB1\templates\Parts\lists\_info_card.html.twig:46 - + entity.edit.btn Edit @@ -2769,7 +2769,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:92 Part-DB1\templates\Parts\lists\_info_card.html.twig:63 - + entity.info.children_count Count of children elements @@ -2781,7 +2781,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_base_form.html.twig:3 Part-DB1\templates\security\2fa_base_form.html.twig:5 - + tfa.check.title Two-factor authentication needed @@ -2791,7 +2791,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_base_form.html.twig:39 Part-DB1\templates\security\2fa_base_form.html.twig:39 - + tfa.code.trusted_pc This is a trusted computer (if this is enabled, no further two-factor queries are performed on this computer) @@ -2803,7 +2803,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_base_form.html.twig:52 Part-DB1\templates\security\login.html.twig:58 - + login.btn Login @@ -2817,7 +2817,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_login.html.twig:13 Part-DB1\templates\_navbar.html.twig:40 - + user.logout Logout @@ -2827,7 +2827,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_form.html.twig:6 Part-DB1\templates\security\2fa_form.html.twig:6 - + tfa.check.code.label Authenticator app code @@ -2837,7 +2837,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_form.html.twig:10 Part-DB1\templates\security\2fa_form.html.twig:10 - + tfa.check.code.help Enter the 6-digit code from your Authenticator app or one of your backup codes if the Authenticator is not available. @@ -2848,7 +2848,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:3 templates\security\login.html.twig:3 - + login.title Login @@ -2859,7 +2859,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:7 templates\security\login.html.twig:7 - + login.card_title Login @@ -2870,7 +2870,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:31 templates\security\login.html.twig:31 - + login.username.label Username @@ -2881,7 +2881,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:34 templates\security\login.html.twig:34 - + login.username.placeholder Username @@ -2892,7 +2892,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:38 templates\security\login.html.twig:38 - + login.password.label Password @@ -2903,7 +2903,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:40 templates\security\login.html.twig:40 - + login.password.placeholder Password @@ -2914,7 +2914,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:50 templates\security\login.html.twig:50 - + login.rememberme Remember me (should not be used on shared computers) @@ -2924,7 +2924,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:64 Part-DB1\templates\security\login.html.twig:64 - + pw_reset.password_forget Forgot username/password? @@ -2934,7 +2934,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\pw_reset_new_pw.html.twig:5 Part-DB1\templates\security\pw_reset_new_pw.html.twig:5 - + pw_reset.new_pw.header.title Set new password @@ -2944,7 +2944,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\pw_reset_request.html.twig:5 Part-DB1\templates\security\pw_reset_request.html.twig:5 - + pw_reset.request.header.title Request a new password @@ -2956,7 +2956,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_login.html.twig:7 Part-DB1\templates\security\U2F\u2f_register.html.twig:10 - + tfa_u2f.http_warning You are accessing this page using the insecure HTTP method, so U2F will most likely not work (Bad Request error message). Ask an administrator to set up the secure HTTPS method if you want to use security keys. @@ -2968,7 +2968,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_login.html.twig:10 Part-DB1\templates\security\U2F\u2f_register.html.twig:22 - + r_u2f_two_factor.pressbutton Please plug in your security key and press its button! @@ -2978,7 +2978,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:3 Part-DB1\templates\security\U2F\u2f_register.html.twig:3 - + tfa_u2f.add_key.title Add security key @@ -2990,7 +2990,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:6 Part-DB1\templates\Users\_2fa_settings.html.twig:111 - + tfa_u2f.explanation With the help of a U2F/FIDO compatible security key (e.g. YubiKey or NitroKey), user-friendly and secure two-factor authentication can be achieved. The security keys can be registered here, and if two-factor verification is required, the key only needs to be inserted via USB or typed against the device via NFC. @@ -3000,7 +3000,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:7 Part-DB1\templates\security\U2F\u2f_register.html.twig:7 - + tfa_u2f.add_key.backup_hint To ensure access even if the key is lost, it is recommended to register a second key as backup and store it in a safe place! @@ -3010,7 +3010,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:16 Part-DB1\templates\security\U2F\u2f_register.html.twig:16 - + r_u2f_two_factor.name Shown key name (e.g. Backup) @@ -3020,7 +3020,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:19 Part-DB1\templates\security\U2F\u2f_register.html.twig:19 - + tfa_u2f.add_key.add_button Add security key @@ -3030,7 +3030,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:27 Part-DB1\templates\security\U2F\u2f_register.html.twig:27 - + tfa_u2f.add_key.back_to_settings Back to settings @@ -3043,7 +3043,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:8 new - + statistics.title Statistics @@ -3054,7 +3054,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:14 new - + statistics.parts Parts @@ -3065,7 +3065,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:19 new - + statistics.data_structures Data structures @@ -3076,7 +3076,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:24 new - + statistics.attachments Attachments @@ -3091,7 +3091,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:104 new - + statistics.property Property @@ -3106,7 +3106,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:105 new - + statistics.value Value @@ -3117,7 +3117,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:40 new - + statistics.distinct_parts_count Number of distinct parts @@ -3128,7 +3128,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:44 new - + statistics.parts_instock_sum Sum of all parts stocks @@ -3139,7 +3139,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:48 new - + statistics.parts_with_price Number of parts with price information @@ -3150,7 +3150,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:65 new - + statistics.categories_count Number of categories @@ -3161,7 +3161,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:69 new - + statistics.footprints_count Number of footprints @@ -3172,7 +3172,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:73 new - + statistics.manufacturers_count Number of manufacturers @@ -3183,7 +3183,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:77 new - + statistics.storelocations_count Number of storage locations @@ -3194,7 +3194,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:81 new - + statistics.suppliers_count Number of suppliers @@ -3205,7 +3205,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:85 new - + statistics.currencies_count Number of currencies @@ -3216,7 +3216,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:89 new - + statistics.measurement_units_count Number of measurement units @@ -3227,7 +3227,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:93 new - + statistics.devices_count Number of projects @@ -3238,7 +3238,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:110 new - + statistics.attachment_types_count Number of attachment types @@ -3249,7 +3249,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:114 new - + statistics.all_attachments_count Number of all attachments @@ -3260,7 +3260,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:118 new - + statistics.user_uploaded_attachments_count Number of user uploaded attachments @@ -3271,7 +3271,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:122 new - + statistics.private_attachments_count Number of private attachments @@ -3282,7 +3282,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:126 new - + statistics.external_attachments_count Number of external attachments (URL) @@ -3294,7 +3294,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:3 Part-DB1\templates\Users\backup_codes.html.twig:9 - + tfa_backup.codes.title Backup codes @@ -3304,7 +3304,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:12 Part-DB1\templates\Users\backup_codes.html.twig:12 - + tfa_backup.codes.explanation Print out these codes and keep them in a safe place! @@ -3314,7 +3314,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:13 Part-DB1\templates\Users\backup_codes.html.twig:13 - + tfa_backup.codes.help If you no longer have access to your device with the Authenticator App (lost smartphone, data loss, etc.) you can use one of these codes to access your account and possibly set up a new Authenticator App. Each of these codes can be used once, it is recommended to delete used codes. Anyone with access to these codes can potentially access your account, so keep them in a safe place. @@ -3324,7 +3324,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:16 Part-DB1\templates\Users\backup_codes.html.twig:16 - + tfa_backup.username Username @@ -3334,7 +3334,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:29 Part-DB1\templates\Users\backup_codes.html.twig:29 - + tfa_backup.codes.page_generated_on Page generated on %date% @@ -3344,7 +3344,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:32 Part-DB1\templates\Users\backup_codes.html.twig:32 - + tfa_backup.codes.print Print @@ -3354,7 +3354,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:35 Part-DB1\templates\Users\backup_codes.html.twig:35 - + tfa_backup.codes.copy_clipboard Copy to clipboard @@ -3371,7 +3371,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:3 templates\Users\user_info.html.twig:6 - + user.info.label User information @@ -3385,7 +3385,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:18 src\Form\UserSettingsType.php:32 - + user.firstName.label First name @@ -3399,7 +3399,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:24 src\Form\UserSettingsType.php:35 - + user.lastName.label Last name @@ -3413,7 +3413,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:30 src\Form\UserSettingsType.php:41 - + user.email.label Email @@ -3427,7 +3427,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:37 src\Form\UserSettingsType.php:38 - + user.department.label Department @@ -3441,7 +3441,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:47 src\Form\UserSettingsType.php:30 - + user.username.label Username @@ -3454,7 +3454,7 @@ Sub elements will be moved upwards. Part-DB1\src\Services\ElementTypeNameGenerator.php:93 templates\Users\user_info.html.twig:53 - + group.label Group: @@ -3464,7 +3464,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\user_info.html.twig:67 Part-DB1\templates\Users\user_info.html.twig:67 - + user.permissions Permissions @@ -3481,7 +3481,7 @@ Sub elements will be moved upwards. templates\Users\user_settings.html.twig:3 templates\Users\user_settings.html.twig:6 - + user.settings.label User settings @@ -3492,7 +3492,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\user_settings.html.twig:18 templates\Users\user_settings.html.twig:14 - + user_settings.data.label Personal data @@ -3503,7 +3503,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\user_settings.html.twig:22 templates\Users\user_settings.html.twig:18 - + user_settings.configuration.label Configuration @@ -3514,7 +3514,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\user_settings.html.twig:55 templates\Users\user_settings.html.twig:48 - + user.settings.change_pw Change password @@ -3524,7 +3524,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:6 Part-DB1\templates\Users\_2fa_settings.html.twig:6 - + user.settings.2fa_settings Two-Factor Authentication @@ -3534,7 +3534,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:13 Part-DB1\templates\Users\_2fa_settings.html.twig:13 - + tfa.settings.google.tab Authenticator app @@ -3544,7 +3544,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:17 Part-DB1\templates\Users\_2fa_settings.html.twig:17 - + tfa.settings.bakup.tab Backup codes @@ -3554,7 +3554,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:21 Part-DB1\templates\Users\_2fa_settings.html.twig:21 - + tfa.settings.u2f.tab Security keys (U2F) @@ -3564,7 +3564,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:25 Part-DB1\templates\Users\_2fa_settings.html.twig:25 - + tfa.settings.trustedDevices.tab Trusted devices @@ -3574,7 +3574,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:33 Part-DB1\templates\Users\_2fa_settings.html.twig:33 - + tfa_google.disable.confirm_title Do you really want to disable the Authenticator App? @@ -3584,10 +3584,10 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:33 Part-DB1\templates\Users\_2fa_settings.html.twig:33 - + tfa_google.disable.confirm_message - If you disable the Authenticator App, all backup codes will be deleted, so you may need to reprint them.<br> -Also note that without two-factor authentication, your account is no longer as well protected against attackers! + +Also note that without two-factor authentication, your account is no longer as well protected against attackers!]]> @@ -3595,7 +3595,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:39 Part-DB1\templates\Users\_2fa_settings.html.twig:39 - + tfa_google.disabled_message Authenticator app deactivated! @@ -3605,9 +3605,9 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:48 Part-DB1\templates\Users\_2fa_settings.html.twig:48 - + tfa_google.step.download - Download an authenticator app (e.g. <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Google Authenticator</a> oder <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp">FreeOTP Authenticator</a>) + Google Authenticator oder FreeOTP Authenticator)]]> @@ -3615,7 +3615,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:49 Part-DB1\templates\Users\_2fa_settings.html.twig:49 - + tfa_google.step.scan Scan the adjacent QR Code with the app or enter the data manually @@ -3625,7 +3625,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:50 Part-DB1\templates\Users\_2fa_settings.html.twig:50 - + tfa_google.step.input_code Enter the generated code in the field below and confirm @@ -3635,7 +3635,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:51 Part-DB1\templates\Users\_2fa_settings.html.twig:51 - + tfa_google.step.download_backup Print out your backup codes and store them in a safe place @@ -3645,7 +3645,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:58 Part-DB1\templates\Users\_2fa_settings.html.twig:58 - + tfa_google.manual_setup Manual setup @@ -3655,7 +3655,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:62 Part-DB1\templates\Users\_2fa_settings.html.twig:62 - + tfa_google.manual_setup.type Type @@ -3665,7 +3665,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:63 Part-DB1\templates\Users\_2fa_settings.html.twig:63 - + tfa_google.manual_setup.username Username @@ -3675,7 +3675,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:64 Part-DB1\templates\Users\_2fa_settings.html.twig:64 - + tfa_google.manual_setup.secret Secret @@ -3685,7 +3685,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:65 Part-DB1\templates\Users\_2fa_settings.html.twig:65 - + tfa_google.manual_setup.digit_count Digit count @@ -3695,7 +3695,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:74 Part-DB1\templates\Users\_2fa_settings.html.twig:74 - + tfa_google.enabled_message Authenticator App enabled @@ -3705,7 +3705,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:83 Part-DB1\templates\Users\_2fa_settings.html.twig:83 - + tfa_backup.disabled Backup codes disabled. Setup authenticator app to enable backup codes. @@ -3717,7 +3717,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:84 Part-DB1\templates\Users\_2fa_settings.html.twig:92 - + tfa_backup.explanation You can use these backup codes to access your account even if you lose the device with the Authenticator App. Print out the codes and keep them in a safe place. @@ -3727,7 +3727,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:88 Part-DB1\templates\Users\_2fa_settings.html.twig:88 - + tfa_backup.reset_codes.confirm_title Really reset codes? @@ -3737,7 +3737,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:88 Part-DB1\templates\Users\_2fa_settings.html.twig:88 - + tfa_backup.reset_codes.confirm_message This will delete all previous codes and generate a set of new codes. This cannot be undone. Remember to print out the new codes and store them in a safe place! @@ -3747,7 +3747,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:91 Part-DB1\templates\Users\_2fa_settings.html.twig:91 - + tfa_backup.enabled Backup codes enabled @@ -3757,7 +3757,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:99 Part-DB1\templates\Users\_2fa_settings.html.twig:99 - + tfa_backup.show_codes Show backup codes @@ -3767,7 +3767,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:114 Part-DB1\templates\Users\_2fa_settings.html.twig:114 - + tfa_u2f.table_caption Registered security keys @@ -3777,7 +3777,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:115 Part-DB1\templates\Users\_2fa_settings.html.twig:115 - + tfa_u2f.delete_u2f.confirm_title Really remove this security key? @@ -3787,7 +3787,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:116 Part-DB1\templates\Users\_2fa_settings.html.twig:116 - + tfa_u2f.delete_u2f.confirm_message If you remove this key, then no more login with this key will be possible. If no security keys remain, two-factor authentication will be disabled. @@ -3797,7 +3797,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:123 Part-DB1\templates\Users\_2fa_settings.html.twig:123 - + tfa_u2f.keys.name Key name @@ -3807,7 +3807,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:124 Part-DB1\templates\Users\_2fa_settings.html.twig:124 - + tfa_u2f.keys.added_date Registration date @@ -3817,7 +3817,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:134 Part-DB1\templates\Users\_2fa_settings.html.twig:134 - + tfa_u2f.key_delete Delete key @@ -3827,7 +3827,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:141 Part-DB1\templates\Users\_2fa_settings.html.twig:141 - + tfa_u2f.no_keys_registered No keys registered yet. @@ -3837,7 +3837,7 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:144 Part-DB1\templates\Users\_2fa_settings.html.twig:144 - + tfa_u2f.add_new_key Register new security key @@ -3847,10 +3847,10 @@ Also note that without two-factor authentication, your account is no longer as w Part-DB1\templates\Users\_2fa_settings.html.twig:148 Part-DB1\templates\Users\_2fa_settings.html.twig:148 - + tfa_trustedDevices.explanation - When checking the second factor, the current computer can be marked as trustworthy, so no more two-factor checks on this computer are needed. -If you have done this incorrectly or if a computer is no longer trusted, you can reset the status of <i>all </i>computers here. + all computers here.]]> @@ -3858,7 +3858,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Users\_2fa_settings.html.twig:149 Part-DB1\templates\Users\_2fa_settings.html.twig:149 - + tfa_trustedDevices.invalidate.confirm_title Really remove all trusted computers? @@ -3868,7 +3868,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Users\_2fa_settings.html.twig:150 Part-DB1\templates\Users\_2fa_settings.html.twig:150 - + tfa_trustedDevices.invalidate.confirm_message You will have to perform two-factor authentication again on all computers. Make sure you have your two-factor device at hand. @@ -3878,7 +3878,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Users\_2fa_settings.html.twig:154 Part-DB1\templates\Users\_2fa_settings.html.twig:154 - + tfa_trustedDevices.invalidate.btn Reset trusted devices @@ -3889,7 +3889,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:4 templates\base.html.twig:29 - + sidebar.toggle Toggle Sidebar @@ -3898,7 +3898,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:22 - + navbar.scanner.link Scanner @@ -3909,7 +3909,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:36 templates\base.html.twig:97 - + user.loggedin.label Logged in as @@ -3920,7 +3920,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:42 templates\base.html.twig:103 - + user.login Login @@ -3930,7 +3930,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:50 Part-DB1\templates\_navbar.html.twig:48 - + ui.toggle_darkmode Darkmode @@ -3944,7 +3944,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:106 src\Form\UserSettingsType.php:44 - + user.language_select Switch Language @@ -3955,7 +3955,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:4 templates\base.html.twig:49 - + search.options.label Search options @@ -3964,7 +3964,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:23 - + tags.label Tags @@ -3979,7 +3979,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\Parts\show_part_info.html.twig:36 src\Form\PartType.php:77 - + storelocation.label Storage location @@ -3990,7 +3990,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:31 templates\base.html.twig:65 - + ordernumber.label.short supplier partnr. @@ -4003,7 +4003,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:89 templates\base.html.twig:67 - + supplier.label Supplier @@ -4014,7 +4014,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:52 templates\base.html.twig:75 - + search.deactivateBarcode Deact. Barcode @@ -4025,7 +4025,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:56 templates\base.html.twig:77 - + search.regexmatching Reg.Ex. Matching @@ -4035,7 +4035,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:68 Part-DB1\templates\_navbar_search.html.twig:62 - + search.submit Go! @@ -4051,7 +4051,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:202 templates\base.html.twig:230 - + project.labelp Projects @@ -4064,7 +4064,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:192 templates\base.html.twig:220 - + actions Actions @@ -4077,7 +4077,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:196 templates\base.html.twig:224 - + datasource Data source @@ -4090,7 +4090,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:200 templates\base.html.twig:228 - + manufacturer.labelp Manufacturers @@ -4103,7 +4103,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:201 templates\base.html.twig:229 - + supplier.labelp Suppliers @@ -4119,7 +4119,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:173 Part-DB1\src\Controller\PartController.php:268 - + attachment.download_failed Download of the external attachment failed. @@ -4129,7 +4129,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:222 Part-DB1\src\Controller\AdminPages\BaseAdminController.php:190 - + entity.edit_flash Changes saved successful. @@ -4139,7 +4139,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:231 Part-DB1\src\Controller\AdminPages\BaseAdminController.php:196 - + entity.edit_flash.invalid Can not save changed. Please check your input! @@ -4149,7 +4149,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:302 Part-DB1\src\Controller\AdminPages\BaseAdminController.php:252 - + entity.created_flash Element created. @@ -4159,7 +4159,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:308 Part-DB1\src\Controller\AdminPages\BaseAdminController.php:258 - + entity.created_flash.invalid Could not create element. Please check your input! @@ -4170,7 +4170,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:352 src\Controller\BaseAdminController.php:154 - + attachment_type.deleted Element deleted! @@ -4186,7 +4186,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:150 Part-DB1\src\Controller\UserSettingsController.php:182 - + csfr_invalid CSFR Token invalid. Please reload this page or contact an administrator if this message stays. @@ -4195,7 +4195,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LabelController.php:125 - + label_generator.no_entities_found No entities matching the range found. @@ -4206,7 +4206,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:154 new - + log.undo.target_not_found Target element could not be found in DB! @@ -4217,7 +4217,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:160 new - + log.undo.revert_success Reverted to timestamp successfully. @@ -4228,7 +4228,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:180 new - + log.undo.element_undelete_success Undeleted element successfully. @@ -4239,7 +4239,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:182 new - + log.undo.element_element_already_undeleted Element was already undeleted! @@ -4250,7 +4250,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:189 new - + log.undo.element_delete_success Element deleted successfully. @@ -4261,7 +4261,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:191 new - + log.undo.element.element_already_delted Element was already deleted! @@ -4272,7 +4272,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:198 new - + log.undo.element_change_undone Change undone successfully! @@ -4283,7 +4283,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:200 new - + log.undo.do_undelete_before You have to undelete the element before you can undo this change! @@ -4294,7 +4294,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:203 new - + log.undo.log_type_invalid This log entry can not be undone! @@ -4305,7 +4305,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:182 src\Controller\PartController.php:80 - + part.edited_flash Saved changes! @@ -4315,7 +4315,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:186 Part-DB1\src\Controller\PartController.php:186 - + part.edited_flash.invalid Error during saving: Please check your inputs! @@ -4325,7 +4325,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:216 Part-DB1\src\Controller\PartController.php:219 - + part.deleted Part deleted successful. @@ -4338,7 +4338,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can src\Controller\PartController.php:113 src\Controller\PartController.php:142 - + part.created_flash Part created! @@ -4348,7 +4348,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:308 Part-DB1\src\Controller\PartController.php:283 - + part.created_flash.invalid Error during creation: Please check your inputs! @@ -4358,7 +4358,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\ScanController.php:68 Part-DB1\src\Controller\ScanController.php:90 - + scan.qr_not_found No element found for the given barcode. @@ -4367,7 +4367,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\ScanController.php:71 - + scan.format_unknown Format unknown! @@ -4376,7 +4376,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\ScanController.php:86 - + scan.qr_success Element found. @@ -4386,7 +4386,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:114 Part-DB1\src\Controller\SecurityController.php:109 - + pw_reset.user_or_email Username / Email @@ -4396,7 +4396,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:131 Part-DB1\src\Controller\SecurityController.php:126 - + pw_reset.request.success Reset request was successful! Please check your emails for further instructions. @@ -4406,7 +4406,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:162 Part-DB1\src\Controller\SecurityController.php:160 - + pw_reset.username Username @@ -4416,7 +4416,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:165 Part-DB1\src\Controller\SecurityController.php:163 - + pw_reset.token Token @@ -4426,7 +4426,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:194 Part-DB1\src\Controller\SecurityController.php:192 - + pw_reset.new_pw.error Username or Token invalid! Please check your input. @@ -4436,7 +4436,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:196 Part-DB1\src\Controller\SecurityController.php:194 - + pw_reset.new_pw.success Password was reset successfully. You can now login with your new password. @@ -4446,7 +4446,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserController.php:107 Part-DB1\src\Controller\UserController.php:99 - + user.edit.reset_success All two-factor authentication methods were successfully disabled. @@ -4456,7 +4456,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:101 Part-DB1\src\Controller\UserSettingsController.php:92 - + tfa_backup.no_codes_enabled No backup codes enabled! @@ -4466,7 +4466,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:138 Part-DB1\src\Controller\UserSettingsController.php:132 - + tfa_u2f.u2f_delete.not_existing No security key with this ID is existing. @@ -4476,7 +4476,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:145 Part-DB1\src\Controller\UserSettingsController.php:139 - + tfa_u2f.u2f_delete.access_denied You can not delete the security keys of other users! @@ -4486,7 +4486,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:153 Part-DB1\src\Controller\UserSettingsController.php:147 - + tfa.u2f.u2f_delete.success Security key successfully removed. @@ -4496,7 +4496,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:188 Part-DB1\src\Controller\UserSettingsController.php:180 - + tfa_trustedDevice.invalidate.success Trusted devices successfully reset. @@ -4507,7 +4507,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:226 src\Controller\UserController.php:98 - + user.settings.saved_flash Settings saved! @@ -4518,7 +4518,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:288 src\Controller\UserController.php:130 - + user.settings.pw_changed_flash Password changed! @@ -4528,7 +4528,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:317 Part-DB1\src\Controller\UserSettingsController.php:306 - + user.settings.2fa.google.activated Authenticator App successfully activated. @@ -4538,7 +4538,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:328 Part-DB1\src\Controller\UserSettingsController.php:315 - + user.settings.2fa.google.disabled Authenticator App successfully deactivated. @@ -4548,7 +4548,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:346 Part-DB1\src\Controller\UserSettingsController.php:332 - + user.settings.2fa.backup_codes.regenerated New backup codes successfully generated. @@ -4558,7 +4558,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\AttachmentDataTable.php:148 Part-DB1\src\DataTables\AttachmentDataTable.php:148 - + attachment.table.filename File name @@ -4568,7 +4568,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\AttachmentDataTable.php:153 Part-DB1\src\DataTables\AttachmentDataTable.php:153 - + attachment.table.filesize File size @@ -4588,7 +4588,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:193 Part-DB1\src\DataTables\PartsDataTable.php:200 - + true true @@ -4610,7 +4610,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:201 Part-DB1\src\Form\Type\SIUnitType.php:139 - + false false @@ -4620,7 +4620,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\Column\LogEntryTargetColumn.php:128 Part-DB1\src\DataTables\Column\LogEntryTargetColumn.php:119 - + log.target_deleted deleted @@ -4631,7 +4631,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\Column\RevertLogColumn.php:60 new - + log.undo.undelete Undelete element @@ -4642,7 +4642,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\Column\RevertLogColumn.php:66 new - + log.undo.undo Undo change @@ -4653,7 +4653,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\Column\RevertLogColumn.php:86 new - + log.undo.revert Revert element to this timestamp @@ -4663,7 +4663,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:173 Part-DB1\src\DataTables\LogDataTable.php:161 - + log.id ID @@ -4673,7 +4673,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:178 Part-DB1\src\DataTables\LogDataTable.php:166 - + log.timestamp Timestamp @@ -4683,7 +4683,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:183 Part-DB1\src\DataTables\LogDataTable.php:171 - + log.type Event @@ -4693,7 +4693,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:191 Part-DB1\src\DataTables\LogDataTable.php:179 - + log.level Level @@ -4703,7 +4703,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:200 Part-DB1\src\DataTables\LogDataTable.php:188 - + log.user User @@ -4713,7 +4713,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:213 Part-DB1\src\DataTables\LogDataTable.php:201 - + log.target_type Target type @@ -4723,7 +4723,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:226 Part-DB1\src\DataTables\LogDataTable.php:214 - + log.target Target @@ -4734,7 +4734,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:218 new - + log.extra Extra @@ -4744,7 +4744,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:168 Part-DB1\src\DataTables\PartsDataTable.php:116 - + part.table.name Name @@ -4754,7 +4754,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:178 Part-DB1\src\DataTables\PartsDataTable.php:126 - + part.table.id Id @@ -4764,7 +4764,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:182 Part-DB1\src\DataTables\PartsDataTable.php:130 - + part.table.description Description @@ -4774,7 +4774,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:185 Part-DB1\src\DataTables\PartsDataTable.php:133 - + part.table.category Category @@ -4784,7 +4784,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:190 Part-DB1\src\DataTables\PartsDataTable.php:138 - + part.table.footprint Footprint @@ -4794,7 +4794,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:194 Part-DB1\src\DataTables\PartsDataTable.php:142 - + part.table.manufacturer Manufacturer @@ -4804,7 +4804,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:197 Part-DB1\src\DataTables\PartsDataTable.php:145 - + part.table.storeLocations Storage locations @@ -4814,7 +4814,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:216 Part-DB1\src\DataTables\PartsDataTable.php:164 - + part.table.amount Amount @@ -4824,7 +4824,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:224 Part-DB1\src\DataTables\PartsDataTable.php:172 - + part.table.minamount Min. Amount @@ -4834,7 +4834,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:232 Part-DB1\src\DataTables\PartsDataTable.php:180 - + part.table.partUnit Measurement Unit @@ -4844,7 +4844,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:236 Part-DB1\src\DataTables\PartsDataTable.php:184 - + part.table.addedDate Created at @@ -4854,7 +4854,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:240 Part-DB1\src\DataTables\PartsDataTable.php:188 - + part.table.lastModified Last modified @@ -4864,7 +4864,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:244 Part-DB1\src\DataTables\PartsDataTable.php:192 - + part.table.needsReview Needs review @@ -4874,7 +4874,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:251 Part-DB1\src\DataTables\PartsDataTable.php:199 - + part.table.favorite Favorite @@ -4884,7 +4884,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:258 Part-DB1\src\DataTables\PartsDataTable.php:206 - + part.table.manufacturingStatus Status @@ -4898,7 +4898,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:210 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.unknown Unknown @@ -4910,7 +4910,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:211 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.announced Announced @@ -4922,7 +4922,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:212 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.active Active @@ -4934,7 +4934,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:213 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.nrfnd Not recommended for new designs @@ -4946,7 +4946,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:214 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.eol End of life @@ -4958,7 +4958,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:215 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.discontinued Discontinued @@ -4968,7 +4968,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:271 Part-DB1\src\DataTables\PartsDataTable.php:219 - + part.table.mpn MPN @@ -4978,7 +4978,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:275 Part-DB1\src\DataTables\PartsDataTable.php:223 - + part.table.mass Mass @@ -4988,7 +4988,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:279 Part-DB1\src\DataTables\PartsDataTable.php:227 - + part.table.tags Tags @@ -4998,7 +4998,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:283 Part-DB1\src\DataTables\PartsDataTable.php:231 - + part.table.attachments Attachments @@ -5008,7 +5008,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\EventSubscriber\UserSystem\LoginSuccessSubscriber.php:82 Part-DB1\src\EventSubscriber\LoginSuccessListener.php:82 - + flash.login_successful Login successful @@ -5019,7 +5019,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:77 src\Form\ImportType.php:68 - + JSON JSON @@ -5030,7 +5030,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:77 src\Form\ImportType.php:68 - + XML XML @@ -5041,7 +5041,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:77 src\Form\ImportType.php:68 - + CSV CSV @@ -5052,7 +5052,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:77 src\Form\ImportType.php:68 - + YAML YAML @@ -5062,7 +5062,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:124 Part-DB1\src\Form\AdminPages\ImportType.php:124 - + import.abort_on_validation.help When this option is activated, the whole import process is aborted if invalid data is detected. If not selected, the invalid data is ignored and the importer will try to import the other elements. @@ -5073,7 +5073,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:86 src\Form\ImportType.php:70 - + import.csv_separator CSV separator @@ -5084,7 +5084,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:93 src\Form\ImportType.php:72 - + parent.label Parent element @@ -5095,7 +5095,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:101 src\Form\ImportType.php:75 - + import.file File @@ -5106,7 +5106,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:111 src\Form\ImportType.php:78 - + import.preserve_children Preserve child elements on import @@ -5117,7 +5117,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:120 src\Form\ImportType.php:80 - + import.abort_on_validation Abort on invalid data @@ -5128,7 +5128,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:132 src\Form\ImportType.php:85 - + import.btn Import @@ -5138,7 +5138,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:113 Part-DB1\src\Form\AttachmentFormType.php:109 - + attachment.edit.secure_file.help An attachment marked private can only be accessed by authenticated users with the proper permission. If this is activated no thumbnails are generated and access to file is less performant. @@ -5148,7 +5148,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:127 Part-DB1\src\Form\AttachmentFormType.php:123 - + attachment.edit.url.help You can specify an URL to an external file here, or input an keyword which is used to search in built-in resources (e.g. footprints) @@ -5158,7 +5158,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:82 Part-DB1\src\Form\AttachmentFormType.php:79 - + attachment.edit.name Name @@ -5168,7 +5168,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:85 Part-DB1\src\Form\AttachmentFormType.php:82 - + attachment.edit.attachment_type Attachment type @@ -5178,7 +5178,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:94 Part-DB1\src\Form\AttachmentFormType.php:91 - + attachment.edit.show_in_table Show in table @@ -5188,7 +5188,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:105 Part-DB1\src\Form\AttachmentFormType.php:102 - + attachment.edit.secure_file Private attachment @@ -5198,7 +5198,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:119 Part-DB1\src\Form\AttachmentFormType.php:115 - + attachment.edit.url URL @@ -5208,7 +5208,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:133 Part-DB1\src\Form\AttachmentFormType.php:129 - + attachment.edit.download_url Download external file @@ -5218,7 +5218,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:146 Part-DB1\src\Form\AttachmentFormType.php:142 - + attachment.edit.file Upload file @@ -5228,7 +5228,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:68 Part-DB1\src\Services\ElementTypeNameGenerator.php:86 - + part.label Part @@ -5238,7 +5238,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:68 Part-DB1\src\Services\ElementTypeNameGenerator.php:87 - + part_lot.label Part lot @@ -5247,7 +5247,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.none None @@ -5256,7 +5256,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.qr QR Code (recommended) @@ -5265,7 +5265,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.code128 Code 128 (recommended) @@ -5274,7 +5274,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.code39 Code 39 (recommended) @@ -5283,7 +5283,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.code93 Code 93 @@ -5292,7 +5292,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.datamatrix Datamatrix @@ -5301,7 +5301,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:122 - + label_options.lines_mode.html Placeholders @@ -5310,7 +5310,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:122 - + label.options.lines_mode.twig Twig @@ -5319,16 +5319,16 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:126 - + label_options.lines_mode.help - If you select Twig here, the content field is interpreted as Twig template. See <a href="https://twig.symfony.com/doc/3.x/templates.html">Twig documentation</a> and <a href="https://docs.part-db.de/usage/labels.html#twig-mode">Wiki</a> for more information. + Twig documentation and Wiki for more information.]]> Part-DB1\src\Form\LabelOptionsType.php:47 - + label_options.page_size.label Label size @@ -5337,7 +5337,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:66 - + label_options.supported_elements.label Target type @@ -5346,7 +5346,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:75 - + label_options.barcode_type.label Barcode @@ -5355,7 +5355,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:102 - + label_profile.lines.label Content @@ -5364,7 +5364,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:111 - + label_options.additional_css.label Additional styles (CSS) @@ -5373,7 +5373,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:120 - + label_options.lines_mode.label Parser mode @@ -5382,7 +5382,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:51 - + label_options.width.placeholder Width @@ -5391,7 +5391,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:60 - + label_options.height.placeholder Height @@ -5400,7 +5400,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\LabelDialogType.php:49 - + label_generator.target_id.range_hint You can specify multiple IDs (e.g. 1,2,3) and/or a range (1-3) here to generate labels for multiple elements at once. @@ -5409,7 +5409,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\LabelDialogType.php:46 - + label_generator.target_id.label Target IDs @@ -5418,7 +5418,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\LabelDialogType.php:59 - + label_generator.update Update @@ -5427,7 +5427,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\ScanDialogType.php:36 - + scan_dialog.input Input @@ -5436,7 +5436,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\ScanDialogType.php:44 - + scan_dialog.submit Submit @@ -5445,7 +5445,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:41 - + parameters.name.placeholder e.g. DC Current Gain @@ -5454,7 +5454,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:50 - + parameters.symbol.placeholder e.g. h_{FE} @@ -5463,7 +5463,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:60 - + parameters.text.placeholder e.g. Test conditions @@ -5472,7 +5472,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:71 - + parameters.max.placeholder e.g. 350 @@ -5481,7 +5481,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:82 - + parameters.min.placeholder e.g. 100 @@ -5490,7 +5490,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:93 - + parameters.typical.placeholder e.g. 200 @@ -5499,7 +5499,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:103 - + parameters.unit.placeholder e.g. V @@ -5508,7 +5508,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:114 - + parameter.group.placeholder e.g. Technical Specifications @@ -5518,7 +5518,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:72 Part-DB1\src\Form\Part\OrderdetailType.php:75 - + orderdetails.edit.supplierpartnr Supplier part number @@ -5528,7 +5528,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:81 Part-DB1\src\Form\Part\OrderdetailType.php:84 - + orderdetails.edit.supplier Supplier @@ -5538,7 +5538,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:87 Part-DB1\src\Form\Part\OrderdetailType.php:90 - + orderdetails.edit.url Link to offer @@ -5548,7 +5548,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:93 Part-DB1\src\Form\Part\OrderdetailType.php:96 - + orderdetails.edit.obsolete No longer available @@ -5558,7 +5558,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:75 Part-DB1\src\Form\Part\OrderdetailType.php:78 - + orderdetails.edit.supplierpartnr.placeholder e.g. BC 547 @@ -5568,7 +5568,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:101 Part-DB1\src\Form\Part\PartBaseType.php:99 - + part.edit.name Name @@ -5578,7 +5578,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:109 Part-DB1\src\Form\Part\PartBaseType.php:107 - + part.edit.description Description @@ -5588,7 +5588,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:120 Part-DB1\src\Form\Part\PartBaseType.php:118 - + part.edit.mininstock Minimum stock @@ -5598,7 +5598,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:129 Part-DB1\src\Form\Part\PartBaseType.php:127 - + part.edit.category Category @@ -5608,7 +5608,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:135 Part-DB1\src\Form\Part\PartBaseType.php:133 - + part.edit.footprint Footprint @@ -5618,7 +5618,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:142 Part-DB1\src\Form\Part\PartBaseType.php:140 - + part.edit.tags Tags @@ -5628,7 +5628,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:154 Part-DB1\src\Form\Part\PartBaseType.php:152 - + part.edit.manufacturer.label Manufacturer @@ -5638,7 +5638,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:161 Part-DB1\src\Form\Part\PartBaseType.php:159 - + part.edit.manufacturer_url.label Link to product page @@ -5648,7 +5648,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:167 Part-DB1\src\Form\Part\PartBaseType.php:165 - + part.edit.mpn Manufacturer part number @@ -5658,7 +5658,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:173 Part-DB1\src\Form\Part\PartBaseType.php:171 - + part.edit.manufacturing_status Manufacturing status @@ -5668,7 +5668,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:181 Part-DB1\src\Form\Part\PartBaseType.php:179 - + part.edit.needs_review Needs review @@ -5678,7 +5678,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:189 Part-DB1\src\Form\Part\PartBaseType.php:187 - + part.edit.is_favorite Favorite @@ -5688,7 +5688,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:197 Part-DB1\src\Form\Part\PartBaseType.php:195 - + part.edit.mass Mass @@ -5698,7 +5698,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:203 Part-DB1\src\Form\Part\PartBaseType.php:201 - + part.edit.partUnit Measuring unit @@ -5708,7 +5708,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:212 Part-DB1\src\Form\Part\PartBaseType.php:210 - + part.edit.comment Notes @@ -5718,7 +5718,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:250 Part-DB1\src\Form\Part\PartBaseType.php:246 - + part.edit.master_attachment Preview image @@ -5729,7 +5729,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:276 src\Form\PartType.php:91 - + part.edit.save Save changes @@ -5740,7 +5740,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:277 src\Form\PartType.php:92 - + part.edit.reset Reset changes @@ -5750,7 +5750,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:105 Part-DB1\src\Form\Part\PartBaseType.php:103 - + part.edit.name.placeholder e.g. BC547 @@ -5760,7 +5760,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:115 Part-DB1\src\Form\Part\PartBaseType.php:113 - + part.edit.description.placeholder e.g. NPN 45V, 0,1A, 0,5W @@ -5770,7 +5770,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:123 Part-DB1\src\Form\Part\PartBaseType.php:121 - + part.editmininstock.placeholder e.g. 1 @@ -5780,7 +5780,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:69 Part-DB1\src\Form\Part\PartLotType.php:69 - + part_lot.edit.description Description @@ -5790,7 +5790,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:78 Part-DB1\src\Form\Part\PartLotType.php:78 - + part_lot.edit.location Storage location @@ -5800,7 +5800,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:89 Part-DB1\src\Form\Part\PartLotType.php:89 - + part_lot.edit.amount Amount @@ -5810,7 +5810,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:98 Part-DB1\src\Form\Part\PartLotType.php:97 - + part_lot.edit.instock_unknown Amount unknown @@ -5820,7 +5820,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:109 Part-DB1\src\Form\Part\PartLotType.php:108 - + part_lot.edit.needs_refill Needs refill @@ -5830,7 +5830,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:120 Part-DB1\src\Form\Part\PartLotType.php:119 - + part_lot.edit.expiration_date Expiration date @@ -5840,7 +5840,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:128 Part-DB1\src\Form\Part\PartLotType.php:125 - + part_lot.edit.comment Notes @@ -5850,7 +5850,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Permissions\PermissionsType.php:99 Part-DB1\src\Form\Permissions\PermissionsType.php:99 - + perm.group.other Miscellaneous @@ -5860,7 +5860,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\TFAGoogleSettingsType.php:97 Part-DB1\src\Form\TFAGoogleSettingsType.php:97 - + tfa_google.enable Enable authenticator app @@ -5870,7 +5870,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\TFAGoogleSettingsType.php:101 Part-DB1\src\Form\TFAGoogleSettingsType.php:101 - + tfa_google.disable Deactivate authenticator app @@ -5880,7 +5880,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\TFAGoogleSettingsType.php:74 Part-DB1\src\Form\TFAGoogleSettingsType.php:74 - + google_confirmation Confirmation code @@ -5891,7 +5891,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:108 src\Form\UserSettingsType.php:46 - + user.timezone.label Timezone @@ -5901,7 +5901,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:133 Part-DB1\src\Form\UserSettingsType.php:132 - + user.currency.label Preferred currency @@ -5912,7 +5912,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:139 src\Form\UserSettingsType.php:53 - + save Apply changes @@ -5923,7 +5923,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:140 src\Form\UserSettingsType.php:54 - + reset Discard changes @@ -5934,7 +5934,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:104 src\Form\UserSettingsType.php:45 - + user_settings.language.placeholder Serverwide language @@ -5945,7 +5945,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:115 src\Form\UserSettingsType.php:48 - + user_settings.timezone.placeholder Serverwide Timezone @@ -5955,7 +5955,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:79 Part-DB1\src\Services\ElementTypeNameGenerator.php:79 - + attachment.label Attachment @@ -5965,7 +5965,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:81 Part-DB1\src\Services\ElementTypeNameGenerator.php:81 - + attachment_type.label Attachment type @@ -5975,7 +5975,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:82 Part-DB1\src\Services\ElementTypeNameGenerator.php:82 - + project.label Project @@ -5985,7 +5985,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:85 Part-DB1\src\Services\ElementTypeNameGenerator.php:85 - + measurement_unit.label Measurement unit @@ -5995,7 +5995,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:90 Part-DB1\src\Services\ElementTypeNameGenerator.php:90 - + currency.label Currency @@ -6005,7 +6005,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:91 Part-DB1\src\Services\ElementTypeNameGenerator.php:91 - + orderdetail.label Order detail @@ -6015,7 +6015,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:92 Part-DB1\src\Services\ElementTypeNameGenerator.php:92 - + pricedetail.label Price detail @@ -6025,7 +6025,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:94 Part-DB1\src\Services\ElementTypeNameGenerator.php:94 - + user.label User @@ -6034,7 +6034,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:95 - + parameter.label Parameter @@ -6043,7 +6043,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:96 - + label_profile.label Label profile @@ -6054,7 +6054,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\LogSystem\LogEntryExtraFormatter.php:161 new - + log.element_deleted.old_name.unknown Unknown @@ -6064,7 +6064,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\MarkdownParser.php:73 Part-DB1\src\Services\MarkdownParser.php:73 - + markdown.loading Loading markdown. If this message does not disappear, try to reload the page. @@ -6074,7 +6074,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\PasswordResetManager.php:98 Part-DB1\src\Services\PasswordResetManager.php:98 - + pw_reset.email.subject Password reset for your Part-DB account @@ -6083,7 +6083,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:108 - + tree.tools.tools Tools @@ -6094,7 +6094,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:107 src\Services\ToolsTreeBuilder.php:74 - + tree.tools.edit Edit @@ -6105,7 +6105,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:108 src\Services\ToolsTreeBuilder.php:81 - + tree.tools.show Show @@ -6115,7 +6115,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:111 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:109 - + tree.tools.system System @@ -6124,7 +6124,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:123 - + tree.tools.tools.label_dialog Label generator @@ -6133,7 +6133,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:130 - + tree.tools.tools.label_scanner Scanner @@ -6144,7 +6144,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:126 src\Services\ToolsTreeBuilder.php:62 - + tree.tools.edit.attachment_types Attachment types @@ -6155,7 +6155,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:132 src\Services\ToolsTreeBuilder.php:64 - + tree.tools.edit.categories Categories @@ -6166,7 +6166,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:138 src\Services\ToolsTreeBuilder.php:66 - + tree.tools.edit.projects Projects @@ -6177,7 +6177,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:144 src\Services\ToolsTreeBuilder.php:68 - + tree.tools.edit.suppliers Suppliers @@ -6188,7 +6188,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:150 src\Services\ToolsTreeBuilder.php:70 - + tree.tools.edit.manufacturer Manufacturers @@ -6198,7 +6198,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:179 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:156 - + tree.tools.edit.storelocation Storage locations @@ -6208,7 +6208,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:185 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:162 - + tree.tools.edit.footprint Footprints @@ -6218,7 +6218,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:191 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:168 - + tree.tools.edit.currency Currencies @@ -6228,7 +6228,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:197 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:174 - + tree.tools.edit.measurement_unit Measurement Unit @@ -6237,7 +6237,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:203 - + tree.tools.edit.label_profile Label profiles @@ -6247,7 +6247,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:209 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:180 - + tree.tools.edit.part New part @@ -6258,7 +6258,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:197 src\Services\ToolsTreeBuilder.php:77 - + tree.tools.show.all_parts Show all parts @@ -6268,7 +6268,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:232 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:203 - + tree.tools.show.all_attachments Attachments @@ -6279,7 +6279,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:210 new - + tree.tools.show.statistics Statistics @@ -6289,7 +6289,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:258 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:229 - + tree.tools.system.users Users @@ -6299,7 +6299,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:264 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:235 - + tree.tools.system.groups Groups @@ -6310,7 +6310,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:242 new - + tree.tools.system.event_log Event log @@ -6321,7 +6321,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\TreeViewGenerator.php:95 src\Services\TreeBuilder.php:124 - + entity.tree.new New Element @@ -6331,7 +6331,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Parts\info\_attachments_info.html.twig:34 obsolete - + attachment.external_file External file @@ -6341,7 +6341,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Parts\info\_attachments_info.html.twig:62 obsolete - + attachment.edit Edit @@ -6352,7 +6352,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:88 obsolete - + barcode.scan Scan Barcode @@ -6363,7 +6363,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can src\Form\UserSettingsType.php:49 obsolete - + user.theme.label Theme @@ -6374,7 +6374,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can src\Form\UserSettingsType.php:50 obsolete - + user_settings.theme.placeholder Serverwide Theme @@ -6385,7 +6385,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.user_login.ip IP @@ -6399,7 +6399,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.undo_mode.undo Change undone @@ -6413,7 +6413,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.undo_mode.revert Element reverted @@ -6424,7 +6424,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.element_created.original_instock Old instock @@ -6435,7 +6435,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.element_deleted.old_name Old name @@ -6446,7 +6446,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.element_edited.changed_fields Changed fields @@ -6457,7 +6457,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.instock_changed.comment Comment @@ -6468,7 +6468,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.collection_deleted.deleted Deleted element: @@ -6479,7 +6479,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + go.exclamation Go! @@ -6490,7 +6490,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + language.english English @@ -6501,7 +6501,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + language.german German @@ -6511,7 +6511,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.password_change_needed Password change needed! @@ -6521,7 +6521,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.table.type Attachment type @@ -6531,7 +6531,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.table.element Associated element @@ -6541,7 +6541,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.edit.isPicture Picture? @@ -6551,7 +6551,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.edit.is3DModel 3D model? @@ -6561,7 +6561,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.edit.isBuiltin Builtin? @@ -6571,7 +6571,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.default_comment.placeholder e.g. useful for switching @@ -6581,7 +6581,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + tfa_backup.regenerate_codes Generate new backup codes @@ -6591,7 +6591,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.noneofitschild.self A element can not be its own parent. @@ -6601,7 +6601,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.noneofitschild.children The parent can not be one of the children of itself. @@ -6611,7 +6611,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.part_lot.location_full.no_increasment The storage location was marked as full, so you can not increase the instock amount. (New amount max. {{ old_amount }}) @@ -6621,7 +6621,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.part_lot.location_full The storage location was marked as full, so you can not add a new part to it. @@ -6631,7 +6631,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.part_lot.only_existing The storage location was marked as "only existing", so you can not add new part to it. @@ -6641,7 +6641,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.part_lot.single_part The storage location was marked as "single part", so you can not add a new part to it. @@ -6651,7 +6651,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.active.help The part is currently and in the foreseeable future in production @@ -6661,7 +6661,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.announced.help The part was announced but is not available yet. @@ -6671,7 +6671,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.discontinued.help The part is discontinued and not produced anymore. @@ -6681,7 +6681,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.eol.help The product has reached its end-of-life and the production will be stopped soon. @@ -6691,7 +6691,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.nrfnd.help The part is currently in production but is not recommended for new designs. @@ -6701,7 +6701,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.unknown.help The manufacturing status of the part is not known. @@ -6711,7 +6711,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.success Success @@ -6721,7 +6721,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.error Error @@ -6731,7 +6731,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.warning Warning @@ -6741,7 +6741,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.notice Notice @@ -6751,7 +6751,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.info Info @@ -6761,7 +6761,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.noLockout You can not withdraw yourself the "change permission" permission, to prevent that you lockout yourself accidentally. @@ -6771,7 +6771,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment_type.edit.filetype_filter Allowed file extensions @@ -6781,7 +6781,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment_type.edit.filetype_filter.help You can specify a comma separated list of file extension or mime types, which an uploaded file must have when assigned to this attachment type. To allow all supported image files, you can use image/*. @@ -6791,7 +6791,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment_type.edit.filetype_filter.placeholder e.g. .txt, application/pdf, image/* @@ -6802,7 +6802,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + part.name.placeholder e.g. BC547 @@ -6812,7 +6812,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + entity.edit.not_selectable Not selectable @@ -6822,7 +6822,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + entity.edit.not_selectable.help If this option is activated, this element can not be assigned to a part property. Useful if this element is just used for grouping. @@ -6832,7 +6832,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + bbcode.hint You can use BBCode here (e.g. [b]Bold[/b]) @@ -6842,7 +6842,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + entity.create Create element @@ -6852,7 +6852,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + entity.edit.save Save @@ -6862,7 +6862,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_footprints Disable footprints @@ -6872,7 +6872,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_footprints.help If this option is activated, the footprint property is disabled for all parts with this category. @@ -6882,7 +6882,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_manufacturers Disable manufacturers @@ -6892,7 +6892,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_manufacturers.help If this option is activated, the manufacturer property is disabled for all parts with this category. @@ -6902,7 +6902,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_autodatasheets Disable automatic datasheet links @@ -6912,7 +6912,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_autodatasheets.help If this option is activated, no automatic links to datasheets are created for parts with this category. @@ -6922,7 +6922,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_properties Disable properties @@ -6932,7 +6932,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_properties.help If this option is activated, the part properties are disabled for parts with this category. @@ -6942,7 +6942,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.partname_hint Part name hint @@ -6952,7 +6952,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.partname_hint.placeholder e.g. 100nF @@ -6962,7 +6962,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.partname_regex Name filter @@ -6972,7 +6972,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.default_description Default description @@ -6982,7 +6982,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.default_description.placeholder e.g. Capacitor, 10mm x 10mm, SMD @@ -6992,7 +6992,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.default_comment Default notes @@ -7002,7 +7002,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + company.edit.address Address @@ -7012,7 +7012,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + company.edit.address.placeholder e.g. Examplestreet 314 Exampletown @@ -7023,7 +7023,7 @@ Exampletown obsolete obsolete - + company.edit.phone_number Phone number @@ -7033,7 +7033,7 @@ Exampletown obsolete obsolete - + company.edit.phone_number.placeholder +49 12345 6789 @@ -7043,7 +7043,7 @@ Exampletown obsolete obsolete - + company.edit.fax_number Fax number @@ -7053,7 +7053,7 @@ Exampletown obsolete obsolete - + company.edit.email Email @@ -7063,7 +7063,7 @@ Exampletown obsolete obsolete - + company.edit.email.placeholder e.g. contact@foo.bar @@ -7073,7 +7073,7 @@ Exampletown obsolete obsolete - + company.edit.website Website @@ -7083,7 +7083,7 @@ Exampletown obsolete obsolete - + company.edit.website.placeholder https://www.foo.bar @@ -7093,7 +7093,7 @@ Exampletown obsolete obsolete - + company.edit.auto_product_url Product URL @@ -7103,7 +7103,7 @@ Exampletown obsolete obsolete - + company.edit.auto_product_url.help This field is used to determine a link to the part on the company page. %PARTNUMBER% will be replaced with the order number. @@ -7113,7 +7113,7 @@ Exampletown obsolete obsolete - + company.edit.auto_product_url.placeholder https://foo.bar/product/%PARTNUMBER% @@ -7123,7 +7123,7 @@ Exampletown obsolete obsolete - + currency.edit.iso_code ISO code @@ -7133,7 +7133,7 @@ Exampletown obsolete obsolete - + currency.edit.exchange_rate Exchange rate @@ -7143,7 +7143,7 @@ Exampletown obsolete obsolete - + footprint.edit.3d_model 3D model @@ -7153,7 +7153,7 @@ Exampletown obsolete obsolete - + mass_creation.lines Input @@ -7163,7 +7163,7 @@ Exampletown obsolete obsolete - + mass_creation.lines.placeholder Element 1 Element 1.1 @@ -7178,7 +7178,7 @@ Element 3 obsolete obsolete - + entity.mass_creation.btn Create @@ -7188,7 +7188,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.is_integer Is integer @@ -7198,7 +7198,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.is_integer.help If this option is activated, all values with this unit will be rounded to whole numbers. @@ -7208,7 +7208,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.use_si_prefix Use SI prefix @@ -7218,7 +7218,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.use_si_prefix.help If this option is activated, values are outputted with SI prefixes (e.g. 1,2kg instead of 1200g) @@ -7228,7 +7228,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.unit_symbol Unit symbol @@ -7238,7 +7238,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.unit_symbol.placeholder e.g. m @@ -7248,7 +7248,7 @@ Element 3 obsolete obsolete - + storelocation.edit.is_full.label Storelocation full @@ -7258,7 +7258,7 @@ Element 3 obsolete obsolete - + storelocation.edit.is_full.help If this option is selected, it is neither possible to add new parts to this storelocation or to increase the amount of existing parts. @@ -7268,7 +7268,7 @@ Element 3 obsolete obsolete - + storelocation.limit_to_existing.label Limit to existing parts @@ -7278,7 +7278,7 @@ Element 3 obsolete obsolete - + storelocation.limit_to_existing.help If this option is activated, it is not possible to add new parts to this storelocation, but the amount of existing parts can be increased. @@ -7288,7 +7288,7 @@ Element 3 obsolete obsolete - + storelocation.only_single_part.label Only single part @@ -7298,7 +7298,7 @@ Element 3 obsolete obsolete - + storelocation.only_single_part.help If this option is activated, only a single part (with every amount) can be assigned to this storage location. Useful for small SMD boxes or feeders. @@ -7308,7 +7308,7 @@ Element 3 obsolete obsolete - + storelocation.storage_type.label Storage type @@ -7318,7 +7318,7 @@ Element 3 obsolete obsolete - + storelocation.storage_type.help You can select a measurement unit here, which a part must have to be able to be assigned to this storage location @@ -7328,7 +7328,7 @@ Element 3 obsolete obsolete - + supplier.edit.default_currency Default currency @@ -7338,7 +7338,7 @@ Element 3 obsolete obsolete - + supplier.shipping_costs.label Shipping Costs @@ -7348,7 +7348,7 @@ Element 3 obsolete obsolete - + user.username.placeholder e.g. j.doe @@ -7358,7 +7358,7 @@ Element 3 obsolete obsolete - + user.firstName.placeholder e.g John @@ -7368,7 +7368,7 @@ Element 3 obsolete obsolete - + user.lastName.placeholder e.g. Doe @@ -7378,7 +7378,7 @@ Element 3 obsolete obsolete - + user.email.placeholder j.doe@ecorp.com @@ -7388,7 +7388,7 @@ Element 3 obsolete obsolete - + user.department.placeholder e.g. Development @@ -7398,7 +7398,7 @@ Element 3 obsolete obsolete - + user.settings.pw_new.label New password @@ -7408,7 +7408,7 @@ Element 3 obsolete obsolete - + user.settings.pw_confirm.label Confirm new password @@ -7418,7 +7418,7 @@ Element 3 obsolete obsolete - + user.edit.needs_pw_change User needs to change password @@ -7428,7 +7428,7 @@ Element 3 obsolete obsolete - + user.edit.user_disabled User disabled (no login possible) @@ -7438,7 +7438,7 @@ Element 3 obsolete obsolete - + user.create Create user @@ -7448,7 +7448,7 @@ Element 3 obsolete obsolete - + user.edit.save Save @@ -7458,7 +7458,7 @@ Element 3 obsolete obsolete - + entity.edit.reset Discard changes @@ -7469,7 +7469,7 @@ Element 3 obsolete obsolete - + part.withdraw.btn Withdraw @@ -7480,7 +7480,7 @@ Element 3 obsolete obsolete - + part.withdraw.comment: Comment/Purpose @@ -7491,7 +7491,7 @@ Element 3 obsolete obsolete - + part.add.caption Add parts @@ -7502,7 +7502,7 @@ Element 3 obsolete obsolete - + part.add.btn Add @@ -7513,7 +7513,7 @@ Element 3 obsolete obsolete - + part.add.comment Comment/Purpose @@ -7524,7 +7524,7 @@ Element 3 obsolete obsolete - + admin.comment Notes @@ -7535,7 +7535,7 @@ Element 3 obsolete obsolete - + manufacturer_url.label Manufacturer link @@ -7546,7 +7546,7 @@ Element 3 obsolete obsolete - + part.description.placeholder e.g. NPN 45V 0,1A 0,5W @@ -7557,7 +7557,7 @@ Element 3 obsolete obsolete - + part.instock.placeholder e.g. 10 @@ -7568,7 +7568,7 @@ Element 3 obsolete obsolete - + part.mininstock.placeholder e.g. 5 @@ -7578,7 +7578,7 @@ Element 3 obsolete obsolete - + part.order.price_per Price per @@ -7588,7 +7588,7 @@ Element 3 obsolete obsolete - + part.withdraw.caption Withdraw parts @@ -7598,7 +7598,7 @@ Element 3 obsolete obsolete - + datatable.datatable.lengthMenu _MENU_ @@ -7608,7 +7608,7 @@ Element 3 obsolete obsolete - + perm.group.parts Parts @@ -7618,7 +7618,7 @@ Element 3 obsolete obsolete - + perm.group.structures Data structures @@ -7628,7 +7628,7 @@ Element 3 obsolete obsolete - + perm.group.system System @@ -7638,7 +7638,7 @@ Element 3 obsolete obsolete - + perm.parts Parts @@ -7648,7 +7648,7 @@ Element 3 obsolete obsolete - + perm.read View @@ -7658,7 +7658,7 @@ Element 3 obsolete obsolete - + perm.edit Edit @@ -7668,7 +7668,7 @@ Element 3 obsolete obsolete - + perm.create Create @@ -7678,7 +7678,7 @@ Element 3 obsolete obsolete - + perm.part.move Change category @@ -7688,7 +7688,7 @@ Element 3 obsolete obsolete - + perm.delete Delete @@ -7698,7 +7698,7 @@ Element 3 obsolete obsolete - + perm.part.search Search @@ -7708,7 +7708,7 @@ Element 3 obsolete obsolete - + perm.part.all_parts List all parts @@ -7718,7 +7718,7 @@ Element 3 obsolete obsolete - + perm.part.no_price_parts List parts without price info @@ -7728,7 +7728,7 @@ Element 3 obsolete obsolete - + perm.part.obsolete_parts List obsolete parts @@ -7738,7 +7738,7 @@ Element 3 obsolete obsolete - + perm.part.unknown_instock_parts Show parts with unknown instock @@ -7748,7 +7748,7 @@ Element 3 obsolete obsolete - + perm.part.change_favorite Change favorite status @@ -7758,7 +7758,7 @@ Element 3 obsolete obsolete - + perm.part.show_favorite List favorite parts @@ -7768,7 +7768,7 @@ Element 3 obsolete obsolete - + perm.part.show_last_edit_parts Show last edited/added parts @@ -7778,7 +7778,7 @@ Element 3 obsolete obsolete - + perm.part.show_users Show last modifying user @@ -7788,7 +7788,7 @@ Element 3 obsolete obsolete - + perm.part.show_history Show history @@ -7798,7 +7798,7 @@ Element 3 obsolete obsolete - + perm.part.name Name @@ -7808,7 +7808,7 @@ Element 3 obsolete obsolete - + perm.part.description Description @@ -7818,7 +7818,7 @@ Element 3 obsolete obsolete - + perm.part.instock Instock @@ -7828,7 +7828,7 @@ Element 3 obsolete obsolete - + perm.part.mininstock Minimum instock @@ -7838,7 +7838,7 @@ Element 3 obsolete obsolete - + perm.part.comment Notes @@ -7848,7 +7848,7 @@ Element 3 obsolete obsolete - + perm.part.storelocation Storage location @@ -7858,7 +7858,7 @@ Element 3 obsolete obsolete - + perm.part.manufacturer Manufacturer @@ -7868,7 +7868,7 @@ Element 3 obsolete obsolete - + perm.part.orderdetails Order information @@ -7878,7 +7878,7 @@ Element 3 obsolete obsolete - + perm.part.prices Prices @@ -7888,7 +7888,7 @@ Element 3 obsolete obsolete - + perm.part.attachments File attachments @@ -7898,7 +7898,7 @@ Element 3 obsolete obsolete - + perm.part.order Orders @@ -7908,7 +7908,7 @@ Element 3 obsolete obsolete - + perm.storelocations Storage locations @@ -7918,7 +7918,7 @@ Element 3 obsolete obsolete - + perm.move Move @@ -7928,7 +7928,7 @@ Element 3 obsolete obsolete - + perm.list_parts List parts @@ -7938,7 +7938,7 @@ Element 3 obsolete obsolete - + perm.part.footprints Footprints @@ -7948,7 +7948,7 @@ Element 3 obsolete obsolete - + perm.part.categories Categories @@ -7958,7 +7958,7 @@ Element 3 obsolete obsolete - + perm.part.supplier Suppliers @@ -7968,7 +7968,7 @@ Element 3 obsolete obsolete - + perm.part.manufacturers Manufacturers @@ -7978,7 +7978,7 @@ Element 3 obsolete obsolete - + perm.projects Projects @@ -7988,7 +7988,7 @@ Element 3 obsolete obsolete - + perm.part.attachment_types Attachment types @@ -7998,7 +7998,7 @@ Element 3 obsolete obsolete - + perm.tools.import Import @@ -8008,7 +8008,7 @@ Element 3 obsolete obsolete - + perm.tools.labels Labels @@ -8018,7 +8018,7 @@ Element 3 obsolete obsolete - + perm.tools.calculator Resistor calculator @@ -8028,7 +8028,7 @@ Element 3 obsolete obsolete - + perm.tools.footprints Footprints @@ -8038,7 +8038,7 @@ Element 3 obsolete obsolete - + perm.tools.ic_logos IC logos @@ -8048,7 +8048,7 @@ Element 3 obsolete obsolete - + perm.tools.statistics Statistics @@ -8058,7 +8058,7 @@ Element 3 obsolete obsolete - + perm.edit_permissions Edit permissions @@ -8068,7 +8068,7 @@ Element 3 obsolete obsolete - + perm.users.edit_user_name Edit user name @@ -8078,7 +8078,7 @@ Element 3 obsolete obsolete - + perm.users.edit_change_group Change group @@ -8088,7 +8088,7 @@ Element 3 obsolete obsolete - + perm.users.edit_infos Edit info @@ -8098,7 +8098,7 @@ Element 3 obsolete obsolete - + perm.users.edit_permissions Edit permissions @@ -8108,7 +8108,7 @@ Element 3 obsolete obsolete - + perm.users.set_password Set password @@ -8118,7 +8118,7 @@ Element 3 obsolete obsolete - + perm.users.change_user_settings Change user settings @@ -8128,7 +8128,7 @@ Element 3 obsolete obsolete - + perm.database.see_status Show status @@ -8138,7 +8138,7 @@ Element 3 obsolete obsolete - + perm.database.update_db Update DB @@ -8148,7 +8148,7 @@ Element 3 obsolete obsolete - + perm.database.read_db_settings Read DB settings @@ -8158,7 +8158,7 @@ Element 3 obsolete obsolete - + perm.database.write_db_settings Write DB settings @@ -8168,7 +8168,7 @@ Element 3 obsolete obsolete - + perm.config.read_config Read config @@ -8178,7 +8178,7 @@ Element 3 obsolete obsolete - + perm.config.edit_config Edit config @@ -8188,7 +8188,7 @@ Element 3 obsolete obsolete - + perm.config.server_info Server info @@ -8198,7 +8198,7 @@ Element 3 obsolete obsolete - + perm.config.use_debug Use debug tools @@ -8208,7 +8208,7 @@ Element 3 obsolete obsolete - + perm.show_logs Show logs @@ -8218,7 +8218,7 @@ Element 3 obsolete obsolete - + perm.delete_logs Delete logs @@ -8228,7 +8228,7 @@ Element 3 obsolete obsolete - + perm.self.edit_infos Edit info @@ -8238,7 +8238,7 @@ Element 3 obsolete obsolete - + perm.self.edit_username Edit username @@ -8248,7 +8248,7 @@ Element 3 obsolete obsolete - + perm.self.show_permissions View permissions @@ -8258,7 +8258,7 @@ Element 3 obsolete obsolete - + perm.self.show_logs Show own log entries @@ -8268,7 +8268,7 @@ Element 3 obsolete obsolete - + perm.self.create_labels Create labels @@ -8278,7 +8278,7 @@ Element 3 obsolete obsolete - + perm.self.edit_options Edit options @@ -8288,7 +8288,7 @@ Element 3 obsolete obsolete - + perm.self.delete_profiles Delete profiles @@ -8298,7 +8298,7 @@ Element 3 obsolete obsolete - + perm.self.edit_profiles Edit profiles @@ -8308,7 +8308,7 @@ Element 3 obsolete obsolete - + perm.part.tools Tools @@ -8318,7 +8318,7 @@ Element 3 obsolete obsolete - + perm.groups Groups @@ -8328,7 +8328,7 @@ Element 3 obsolete obsolete - + perm.users Users @@ -8338,7 +8338,7 @@ Element 3 obsolete obsolete - + perm.database Database @@ -8348,7 +8348,7 @@ Element 3 obsolete obsolete - + perm.config Configuration @@ -8358,7 +8358,7 @@ Element 3 obsolete obsolete - + perm.system System @@ -8368,7 +8368,7 @@ Element 3 obsolete obsolete - + perm.self Own user @@ -8378,7 +8378,7 @@ Element 3 obsolete obsolete - + perm.labels Labels @@ -8388,7 +8388,7 @@ Element 3 obsolete obsolete - + perm.part.category Category @@ -8398,7 +8398,7 @@ Element 3 obsolete obsolete - + perm.part.minamount Minimum amount @@ -8408,7 +8408,7 @@ Element 3 obsolete obsolete - + perm.part.footprint Footprint @@ -8418,7 +8418,7 @@ Element 3 obsolete obsolete - + perm.part.mpn MPN @@ -8428,7 +8428,7 @@ Element 3 obsolete obsolete - + perm.part.status Manufacturing status @@ -8438,7 +8438,7 @@ Element 3 obsolete obsolete - + perm.part.tags Tags @@ -8448,7 +8448,7 @@ Element 3 obsolete obsolete - + perm.part.unit Part unit @@ -8458,7 +8458,7 @@ Element 3 obsolete obsolete - + perm.part.mass Mass @@ -8468,7 +8468,7 @@ Element 3 obsolete obsolete - + perm.part.lots Part lots @@ -8478,7 +8478,7 @@ Element 3 obsolete obsolete - + perm.show_users Show last modifying user @@ -8488,7 +8488,7 @@ Element 3 obsolete obsolete - + perm.currencies Currencies @@ -8498,7 +8498,7 @@ Element 3 obsolete obsolete - + perm.measurement_units Measurement unit @@ -8508,7 +8508,7 @@ Element 3 obsolete obsolete - + user.settings.pw_old.label Old password @@ -8518,7 +8518,7 @@ Element 3 obsolete obsolete - + pw_reset.submit Reset password @@ -8528,7 +8528,7 @@ Element 3 obsolete obsolete - + u2f_two_factor Security key (U2F) @@ -8538,13 +8538,13 @@ Element 3 obsolete obsolete - + google Google - + tfa.provider.webauthn_two_factor_provider Security key @@ -8554,7 +8554,7 @@ Element 3 obsolete obsolete - + tfa.provider.google Authenticator app @@ -8564,7 +8564,7 @@ Element 3 obsolete obsolete - + Login successful Login successful @@ -8574,7 +8574,7 @@ Element 3 obsolete obsolete - + log.type.exception Unhandled exception (obsolete) @@ -8584,7 +8584,7 @@ Element 3 obsolete obsolete - + log.type.user_login User login @@ -8594,7 +8594,7 @@ Element 3 obsolete obsolete - + log.type.user_logout User logout @@ -8604,7 +8604,7 @@ Element 3 obsolete obsolete - + log.type.unknown Unknown @@ -8614,7 +8614,7 @@ Element 3 obsolete obsolete - + log.type.element_created Element created @@ -8624,7 +8624,7 @@ Element 3 obsolete obsolete - + log.type.element_edited Element edited @@ -8634,7 +8634,7 @@ Element 3 obsolete obsolete - + log.type.element_deleted Element deleted @@ -8644,7 +8644,7 @@ Element 3 obsolete obsolete - + log.type.database_updated Database updated @@ -8653,7 +8653,7 @@ Element 3 obsolete - + perm.revert_elements Revert element @@ -8662,7 +8662,7 @@ Element 3 obsolete - + perm.show_history Show history @@ -8671,7 +8671,7 @@ Element 3 obsolete - + perm.tools.lastActivity Show last activity @@ -8680,7 +8680,7 @@ Element 3 obsolete - + perm.tools.timeTravel Show old element versions (time travel) @@ -8689,7 +8689,7 @@ Element 3 obsolete - + tfa_u2f.key_added_successful Security key added successfully. @@ -8698,7 +8698,7 @@ Element 3 obsolete - + Username Username @@ -8707,7 +8707,7 @@ Element 3 obsolete - + log.type.security.google_disabled Authenticator App disabled @@ -8716,7 +8716,7 @@ Element 3 obsolete - + log.type.security.u2f_removed Security key removed @@ -8725,7 +8725,7 @@ Element 3 obsolete - + log.type.security.u2f_added Security key added @@ -8734,7 +8734,7 @@ Element 3 obsolete - + log.type.security.backup_keys_reset Backup keys regenerated @@ -8743,7 +8743,7 @@ Element 3 obsolete - + log.type.security.google_enabled Authenticator App enabled @@ -8752,7 +8752,7 @@ Element 3 obsolete - + log.type.security.password_changed Password changed @@ -8761,7 +8761,7 @@ Element 3 obsolete - + log.type.security.trusted_device_reset Trusted devices resetted @@ -8770,7 +8770,7 @@ Element 3 obsolete - + log.type.collection_element_deleted Element of Collection deleted @@ -8779,7 +8779,7 @@ Element 3 obsolete - + log.type.security.password_reset Password reset @@ -8788,7 +8788,7 @@ Element 3 obsolete - + log.type.security.2fa_admin_reset Two Factor Reset by Administrator @@ -8797,7 +8797,7 @@ Element 3 obsolete - + log.type.user_not_allowed Unauthorized access attempt @@ -8806,7 +8806,7 @@ Element 3 obsolete - + log.database_updated.success Success @@ -8815,7 +8815,7 @@ Element 3 obsolete - + label_options.barcode_type.2D 2D @@ -8824,7 +8824,7 @@ Element 3 obsolete - + label_options.barcode_type.1D 1D @@ -8833,7 +8833,7 @@ Element 3 obsolete - + perm.part.parameters Parameters @@ -8842,7 +8842,7 @@ Element 3 obsolete - + perm.attachment_show_private View private attachments @@ -8851,7 +8851,7 @@ Element 3 obsolete - + perm.tools.label_scanner Label scanner @@ -8860,7 +8860,7 @@ Element 3 obsolete - + perm.self.read_profiles Read profiles @@ -8869,7 +8869,7 @@ Element 3 obsolete - + perm.self.create_profiles Create profiles @@ -8878,2551 +8878,2551 @@ Element 3 obsolete - + perm.labels.use_twig Use twig mode - + label_profile.showInDropdown Show in quick select - + group.edit.enforce_2fa Enforce Two-factor authentication (2FA) - + group.edit.enforce_2fa.help If this option is enabled, every direct member of this group, has to configure at least one second-factor for authentication. Recommended for administrative groups with much permissions. - + selectpicker.empty Nothing selected - + selectpicker.nothing_selected Nothing selected - + entity.delete.must_not_contain_parts Element "%PATH%" still contains parts! You have to move the parts, to be able to delete this element. - + entity.delete.must_not_contain_attachments Attachment type still contains attachments. Change their type, to be able to delete this attachment type. - + entity.delete.must_not_contain_prices Currency still contains price details. You have to change their currency to be able to delete this element. - + entity.delete.must_not_contain_users Users still uses this group! Change their group, to be able to delete this group. - + part.table.edit Edit - + part.table.edit.title Edit part - + part_list.action.action.title Select action - + part_list.action.action.group.favorite Favorite status - + part_list.action.action.favorite Favorite - + part_list.action.action.unfavorite Unfavorite - + part_list.action.action.group.change_field Change field - + part_list.action.action.change_category Change category - + part_list.action.action.change_footprint Change footprint - + part_list.action.action.change_manufacturer Change manufacturer - + part_list.action.action.change_unit Change part unit - + part_list.action.action.delete Delete - + part_list.action.submit Submit - + part_list.action.part_count %count% parts selected! - + company.edit.quick.website Open website - + company.edit.quick.email Send email - + company.edit.quick.phone Call phone - + company.edit.quick.fax Send fax - + company.fax_number.placeholder e.g. +49 1234 567890 - + part.edit.save_and_clone Save and clone - + validator.file_ext_not_allowed File extension not allowed for this attachment type. - + tools.reel_calc.title SMD Reel calculator - + tools.reel_calc.inner_dia Inner diameter - + tools.reel_calc.outer_dia Outer diameter - + tools.reel_calc.tape_thick Tape thickness - + tools.reel_calc.part_distance Part distance - + tools.reel_calc.update Update - + tools.reel_calc.parts_per_meter Parts per meter - + tools.reel_calc.result_length Tape length - + tools.reel_calc.result_amount Approx. parts count - + tools.reel_calc.outer_greater_inner_error Error: Outer diameter must be greater than inner diameter! - + tools.reel_calc.missing_values.error Please fill in all values! - + tools.reel_calc.load_preset Load preset - + tools.reel_calc.explanation This calculator gives you an estimation, how many parts are remaining on an SMD reel. Measure the noted the dimensions on the reel (or use some of the presets) and click "Update" to get an result. - + perm.tools.reel_calculator SMD Reel calculator - + tree.tools.tools.reel_calculator SMD Reel calculator - + user.pw_change_needed.flash Your password needs to be changed! Please set a new password. - + tree.root_node.text Root node - + part_list.action.select_null Empty element - + part_list.action.delete-title Do you really want to delete these parts? - + part_list.action.delete-message These parts and any associated information (like attachments, price information, etc.) will be deleted. This can not be undone! - + part.table.actions.success Actions finished successfully. - + attachment.edit.delete.confirm Do you really want to delete this attachment? - + filter.text_constraint.value.operator.EQ Is - + filter.text_constraint.value.operator.NEQ Is not - + filter.text_constraint.value.operator.STARTS Starts with - + filter.text_constraint.value.operator.CONTAINS Contains - + filter.text_constraint.value.operator.ENDS Ends with - + filter.text_constraint.value.operator.LIKE LIKE pattern - + filter.text_constraint.value.operator.REGEX Regular expression - + filter.number_constraint.value.operator.BETWEEN Between - + filter.number_constraint.AND and - + filter.entity_constraint.operator.EQ Is (excluding children) - + filter.entity_constraint.operator.NEQ Is not (excluding children) - + filter.entity_constraint.operator.INCLUDING_CHILDREN Is (including children) - + filter.entity_constraint.operator.EXCLUDING_CHILDREN Is not (including children) - + part.filter.dbId Database ID - + filter.tags_constraint.operator.ANY Any of the tags - + filter.tags_constraint.operator.ALL All the tags - + filter.tags_constraint.operator.NONE None of the tags - + part.filter.lot_count Number of lots - + part.filter.attachments_count Number of attachments - + part.filter.orderdetails_count Number of orderdetails - + part.filter.lotExpirationDate Lot expiration date - + part.filter.lotNeedsRefill Any lot needs refill - + part.filter.lotUnknwonAmount Any lot has unknown amount - + part.filter.attachmentName Attachment name - + filter.choice_constraint.operator.ANY Any of - + filter.choice_constraint.operator.NONE None of - + part.filter.amount_sum Total amount - + filter.submit Update - + filter.discard Discard changes - + filter.clear_filters Clear all filters - + filter.title Filter - + filter.parameter_value_constraint.operator.= Typ. Value = - + filter.parameter_value_constraint.operator.!= Typ. Value != - + filter.parameter_value_constraint.operator.< - Typ. Value < + - + filter.parameter_value_constraint.operator.> - Typ. Value > + ]]> - + filter.parameter_value_constraint.operator.<= - Typ. Value <= + - + filter.parameter_value_constraint.operator.>= - Typ. Value >= + =]]> - + filter.parameter_value_constraint.operator.BETWEEN Typ. Value is between - + filter.parameter_value_constraint.operator.IN_RANGE In Value range - + filter.parameter_value_constraint.operator.NOT_IN_RANGE Not in Value range - + filter.parameter_value_constraint.operator.GREATER_THAN_RANGE Greater than Value range - + filter.parameter_value_constraint.operator.GREATER_EQUAL_RANGE Greater equal than Value range - + filter.parameter_value_constraint.operator.LESS_THAN_RANGE Less than Value range - + filter.parameter_value_constraint.operator.LESS_EQUAL_RANGE Less equal than Value range - + filter.parameter_value_constraint.operator.RANGE_IN_RANGE Range is completely in Value range - + filter.parameter_value_constraint.operator.RANGE_INTERSECT_RANGE Range intersects Value range - + filter.text_constraint.value No value set - + filter.number_constraint.value1 No value set - + filter.number_constraint.value2 Maximum value - + filter.datetime_constraint.value1 No datetime set - + filter.datetime_constraint.value2 Maximum datetime - + filter.constraint.add Add constraint - + part.filter.parameters_count Number of parameters - + part.filter.lotDescription Lot description - + parts_list.search.searching_for - Searching parts with keyword <b>%keyword%</b> + %keyword%]]> - + parts_list.search_options.caption Enabled search options - + attachment.table.element_type Associated element type - + log.level.debug Debug - + log.level.info Info - + log.level.notice Notice - + log.level.warning Warning - + log.level.error Error - + log.level.critical Critical - + log.level.alert Alert - + log.level.emergency Emergency - + log.type.security Security related event - + log.type.instock_changed [LEGACY] Instock changed - + log.target_id Target element ID - + entity.info.parts_count_recursive Number of parts with this element or its sub elements - + tools.server_infos.title Server info - + permission.preset.read_only Read-Only - + permission.preset.read_only.desc Only allow read operations on data - + permission.preset.all_inherit Inherit all - + permission.preset.all_inherit.desc Set all permissions to Inherit - + permission.preset.all_forbid Forbid all - + permission.preset.all_forbid.desc Set all permissions to Forbid - + permission.preset.all_allow Allow all - + permission.preset.all_allow.desc Set all permissions to allow - + perm.server_infos Server info - + permission.preset.editor Editor - + permission.preset.editor.desc Allow changing parts and data structures - + permission.preset.admin Admin - + permission.preset.admin.desc Allow administrative actions - + permission.preset.button Apply preset - + perm.attachments.show_private Show private attachments - + perm.attachments.list_attachments Show list of all attachments - + user.edit.permission_success Permission preset applied successfully. Check if the new permissions fit your needs. - + perm.group.data Data - + part_list.action.action.group.needs_review Needs Review - + part_list.action.action.set_needs_review Set Needs Review Status - + part_list.action.action.unset_needs_review Unset Needs Review Status - + part.edit.ipn Internal Part Number (IPN) - + part.ipn.not_defined Not defined - + part.table.ipn IPN - + currency.edit.update_rate Retrieve exchange rate - + currency.edit.exchange_rate_update.unsupported_currency The currency is unsupported by the exchange rate provider. Check your exchange rate provider configuration. - + currency.edit.exchange_rate_update.generic_error Unable to retrieve the exchange rate. Check your exchange rate provider configuration. - + currency.edit.exchange_rate_updated.success Retrieved the exchange rate successfully. - + project.bom.quantity BOM Qty. - + project.bom.mountnames Mount names - + project.bom.name Name - + project.bom.comment Notes - + project.bom.part Part - + project.bom.add_entry Add entry - + part_list.action.group.projects Projects - + part_list.action.projects.add_to_project Add parts to project - + project.bom.delete.confirm Do you really want to delete this BOM entry? - + project.add_parts_to_project Add parts to project BOM - + part.info.add_part_to_project Add this part to a project - + project_bom_entry.label BOM entry - + project.edit.status Project status - + project.status.draft Draft - + project.status.planning Planning - + project.status.in_production In production - + project.status.finished Finished - + project.status.archived Archived - + part.new_build_part.error.build_part_already_exists The project already has a build part! - + project.edit.associated_build_part Associated builds part - + project.edit.associated_build_part.add Add builds part - + project.edit.associated_build.hint This part represents the builds of this project, which are stored somewhere. - + part.info.projectBuildPart.hint This part represents the builds of the following project and is associated with it - + part.is_build_part Is project builds part - + project.info.title Project info - + project.info.bom_entries_count BOM entries - + project.info.sub_projects_count Subprojects - + project.info.bom_add_parts Add BOM entries - + project.info.info.label Info - + project.info.sub_projects.label Subprojects - + project.bom.price Price - + part.info.withdraw_modal.title.withdraw Withdraw parts from lot - + part.info.withdraw_modal.title.add Add parts to lot - + part.info.withdraw_modal.title.move Move parts from lot to another lot - + part.info.withdraw_modal.amount Amount - + part.info.withdraw_modal.move_to Move to - + part.info.withdraw_modal.comment Comment - + part.info.withdraw_modal.comment.hint You can set a comment here describing why you are doing this operation (e.g. for what you need the parts). This info will be saved in the log. - + modal.close Close - + modal.submit Submit - + part.withdraw.success Added/Moved/Withdrawn parts successfully. - + perm.parts_stock Parts Stock - + perm.parts_stock.withdraw Withdraw parts from stock - + perm.parts_stock.add Add parts to stock - + perm.parts_stock.move Move parts between lots - + user.permissions_schema_updated The permission schema of your user were upgraded to the latest version. - + log.type.part_stock_changed Part Stock changed - + log.part_stock_changed.withdraw Stock withdrawn - + log.part_stock_changed.add Stock added - + log.part_stock_changed.move Stock moved - + log.part_stock_changed.comment Comment - + log.part_stock_changed.change Change - + log.part_stock_changed.move_target Move target - + tools.builtin_footprints_viewer.title Builtin footprint image gallery - + tools.builtin_footprints_viewer.hint This gallery lists all available built-in footprint images. If you want to use them in an attachment, type in the name (or a keyword) in the path field of the attachment and select the image from the dropdown select. - + tools.ic_logos.title IC logos - + part_list.action.group.labels Labels - + part_list.action.projects.generate_label Generate labels (for parts) - + part_list.action.projects.generate_label_lot Generate labels (for part lots) - + part_list.action.generate_label.empty Empty label - + project.info.builds.label Build - + project.builds.build_not_possible Build not possible: Parts not stocked - + project.builds.following_bom_entries_miss_instock The following parts have not enough stock to build this project at least once: - + project.builds.stocked stocked - + project.builds.needed needed - + project.builds.build_possible Build possible - + project.builds.number_of_builds_possible - You have enough stocked to build <b>%max_builds%</b> builds of this project. + %max_builds% builds of this project.]]> - + project.builds.check_project_status - The current project status is <b>"%project_status%"</b>. You should check if you really want to build the project with this status! + "%project_status%". You should check if you really want to build the project with this status!]]> - + project.builds.following_bom_entries_miss_instock_n You do not have enough parts stocked to build this project %number_of_builds% times. The following parts have missing instock: - + project.build.flash.invalid_input Can not build project. Check input! - + project.build.required_qty Required quantity - + project.build.btn_build Build - + project.build.help Choose from which part lots the stock to build this project should be taken (and in which amount). Check the checkbox for each BOM Entry, when you are finished withdrawing the parts, or use the top checkbox to check all boxes at once. - + project.build.buildsPartLot.new_lot Create new lot - + project.build.add_builds_to_builds_part Add builds to project builds part - + project.build.builds_part_lot Target lot - + project.builds.number_of_builds Build amount - + project.builds.no_stocked_builds Number of stocked builds - + user.change_avatar.label Change profile picture - + user_settings.change_avatar.label Change profile picture - + user_settings.remove_avatar.label Remove profile picture - + part.edit.name.category_hint Hint from category - + category.edit.partname_regex.placeholder e.g "/Capacitor \d+ nF/i" - + category.edit.partname_regex.help A PCRE-compatible regular expression, which a part name have to match. - + entity.select.add_hint - Use -> to create nested structures, e.g. "Node 1->Node 1.1" + to create nested structures, e.g. "Node 1->Node 1.1"]]> - + entity.select.group.new_not_added_to_DB New (not added to DB yet) - + part.edit.save_and_new Save and create new empty part - + homepage.first_steps.title First steps - + homepage.first_steps.introduction - Your database is still empty. You might want to read the <a href="%url%">documentation</a> or start to creating the following data structures: + documentation or start to creating the following data structures:]]> - + homepage.first_steps.create_part - Or you can directly <a href="%url%">create a new part</a>. + create a new part.]]> - + homepage.first_steps.hide_hint This box will hide as soon as you have created your first part. - + homepage.forum.text - For questions about Part-DB use the <a href="%href%" class="link-external" target="_blank">discussion forum</a> + discussion forum]]> - + log.element_edited.changed_fields.category Category - + log.element_edited.changed_fields.footprint Footprint - + log.element_edited.changed_fields.manufacturer Manufacturer - + log.element_edited.changed_fields.value_typical typ. value - + log.element_edited.changed_fields.pw_reset_expires Password reset - + log.element_edited.changed_fields.comment Notes - + log.element_edited.changed_fields.supplierpartnr Supplier part number - + log.element_edited.changed_fields.supplier_product_url Link to offer - + log.element_edited.changed_fields.price Price - + log.element_edited.changed_fields.min_discount_quantity Minimum discount amount - + log.element_edited.changed_fields.original_filename Original filename - + log.element_edited.changed_fields.path Filepath - + log.element_edited.changed_fields.description Description - + log.element_edited.changed_fields.manufacturing_status Manufacturing status - + log.element_edited.changed_fields.options.barcode_type Barcode type - + log.element_edited.changed_fields.status Status - + log.element_edited.changed_fields.quantity BOM Qty. - + log.element_edited.changed_fields.mountnames Mountnames - + log.element_edited.changed_fields.name Name - + log.element_edited.changed_fields.part Part - + log.element_edited.changed_fields.price_currency Currency of price - + log.element_edited.changed_fields.partname_hint Part name hint - + log.element_edited.changed_fields.partname_regex Name filter - + log.element_edited.changed_fields.disable_footprints Disable footprints - + log.element_edited.changed_fields.disable_manufacturers Disable manufacturers - + log.element_edited.changed_fields.disable_autodatasheets Disable automatic datasheet links - + log.element_edited.changed_fields.disable_properties Disable properties - + log.element_edited.changed_fields.default_description Default description - + log.element_edited.changed_fields.default_comment Default notes - + log.element_edited.changed_fields.filetype_filter Allowed file extensions - + log.element_edited.changed_fields.not_selectable Not selected - + log.element_edited.changed_fields.parent Parent element - + log.element_edited.changed_fields.shipping_costs Shipping costs - + log.element_edited.changed_fields.default_currency Default currency - + log.element_edited.changed_fields.address Address - + log.element_edited.changed_fields.phone_number Phone number - + log.element_edited.changed_fields.fax_number Fax number - + log.element_edited.changed_fields.email_address Email - + log.element_edited.changed_fields.website Website - + log.element_edited.changed_fields.auto_product_url Product URL - + log.element_edited.changed_fields.is_full Storelocation full - + log.element_edited.changed_fields.limit_to_existing_parts Limit to existing parts - + log.element_edited.changed_fields.only_single_part Only single part - + log.element_edited.changed_fields.storage_type Storage type - + log.element_edited.changed_fields.footprint_3d 3D model - + log.element_edited.changed_fields.master_picture_attachment Preview image - + log.element_edited.changed_fields.exchange_rate Exchange rate - + log.element_edited.changed_fields.iso_code Exchange rate - + log.element_edited.changed_fields.unit Unit symbol - + log.element_edited.changed_fields.is_integer Is integer - + log.element_edited.changed_fields.use_si_prefix Use SI prefix - + log.element_edited.changed_fields.options.width Width - + log.element_edited.changed_fields.options.height Height - + log.element_edited.changed_fields.options.supported_element Target type - + log.element_edited.changed_fields.options.additional_css Additional styles (CSS) - + log.element_edited.changed_fields.options.lines Content - + log.element_edited.changed_fields.permissions.data Permissions - + log.element_edited.changed_fields.disabled Disabled - + log.element_edited.changed_fields.theme Theme - + log.element_edited.changed_fields.timezone Timezone - + log.element_edited.changed_fields.language Language - + log.element_edited.changed_fields.email Email - + log.element_edited.changed_fields.department Department - + log.element_edited.changed_fields.last_name Last name - + log.element_edited.changed_fields.first_name First name - + log.element_edited.changed_fields.group Group - + log.element_edited.changed_fields.currency Preferred currency - + log.element_edited.changed_fields.enforce2FA Enforce 2FA - + log.element_edited.changed_fields.symbol Symbol - + log.element_edited.changed_fields.value_min Min. value - + log.element_edited.changed_fields.value_max Max. value - + log.element_edited.changed_fields.value_text Text value - + log.element_edited.changed_fields.show_in_table Show in table - + log.element_edited.changed_fields.attachment_type Show in table - + log.element_edited.changed_fields.needs_review Needs review - + log.element_edited.changed_fields.tags Tags - + log.element_edited.changed_fields.mass Mass - + log.element_edited.changed_fields.ipn IPN - + log.element_edited.changed_fields.favorite Favorite - + log.element_edited.changed_fields.minamount Minimum stock - + log.element_edited.changed_fields.manufacturer_product_url Link to product page - + log.element_edited.changed_fields.manufacturer_product_number MPN - + log.element_edited.changed_fields.partUnit Measuring Unit - + log.element_edited.changed_fields.expiration_date Expiration date - + log.element_edited.changed_fields.amount Amount - + log.element_edited.changed_fields.storage_location Storage location - + attachment.max_file_size Maximum file size - + user.saml_user SSO / SAML user - + user.saml_user.pw_change_hint Your user uses single sign-on (SSO). You can not change the password and 2FA settings here. Configure them on your central SSO provider instead! - + login.sso_saml_login Single Sign-On Login (SSO) - + login.local_login_hint The form below is only for log in with a local user. If you want to log in via single sign-on, press the button above. - + part_list.action.action.export Export parts - + part_list.action.export_json Export to JSON - + part_list.action.export_csv Export to CSV - + part_list.action.export_yaml Export to YAML - + part_list.action.export_xml Export to XML - + parts.import.title Import parts - + parts.import.errors.title Import violations - + parts.import.flash.error Errors during import. This is most likely caused by some invalid data. - + parts.import.format.auto Automatic (based on file extension) - + parts.import.flash.error.unknown_format Could not determine the format from the given file! - + parts.import.flash.error.invalid_file File invalid. Please check that you have selected the right format! - + parts.import.part_category.label Category override - + parts.import.part_category.help If you select a value here, all imported parts will be assigned to this category. No matter what was set in the data. - + import.create_unknown_datastructures Create unknown datastructures - + import.create_unknown_datastructures.help If this is selected, datastructures (like categories, footprints, etc.) which does not exist in the database yet, will be automatically created. If this is not selected, only existing data structures will be used, and if no matching data structure is found, the part will get assigned nothing - + import.path_delimiter Path delimiter - + import.path_delimiter.help The delimiter used to mark different levels in data structure pathes like category, footprint, etc. - + parts.import.help_documentation - See the <a href="%link%">documentation</a> for more information on the file format. + documentation for more information on the file format.]]> - + parts.import.help You can import parts from existing files with this tool. The parts will be directly written to database, so please check your file beforehand for correct content before uploading it here. - + parts.import.flash.success Part import successful! - + parts.import.errors.imported_entities Imported parts - + perm.import Import data - + parts.import.part_needs_review.label Mark all imported parts as "Needs review" - + parts.import.part_needs_review.help If this option is selected, then all parts will be marked as "Needs review", no matter what was set in the data. - + project.bom_import.flash.success Imported %count% BOM entries successfully. - + project.bom_import.type Type - + project.bom_import.type.kicad_pcbnew KiCAD Pcbnew BOM (CSV file) - + project.bom_import.clear_existing_bom Clear existing BOM entries before importing - + project.bom_import.clear_existing_bom.help Selecting this option will remove all existing BOM entries in the project and overwrite them with the imported BOM file! - + project.bom_import.flash.invalid_file File could not be imported. Please check that you have selected the right file type. Error message: %message% - + project.bom_import.flash.invalid_entries Validation error! Please check your data! - + project.import_bom Import BOM for project - + project.edit.bom.import_bom Import BOM - + measurement_unit.new New Measurement Unit - + measurement_unit.edit Edit Measurement Unit - + user.aboutMe.label About Me - + storelocation.owner.label Owner - + storelocation.part_owner_must_match.label Part Lot owner must match storage location owner - + part_lot.owner Owner - + part_lot.owner.help Only the owner can withdraw or add stock to this lot. - + log.element_edited.changed_fields.owner Owner - + log.element_edited.changed_fields.instock_unknown Amount unknown - + log.element_edited.changed_fields.needs_refill Refill needed - + part.withdraw.access_denied Not allowed to do the desired action. Please check your permissions and the owner of the part lots. - + part.info.amount.less_than_desired Less than desired - + log.cli_user CLI user - + log.element_edited.changed_fields.part_owner_must_match Part owner must match storage location owner - + part.filter.lessThanDesired - In stock less than desired (total amount < min. amount) + - + part.filter.lotOwner Lot owner - + user.show_email_on_profile.label Show email on public profile page - + log.details.title Log details - + log.user_login.login_from_ip Login from IP address - + log.user_login.ip_anonymize_hint If the last digits of the IP address are missing, then the GPDR mode is enabled, in which IP addresses are anynomized. - + log.user_not_allowed.unauthorized_access_attempt_to Unauthorized access attempt to page - + log.user_not_allowed.hint The request was blocked. No action should be required. - + log.no_comment No comment - + log.element_changed.field Field - + log.element_changed.data_before Data before change - + error_table.error An error occured during your request. - + part.table.invalid_regex Invalid regular expression (regex) - + log.element_changed.data_after Data after change - + log.element_changed.diff Difference - + log.undo.undo.short Undo - + log.undo.revert.short Revert to this timestamp - + log.view_version View version - + log.undo.undelete.short Undelete - + log.element_edited.changed_fields.id ID - + log.element_edited.changed_fields.id_owner Owner - + log.element_edited.changed_fields.parent_id Parent - + log.details.delete_entry Delete log entry - + log.delete.message.title Do you really want to delete the log entry? - + log.delete.message If this is an element history entry, this breaks the element history! This can lead to unexpected results when using the time travel function. - + log.collection_deleted.on_collection on Collection - + log.element_edited.changed_fields.attachments Attachments - + tfa_u2f.add_key.registration_error An error occurred during the registration of the security key. Try again or use another security key! - + log.target_type.none None - + ui.darkmode.light Light - + ui.darkmode.dark Dark - + ui.darkmode.auto Auto (decide based on system settings) - + label_generator.no_lines_given No text content given! The labels will remain empty. - + user.password_strength.very_weak Very weak - + user.password_strength.weak Weak - + user.password_strength.medium Medium - + user.password_strength.strong Strong - + user.password_strength.very_strong Very strong - + perm.users.impersonate Impersonate other users - + user.impersonated_by.label Impersonated by - + user.stop_impersonation Stop impersonation - + user.impersonate.btn Impersonate - + user.impersonate.confirm.title Do you really want to impersonate this user? - + user.impersonate.confirm.message This will be logged. You should only do this with a good reason. @@ -11430,781 +11430,781 @@ Please note, that you can not impersonate a disabled user. If you try you will g - + log.type.security.user_impersonated User impersonated - + info_providers.providers_list.title Information providers - + info_providers.providers_list.active Active - + info_providers.providers_list.disabled Disabled - + info_providers.capabilities.basic Basic - + info_providers.capabilities.footprint Footprint - + info_providers.capabilities.picture Picture - + info_providers.capabilities.datasheet Datasheets - + info_providers.capabilities.price Prices - + part.info_provider_reference.badge The information provider used to create this part. - + part.info_provider_reference Created by Information provider - + oauth_client.connect.btn Connect OAuth - + info_providers.table.provider.label Provider - + info_providers.search.keyword Keyword - + info_providers.search.submit Search - + info_providers.search.providers.help Select the providers in which should be searched. - + info_providers.search.providers Providers - + info_providers.search.info_providers_list Show all available info providers - + info_providers.search.title Create parts from info provider - + oauth_client.flash.connection_successful Connected to OAuth application successfully! - + perm.part.info_providers Info providers - + perm.part.info_providers.create_parts Create parts from info provider - + entity.edit.alternative_names.label Alternative names - + entity.edit.alternative_names.help The alternative names given here, are used to find this element based on the results of the information providers. - + info_providers.form.help_prefix Provider - + update_manager.new_version_available.title New version available - + update_manager.new_version_available.text A new version of Part-DB is available. Check it out here - + update_manager.new_version_available.only_administrators_can_see Only administrators can see this message. - + perm.system.show_available_updates Show available Part-DB updates - + user.settings.api_tokens API tokens - + user.settings.api_tokens.description Using an API token, other applications can access Part-DB with your user rights, to perform various actions using the Part-DB REST API. If you delete an API token here, the application, which uses the token, will no longer be able to access Part-DB on your behalf. - + api_tokens.name Name - + api_tokens.access_level Access level - + api_tokens.expiration_date Expiration date - + api_tokens.added_date Added at - + api_tokens.last_time_used Last time used - + datetime.never Never - + api_token.valid Valid - + api_token.expired Expired - + user.settings.show_api_documentation Show API documentation - + api_token.create_new Create new API token - + api_token.level.read_only Read-Only - + api_token.level.edit Edit - + api_token.level.admin Admin - + api_token.level.full Full - + api_tokens.access_level.help You can restrict, what the API token can access. The access is always limited by the permissions of your user. - + api_tokens.expiration_date.help After this date, the token is not usable anymore. Leave empty if the token should never expire. - + api_tokens.your_token_is Your API token is - + api_tokens.please_save_it Please save it. You will not be able to see it again! - + api_tokens.create_new.back_to_user_settings Back to user settings - + project.build.dont_check_quantity Do not check quantities - + project.build.dont_check_quantity.help If this option is selected, the given withdraw quantities are used as given, no matter if more or less parts are actually required to build this project. - + part_list.action.invert_selection Invert selection - + perm.api API - + perm.api.access_api Access API - + perm.api.manage_tokens Manage API tokens - + user.settings.api_tokens.delete.title Do you really want to delete this API token? - + user.settings.api_tokens.delete Delete - + user.settings.api_tokens.delete.message The application, which uses this API token, will no longer have access to Part-DB. This action can not be undone! - + api_tokens.deleted API token deleted successfully! - + user.settings.api_tokens.no_api_tokens_yet No API tokens configured yet. - + api_token.ends_with Ends with - + entity.select.creating_new_entities_not_allowed You are not allowed to create new entities of this type! Please choose a pre-existing one. - + scan_dialog.mode Barcode type - + scan_dialog.mode.auto Auto detect - + scan_dialog.mode.ipn IPN barcode - + scan_dialog.mode.internal Part-DB barcode - + part_association.label Part association - + part.edit.tab.associations Associated parts - + part_association.edit.other_part Associated part - + part_association.edit.type Relation Type - + part_association.edit.comment Notes - + part_association.edit.type.help You can select here, how the chosen part is related to this part. - + part_association.table.from_this_part Associations from this part to others - + part_association.table.from From - + part_association.table.type Relation - + part_association.table.to To - + part_association.type.compatible Is compatible with - + part_association.table.to_this_part Associations to this part from others - + part_association.type.other Other (custom value) - + part_association.type.supersedes Supersedes - + part_association.edit.other_type Custom type - + part_association.edit.delete.confirm Do you really want to delete this association? This can not be undone. - + part_lot.edit.advanced Expand advanced options - + part_lot.edit.vendor_barcode Vendor barcode - + part_lot.edit.vendor_barcode.help If this lot already have a barcode (e.g. put there by the vendor), you can input its content here, to easily scan it. - + scan_dialog.mode.vendor Vendor barcode (configured in part lot) - + project.bom.instockAmount Stocked amount - + collection_type.new_element.tooltip This element was newly created and was not persisted to the database yet. - + part.merge.title Merge part - + part.merge.title.into into - + part.merge.confirm.title - Do you really want to merge <b>%other%</b> into <b>%target%</b>? + %other% into %target%?]]> - + part.merge.confirm.message - <b>%other%</b> will be deleted, and the part will be saved with the shown information. + %other% will be deleted, and the part will be saved with the shown information.]]> - + part.info.merge_modal.title Merge parts - + part.info.merge_modal.other_part Other part - + part.info.merge_modal.other_into_this Merge other part into this one (delete other part, keep this one) - + part.info.merge_modal.this_into_other Merge this part into other one (delete this part, keep other one) - + part.info.merge_btn Merge part - + part.update_part_from_info_provider.btn Update part from info providers - + info_providers.update_part.title Update existing part from info provider - + part.merge.flash.please_review Data not saved yet. Review the changes and click save to persist the new data. - + user.edit.flash.permissions_fixed Permissions required by other permissions were missing. This was corrected. Please check if the permissions are as you intended. - + permission.legend.dependency_note Please note that some permission operations depend on each other. If you encounter a warning that missing permissions were corrected and a permission was set to allow again, you have to set the dependent operation to forbid too. The dependents can normally found right of an operation. - + log.part_stock_changed.timestamp Timestamp - + part.info.withdraw_modal.timestamp Action timestamp - + part.info.withdraw_modal.timestamp.hint This field allows you to specify the real date, when the stock operation actually was performed, and not just when it was logged. This value is saved in the extra field of the log entry. - + part.info.withdraw_modal.delete_lot_if_empty Delete this lot, if it becomes empty - + info_providers.search.error.client_exception An error occurred while communicating with the information provider. Check the configuration for this provider and refresh the OAuth tokens if possible. - + eda_info.reference_prefix.placeholder e.g. R - + eda_info.reference_prefix Reference prefix - + eda_info.kicad_section.title KiCad specific settings - + eda_info.value Value - + eda_info.value.placeholder e.g. 100n - + eda_info.exclude_from_bom Exclude part from BOM - + eda_info.exclude_from_board Exclude part from PCB/Board - + eda_info.exclude_from_sim Exclude part from simulation - + eda_info.kicad_symbol KiCad schematic symbol - + eda_info.kicad_symbol.placeholder e.g. Transistor_BJT:BC547 - + eda_info.kicad_footprint KiCad footprint - + eda_info.kicad_footprint.placeholder e.g. Package_TO_SOT_THT:TO-92 - + part.edit.tab.eda EDA information - + api.api_endpoints.title API endpoints - + api.api_endpoints.partdb Part-DB API - + api.api_endpoints.kicad_root_url KiCad API root URL - + eda_info.visibility Force visibility - + eda_info.visibility.help By default, the visibility to the EDA software is automatically determined. With this checkbox, you can force the part to be visible or invisible. - + part.withdraw.zero_amount You tried to withdraw/add an amount of zero! No action was performed. - + login.flash.access_denied_please_login Access denied! Please log in to continue. - + attachment.upload_multiple_files Upload files - + entity.mass_creation_flash Created %COUNT% entities successfully. diff --git a/translations/security.en.xlf b/translations/security.en.xlf index 43f2a92f..3ca2bee3 100644 --- a/translations/security.en.xlf +++ b/translations/security.en.xlf @@ -2,13 +2,13 @@ - + user.login_error.user_disabled Your account is disabled! Contact an administrator if you think this is wrong. - + saml.error.cannot_login_local_user_per_saml You cannot login as local user via SSO! Use your local user password instead. diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index 4b11270b..d4650059 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -37,7 +37,7 @@ Part-DB1\src\Entity\UserSystem\Group.php:0 Part-DB1\src\Entity\UserSystem\User.php:0 - + part.master_attachment.must_be_picture The preview attachment must be a valid picture! @@ -82,7 +82,7 @@ src\Entity\StructuralDBElement.php:0 src\Entity\Supplier.php:0 - + structural.entity.unique_name An element with this name already exists on this level! @@ -102,7 +102,7 @@ Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0 Part-DB1\src\Entity\Parameters\SupplierParameter.php:0 - + parameters.validator.min_lesser_typical Value must be lesser or equal the the typical value ({{ compared_value }}). @@ -122,7 +122,7 @@ Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0 Part-DB1\src\Entity\Parameters\SupplierParameter.php:0 - + parameters.validator.min_lesser_max Value must be lesser than the maximum value ({{ compared_value }}). @@ -142,7 +142,7 @@ Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0 Part-DB1\src\Entity\Parameters\SupplierParameter.php:0 - + parameters.validator.max_greater_typical Value must be greater or equal than the typical value ({{ compared_value }}). @@ -152,7 +152,7 @@ Part-DB1\src\Entity\UserSystem\User.php:0 Part-DB1\src\Entity\UserSystem\User.php:0 - + validator.user.username_already_used A user with this name is already exisiting @@ -162,7 +162,7 @@ Part-DB1\src\Entity\UserSystem\User.php:0 Part-DB1\src\Entity\UserSystem\User.php:0 - + user.invalid_username The username must contain only letters, numbers, underscores, dots, pluses or minuses! @@ -171,7 +171,7 @@ obsolete - + validator.noneofitschild.self An element can not be its own parent! @@ -180,172 +180,178 @@ obsolete - + validator.noneofitschild.children You can not assign children element as parent (This would cause loops)! - + validator.select_valid_category Please select a valid category! - + validator.part_lot.only_existing Can not add new parts to this location as it is marked as "Only Existing" - + validator.part_lot.location_full.no_increase Location is full. Amount can not be increased (new value must be smaller than {{ old_amount }}). - + validator.part_lot.location_full Location is full. Can not add new parts to it. - + validator.part_lot.single_part This location can only contain a single part and it is already full! - + validator.attachment.must_not_be_null You must select an attachment type! - + validator.orderdetail.supplier_must_not_be_null You must select an supplier! - + validator.measurement_unit.use_si_prefix_needs_unit To enable SI prefixes, you have to set a unit symbol! - + part.ipn.must_be_unique The internal part number must be unique. {{ value }} is already in use! - + validator.project.bom_entry.name_or_part_needed You have to choose a part for a part BOM entry or set a name for a non-part BOM entry. - + project.bom_entry.name_already_in_bom There is already an BOM entry with this name! - + project.bom_entry.part_already_in_bom This part already exists in the BOM! - + project.bom_entry.mountnames_quantity_mismatch The number of mountnames has to match the BOMs quantity! - + project.bom_entry.can_not_add_own_builds_part You can not add a project's own builds part to the BOM. - + project.bom_has_to_include_all_subelement_parts The project BOM has to include all subprojects builds parts. Part %part_name% of project %project_name% missing! - + project.bom_entry.price_not_allowed_on_parts Prices are not allowed on BOM entries associated with a part. Define the price on the part instead. - + validator.project_build.lot_bigger_than_needed You have selected more quantity to withdraw than needed! Remove unnecessary quantity. - + validator.project_build.lot_smaller_than_needed You have selected less quantity to withdraw than needed for the build! Add additional quantity. - + part.name.must_match_category_regex The part name does not match the regular expression stated by the category: %regex% - + validator.attachment.name_not_blank Set a value here, or upload a file to automatically use its filename as name for the attachment. - + validator.part_lot.owner_must_match_storage_location_owner The owner of this lot must match the owner of the selected storage location (%owner_name%)! - + validator.part_lot.owner_must_not_be_anonymous A lot owner must not be the anonymous user! - + validator.part_association.must_set_an_value_if_type_is_other If you set the type to "other", then you have to set a descriptive value for it! - + validator.part_association.part_cannot_be_associated_with_itself A part can not be associated with itself! - + validator.part_association.already_exists The association with this part already exists! - + validator.part_lot.vendor_barcode_must_be_unique This vendor barcode value was already used in another lot. The barcode must be unique! - + validator.year_2038_bug_on_32bit Due to technical limitations, it is not possible to select dates after the 2038-01-19 on 32-bit systems! + + + validator.invalid_range + The given range is not valid! + + diff --git a/yarn.lock b/yarn.lock index 94a75ea1..6f21035b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2136,9 +2136,9 @@ integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.19.3" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.3.tgz#e469a13e4186c9e1c0418fb17be8bc8ff1b19a7a" - integrity sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg== + version "4.19.5" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" + integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== dependencies: "@types/node" "*" "@types/qs" "*" @@ -2217,9 +2217,9 @@ "@types/node" "*" "@types/node@*": - version "20.14.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.2.tgz#a5f4d2bcb4b6a87bffcaa717718c5a0f208f4a18" - integrity sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q== + version "20.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.8.tgz#45c26a2a5de26c3534a9504530ddb3b27ce031ac" + integrity sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA== dependencies: undici-types "~5.26.4" @@ -2499,10 +2499,10 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn-walk@^7.1.1: version "7.2.0" @@ -2510,19 +2510,21 @@ acorn-walk@^7.1.1: integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== acorn-walk@^8.0.0: - version "8.3.2" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" - integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + version "8.3.3" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" + integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== + dependencies: + acorn "^8.11.0" acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.0.5, acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2: - version "8.11.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.0.4, acorn@^8.0.5, acorn@^8.11.0, acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2: + version "8.12.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" + integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== adjust-sourcemap-loader@^4.0.0: version "4.0.0" @@ -2800,14 +2802,14 @@ browser-process-hrtime@^1.0.0: integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== browserslist@^4.0.0, browserslist@^4.21.10, browserslist@^4.22.2, browserslist@^4.23.0: - version "4.23.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" - integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + version "4.23.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.1.tgz#ce4af0534b3d37db5c1a4ca98b9080f985041e96" + integrity sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw== dependencies: - caniuse-lite "^1.0.30001587" - electron-to-chromium "^1.4.668" + caniuse-lite "^1.0.30001629" + electron-to-chromium "^1.4.796" node-releases "^2.0.14" - update-browserslist-db "^1.0.13" + update-browserslist-db "^1.0.16" bs-custom-file-input@^1.3.4: version "1.3.4" @@ -2889,10 +2891,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587: - version "1.0.30001629" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001629.tgz#907a36f4669031bd8a1a8dbc2fa08b29e0db297e" - integrity sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001629: + version "1.0.30001636" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz#b15f52d2bdb95fad32c2f53c0b68032b85188a78" + integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg== chalk@^2.4.2: version "2.4.2" @@ -3758,10 +3760,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.668: - version "1.4.792" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.792.tgz#738712f99d02f70c5754ca4264782915fa946849" - integrity sha512-rkg5/N3L+Y844JyfgPUyuKK0Hk0efo3JNxUDKvz3HgP6EmN4rNGhr2D8boLsfTV/hGo7ZGAL8djw+jlg99zQyA== +electron-to-chromium@^1.4.796: + version "1.4.810" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.810.tgz#7dee01b090b9e048e6db752f7b30921790230654" + integrity sha512-Kaxhu4T7SJGpRQx99tq216gCq2nMxJo+uuT6uzz9l8TVN2stL7M06MIIXAtr9jsrLs2Glflgf2vMQRepxawOdQ== emoji-regex@^8.0.0: version "8.0.0" @@ -3783,7 +3785,7 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -enhanced-resolve@^5.0.0, enhanced-resolve@^5.16.0: +enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.0: version "5.17.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz#d037603789dd9555b89aaec7eb78845c49089bc5" integrity sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA== @@ -4355,7 +4357,7 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.3" -hasown@^2.0.0: +hasown@^2.0.0, hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -4611,11 +4613,11 @@ is-binary-path@~2.1.0: binary-extensions "^2.0.0" is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + version "2.14.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" + integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== dependencies: - hasown "^2.0.0" + hasown "^2.0.2" is-date-object@^1.0.5: version "1.0.5" @@ -4831,9 +4833,9 @@ jsesc@~0.5.0: integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== json-formatter-js@^2.3.4: - version "2.5.11" - resolved "https://registry.yarnpkg.com/json-formatter-js/-/json-formatter-js-2.5.11.tgz#b21db9248ac1e8ae2cf814985b2f42ce50174439" - integrity sha512-njrwXGOZNZTt7nrhkFDqzfEJqL442QEFLS2deEMHQhOb6m3ubYXopXFJrZCsL8/NQah3OA6O7OrlECPusoMBwQ== + version "2.5.15" + resolved "https://registry.yarnpkg.com/json-formatter-js/-/json-formatter-js-2.5.15.tgz#d9af9ac18bad481ac742ce4215105daa984889f5" + integrity sha512-PHDOzHKxtx9Ik5XQKOnnCx76aKPtlz+ym02MCv3NWimRS8qEgUx/7JDuhEDYY2jH67rb+fFBNJDTMz8JVZXzPg== json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" @@ -4892,9 +4894,9 @@ klona@^2.0.4: integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== launch-editor@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c" - integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== + version "2.8.0" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.8.0.tgz#7255d90bdba414448e2138faa770a74f28451305" + integrity sha512-vJranOAJrI/llyWGRQqiDM+adrw+k83fvmmx3+nV47g3+36xM15jE+zyZ6Ffel02+xSvuM0b2GDRosXZkbb6wA== dependencies: picocolors "^1.0.0" shell-quote "^1.8.1" @@ -4907,9 +4909,9 @@ lie@~3.3.0: immediate "~3.0.5" lilconfig@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" - integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== + version "3.1.2" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" + integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow== lines-and-columns@^1.1.6: version "1.2.4" @@ -4999,16 +5001,16 @@ make-dir@^3.0.2: semver "^6.0.0" marked-gfm-heading-id@^3.0.4: - version "3.1.3" - resolved "https://registry.yarnpkg.com/marked-gfm-heading-id/-/marked-gfm-heading-id-3.1.3.tgz#7bcfea85901843baf214b96ccc4ff67581c972a9" - integrity sha512-A0cRU4PCueX/5m8VE4mT8uTQ36l3xMYRojz3Eqnk4BmUFZ0T+9Xhn2KvHcANP4qbhfOeuMrWJCTQbASIBR5xeg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/marked-gfm-heading-id/-/marked-gfm-heading-id-3.2.0.tgz#d0344f786b4ab55415048260aad147f4e8e904b6" + integrity sha512-Xfxpr5lXLDLY10XqzSCA9l2dDaiabQUgtYM9hw8yunyVsB/xYBRpiic6BOiY/EAJw1ik1eWr1ET1HKOAPZBhXg== dependencies: github-slugger "^2.0.0" marked-mangle@^1.0.1: - version "1.1.7" - resolved "https://registry.yarnpkg.com/marked-mangle/-/marked-mangle-1.1.7.tgz#07fed8f47ebdc51761aac0364723468644cea49d" - integrity sha512-bLsXKovJEEs/Dl++TBPmjX8ALFmrH5G0doTs+BdDOloBKWYRf3acyJghce78SnwInDkNPJ6crubr4MnFG7urOA== + version "1.1.8" + resolved "https://registry.yarnpkg.com/marked-mangle/-/marked-mangle-1.1.8.tgz#45e6ef91ae4d8865e542969218ce3d98996281fa" + integrity sha512-vN6a7AxdxP9PHQ6xI0EN/CesMOjcjqP0+JN/0cO2zypzyiyUhev82nWxA+/9JxYQawDErq8rT3G19AM+IgpmlQ== marked@4.0.12: version "4.0.12" @@ -5253,9 +5255,9 @@ object-assign@^4.0.1: integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== object-is@^1.1.5: version "1.1.6" @@ -6823,7 +6825,7 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.13: +update-browserslist-db@^1.0.16: version "1.0.16" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== @@ -7037,9 +7039,9 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.74.0: - version "5.91.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9" - integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw== + version "5.92.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.92.1.tgz#eca5c1725b9e189cffbd86e8b6c3c7400efc5788" + integrity sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" @@ -7047,10 +7049,10 @@ webpack@^5.74.0: "@webassemblyjs/wasm-edit" "^1.12.1" "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" + acorn-import-attributes "^1.9.5" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.16.0" + enhanced-resolve "^5.17.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" @@ -7119,14 +7121,14 @@ wrappy@1: integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== ws@^7.3.1, ws@^7.4.6: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.13.0: - version "8.17.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" - integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xml-name-validator@^3.0.0: version "3.0.0"