diff --git a/.env b/.env index 9a6ce846..982d4bbd 100644 --- a/.env +++ b/.env @@ -31,7 +31,8 @@ DATABASE_EMULATE_NATURAL_SORT=0 # General settings ################################################################################### -# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates or when no request context is available. +# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates +# This must end with a slash! DEFAULT_URI="https://partdb.changeme.invalid/" ################################################################################### @@ -133,5 +134,4 @@ CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' ###> symfony/framework-bundle ### APP_ENV=prod APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 -APP_SHARE_DIR=var/share ###< symfony/framework-bundle ### diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 637a66b5..00000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,186 +0,0 @@ -# Copilot Instructions for Part-DB - -Part-DB is an Open-Source inventory management system for electronic components built with Symfony 7.4 and modern web technologies. - -## Technology Stack - -- **Backend**: PHP 8.2+, Symfony 7.4, Doctrine ORM -- **Frontend**: Bootstrap 5, Hotwire Stimulus/Turbo, TypeScript, Webpack Encore -- **Database**: MySQL 5.7+/MariaDB 10.4+/PostgreSQL 10+/SQLite -- **Testing**: PHPUnit with DAMA Doctrine Test Bundle -- **Code Quality**: Easy Coding Standard (ECS), PHPStan (level 5) - -## Project Structure - -- `src/`: PHP application code organized by purpose (Controller, Entity, Service, Form, etc.) -- `assets/`: Frontend TypeScript/JavaScript and CSS files -- `templates/`: Twig templates for views -- `tests/`: PHPUnit tests mirroring the `src/` structure -- `config/`: Symfony configuration files -- `public/`: Web-accessible files -- `translations/`: Translation files for multi-language support - -## Coding Standards - -### PHP Code - -- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) and [Symfony coding standards](https://symfony.com/doc/current/contributing/code/standards.html) -- Use type hints for all parameters and return types -- Always declare strict types: `declare(strict_types=1);` at the top of PHP files -- Use PHPDoc blocks for complex logic or when type information is needed - -### TypeScript/JavaScript - -- Use TypeScript for new frontend code -- Follow existing Stimulus controller patterns in `assets/controllers/` -- Use Bootstrap 5 components and utilities -- Leverage Hotwire Turbo for dynamic page updates - -### Naming Conventions - -- Entities: Use descriptive names that reflect database models (e.g., `Part`, `StorageLocation`) -- Controllers: Suffix with `Controller` (e.g., `PartController`) -- Services: Descriptive names reflecting their purpose (e.g., `PartService`, `LabelGenerator`) -- Tests: Match the class being tested with `Test` suffix (e.g., `PartTest`, `PartControllerTest`) - -## Development Workflow - -### Dependencies - -- Install PHP dependencies: `composer install` -- Install JS dependencies: `yarn install` -- Build frontend assets: `yarn build` (production) or `yarn watch` (development) - -### Database - -- Create database: `php bin/console doctrine:database:create --env=dev` -- Run migrations: `php bin/console doctrine:migrations:migrate --env=dev` -- Load fixtures: `php bin/console partdb:fixtures:load -n --env=dev` - -Or use Makefile shortcuts: -- `make dev-setup`: Complete development environment setup -- `make dev-reset`: Reset development environment (cache clear + migrate) - -### Testing - -- Set up test environment: `make test-setup` -- Run all tests: `php bin/phpunit` -- Run specific test: `php bin/phpunit tests/Path/To/SpecificTest.php` -- Run tests with coverage: `php bin/phpunit --coverage-html var/coverage` -- Test environment uses SQLite by default for speed - -### Static Analysis - -- Run PHPStan: `composer phpstan` or `COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5` -- PHPStan configuration is in `phpstan.dist.neon` - -### Running the Application - -- Development server: `symfony serve` (requires Symfony CLI) -- Or configure Apache/nginx to serve from `public/` directory -- Set `APP_ENV=dev` in `.env.local` for development mode - -## Best Practices - -### Security - -- Always sanitize user input -- Use Symfony's security component for authentication/authorization -- Check permissions using the permission system before allowing actions -- Never expose sensitive data in logs or error messages -- Use parameterized queries (Doctrine handles this automatically) - -### Performance - -- Use Doctrine query builder for complex queries instead of DQL when possible -- Lazy load relationships to avoid N+1 queries -- Cache results when appropriate using Symfony's cache component -- Use pagination for large result sets (DataTables integration available) - -### Database - -- Always create migrations for schema changes: `php bin/console make:migration` -- Review migration files before running them -- Use Doctrine annotations or attributes for entity mapping -- Follow existing entity patterns for relationships and lifecycle callbacks - -### Frontend - -- Use Stimulus controllers for interactive components -- Leverage Turbo for dynamic page updates without full page reloads -- Use Bootstrap 5 classes for styling -- Keep JavaScript modular and organized in controllers -- Use the translation system for user-facing strings - -### Translations - -- Use translation keys, not hardcoded strings: `{{ 'part.info.title'|trans }}` -- Add new translation keys to `translations/` files -- Primary language is English (en) -- Translations are managed via Crowdin, but can be edited locally if needed - -### Testing - -- Write unit tests for services and helpers -- Write functional tests for controllers -- Use fixtures for test data -- Tests should be isolated and not depend on execution order -- Mock external dependencies when appropriate -- Follow existing test patterns in the repository - -## Common Patterns - -### Creating an Entity - -1. Create entity class in `src/Entity/` with Doctrine attributes -2. Generate migration: `php bin/console make:migration` -3. Review and run migration: `php bin/console doctrine:migrations:migrate` -4. Create repository if needed in `src/Repository/` -5. Add fixtures in `src/DataFixtures/` for testing - -### Adding a Form - -1. Create form type in `src/Form/` -2. Extend `AbstractType` and implement `buildForm()` and `configureOptions()` -3. Use in controller and render in Twig template -4. Follow existing form patterns for consistency - -### Creating a Controller Action - -1. Add method to appropriate controller in `src/Controller/` -2. Use route attributes for routing -3. Check permissions using security voters -4. Return Response or render Twig template -5. Add corresponding template in `templates/` - -### Adding a Service - -1. Create service class in `src/Services/` -2. Use dependency injection via constructor -3. Tag service in `config/services.yaml` if needed -4. Services are autowired by default - -## Important Notes - -- Part-DB uses fine-grained permissions - always check user permissions before actions -- Multi-language support is critical - use translation keys everywhere -- The application supports multiple database backends - write portable code -- Responsive design is important - test on mobile/tablet viewports -- Event system is used for logging changes - emit events when appropriate -- API Platform is integrated for REST API endpoints - -## Multi-tenancy Considerations - -- Part-DB is designed as a single-tenant application with multiple users -- User groups have different permission levels -- Always scope queries to respect user permissions -- Use the security context to get current user information - -## Resources - -- [Documentation](https://docs.part-db.de/) -- [Contributing Guide](CONTRIBUTING.md) -- [Symfony Documentation](https://symfony.com/doc/current/index.html) -- [Doctrine Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/current/) -- [Bootstrap 5 Documentation](https://getbootstrap.com/docs/5.1/) -- [Hotwire Documentation](https://hotwired.dev/) diff --git a/.github/workflows/assets_artifact_build.yml b/.github/workflows/assets_artifact_build.yml index 8ce7ccf6..c950375b 100644 --- a/.github/workflows/assets_artifact_build.yml +++ b/.github/workflows/assets_artifact_build.yml @@ -22,7 +22,7 @@ jobs: APP_ENV: prod steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -60,7 +60,7 @@ jobs: ${{ runner.os }}-yarn- - name: Setup node - uses: actions/setup-node@v6 + uses: actions/setup-node@v4 with: node-version: '20' @@ -80,13 +80,13 @@ jobs: run: zip -r /tmp/partdb_assets.zip public/build/ vendor/ - name: Upload assets artifact - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v4 with: name: Only dependencies and built assets path: /tmp/partdb_assets.zip - name: Upload full artifact - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v4 with: name: Full Part-DB including dependencies and built assets path: /tmp/partdb_with_assets.zip diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index ce3243ca..c912e769 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: Docker meta id: docker_meta diff --git a/.github/workflows/docker_frankenphp.yml b/.github/workflows/docker_frankenphp.yml index 1180f0c5..0b2eb874 100644 --- a/.github/workflows/docker_frankenphp.yml +++ b/.github/workflows/docker_frankenphp.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: Docker meta id: docker_meta diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index d8b099eb..1de98ee9 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 822f5af6..66e2f40c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.2', '8.3', '8.4', '8.5' ] + php-versions: ['8.2', '8.3', '8.4' ] db-type: [ 'mysql', 'sqlite', 'postgres' ] env: @@ -46,7 +46,7 @@ jobs: if: matrix.db-type == 'postgres' - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -104,7 +104,7 @@ jobs: run: composer install --prefer-dist --no-progress - name: Setup node - uses: actions/setup-node@v6 + uses: actions/setup-node@v4 with: node-version: '20' diff --git a/.gitignore b/.gitignore index dd5c43db..76655919 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,3 @@ yarn-error.log ###> phpstan/phpstan ### phpstan.neon ###< phpstan/phpstan ### - -.claude/ -CLAUDE.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5994a115..d31c904e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # How to contribute -Thank you for considering contributing to Part-DB! -Please read the text below, so your contributed content can be incorporated into Part-DB easily. +Thank you for consider to contribute to Part-DB! +Please read the text below, so your contributed content can be contributed easily to Part-DB. You can contribute to Part-DB in various ways: * Report bugs and request new features via [issues](https://github.com/Part-DB/Part-DB-server/issues) @@ -18,38 +18,38 @@ Part-DB uses translation keys (e.g. part.info.title) that are sorted by their us was translated in other languages (this is possible via the "Other languages" dropdown in the translation editor). ## Project structure -Part-DB uses Symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html). +Part-DB uses symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html). Interesting folders are: -* `public`: Everything in this directory will be publicly accessible via web. Use this folder to serve static images. -* `assets`: The frontend assets are saved here. You can find the JavaScript and CSS code here. -* `src`: Part-DB's PHP code is saved here. Note that the subdirectories are structured by the classes' purposes (so use `Controller` for Controllers, `Entity` for Database models, etc.) -* `translations`: The translations used in Part-DB are saved here. +* `public`: Everything in this directory will be publicy accessible via web. Use this folder to serve static images. +* `assets`: The frontend assets are saved here. You can find the javascript and CSS code here. +* `src`: Part-DB's PHP code is saved here. Note that the sub directories are structured by the classes purposes (so use `Controller` Controllers, `Entities` for Database models, etc.) +* `translations`: The translations used in Part-DB are saved here * `templates`: The templates (HTML) that are used by Twig to render the different pages. Email templates are also saved here. -* `tests/`: Tests that can be run by PHPUnit. +* `tests/`: Tests that can be run by PHPunit. ## Development environment -For setting up a development environment, you will need to install PHP, Composer, a database server (MySQL or MariaDB) and yarn (which needs a Node.js environment). -* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (Symfony profiler) and other features that +For setting up an development you will need to install PHP, composer, a database server (MySQL or MariaDB) and yarn (which needs an nodejs environment). +* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (symfony profiler) and other features that will simplify development. -* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies. -* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files. -* For running Part-DB, it is recommended to use [Symfony CLI](https://symfony.com/download). -That way you can run a correctly configured webserver with `symfony serve`. +* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies +* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files +* For running Part-DB it is recommended to use [Symfony CLI](https://symfony.com/download). +That way you can run a correct configured webserver with `symfony serve` ## Coding style -Code should follow the [PSR-12 Standard](https://www.php-fig.org/psr/psr-12/) and Symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html). +Code should follow the [PSR12-Standard](https://www.php-fig.org/psr/psr-12/) and symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html). Part-DB uses [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) to check and fix coding style violations: -* To check your code for valid code style, run `vendor/bin/ecs check src/` -* To fix violations, run `vendor/bin/ecs check src/ --fix` (please check afterwards if the code is still valid) +* To check your code for valid code style run `vendor/bin/ecs check src/` +* To fix violations run `vendor/bin/ecs check src/` (please checks afterwards if the code is valid afterwards) ## GitHub actions -Part-DB uses GitHub Actions to run various tests and checks on the code: +Part-DB uses GitHub actions to run various tests and checks on the code: * Yarn dependencies can compile -* PHPUnit tests run successfully -* Config files, translations, and templates have valid syntax -* Doctrine schema is valid -* No known vulnerable dependencies are used -* Static analysis is successful (phpstan with `--level=2`) +* PHPunit tests run successful +* Config files, translations and templates has valid syntax +* Doctrine schema valid +* No known vulnerable dependecies are used +* Static analysis successful (phpstan with `--level=2`) -Further, the code coverage of the PHPUnit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server). +Further the code coverage of the PHPunit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server). diff --git a/Makefile b/Makefile deleted file mode 100644 index bc4d0bf3..00000000 --- a/Makefile +++ /dev/null @@ -1,91 +0,0 @@ -# PartDB Makefile for Test Environment Management - -.PHONY: help deps-install lint format format-check test coverage pre-commit all test-typecheck \ -test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run test-reset \ -section-dev dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset - -# Default target -help: ## Show this help - @awk 'BEGIN {FS = ":.*##"}; /^[a-zA-Z0-9][a-zA-Z0-9_-]+:.*##/ {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) - -# Dependencies -deps-install: ## Install PHP dependencies with unlimited memory - @echo "📦 Installing PHP dependencies..." - COMPOSER_MEMORY_LIMIT=-1 composer install - yarn install - @echo "✅ Dependencies installed" - -# Complete test environment setup -test-setup: test-clean test-db-create test-db-migrate test-fixtures ## Complete test setup (clean, create DB, migrate, fixtures) - @echo "✅ Test environment setup complete!" - -# Clean test environment -test-clean: ## Clean test cache and database files - @echo "🧹 Cleaning test environment..." - rm -rf var/cache/test - rm -f var/app_test.db - @echo "✅ Test environment cleaned" - -# Create test database -test-db-create: ## Create test database (if not exists) - @echo "🗄️ Creating test database..." - -php bin/console doctrine:database:create --if-not-exists --env test || echo "⚠️ Database creation failed (expected for SQLite) - continuing..." - -# Run database migrations for test environment -test-db-migrate: ## Run database migrations for test environment - @echo "🔄 Running database migrations..." - COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env test - -# Clear test cache -test-cache-clear: ## Clear test cache - @echo "🗑️ Clearing test cache..." - rm -rf var/cache/test - @echo "✅ Test cache cleared" - -# Load test fixtures -test-fixtures: ## Load test fixtures - @echo "📦 Loading test fixtures..." - php bin/console partdb:fixtures:load -n --env test - -# Run PHPUnit tests -test-run: ## Run PHPUnit tests - @echo "🧪 Running tests..." - php bin/phpunit - -# Quick test reset (clean + migrate + fixtures, skip DB creation) -test-reset: test-cache-clear test-db-migrate test-fixtures - @echo "✅ Test environment reset complete!" - -test-typecheck: ## Run static analysis (PHPStan) - @echo "🧪 Running type checks..." - COMPOSER_MEMORY_LIMIT=-1 composer phpstan - -# Development helpers -dev-setup: dev-clean dev-db-create dev-db-migrate dev-warmup ## Complete development setup (clean, create DB, migrate, warmup) - @echo "✅ Development environment setup complete!" - -dev-clean: ## Clean development cache and database files - @echo "🧹 Cleaning development environment..." - rm -rf var/cache/dev - rm -f var/app_dev.db - @echo "✅ Development environment cleaned" - -dev-db-create: ## Create development database (if not exists) - @echo "🗄️ Creating development database..." - -php bin/console doctrine:database:create --if-not-exists --env dev || echo "⚠️ Database creation failed (expected for SQLite) - continuing..." - -dev-db-migrate: ## Run database migrations for development environment - @echo "🔄 Running database migrations..." - COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env dev - -dev-cache-clear: ## Clear development cache - @echo "🗑️ Clearing development cache..." - rm -rf var/cache/dev - @echo "✅ Development cache cleared" - -dev-warmup: ## Warm up development cache - @echo "🔥 Warming up development cache..." - COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G bin/console cache:warmup --env dev -n - -dev-reset: dev-cache-clear dev-db-migrate ## Quick development reset (cache clear + migrate) - @echo "✅ Development environment reset complete!" \ No newline at end of file diff --git a/README.md b/README.md index 993a1a9c..c9d25016 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Static analysis](https://github.com/Part-DB/Part-DB-symfony/workflows/Static%20analysis/badge.svg) [![codecov](https://codecov.io/gh/Part-DB/Part-DB-server/branch/master/graph/badge.svg)](https://codecov.io/gh/Part-DB/Part-DB-server) ![GitHub License](https://img.shields.io/github/license/Part-DB/Part-DB-symfony) -![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%208.2-green) +![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%208.1-green) ![Docker Pulls](https://img.shields.io/docker/pulls/jbtronics/part-db1) ![Docker Build Status](https://github.com/Part-DB/Part-DB-symfony/workflows/Docker%20Image%20Build/badge.svg) @@ -29,8 +29,8 @@ If you want to test Part-DB without installing it, you can use [this](https://de You can log in with username: *user* and password: *user*. -Every change to the master branch gets automatically deployed, so it represents the current development progress and -may not be completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading +Every change to the master branch gets automatically deployed, so it represents the current development progress and is +may not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading the page for the first time. @@ -142,7 +142,7 @@ There you will find various methods to support development on a monthly or a one ## Built with -* [Symfony 6](https://symfony.com/): The main framework used for the serverside PHP +* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP * [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme * [Fontawesome](https://fontawesome.com/): Used as icon set * [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend diff --git a/VERSION b/VERSION index c043eea7..7ec1d6db 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.1 +2.1.0 diff --git a/assets/stimulus_bootstrap.js b/assets/bootstrap.js similarity index 100% rename from assets/stimulus_bootstrap.js rename to assets/bootstrap.js diff --git a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js index bb9fcd1f..095b0d25 100644 --- a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js +++ b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js @@ -20,12 +20,11 @@ import {Plugin} from 'ckeditor5'; require('./lang/de.js'); -require('./lang/en.js'); import { addListToDropdown, createDropdown } from 'ckeditor5'; import {Collection} from 'ckeditor5'; -import {UIModel} from 'ckeditor5'; +import {Model} from 'ckeditor5'; export default class PartDBLabelUI extends Plugin { init() { @@ -152,28 +151,18 @@ const PLACEHOLDERS = [ function getDropdownItemsDefinitions(t) { const itemDefinitions = new Collection(); - let first = true; - for ( const group of PLACEHOLDERS) { - //Add group header - - //Skip separator for first group - if (!first) { - - itemDefinitions.add({ - 'type': 'separator', - model: new UIModel( { - withText: true, - }) - }); - } else { - first = false; - } + itemDefinitions.add({ + 'type': 'separator', + model: new Model( { + withText: true, + }) + }); itemDefinitions.add({ type: 'button', - model: new UIModel( { + model: new Model( { label: t(group.label), withText: true, isEnabled: false, @@ -184,7 +173,7 @@ function getDropdownItemsDefinitions(t) { for ( const entry of group.entries) { const definition = { type: 'button', - model: new UIModel( { + model: new Model( { commandParam: entry[0], label: t(entry[1]), tooltip: entry[0], diff --git a/assets/ckeditor/plugins/PartDBLabel/lang/de.js b/assets/ckeditor/plugins/PartDBLabel/lang/de.js index e0ca0521..748b1607 100644 --- a/assets/ckeditor/plugins/PartDBLabel/lang/de.js +++ b/assets/ckeditor/plugins/PartDBLabel/lang/de.js @@ -17,9 +17,15 @@ * along with this program. If not, see . */ -import {add} from "ckeditor5"; +// Make sure that the global object is defined. If not, define it. +window.CKEDITOR_TRANSLATIONS = window.CKEDITOR_TRANSLATIONS || {}; -add( "de", { +// Make sure that the dictionary for Polish translations exist. +window.CKEDITOR_TRANSLATIONS[ 'de' ] = window.CKEDITOR_TRANSLATIONS[ 'de' ] || {}; +window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary = window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary || {}; + +// Extend the dictionary for Polish translations with your translations: +Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, { 'Label Placeholder': 'Label Platzhalter', 'Part': 'Bauteil', @@ -82,4 +88,5 @@ add( "de", { 'Instance name': 'Instanzname', 'Target type': 'Zieltyp', 'URL of this Part-DB instance': 'URL dieser Part-DB Instanz', -}); + +} ); \ No newline at end of file diff --git a/assets/ckeditor/plugins/PartDBLabel/lang/en.js b/assets/ckeditor/plugins/PartDBLabel/lang/en.js deleted file mode 100644 index 8f77aaf1..00000000 --- a/assets/ckeditor/plugins/PartDBLabel/lang/en.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). - * - * Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import {add} from "ckeditor5"; - -add( "en", { - 'Label Placeholder': 'Label placeholder', - 'Part': 'Part', - - 'Database ID': 'Database ID', - 'Part name': 'Part name', - 'Category': 'Category', - 'Category (Full path)': 'Category (full path)', - 'Manufacturer': 'Manufacturer', - 'Manufacturer (Full path)': 'Manufacturer (full path)', - 'Footprint': 'Footprint', - 'Footprint (Full path)': 'Footprint (full path)', - 'Mass': 'Mass', - 'Manufacturer Product Number (MPN)': 'Manufacturer Product Number (MPN)', - 'Internal Part Number (IPN)': 'Internal Part Number (IPN)', - 'Tags': 'Tags', - 'Manufacturing status': 'Manufacturing status', - 'Description': 'Description', - 'Description (plain text)': 'Description (plain text)', - 'Comment': 'Comment', - 'Comment (plain text)': 'Comment (plain text)', - 'Last modified datetime': 'Last modified datetime', - 'Creation datetime': 'Creation datetime', - 'IPN as QR code': 'IPN as QR code', - 'IPN as Code 128 barcode': 'IPN as Code 128 barcode', - 'IPN as Code 39 barcode': 'IPN as Code 39 barcode', - - 'Lot ID': 'Lot ID', - 'Lot name': 'Lot name', - 'Lot comment': 'Lot comment', - 'Lot expiration date': 'Lot expiration date', - 'Lot amount': 'Lot amount', - 'Storage location': 'Storage location', - 'Storage location (Full path)': 'Storage location (full path)', - 'Full name of the lot owner': 'Full name of the lot owner', - 'Username of the lot owner': 'Username of the lot owner', - - 'Barcodes': 'Barcodes', - 'Content of the 1D barcodes (like Code 39)': 'Content of the 1D barcodes (like Code 39)', - 'Content of the 2D barcodes (QR codes)': 'Content of the 2D barcodes (QR codes)', - 'QR code linking to this element': 'QR code linking to this element', - 'Code 128 barcode linking to this element': 'Code 128 barcode linking to this element', - 'Code 39 barcode linking to this element': 'Code 39 barcode linking to this element', - 'Code 93 barcode linking to this element': 'Code 93 barcode linking to this element', - 'Datamatrix code linking to this element': 'Datamatrix code linking to this element', - - 'Location ID': 'Location ID', - 'Name': 'Name', - 'Full path': 'Full path', - 'Parent name': 'Parent name', - 'Parent full path': 'Parent full path', - 'Full name of the location owner': 'Full name of the location owner', - 'Username of the location owner': 'Username of the location owner', - - 'Username': 'Username', - 'Username (including name)': 'Username (including name)', - 'Current datetime': 'Current datetime', - 'Current date': 'Current date', - 'Current time': 'Current time', - 'Instance name': 'Instance name', - 'Target type': 'Target type', - 'URL of this Part-DB instance': 'URL of this Part-DB instance', -} ); diff --git a/assets/controllers/bulk_import_controller.js b/assets/controllers/bulk_import_controller.js deleted file mode 100644 index 49e4d60f..00000000 --- a/assets/controllers/bulk_import_controller.js +++ /dev/null @@ -1,359 +0,0 @@ -import { Controller } from "@hotwired/stimulus" -import { generateCsrfHeaders } from "./csrf_protection_controller" - -export default class extends Controller { - static targets = ["progressBar", "progressText"] - static values = { - jobId: Number, - partId: Number, - researchUrl: String, - researchAllUrl: String, - markCompletedUrl: String, - markSkippedUrl: String, - markPendingUrl: String - } - - connect() { - // Auto-refresh progress if job is in progress - if (this.hasProgressBarTarget) { - this.startProgressUpdates() - } - - // Restore scroll position after page reload (if any) - this.restoreScrollPosition() - } - - getHeaders() { - const headers = { - 'Content-Type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest' - } - - // Add CSRF headers if available - const form = document.querySelector('form') - if (form) { - const csrfHeaders = generateCsrfHeaders(form) - Object.assign(headers, csrfHeaders) - } - - return headers - } - - async fetchWithErrorHandling(url, options = {}, timeout = 30000) { - const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), timeout) - - try { - const response = await fetch(url, { - ...options, - headers: { ...this.getHeaders(), ...options.headers }, - signal: controller.signal - }) - - clearTimeout(timeoutId) - - if (!response.ok) { - const errorText = await response.text() - throw new Error(`Server error (${response.status}): ${errorText}`) - } - - return await response.json() - } catch (error) { - clearTimeout(timeoutId) - - if (error.name === 'AbortError') { - throw new Error('Request timed out. Please try again.') - } else if (error.message.includes('Failed to fetch')) { - throw new Error('Network error. Please check your connection and try again.') - } else { - throw error - } - } - } - - disconnect() { - if (this.progressInterval) { - clearInterval(this.progressInterval) - } - } - - startProgressUpdates() { - // Progress updates are handled via page reload for better reliability - // No need for periodic updates since state changes trigger page refresh - } - - restoreScrollPosition() { - const savedPosition = sessionStorage.getItem('bulkImportScrollPosition') - if (savedPosition) { - // Restore scroll position after a small delay to ensure page is fully loaded - setTimeout(() => { - window.scrollTo(0, parseInt(savedPosition)) - // Clear the saved position so it doesn't interfere with normal navigation - sessionStorage.removeItem('bulkImportScrollPosition') - }, 100) - } - } - - async markCompleted(event) { - const partId = event.currentTarget.dataset.partId - - try { - const url = this.markCompletedUrlValue.replace('__PART_ID__', partId) - const data = await this.fetchWithErrorHandling(url, { method: 'POST' }) - - if (data.success) { - this.updateProgressDisplay(data) - this.markRowAsCompleted(partId) - - if (data.job_completed) { - this.showJobCompletedMessage() - } - } else { - this.showErrorMessage(data.error || 'Failed to mark part as completed') - } - } catch (error) { - console.error('Error marking part as completed:', error) - this.showErrorMessage(error.message || 'Failed to mark part as completed') - } - } - - async markSkipped(event) { - const partId = event.currentTarget.dataset.partId - const reason = prompt('Reason for skipping (optional):') || '' - - try { - const url = this.markSkippedUrlValue.replace('__PART_ID__', partId) - const data = await this.fetchWithErrorHandling(url, { - method: 'POST', - body: JSON.stringify({ reason }) - }) - - if (data.success) { - this.updateProgressDisplay(data) - this.markRowAsSkipped(partId) - } else { - this.showErrorMessage(data.error || 'Failed to mark part as skipped') - } - } catch (error) { - console.error('Error marking part as skipped:', error) - this.showErrorMessage(error.message || 'Failed to mark part as skipped') - } - } - - async markPending(event) { - const partId = event.currentTarget.dataset.partId - - try { - const url = this.markPendingUrlValue.replace('__PART_ID__', partId) - const data = await this.fetchWithErrorHandling(url, { method: 'POST' }) - - if (data.success) { - this.updateProgressDisplay(data) - this.markRowAsPending(partId) - } else { - this.showErrorMessage(data.error || 'Failed to mark part as pending') - } - } catch (error) { - console.error('Error marking part as pending:', error) - this.showErrorMessage(error.message || 'Failed to mark part as pending') - } - } - - updateProgressDisplay(data) { - if (this.hasProgressBarTarget) { - this.progressBarTarget.style.width = `${data.progress}%` - this.progressBarTarget.setAttribute('aria-valuenow', data.progress) - } - - if (this.hasProgressTextTarget) { - this.progressTextTarget.textContent = `${data.completed_count} / ${data.total_count} completed` - } - } - - markRowAsCompleted(partId) { - // Save scroll position and refresh page to show updated state - sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) - window.location.reload() - } - - markRowAsSkipped(partId) { - // Save scroll position and refresh page to show updated state - sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) - window.location.reload() - } - - markRowAsPending(partId) { - // Save scroll position and refresh page to show updated state - sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) - window.location.reload() - } - - showJobCompletedMessage() { - const alert = document.createElement('div') - alert.className = 'alert alert-success alert-dismissible fade show' - alert.innerHTML = ` - - Job completed! All parts have been processed. - - ` - - const container = document.querySelector('.card-body') - container.insertBefore(alert, container.firstChild) - } - - async researchPart(event) { - event.preventDefault() - event.stopPropagation() - - const partId = event.currentTarget.dataset.partId - const spinner = event.currentTarget.querySelector(`[data-research-spinner="${partId}"]`) - const button = event.currentTarget - - // Show loading state - if (spinner) { - spinner.style.display = 'inline-block' - } - button.disabled = true - - try { - const url = this.researchUrlValue.replace('__PART_ID__', partId) - const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), 30000) // 30 second timeout - - const response = await fetch(url, { - method: 'POST', - headers: this.getHeaders(), - signal: controller.signal - }) - - clearTimeout(timeoutId) - - if (!response.ok) { - const errorText = await response.text() - throw new Error(`Server error (${response.status}): ${errorText}`) - } - - const data = await response.json() - - if (data.success) { - this.showSuccessMessage(`Research completed for part. Found ${data.results_count} results.`) - // Save scroll position and reload to show updated results - sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) - window.location.reload() - } else { - this.showErrorMessage(data.error || 'Research failed') - } - } catch (error) { - console.error('Error researching part:', error) - - if (error.name === 'AbortError') { - this.showErrorMessage('Research timed out. Please try again.') - } else if (error.message.includes('Failed to fetch')) { - this.showErrorMessage('Network error. Please check your connection and try again.') - } else { - this.showErrorMessage(error.message || 'Research failed due to an unexpected error') - } - } finally { - // Hide loading state - if (spinner) { - spinner.style.display = 'none' - } - button.disabled = false - } - } - - async researchAllParts(event) { - event.preventDefault() - event.stopPropagation() - - const spinner = document.getElementById('research-all-spinner') - const button = event.currentTarget - - // Show loading state - if (spinner) { - spinner.style.display = 'inline-block' - } - button.disabled = true - - try { - const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), 120000) // 2 minute timeout for bulk operations - - const response = await fetch(this.researchAllUrlValue, { - method: 'POST', - headers: this.getHeaders(), - signal: controller.signal - }) - - clearTimeout(timeoutId) - - if (!response.ok) { - const errorText = await response.text() - throw new Error(`Server error (${response.status}): ${errorText}`) - } - - const data = await response.json() - - if (data.success) { - this.showSuccessMessage(`Research completed for ${data.researched_count} parts.`) - // Save scroll position and reload to show updated results - sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) - window.location.reload() - } else { - this.showErrorMessage(data.error || 'Bulk research failed') - } - } catch (error) { - console.error('Error researching all parts:', error) - - if (error.name === 'AbortError') { - this.showErrorMessage('Bulk research timed out. This may happen with large batches. Please try again or process smaller batches.') - } else if (error.message.includes('Failed to fetch')) { - this.showErrorMessage('Network error. Please check your connection and try again.') - } else { - this.showErrorMessage(error.message || 'Bulk research failed due to an unexpected error') - } - } finally { - // Hide loading state - if (spinner) { - spinner.style.display = 'none' - } - button.disabled = false - } - } - - showSuccessMessage(message) { - this.showToast('success', message) - } - - showErrorMessage(message) { - this.showToast('error', message) - } - - showToast(type, message) { - // Create a simple alert that doesn't disrupt layout - const alertId = 'alert-' + Date.now() - const iconClass = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-triangle' - const alertClass = type === 'success' ? 'alert-success' : 'alert-danger' - - const alertHTML = ` -
- - ${message} - -
- ` - - // Add alert to body - document.body.insertAdjacentHTML('beforeend', alertHTML) - - // Auto-remove after 5 seconds - setTimeout(() => { - const alertElement = document.getElementById(alertId) - if (alertElement) { - alertElement.remove() - } - }, 5000) - } -} \ No newline at end of file diff --git a/assets/controllers/bulk_job_manage_controller.js b/assets/controllers/bulk_job_manage_controller.js deleted file mode 100644 index c26e37c6..00000000 --- a/assets/controllers/bulk_job_manage_controller.js +++ /dev/null @@ -1,92 +0,0 @@ -import { Controller } from "@hotwired/stimulus" -import { generateCsrfHeaders } from "./csrf_protection_controller" - -export default class extends Controller { - static values = { - deleteUrl: String, - stopUrl: String, - deleteConfirmMessage: String, - stopConfirmMessage: String - } - - connect() { - // Controller initialized - } - getHeaders() { - const headers = { - 'X-Requested-With': 'XMLHttpRequest' - } - - // Add CSRF headers if available - const form = document.querySelector('form') - if (form) { - const csrfHeaders = generateCsrfHeaders(form) - Object.assign(headers, csrfHeaders) - } - - return headers - } - async deleteJob(event) { - const jobId = event.currentTarget.dataset.jobId - const confirmMessage = this.deleteConfirmMessageValue || 'Are you sure you want to delete this job?' - - if (confirm(confirmMessage)) { - try { - const deleteUrl = this.deleteUrlValue.replace('__JOB_ID__', jobId) - - const response = await fetch(deleteUrl, { - method: 'DELETE', - headers: this.getHeaders() - }) - - if (!response.ok) { - const errorText = await response.text() - throw new Error(`HTTP ${response.status}: ${errorText}`) - } - - const data = await response.json() - - if (data.success) { - location.reload() - } else { - alert('Error deleting job: ' + (data.error || 'Unknown error')) - } - } catch (error) { - console.error('Error deleting job:', error) - alert('Error deleting job: ' + error.message) - } - } - } - - async stopJob(event) { - const jobId = event.currentTarget.dataset.jobId - const confirmMessage = this.stopConfirmMessageValue || 'Are you sure you want to stop this job?' - - if (confirm(confirmMessage)) { - try { - const stopUrl = this.stopUrlValue.replace('__JOB_ID__', jobId) - - const response = await fetch(stopUrl, { - method: 'POST', - headers: this.getHeaders() - }) - - if (!response.ok) { - const errorText = await response.text() - throw new Error(`HTTP ${response.status}: ${errorText}`) - } - - const data = await response.json() - - if (data.success) { - location.reload() - } else { - alert('Error stopping job: ' + (data.error || 'Unknown error')) - } - } catch (error) { - console.error('Error stopping job:', error) - alert('Error stopping job: ' + error.message) - } - } - } -} \ No newline at end of file diff --git a/assets/controllers/csrf_protection_controller.js b/assets/controllers/csrf_protection_controller.js index 511fffa5..c722f024 100644 --- a/assets/controllers/csrf_protection_controller.js +++ b/assets/controllers/csrf_protection_controller.js @@ -2,8 +2,6 @@ const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/; const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/; // Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager -// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event -// and thus this event-listener will not be executed. document.addEventListener('submit', function (event) { generateCsrfToken(event.target); }, true); @@ -35,8 +33,8 @@ export function generateCsrfToken (formElement) { if (!csrfCookie && nameCheck.test(csrfToken)) { csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken); csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18)))); + csrfField.dispatchEvent(new Event('change', { bubbles: true })); } - csrfField.dispatchEvent(new Event('change', { bubbles: true })); if (csrfCookie && tokenCheck.test(csrfToken)) { const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict'; diff --git a/assets/controllers/elements/attachment_autocomplete_controller.js b/assets/controllers/elements/attachment_autocomplete_controller.js index 94b01136..0175b284 100644 --- a/assets/controllers/elements/attachment_autocomplete_controller.js +++ b/assets/controllers/elements/attachment_autocomplete_controller.js @@ -34,11 +34,6 @@ export default class extends Controller { connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { persistent: false, create: true, @@ -47,7 +42,7 @@ export default class extends Controller { selectOnTab: true, //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', - dropdownParent: dropdownParent, + dropdownParent: 'body', render: { item: (data, escape) => { return '' + escape(data.label) + ''; diff --git a/assets/controllers/elements/ckeditor_controller.js b/assets/controllers/elements/ckeditor_controller.js index b7c87dab..62a48b15 100644 --- a/assets/controllers/elements/ckeditor_controller.js +++ b/assets/controllers/elements/ckeditor_controller.js @@ -28,27 +28,6 @@ import {EditorWatchdog} from 'ckeditor5'; import "ckeditor5/ckeditor5.css";; import "../../css/components/ckeditor.css"; -const translationContext = require.context( - 'ckeditor5/translations', - false, - //Only load the translation files we will really need - /(de|it|fr|ru|ja|cs|da|zh|pl|hu)\.js$/ -); - -function loadTranslation(language) { - if (!language || language === 'en') { - return null; - } - const lang = language.slice(0, 2); - const path = `./${lang}.js`; - if (translationContext.keys().includes(path)) { - const module = translationContext(path); - return module.default; - } else { - return null; - } -} - /* stimulusFetch: 'lazy' */ export default class extends Controller { connect() { @@ -84,13 +63,6 @@ export default class extends Controller { } } - //Load translations if not english - let translations = loadTranslation(language); - if (translations) { - //Keep existing translations (e.g. from other plugins), if any - config.translations = [window.CKEDITOR_TRANSLATIONS, translations]; - } - const watchdog = new EditorWatchdog(); watchdog.setCreator((elementOrData, editorConfig) => { return EDITOR_TYPE.create(elementOrData, editorConfig) @@ -106,15 +78,6 @@ export default class extends Controller { editor_div.classList.add(...new_classes.split(",")); } - // Automatic synchronization of source input - editor.model.document.on("change:data", () => { - editor.updateSourceElement(); - - // Dispatch the input event for further treatment - const event = new Event("input"); - this.element.dispatchEvent(event); - }); - //This return is important! Otherwise we get mysterious errors in the console //See: https://github.com/ckeditor/ckeditor5/issues/5897#issuecomment-628471302 return editor; diff --git a/assets/controllers/elements/ipn_suggestion_controller.js b/assets/controllers/elements/ipn_suggestion_controller.js deleted file mode 100644 index c8b543cb..00000000 --- a/assets/controllers/elements/ipn_suggestion_controller.js +++ /dev/null @@ -1,250 +0,0 @@ -import { Controller } from "@hotwired/stimulus"; -import "../../css/components/autocomplete_bootstrap_theme.css"; - -export default class extends Controller { - static targets = ["input"]; - static values = { - partId: Number, - partCategoryId: Number, - partDescription: String, - suggestions: Object, - commonSectionHeader: String, // Dynamic header for common Prefixes - partIncrementHeader: String, // Dynamic header for new possible part increment - suggestUrl: String, - }; - - connect() { - this.configureAutocomplete(); - this.watchCategoryChanges(); - this.watchDescriptionChanges(); - } - - templates = { - commonSectionHeader({ title, html }) { - return html` -
-
- ${title} -
-
-
- `; - }, - partIncrementHeader({ title, html }) { - return html` -
-
- ${title} -
-
-
- `; - }, - list({ html }) { - return html` - - `; - }, - item({ suggestion, description, html }) { - return html` -
  • -
    -
    -
    - - - -
    -
    -
    ${suggestion}
    -
    ${description}
    -
    -
    -
    -
  • - `; - }, - }; - - configureAutocomplete() { - const inputField = this.inputTarget; - const commonPrefixes = this.suggestionsValue.commonPrefixes || []; - const prefixesPartIncrement = this.suggestionsValue.prefixesPartIncrement || []; - const commonHeader = this.commonSectionHeaderValue; - const partIncrementHeader = this.partIncrementHeaderValue; - - if (!inputField || (!commonPrefixes.length && !prefixesPartIncrement.length)) return; - - // Check whether the panel should be created at the update - if (this.isPanelInitialized) { - const existingPanel = inputField.parentNode.querySelector(".aa-Panel"); - if (existingPanel) { - // Only remove the panel in the update phase - - existingPanel.remove(); - } - } - - // Create panel - const panel = document.createElement("div"); - panel.classList.add("aa-Panel"); - panel.style.display = "none"; - - // Create panel layout - const panelLayout = document.createElement("div"); - panelLayout.classList.add("aa-PanelLayout", "aa-Panel--scrollable"); - - // Section for prefixes part increment - if (prefixesPartIncrement.length) { - const partIncrementSection = document.createElement("section"); - partIncrementSection.classList.add("aa-Source"); - - const partIncrementHeaderHtml = this.templates.partIncrementHeader({ - title: partIncrementHeader, - html: String.raw, - }); - partIncrementSection.innerHTML += partIncrementHeaderHtml; - - const partIncrementList = document.createElement("ul"); - partIncrementList.classList.add("aa-List"); - partIncrementList.setAttribute("role", "listbox"); - - prefixesPartIncrement.forEach((prefix) => { - const itemHTML = this.templates.item({ - suggestion: prefix.title, - description: prefix.description, - html: String.raw, - }); - partIncrementList.innerHTML += itemHTML; - }); - - partIncrementSection.appendChild(partIncrementList); - panelLayout.appendChild(partIncrementSection); - } - - // Section for common prefixes - if (commonPrefixes.length) { - const commonSection = document.createElement("section"); - commonSection.classList.add("aa-Source"); - - const commonSectionHeader = this.templates.commonSectionHeader({ - title: commonHeader, - html: String.raw, - }); - commonSection.innerHTML += commonSectionHeader; - - const commonList = document.createElement("ul"); - commonList.classList.add("aa-List"); - commonList.setAttribute("role", "listbox"); - - commonPrefixes.forEach((prefix) => { - const itemHTML = this.templates.item({ - suggestion: prefix.title, - description: prefix.description, - html: String.raw, - }); - commonList.innerHTML += itemHTML; - }); - - commonSection.appendChild(commonList); - panelLayout.appendChild(commonSection); - } - - panel.appendChild(panelLayout); - inputField.parentNode.appendChild(panel); - - inputField.addEventListener("focus", () => { - panel.style.display = "block"; - }); - - inputField.addEventListener("blur", () => { - setTimeout(() => { - panel.style.display = "none"; - }, 100); - }); - - // Selection of an item - panelLayout.addEventListener("mousedown", (event) => { - const target = event.target.closest("li"); - - if (target) { - inputField.value = target.dataset.suggestion; - panel.style.display = "none"; - } - }); - - this.isPanelInitialized = true; - }; - - watchCategoryChanges() { - const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]'); - const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]'); - this.previousCategoryId = Number(this.partCategoryIdValue); - - if (categoryField) { - categoryField.addEventListener("change", () => { - const categoryId = Number(categoryField.value); - const description = String(descriptionField?.value ?? ''); - - // Check whether the category has changed compared to the previous ID - if (categoryId !== this.previousCategoryId) { - this.fetchNewSuggestions(categoryId, description); - this.previousCategoryId = categoryId; - } - }); - } - } - - watchDescriptionChanges() { - const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]'); - const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]'); - this.previousDescription = String(this.partDescriptionValue); - - if (descriptionField) { - descriptionField.addEventListener("input", () => { - const categoryId = Number(categoryField.value); - const description = String(descriptionField?.value ?? ''); - - // Check whether the description has changed compared to the previous one - if (description !== this.previousDescription) { - this.fetchNewSuggestions(categoryId, description); - this.previousDescription = description; - } - }); - } - } - - fetchNewSuggestions(categoryId, description) { - const baseUrl = this.suggestUrlValue; - const partId = this.partIdValue; - const truncatedDescription = description.length > 150 ? description.substring(0, 150) : description; - const encodedDescription = this.base64EncodeUtf8(truncatedDescription); - const url = `${baseUrl}?partId=${partId}&categoryId=${categoryId}` + (description !== '' ? `&description=${encodedDescription}` : ''); - - fetch(url, { - method: "GET", - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - }, - }) - .then((response) => { - if (!response.ok) { - throw new Error(`Error when calling up the IPN-suggestions: ${response.status}`); - } - return response.json(); - }) - .then((data) => { - this.suggestionsValue = data; - this.configureAutocomplete(); - }) - .catch((error) => { - console.error("Errors when loading the new IPN-suggestions:", error); - }); - }; - - base64EncodeUtf8(text) { - const utf8Bytes = new TextEncoder().encode(text); - return btoa(String.fromCharCode(...utf8Bytes)); - }; -} diff --git a/assets/controllers/elements/part_select_controller.js b/assets/controllers/elements/part_select_controller.js index 8a4e19b8..0658f4b4 100644 --- a/assets/controllers/elements/part_select_controller.js +++ b/assets/controllers/elements/part_select_controller.js @@ -10,19 +10,13 @@ export default class extends Controller { connect() { - //Check if tomselect is inside an modal and do not attach the dropdown to body in that case (as it breaks the modal) - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { allowEmptyOption: true, plugins: ['dropdown_input'], searchField: ["name", "description", "category", "footprint"], valueField: "id", labelField: "name", - dropdownParent: dropdownParent, + dropdownParent: 'body', preload: "focus", render: { item: (data, escape) => { diff --git a/assets/controllers/elements/select_controller.js b/assets/controllers/elements/select_controller.js index d70e588c..f933731a 100644 --- a/assets/controllers/elements/select_controller.js +++ b/assets/controllers/elements/select_controller.js @@ -38,17 +38,13 @@ export default class extends Controller { this._emptyMessage = this.element.getAttribute('title'); } - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } let settings = { plugins: ["clear_button"], allowEmptyOption: true, selectOnTab: true, maxOptions: null, - dropdownParent: dropdownParent, + dropdownParent: 'body', render: { item: this.renderItem.bind(this), diff --git a/assets/controllers/elements/select_multiple_controller.js b/assets/controllers/elements/select_multiple_controller.js index 17e85fae..daa6b0a1 100644 --- a/assets/controllers/elements/select_multiple_controller.js +++ b/assets/controllers/elements/select_multiple_controller.js @@ -26,15 +26,10 @@ export default class extends Controller { _tomSelect; connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - this._tomSelect = new TomSelect(this.element, { maxItems: 1000, allowEmptyOption: true, - dropdownParent: dropdownParent, + dropdownParent: 'body', plugins: ['remove_button'], }); } diff --git a/assets/controllers/elements/static_file_autocomplete_controller.js b/assets/controllers/elements/static_file_autocomplete_controller.js index 9703c618..0421a26d 100644 --- a/assets/controllers/elements/static_file_autocomplete_controller.js +++ b/assets/controllers/elements/static_file_autocomplete_controller.js @@ -40,11 +40,6 @@ export default class extends Controller { connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { persistent: false, create: true, @@ -55,7 +50,7 @@ export default class extends Controller { valueField: 'text', searchField: 'text', orderField: 'text', - dropdownParent: dropdownParent, + dropdownParent: 'body', //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', diff --git a/assets/controllers/elements/structural_entity_select_controller.js b/assets/controllers/elements/structural_entity_select_controller.js index 4b220d5b..5c6f9490 100644 --- a/assets/controllers/elements/structural_entity_select_controller.js +++ b/assets/controllers/elements/structural_entity_select_controller.js @@ -40,10 +40,7 @@ export default class extends Controller { const allowAdd = this.element.getAttribute("data-allow-add") === "true"; const addHint = this.element.getAttribute("data-add-hint") ?? ""; - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } + let settings = { @@ -57,7 +54,7 @@ export default class extends Controller { maxItems: 1, delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$", splitOn: null, - dropdownParent: dropdownParent, + dropdownParent: 'body', searchField: [ {field: "text", weight : 2}, diff --git a/assets/controllers/elements/tagsinput_controller.js b/assets/controllers/elements/tagsinput_controller.js index 14725227..53bf7608 100644 --- a/assets/controllers/elements/tagsinput_controller.js +++ b/assets/controllers/elements/tagsinput_controller.js @@ -33,11 +33,6 @@ export default class extends Controller { _tomSelect; connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { plugins: { remove_button:{}, @@ -48,7 +43,7 @@ export default class extends Controller { selectOnTab: true, createOnBlur: true, create: true, - dropdownParent: dropdownParent, + dropdownParent: 'body', }; if(this.element.dataset.autocomplete) { diff --git a/assets/controllers/field_mapping_controller.js b/assets/controllers/field_mapping_controller.js deleted file mode 100644 index 9c9c8ac6..00000000 --- a/assets/controllers/field_mapping_controller.js +++ /dev/null @@ -1,136 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - static targets = ["tbody", "addButton", "submitButton"] - static values = { - mappingIndex: Number, - maxMappings: Number, - prototype: String, - maxMappingsReachedMessage: String - } - - connect() { - this.updateAddButtonState() - this.updateFieldOptions() - this.attachEventListeners() - } - - attachEventListeners() { - // Add event listeners to existing field selects - const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]') - fieldSelects.forEach(select => { - select.addEventListener('change', this.updateFieldOptions.bind(this)) - }) - - // Note: Add button click is handled by Stimulus action in template (data-action="click->field-mapping#addMapping") - // No manual event listener needed - - // Form submit handler - const form = this.element.querySelector('form') - if (form && this.hasSubmitButtonTarget) { - form.addEventListener('submit', this.handleFormSubmit.bind(this)) - } - } - - addMapping() { - const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length - - if (currentMappings >= this.maxMappingsValue) { - alert(this.maxMappingsReachedMessageValue) - return - } - - const newRowHtml = this.prototypeValue.replace(/__name__/g, this.mappingIndexValue) - const tempDiv = document.createElement('div') - tempDiv.innerHTML = newRowHtml - - const fieldWidget = tempDiv.querySelector('select[name*="[field]"]') || tempDiv.children[0] - const providerWidget = tempDiv.querySelector('select[name*="[providers]"]') || tempDiv.children[1] - const priorityWidget = tempDiv.querySelector('input[name*="[priority]"]') || tempDiv.children[2] - - const newRow = document.createElement('tr') - newRow.className = 'mapping-row' - newRow.innerHTML = ` - ${fieldWidget ? fieldWidget.outerHTML : ''} - ${providerWidget ? providerWidget.outerHTML : ''} - ${priorityWidget ? priorityWidget.outerHTML : ''} - - - - ` - - this.tbodyTarget.appendChild(newRow) - this.mappingIndexValue++ - - const newFieldSelect = newRow.querySelector('select[name*="[field]"]') - if (newFieldSelect) { - newFieldSelect.value = '' - newFieldSelect.addEventListener('change', this.updateFieldOptions.bind(this)) - } - - this.updateFieldOptions() - this.updateAddButtonState() - } - - removeMapping(event) { - const row = event.target.closest('tr') - row.remove() - this.updateFieldOptions() - this.updateAddButtonState() - } - - updateFieldOptions() { - const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]') - - const selectedFields = Array.from(fieldSelects) - .map(select => select.value) - .filter(value => value && value !== '') - - fieldSelects.forEach(select => { - Array.from(select.options).forEach(option => { - const isCurrentValue = option.value === select.value - const isEmptyOption = !option.value || option.value === '' - const isAlreadySelected = selectedFields.includes(option.value) - - if (!isEmptyOption && isAlreadySelected && !isCurrentValue) { - option.disabled = true - option.style.display = 'none' - } else { - option.disabled = false - option.style.display = '' - } - }) - }) - } - - updateAddButtonState() { - const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length - - if (this.hasAddButtonTarget) { - if (currentMappings >= this.maxMappingsValue) { - this.addButtonTarget.disabled = true - this.addButtonTarget.title = this.maxMappingsReachedMessageValue - } else { - this.addButtonTarget.disabled = false - this.addButtonTarget.title = '' - } - } - } - - handleFormSubmit(event) { - if (this.hasSubmitButtonTarget) { - this.submitButtonTarget.disabled = true - - // Disable the entire form to prevent changes during processing - const form = event.target - const formElements = form.querySelectorAll('input, select, textarea, button') - formElements.forEach(element => { - if (element !== this.submitButtonTarget) { - element.disabled = true - } - }) - } - } -} \ No newline at end of file diff --git a/assets/controllers/pages/synonyms_collection_controller.js b/assets/controllers/pages/synonyms_collection_controller.js deleted file mode 100644 index 6b2f4811..00000000 --- a/assets/controllers/pages/synonyms_collection_controller.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). - * - * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { Controller } from '@hotwired/stimulus'; - -export default class extends Controller { - static targets = ['items']; - static values = { - prototype: String, - prototypeName: { type: String, default: '__name__' }, - index: { type: Number, default: 0 }, - }; - - connect() { - if (!this.hasIndexValue || Number.isNaN(this.indexValue)) { - this.indexValue = this.itemsTarget?.children.length || 0; - } - } - - add(event) { - event.preventDefault(); - - const encodedProto = this.prototypeValue || ''; - const placeholder = this.prototypeNameValue || '__name__'; - if (!encodedProto || !this.itemsTarget) return; - - const protoHtml = this._decodeHtmlAttribute(encodedProto); - - const idx = this.indexValue; - const html = protoHtml.replace(new RegExp(placeholder, 'g'), String(idx)); - - const wrapper = document.createElement('div'); - wrapper.innerHTML = html; - const newItem = wrapper.firstElementChild; - if (newItem) { - this.itemsTarget.appendChild(newItem); - this.indexValue = idx + 1; - } - } - - remove(event) { - event.preventDefault(); - const row = event.currentTarget.closest('.tc-item'); - if (row) row.remove(); - } - - _decodeHtmlAttribute(str) { - const tmp = document.createElement('textarea'); - tmp.innerHTML = str; - return tmp.value || tmp.textContent || ''; - } -} diff --git a/assets/css/app/layout.css b/assets/css/app/layout.css index 58808926..4be123a7 100644 --- a/assets/css/app/layout.css +++ b/assets/css/app/layout.css @@ -133,7 +133,7 @@ showing the sidebar (on devices with md or higher) */ #sidebar-toggle-button { position: fixed; - left: 2px; + left: 3px; bottom: 50%; } diff --git a/assets/css/app/tables.css b/assets/css/app/tables.css index b2d8882c..8d4b200c 100644 --- a/assets/css/app/tables.css +++ b/assets/css/app/tables.css @@ -94,11 +94,6 @@ th.select-checkbox { display: inline-flex; } -/** Add spacing between column visibility button and length menu */ -.buttons-colvis { - margin-right: 0.2em !important; -} - /** Fix datatables select-checkbox position */ table.dataTable tr.selected td.select-checkbox:after { diff --git a/assets/js/app.js b/assets/js/app.js index c0550373..54b73676 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -28,7 +28,7 @@ import '../css/app/treeview.css'; import '../css/app/images.css'; // start the Stimulus application -import '../stimulus_bootstrap'; +import '../bootstrap'; // Need jQuery? Install it with "yarn add jquery", then uncomment to require it. const $ = require('jquery'); diff --git a/bin/phpunit b/bin/phpunit index ac5eef11..692baccb 100755 --- a/bin/phpunit +++ b/bin/phpunit @@ -1,4 +1,23 @@ #!/usr/bin/env php = 80000) { + require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit'; + } else { + define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php'); + require PHPUNIT_COMPOSER_INSTALL; + PHPUnit\TextUI\Command::main(); + } +} else { + if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) { + echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n"; + exit(1); + } + + require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php'; +} diff --git a/composer.json b/composer.json index 872edf95..80b413f8 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ "doctrine/doctrine-bundle": "^2.0", "doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/orm": "^3.2.0", - "dompdf/dompdf": "^3.1.2", + "dompdf/dompdf": "^v3.0.0", + "part-db/swap-bundle": "^6.0.0", "gregwar/captcha-bundle": "^2.1.0", "hshn/base64-encoded-file": "^5.0", "jbtronics/2fa-webauthn": "^3.0.0", @@ -36,7 +37,6 @@ "league/csv": "^9.8.0", "league/html-to-markdown": "^5.0.1", "liip/imagine-bundle": "^2.2", - "maennchen/zipstream-php": "2.1", "nbgrp/onelogin-saml-bundle": "^v2.0.2", "nelexa/zip": "^4.0", "nelmio/cors-bundle": "^2.3", @@ -45,9 +45,8 @@ "omines/datatables-bundle": "^0.10.0", "paragonie/sodium_compat": "^1.21", "part-db/label-fonts": "^1.0", - "part-db/swap-bundle": "^6.0.0", - "phpoffice/phpspreadsheet": "^5.0.0", "rhukster/dom-sanitizer": "^1.0", + "runtime/frankenphp-symfony": "^0.2.0", "s9e/text-formatter": "^2.1", "scheb/2fa-backup-code": "^v7.11.0", "scheb/2fa-bundle": "^v7.11.0", @@ -56,35 +55,36 @@ "shivas/versioning-bundle": "^4.0", "spatie/db-dumper": "^3.3.1", "symfony/apache-pack": "^1.0", - "symfony/asset": "7.4.*", - "symfony/console": "7.4.*", - "symfony/css-selector": "7.4.*", - "symfony/dom-crawler": "7.4.*", - "symfony/dotenv": "7.4.*", - "symfony/expression-language": "7.4.*", + "symfony/asset": "7.3.*", + "symfony/console": "7.3.*", + "symfony/css-selector": "7.3.*", + "symfony/dom-crawler": "7.3.*", + "symfony/dotenv": "7.3.*", + "symfony/expression-language": "7.3.*", "symfony/flex": "^v2.3.1", - "symfony/form": "7.4.*", - "symfony/framework-bundle": "7.4.*", - "symfony/http-client": "7.4.*", - "symfony/http-kernel": "7.4.*", - "symfony/mailer": "7.4.*", + "symfony/form": "7.3.*", + "symfony/framework-bundle": "7.3.*", + "symfony/http-client": "7.3.*", + "symfony/http-kernel": "7.3.*", + "symfony/mailer": "7.3.*", "symfony/monolog-bundle": "^3.1", - "symfony/process": "7.4.*", - "symfony/property-access": "7.4.*", - "symfony/property-info": "7.4.*", - "symfony/rate-limiter": "7.4.*", - "symfony/runtime": "7.4.*", - "symfony/security-bundle": "7.4.*", - "symfony/serializer": "7.4.*", - "symfony/string": "7.4.*", - "symfony/translation": "7.4.*", - "symfony/twig-bundle": "7.4.*", + "symfony/polyfill-php82": "^1.28", + "symfony/process": "7.3.*", + "symfony/property-access": "7.3.*", + "symfony/property-info": "7.3.*", + "symfony/rate-limiter": "7.3.*", + "symfony/runtime": "7.3.*", + "symfony/security-bundle": "7.3.*", + "symfony/serializer": "7.3.*", + "symfony/string": "7.3.*", + "symfony/translation": "7.3.*", + "symfony/twig-bundle": "7.3.*", "symfony/ux-translator": "^2.10", "symfony/ux-turbo": "^2.0", - "symfony/validator": "7.4.*", - "symfony/web-link": "7.4.*", + "symfony/validator": "7.3.*", + "symfony/web-link": "7.3.*", "symfony/webpack-encore-bundle": "^v2.0.1", - "symfony/yaml": "7.4.*", + "symfony/yaml": "7.3.*", "symplify/easy-coding-standard": "^12.5.20", "tecnickcom/tc-lib-barcode": "^2.1.4", "twig/cssinliner-extra": "^3.0", @@ -109,19 +109,12 @@ "phpunit/phpunit": "^11.5.0", "rector/rector": "^2.0.4", "roave/security-advisories": "dev-latest", - "symfony/browser-kit": "7.4.*", - "symfony/debug-bundle": "7.4.*", + "symfony/browser-kit": "7.3.*", + "symfony/debug-bundle": "7.3.*", "symfony/maker-bundle": "^1.13", - "symfony/phpunit-bridge": "7.4.*", - "symfony/stopwatch": "7.4.*", - "symfony/web-profiler-bundle": "7.4.*" - }, - "replace": { - "symfony/polyfill-mbstring": "*", - "symfony/polyfill-php74": "*", - "symfony/polyfill-php80": "*", - "symfony/polyfill-php81": "*", - "symfony/polyfill-php82": "*" + "symfony/phpunit-bridge": "7.3.*", + "symfony/stopwatch": "7.3.*", + "symfony/web-profiler-bundle": "7.3.*" }, "suggest": { "ext-bcmath": "Used to improve price calculation performance", @@ -164,7 +157,7 @@ "post-update-cmd": [ "@auto-scripts" ], - "phpstan": "php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5" + "phpstan": "vendor/bin/phpstan analyse src --level 5 --memory-limit 1G" }, "conflict": { "symfony/symfony": "*" @@ -172,7 +165,7 @@ "extra": { "symfony": { "allow-contrib": false, - "require": "7.4.*", + "require": "7.3.*", "docker": true } } diff --git a/composer.lock b/composer.lock index e127d1ed..1f67b80f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "624cf08821477aa7b8f6efc0d4300087", + "content-hash": "fe6dfc229f551945cfa6be8ca26a437e", "packages": [ { "name": "amphp/amp", @@ -968,21 +968,21 @@ }, { "name": "api-platform/doctrine-common", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/doctrine-common.git", - "reference": "76ce957843cd050ccd9119915d2cbf9eb5f5ac5c" + "reference": "e0ef3f5d1c4a9d023da519ea120a1d7732e0b1a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/76ce957843cd050ccd9119915d2cbf9eb5f5ac5c", - "reference": "76ce957843cd050ccd9119915d2cbf9eb5f5ac5c", + "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/e0ef3f5d1c4a9d023da519ea120a1d7732e0b1a7", + "reference": "e0ef3f5d1c4a9d023da519ea120a1d7732e0b1a7", "shasum": "" }, "require": { - "api-platform/metadata": "^4.2", - "api-platform/state": "^4.2.4", + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", "doctrine/collections": "^2.1", "doctrine/common": "^3.2.2", "doctrine/persistence": "^3.2 || ^4.0", @@ -995,8 +995,7 @@ "doctrine/mongodb-odm": "^2.10", "doctrine/orm": "^2.17 || ^3.0", "phpspec/prophecy-phpunit": "^2.2", - "phpunit/phpunit": "11.5.x-dev", - "symfony/type-info": "^7.3 || ^8.0" + "phpunit/phpunit": "11.5.x-dev" }, "suggest": { "api-platform/graphql": "For GraphQl mercure subscriptions.", @@ -1013,13 +1012,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1052,46 +1050,45 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/doctrine-common/tree/v4.2.7" + "source": "https://github.com/api-platform/doctrine-common/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-08-18T13:30:43+00:00" }, { "name": "api-platform/doctrine-orm", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/doctrine-orm.git", - "reference": "74c91e76bc2e26592f80b3f872f3f97903cc3055" + "reference": "61a199da6f6014dba2da43ea1a66b2c9dda27263" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/74c91e76bc2e26592f80b3f872f3f97903cc3055", - "reference": "74c91e76bc2e26592f80b3f872f3f97903cc3055", + "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/61a199da6f6014dba2da43ea1a66b2c9dda27263", + "reference": "61a199da6f6014dba2da43ea1a66b2c9dda27263", "shasum": "" }, "require": { - "api-platform/doctrine-common": "^4.2.0-alpha.3@alpha", - "api-platform/metadata": "^4.2", - "api-platform/state": "^4.2.4", + "api-platform/doctrine-common": "^4.1.11", + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", "doctrine/orm": "^2.17 || ^3.0", - "php": ">=8.2" + "php": ">=8.2", + "symfony/property-info": "^6.4 || ^7.1" }, "require-dev": { - "doctrine/doctrine-bundle": "^2.11 || ^3.1", + "doctrine/doctrine-bundle": "^2.11", "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "11.5.x-dev", "ramsey/uuid": "^4.7", "ramsey/uuid-doctrine": "^2.0", - "symfony/cache": "^6.4 || ^7.0 || ^8.0", - "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", - "symfony/property-access": "^6.4 || ^7.0 || ^8.0", - "symfony/property-info": "^6.4 || ^7.1 || ^8.0", - "symfony/serializer": "^6.4 || ^7.0 || ^8.0", - "symfony/type-info": "^7.3 || ^8.0", - "symfony/uid": "^6.4 || ^7.0 || ^8.0", - "symfony/validator": "^6.4.11 || ^7.0 || ^8.0", - "symfony/yaml": "^6.4 || ^7.0 || ^8.0" + "symfony/cache": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/uid": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0" }, "type": "library", "extra": { @@ -1100,13 +1097,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1139,26 +1135,26 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/doctrine-orm/tree/v4.2.7" + "source": "https://github.com/api-platform/doctrine-orm/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-06-06T14:56:47+00:00" }, { "name": "api-platform/documentation", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/documentation.git", - "reference": "8910f2a0aa7910ed807f128510553b24152e5ea5" + "reference": "1a0ac988d659008ef8667d05bc9978863026bab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/documentation/zipball/8910f2a0aa7910ed807f128510553b24152e5ea5", - "reference": "8910f2a0aa7910ed807f128510553b24152e5ea5", + "url": "https://api.github.com/repos/api-platform/documentation/zipball/1a0ac988d659008ef8667d05bc9978863026bab8", + "reference": "1a0ac988d659008ef8667d05bc9978863026bab8", "shasum": "" }, "require": { - "api-platform/metadata": "^4.2", + "api-platform/metadata": "^4.1.11", "php": ">=8.2" }, "require-dev": { @@ -1171,13 +1167,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1202,37 +1197,36 @@ ], "description": "API Platform documentation controller.", "support": { - "source": "https://github.com/api-platform/documentation/tree/v4.2.7" + "source": "https://github.com/api-platform/documentation/tree/v4.2.0-alpha.1" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-06-06T14:56:47+00:00" }, { "name": "api-platform/http-cache", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/http-cache.git", - "reference": "4bb2eab81407f493f54f51be7aa1918f362c14b5" + "reference": "f65f092c90311a87ebb6dda87db3ca08b57c10d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/http-cache/zipball/4bb2eab81407f493f54f51be7aa1918f362c14b5", - "reference": "4bb2eab81407f493f54f51be7aa1918f362c14b5", + "url": "https://api.github.com/repos/api-platform/http-cache/zipball/f65f092c90311a87ebb6dda87db3ca08b57c10d6", + "reference": "f65f092c90311a87ebb6dda87db3ca08b57c10d6", "shasum": "" }, "require": { - "api-platform/metadata": "^4.2", - "api-platform/state": "^4.2.4", + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", "php": ">=8.2", - "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0" + "symfony/http-foundation": "^6.4 || ^7.0" }, "require-dev": { - "guzzlehttp/guzzle": "^6.0 || ^7.0 || ^8.0", + "guzzlehttp/guzzle": "^6.0 || ^7.0", "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "11.5.x-dev", - "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", - "symfony/http-client": "^6.4 || ^7.0 || ^8.0", - "symfony/type-info": "^7.3 || ^8.0" + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/http-client": "^6.4 || ^7.0" }, "type": "library", "extra": { @@ -1241,13 +1235,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1282,39 +1275,38 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/http-cache/tree/v4.2.7" + "source": "https://github.com/api-platform/http-cache/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-06-06T14:56:47+00:00" }, { "name": "api-platform/hydra", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/hydra.git", - "reference": "ce704a53789ac279e0f7aafac48a8b1005df36e3" + "reference": "8c75b814af143c95ffc1857565169ff5b6f1b421" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/hydra/zipball/ce704a53789ac279e0f7aafac48a8b1005df36e3", - "reference": "ce704a53789ac279e0f7aafac48a8b1005df36e3", + "url": "https://api.github.com/repos/api-platform/hydra/zipball/8c75b814af143c95ffc1857565169ff5b6f1b421", + "reference": "8c75b814af143c95ffc1857565169ff5b6f1b421", "shasum": "" }, "require": { - "api-platform/documentation": "^4.2", - "api-platform/json-schema": "^4.2", - "api-platform/jsonld": "^4.2", - "api-platform/metadata": "^4.2", - "api-platform/serializer": "^4.2.4", - "api-platform/state": "^4.2.4", + "api-platform/documentation": "^4.1.11", + "api-platform/json-schema": "^4.1.11", + "api-platform/jsonld": "^4.1.11", + "api-platform/metadata": "^4.1.11", + "api-platform/serializer": "^4.1.11", + "api-platform/state": "^4.1.11", "php": ">=8.2", - "symfony/type-info": "^7.3 || ^8.0", - "symfony/web-link": "^6.4 || ^7.1 || ^8.0" + "symfony/web-link": "^6.4 || ^7.1" }, "require-dev": { - "api-platform/doctrine-common": "^4.2", - "api-platform/doctrine-odm": "^4.2", - "api-platform/doctrine-orm": "^4.2", + "api-platform/doctrine-common": "^4.1", + "api-platform/doctrine-odm": "^4.1", + "api-platform/doctrine-orm": "^4.1", "phpspec/prophecy": "^1.19", "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "11.5.x-dev" @@ -1326,13 +1318,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1369,40 +1360,38 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/hydra/tree/v4.2.7" + "source": "https://github.com/api-platform/hydra/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-07-15T14:10:59+00:00" }, { "name": "api-platform/json-api", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/json-api.git", - "reference": "f7a0680c1183795c46bc2e55a69acb94735cfbe9" + "reference": "7ea9bbe5f801f58b3f78730f6e6cd4b168b450d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/json-api/zipball/f7a0680c1183795c46bc2e55a69acb94735cfbe9", - "reference": "f7a0680c1183795c46bc2e55a69acb94735cfbe9", + "url": "https://api.github.com/repos/api-platform/json-api/zipball/7ea9bbe5f801f58b3f78730f6e6cd4b168b450d4", + "reference": "7ea9bbe5f801f58b3f78730f6e6cd4b168b450d4", "shasum": "" }, "require": { - "api-platform/documentation": "^4.2", - "api-platform/json-schema": "^4.2", - "api-platform/metadata": "^4.2", - "api-platform/serializer": "^4.2.4", - "api-platform/state": "^4.2.4", + "api-platform/documentation": "^4.1.11", + "api-platform/json-schema": "^4.1.11", + "api-platform/metadata": "^4.1.11", + "api-platform/serializer": "^4.1.11", + "api-platform/state": "^4.1.11", "php": ">=8.2", - "symfony/error-handler": "^6.4 || ^7.0 || ^8.0", - "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0", - "symfony/type-info": "^7.3 || ^8.0" + "symfony/error-handler": "^6.4 || ^7.0", + "symfony/http-foundation": "^6.4 || ^7.0" }, "require-dev": { "phpspec/prophecy": "^1.19", "phpspec/prophecy-phpunit": "^2.2", - "phpunit/phpunit": "11.5.x-dev", - "symfony/type-info": "^7.3 || ^8.0" + "phpunit/phpunit": "11.5.x-dev" }, "type": "library", "extra": { @@ -1411,13 +1400,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1451,32 +1439,31 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/json-api/tree/v4.2.7" + "source": "https://github.com/api-platform/json-api/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-08-06T07:56:58+00:00" }, { "name": "api-platform/json-schema", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/json-schema.git", - "reference": "b95eec54ae0353fc068a77fe481c7f4e2e983f33" + "reference": "1d1c6eaa4841f3989e2bec4cdf8167fb0ca42a8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/json-schema/zipball/b95eec54ae0353fc068a77fe481c7f4e2e983f33", - "reference": "b95eec54ae0353fc068a77fe481c7f4e2e983f33", + "url": "https://api.github.com/repos/api-platform/json-schema/zipball/1d1c6eaa4841f3989e2bec4cdf8167fb0ca42a8f", + "reference": "1d1c6eaa4841f3989e2bec4cdf8167fb0ca42a8f", "shasum": "" }, "require": { - "api-platform/metadata": "^4.2", + "api-platform/metadata": "^4.1.11", "php": ">=8.2", - "symfony/console": "^6.4 || ^7.0 || ^8.0", - "symfony/property-info": "^6.4 || ^7.1 || ^8.0", - "symfony/serializer": "^6.4 || ^7.0 || ^8.0", - "symfony/type-info": "^7.3 || ^8.0", - "symfony/uid": "^6.4 || ^7.0 || ^8.0" + "symfony/console": "^6.4 || ^7.0", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/uid": "^6.4 || ^7.0" }, "require-dev": { "phpspec/prophecy-phpunit": "^2.2", @@ -1489,13 +1476,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1532,33 +1518,32 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/json-schema/tree/v4.2.7" + "source": "https://github.com/api-platform/json-schema/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-06-29T12:24:14+00:00" }, { "name": "api-platform/jsonld", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/jsonld.git", - "reference": "c8896d5a3ddf67ac8aa74bb54199b13153fa39c3" + "reference": "e122bf1f04f895e80e6469e0f09d1f06f7508ca6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/jsonld/zipball/c8896d5a3ddf67ac8aa74bb54199b13153fa39c3", - "reference": "c8896d5a3ddf67ac8aa74bb54199b13153fa39c3", + "url": "https://api.github.com/repos/api-platform/jsonld/zipball/e122bf1f04f895e80e6469e0f09d1f06f7508ca6", + "reference": "e122bf1f04f895e80e6469e0f09d1f06f7508ca6", "shasum": "" }, "require": { - "api-platform/metadata": "^4.2", - "api-platform/serializer": "^4.2.4", - "api-platform/state": "^4.2.4", + "api-platform/metadata": "^4.1.11", + "api-platform/serializer": "^4.1.11", + "api-platform/state": "^4.1.11", "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "11.5.x-dev", - "symfony/type-info": "^7.3 || ^8.0" + "phpunit/phpunit": "11.5.x-dev" }, "type": "library", "extra": { @@ -1567,13 +1552,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1612,22 +1596,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/jsonld/tree/v4.2.7" + "source": "https://github.com/api-platform/jsonld/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-07-25T10:05:30+00:00" }, { "name": "api-platform/metadata", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/metadata.git", - "reference": "68e5edff897d4f3bf95ccf1eed464d6e3900a8b2" + "reference": "58b25f9a82c12727afab09b5a311828aacff8e88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/metadata/zipball/68e5edff897d4f3bf95ccf1eed464d6e3900a8b2", - "reference": "68e5edff897d4f3bf95ccf1eed464d6e3900a8b2", + "url": "https://api.github.com/repos/api-platform/metadata/zipball/58b25f9a82c12727afab09b5a311828aacff8e88", + "reference": "58b25f9a82c12727afab09b5a311828aacff8e88", "shasum": "" }, "require": { @@ -1635,22 +1619,22 @@ "php": ">=8.2", "psr/cache": "^1.0 || ^2.0 || ^3.0", "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/property-info": "^6.4 || ^7.1 || ^8.0", - "symfony/string": "^6.4 || ^7.0 || ^8.0", - "symfony/type-info": "^7.3 || ^8.0" + "symfony/property-info": "^6.4 || ^7.1", + "symfony/string": "^6.4 || ^7.0", + "symfony/type-info": "^7.3" }, "require-dev": { - "api-platform/json-schema": "^4.2", - "api-platform/openapi": "^4.2", - "api-platform/state": "^4.2.4", + "api-platform/json-schema": "^4.1.11", + "api-platform/openapi": "^4.1.11", + "api-platform/state": "^4.1.11", "phpspec/prophecy-phpunit": "^2.2", "phpstan/phpdoc-parser": "^1.29 || ^2.0", "phpunit/phpunit": "11.5.x-dev", - "symfony/config": "^6.4 || ^7.0 || ^8.0", - "symfony/routing": "^6.4 || ^7.0 || ^8.0", - "symfony/var-dumper": "^6.4 || ^7.0 || ^8.0", - "symfony/web-link": "^6.4 || ^7.1 || ^8.0", - "symfony/yaml": "^6.4 || ^7.0 || ^8.0" + "symfony/config": "^6.4 || ^7.0", + "symfony/routing": "^6.4 || ^7.0", + "symfony/var-dumper": "^6.4 || ^7.0", + "symfony/web-link": "^6.4 || ^7.1", + "symfony/yaml": "^6.4 || ^7.0" }, "suggest": { "phpstan/phpdoc-parser": "For PHP documentation support.", @@ -1664,13 +1648,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1710,42 +1693,40 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/metadata/tree/v4.2.7" + "source": "https://github.com/api-platform/metadata/tree/v4.1.23" }, - "time": "2025-11-30T13:04:03+00:00" + "time": "2025-09-05T09:06:52+00:00" }, { "name": "api-platform/openapi", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/openapi.git", - "reference": "ea49d6d7170f8ecc1c239e7769708628183096b8" + "reference": "793b53e51a5c24076d4024b6aa77de29e74015cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/openapi/zipball/ea49d6d7170f8ecc1c239e7769708628183096b8", - "reference": "ea49d6d7170f8ecc1c239e7769708628183096b8", + "url": "https://api.github.com/repos/api-platform/openapi/zipball/793b53e51a5c24076d4024b6aa77de29e74015cd", + "reference": "793b53e51a5c24076d4024b6aa77de29e74015cd", "shasum": "" }, "require": { - "api-platform/json-schema": "^4.2", - "api-platform/metadata": "^4.2", - "api-platform/state": "^4.2.4", + "api-platform/json-schema": "^4.1.11", + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", "php": ">=8.2", - "symfony/console": "^6.4 || ^7.0 || ^8.0", - "symfony/filesystem": "^6.4 || ^7.0 || ^8.0", - "symfony/property-access": "^6.4 || ^7.0 || ^8.0", - "symfony/serializer": "^6.4 || ^7.0 || ^8.0", - "symfony/type-info": "^7.3 || ^8.0" + "symfony/console": "^6.4 || ^7.0", + "symfony/filesystem": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/serializer": "^6.4 || ^7.0" }, "require-dev": { - "api-platform/doctrine-common": "^4.2", - "api-platform/doctrine-odm": "^4.2", - "api-platform/doctrine-orm": "^4.2", + "api-platform/doctrine-common": "^4.1", + "api-platform/doctrine-odm": "^4.1", + "api-platform/doctrine-orm": "^4.1", "phpspec/prophecy-phpunit": "^2.2", - "phpunit/phpunit": "11.5.x-dev", - "symfony/type-info": "^7.3 || ^8.0" + "phpunit/phpunit": "11.5.x-dev" }, "type": "library", "extra": { @@ -1754,13 +1735,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1800,46 +1780,45 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/openapi/tree/v4.2.7" + "source": "https://github.com/api-platform/openapi/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-07-29T08:53:27+00:00" }, { "name": "api-platform/serializer", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/serializer.git", - "reference": "c3ea805273d5646a0eabb0161850b4e382bb96ef" + "reference": "70dbdeac9584870be444d78c1a796b6edb9e46a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/serializer/zipball/c3ea805273d5646a0eabb0161850b4e382bb96ef", - "reference": "c3ea805273d5646a0eabb0161850b4e382bb96ef", + "url": "https://api.github.com/repos/api-platform/serializer/zipball/70dbdeac9584870be444d78c1a796b6edb9e46a5", + "reference": "70dbdeac9584870be444d78c1a796b6edb9e46a5", "shasum": "" }, "require": { - "api-platform/metadata": "^4.2", - "api-platform/state": "^4.2.4", + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", "php": ">=8.2", - "symfony/property-access": "^6.4 || ^7.0 || ^8.0", - "symfony/property-info": "^6.4 || ^7.1 || ^8.0", - "symfony/serializer": "^6.4 || ^7.0 || ^8.0", - "symfony/validator": "^6.4.11 || ^7.0 || ^8.0" + "symfony/property-access": "^6.4 || ^7.0", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0" }, "require-dev": { - "api-platform/doctrine-common": "^4.2", - "api-platform/doctrine-odm": "^4.2", - "api-platform/doctrine-orm": "^4.2", - "api-platform/json-schema": "^4.2", - "api-platform/openapi": "^4.2", + "api-platform/doctrine-common": "^4.1", + "api-platform/doctrine-odm": "^4.1", + "api-platform/doctrine-orm": "^4.1", + "api-platform/json-schema": "^4.1", + "api-platform/openapi": "^4.1", "doctrine/collections": "^2.1", "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "11.5.x-dev", "symfony/mercure-bundle": "*", - "symfony/type-info": "^7.3 || ^8.0", - "symfony/var-dumper": "^6.4 || ^7.0 || ^8.0", - "symfony/yaml": "^6.4 || ^7.0 || ^8.0" + "symfony/var-dumper": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0" }, "suggest": { "api-platform/doctrine-odm": "To support Doctrine MongoDB ODM state options.", @@ -1852,13 +1831,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1893,41 +1871,37 @@ "serializer" ], "support": { - "source": "https://github.com/api-platform/serializer/tree/v4.2.7" + "source": "https://github.com/api-platform/serializer/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-08-29T15:13:26+00:00" }, { "name": "api-platform/state", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/state.git", - "reference": "b46ec9e09dd6be3e44461d18097025cf449d23b6" + "reference": "056b07285cdc904984fb44c2614f7df8f4620a95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/state/zipball/b46ec9e09dd6be3e44461d18097025cf449d23b6", - "reference": "b46ec9e09dd6be3e44461d18097025cf449d23b6", + "url": "https://api.github.com/repos/api-platform/state/zipball/056b07285cdc904984fb44c2614f7df8f4620a95", + "reference": "056b07285cdc904984fb44c2614f7df8f4620a95", "shasum": "" }, "require": { - "api-platform/metadata": "^4.2.3", + "api-platform/metadata": "^4.1.18", "php": ">=8.2", "psr/container": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^3.1", - "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", - "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/serializer": "^6.4 || ^7.0", "symfony/translation-contracts": "^3.0" }, "require-dev": { - "api-platform/serializer": "^4.2.4", - "api-platform/validator": "^4.2.4", + "api-platform/validator": "^4.1", "phpunit/phpunit": "11.5.x-dev", - "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0", - "symfony/object-mapper": "^7.4 || ^8.0", - "symfony/type-info": "^7.4 || ^8.0", - "symfony/web-link": "^6.4 || ^7.1 || ^8.0", + "symfony/http-foundation": "^6.4 || ^7.0", + "symfony/web-link": "^6.4 || ^7.1", "willdurand/negotiation": "^3.1" }, "suggest": { @@ -1944,13 +1918,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -1990,59 +1963,55 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/state/tree/v4.2.7" + "source": "https://github.com/api-platform/state/tree/v4.1.23" }, - "time": "2025-11-30T13:03:35+00:00" + "time": "2025-07-16T14:01:52+00:00" }, { "name": "api-platform/symfony", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/symfony.git", - "reference": "1e16952c5cccbd7dd65936a4cefb66a10c72c26f" + "reference": "e35839489b4e76ffc5fc2b0cbadbbaece75b9ad1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/symfony/zipball/1e16952c5cccbd7dd65936a4cefb66a10c72c26f", - "reference": "1e16952c5cccbd7dd65936a4cefb66a10c72c26f", + "url": "https://api.github.com/repos/api-platform/symfony/zipball/e35839489b4e76ffc5fc2b0cbadbbaece75b9ad1", + "reference": "e35839489b4e76ffc5fc2b0cbadbbaece75b9ad1", "shasum": "" }, "require": { - "api-platform/documentation": "^4.2.3", - "api-platform/http-cache": "^4.2.3", - "api-platform/hydra": "^4.2.3", - "api-platform/json-schema": "^4.2.3", - "api-platform/jsonld": "^4.2.3", - "api-platform/metadata": "^4.2.3", - "api-platform/openapi": "^4.2.3", - "api-platform/serializer": "^4.2.4", - "api-platform/state": "^4.2.4", - "api-platform/validator": "^4.2.3", + "api-platform/documentation": "^4.1.11", + "api-platform/http-cache": "^4.1.11", + "api-platform/hydra": "^4.1.11", + "api-platform/json-schema": "^4.1.11", + "api-platform/jsonld": "^4.1.11", + "api-platform/metadata": "^4.1.11", + "api-platform/openapi": "^4.1.11", + "api-platform/serializer": "^4.1.11", + "api-platform/state": "^4.1.11", + "api-platform/validator": "^4.1.11", "php": ">=8.2", - "symfony/asset": "^6.4 || ^7.0 || ^8.0", - "symfony/finder": "^6.4 || ^7.0 || ^8.0", - "symfony/property-access": "^6.4 || ^7.0 || ^8.0", + "symfony/property-access": "^6.4 || ^7.0", "symfony/property-info": "^6.4 || ^7.1", - "symfony/security-core": "^6.4 || ^7.0 || ^8.0", - "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "symfony/security-core": "^6.4 || ^7.0", + "symfony/serializer": "^6.4 || ^7.0", "willdurand/negotiation": "^3.1" }, "require-dev": { - "api-platform/doctrine-common": "^4.2.3", - "api-platform/doctrine-odm": "^4.2.3", - "api-platform/doctrine-orm": "^4.2.3", - "api-platform/elasticsearch": "^4.2.3", - "api-platform/graphql": "^4.2.3", + "api-platform/doctrine-common": "^4.1", + "api-platform/doctrine-odm": "^4.1", + "api-platform/doctrine-orm": "^4.1", + "api-platform/elasticsearch": "^4.1", + "api-platform/graphql": "^4.1", + "api-platform/parameter-validator": "^3.1", "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "11.5.x-dev", - "symfony/expression-language": "^6.4 || ^7.0 || ^8.0", - "symfony/intl": "^6.4 || ^7.0 || ^8.0", + "symfony/expression-language": "^6.4 || ^7.0", "symfony/mercure-bundle": "*", - "symfony/object-mapper": "^7.0 || ^8.0", - "symfony/routing": "^6.4 || ^7.0 || ^8.0", - "symfony/type-info": "^7.3 || ^8.0", - "symfony/validator": "^6.4.11 || ^7.0 || ^8.0", + "symfony/routing": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", "webonyx/graphql-php": "^15.0" }, "suggest": { @@ -2050,8 +2019,8 @@ "api-platform/doctrine-orm": "To support Doctrine ORM.", "api-platform/elasticsearch": "To support Elasticsearch.", "api-platform/graphql": "To support GraphQL.", - "api-platform/hal": "to support the HAL format", "api-platform/ramsey-uuid": "To support Ramsey's UUID identifiers.", + "ocramius/package-versions": "To display the API Platform's version in the debug bar.", "phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.", "psr/cache-implementation": "To use metadata caching.", "symfony/cache": "To have metadata caching when using Symfony integration.", @@ -2072,10 +2041,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { - "dev-main": "4.3.x-dev" + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -2116,32 +2087,32 @@ "symfony" ], "support": { - "source": "https://github.com/api-platform/symfony/tree/v4.2.7" + "source": "https://github.com/api-platform/symfony/tree/v4.1.23" }, - "time": "2025-11-30T13:03:06+00:00" + "time": "2025-09-05T07:30:37+00:00" }, { "name": "api-platform/validator", - "version": "v4.2.7", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/validator.git", - "reference": "a29ba03fa64f4db7522aa19d40c4fe8500b3f160" + "reference": "9f0bde95dccf1d86e6a6165543d601a4a46eaa9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/validator/zipball/a29ba03fa64f4db7522aa19d40c4fe8500b3f160", - "reference": "a29ba03fa64f4db7522aa19d40c4fe8500b3f160", + "url": "https://api.github.com/repos/api-platform/validator/zipball/9f0bde95dccf1d86e6a6165543d601a4a46eaa9a", + "reference": "9f0bde95dccf1d86e6a6165543d601a4a46eaa9a", "shasum": "" }, "require": { - "api-platform/metadata": "^4.2", + "api-platform/metadata": "^4.1.11", "php": ">=8.2", - "symfony/http-kernel": "^6.4 || ^7.1 || ^8.0", - "symfony/serializer": "^6.4 || ^7.1 || ^8.0", - "symfony/type-info": "^7.3 || ^8.0", - "symfony/validator": "^6.4.11 || ^7.1 || ^8.0", - "symfony/web-link": "^6.4 || ^7.1 || ^8.0" + "symfony/http-kernel": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.1", + "symfony/type-info": "^7.2", + "symfony/validator": "^6.4 || ^7.1", + "symfony/web-link": "^6.4 || ^7.1" }, "require-dev": { "phpspec/prophecy-phpunit": "^2.2", @@ -2154,13 +2125,12 @@ "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.0 || ^8.0" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -2192,9 +2162,9 @@ "validator" ], "support": { - "source": "https://github.com/api-platform/validator/tree/v4.2.7" + "source": "https://github.com/api-platform/validator/tree/v4.1.23" }, - "time": "2025-11-30T12:55:42+00:00" + "time": "2025-07-16T14:01:52+00:00" }, { "name": "beberlei/assert", @@ -2387,16 +2357,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.9", + "version": "1.5.8", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54" + "reference": "719026bb30813accb68271fee7e39552a58e9f65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/1905981ee626e6f852448b7aaa978f8666c5bc54", - "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/719026bb30813accb68271fee7e39552a58e9f65", + "reference": "719026bb30813accb68271fee7e39552a58e9f65", "shasum": "" }, "require": { @@ -2443,7 +2413,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.9" + "source": "https://github.com/composer/ca-bundle/tree/1.5.8" }, "funding": [ { @@ -2455,7 +2425,7 @@ "type": "github" } ], - "time": "2025-11-06T11:46:17+00:00" + "time": "2025-08-20T18:49:47+00:00" }, { "name": "composer/package-versions-deprecated", @@ -2530,85 +2500,6 @@ ], "time": "2022-01-17T14:14:24+00:00" }, - { - "name": "composer/pcre", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<1.11.10" - }, - "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" - }, - "type": "library", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, - "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.3.2" - }, - "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-11-12T16:29:46+00:00" - }, { "name": "daverandom/libdns", "version": "v2.1.0", @@ -2730,16 +2621,16 @@ }, { "name": "doctrine/collections", - "version": "2.4.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "9acfeea2e8666536edff3d77c531261c63680160" + "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/9acfeea2e8666536edff3d77c531261c63680160", - "reference": "9acfeea2e8666536edff3d77c531261c63680160", + "url": "https://api.github.com/repos/doctrine/collections/zipball/2eb07e5953eed811ce1b309a7478a3b236f2273d", + "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d", "shasum": "" }, "require": { @@ -2748,11 +2639,11 @@ "symfony/polyfill-php84": "^1.30" }, "require-dev": { - "doctrine/coding-standard": "^14", + "doctrine/coding-standard": "^12", "ext-json": "*", - "phpstan/phpstan": "^2.1.30", - "phpstan/phpstan-phpunit": "^2.0.7", - "phpunit/phpunit": "^10.5.58 || ^11.5.42 || ^12.4" + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.5" }, "type": "library", "autoload": { @@ -2796,7 +2687,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.4.0" + "source": "https://github.com/doctrine/collections/tree/2.3.0" }, "funding": [ { @@ -2812,7 +2703,7 @@ "type": "tidelift" } ], - "time": "2025-10-25T09:18:13+00:00" + "time": "2025-03-22T10:17:19+00:00" }, { "name": "doctrine/common", @@ -2907,16 +2798,16 @@ }, { "name": "doctrine/data-fixtures", - "version": "2.2.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97" + "reference": "f161e20f04ba5440a09330e156b40f04dd70d47f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/7a615ba135e45d67674bb623d90f34f6c7b6bd97", - "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/f161e20f04ba5440a09330e156b40f04dd70d47f", + "reference": "f161e20f04ba5440a09330e156b40f04dd70d47f", "shasum": "" }, "require": { @@ -2930,14 +2821,14 @@ "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { - "doctrine/coding-standard": "^14", + "doctrine/coding-standard": "^13", "doctrine/dbal": "^3.5 || ^4", "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", "doctrine/orm": "^2.14 || ^3", "ext-sqlite3": "*", "fig/log-test": "^1", - "phpstan/phpstan": "2.1.31", - "phpunit/phpunit": "10.5.45 || 12.4.0", + "phpstan/phpstan": "2.1.17", + "phpunit/phpunit": "10.5.45", "symfony/cache": "^6.4 || ^7", "symfony/var-exporter": "^6.4 || ^7" }, @@ -2970,7 +2861,7 @@ ], "support": { "issues": "https://github.com/doctrine/data-fixtures/issues", - "source": "https://github.com/doctrine/data-fixtures/tree/2.2.0" + "source": "https://github.com/doctrine/data-fixtures/tree/2.1.0" }, "funding": [ { @@ -2986,20 +2877,20 @@ "type": "tidelift" } ], - "time": "2025-10-17T20:06:20+00:00" + "time": "2025-07-08T17:48:20+00:00" }, { "name": "doctrine/dbal", - "version": "4.4.0", + "version": "4.3.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "e8c5163fbec0f34e357431bd1e5fc4056cdf4fdc" + "reference": "231959669bb2173194c95636eae7f1b41b2a8b19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/e8c5163fbec0f34e357431bd1e5fc4056cdf4fdc", - "reference": "e8c5163fbec0f34e357431bd1e5fc4056cdf4fdc", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/231959669bb2173194c95636eae7f1b41b2a8b19", + "reference": "231959669bb2173194c95636eae7f1b41b2a8b19", "shasum": "" }, "require": { @@ -3009,17 +2900,17 @@ "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "14.0.0", + "doctrine/coding-standard": "13.0.1", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.2", - "phpstan/phpstan": "2.1.30", - "phpstan/phpstan-phpunit": "2.0.7", + "phpstan/phpstan": "2.1.22", + "phpstan/phpstan-phpunit": "2.0.6", "phpstan/phpstan-strict-rules": "^2", "phpunit/phpunit": "11.5.23", - "slevomat/coding-standard": "8.24.0", - "squizlabs/php_codesniffer": "4.0.0", - "symfony/cache": "^6.3.8|^7.0|^8.0", - "symfony/console": "^5.4|^6.3|^7.0|^8.0" + "slevomat/coding-standard": "8.16.2", + "squizlabs/php_codesniffer": "3.13.1", + "symfony/cache": "^6.3.8|^7.0", + "symfony/console": "^5.4|^6.3|^7.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -3076,7 +2967,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.4.0" + "source": "https://github.com/doctrine/dbal/tree/4.3.3" }, "funding": [ { @@ -3092,7 +2983,7 @@ "type": "tidelift" } ], - "time": "2025-11-29T12:17:09+00:00" + "time": "2025-09-04T23:52:42+00:00" }, { "name": "doctrine/deprecations", @@ -3144,21 +3035,20 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.18.1", + "version": "2.16.1", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "b769877014de053da0e5cbbb63d0ea2f3b2fea76" + "reference": "152d5083f0cd205a278131dc4351a8c94d007fe1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/b769877014de053da0e5cbbb63d0ea2f3b2fea76", - "reference": "b769877014de053da0e5cbbb63d0ea2f3b2fea76", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/152d5083f0cd205a278131dc4351a8c94d007fe1", + "reference": "152d5083f0cd205a278131dc4351a8c94d007fe1", "shasum": "" }, "require": { "doctrine/dbal": "^3.7.0 || ^4.0", - "doctrine/deprecations": "^1.0", "doctrine/persistence": "^3.1 || ^4", "doctrine/sql-formatter": "^1.0.1", "php": "^8.1", @@ -3166,6 +3056,7 @@ "symfony/config": "^6.4 || ^7.0", "symfony/console": "^6.4 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/deprecation-contracts": "^2.1 || ^3", "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3", "symfony/framework-bundle": "^6.4 || ^7.0", "symfony/service-contracts": "^2.5 || ^3" @@ -3180,17 +3071,19 @@ "require-dev": { "doctrine/annotations": "^1 || ^2", "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^14", + "doctrine/coding-standard": "^13", + "doctrine/deprecations": "^1.0", "doctrine/orm": "^2.17 || ^3.1", "friendsofphp/proxy-manager-lts": "^1.0", "phpstan/phpstan": "2.1.1", "phpstan/phpstan-phpunit": "2.0.3", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "^10.5.53 || ^12.3.10", + "phpunit/phpunit": "^9.6.22", "psr/log": "^1.1.4 || ^2.0 || ^3.0", "symfony/doctrine-messenger": "^6.4 || ^7.0", "symfony/expression-language": "^6.4 || ^7.0", "symfony/messenger": "^6.4 || ^7.0", + "symfony/phpunit-bridge": "^7.2", "symfony/property-info": "^6.4 || ^7.0", "symfony/security-bundle": "^6.4 || ^7.0", "symfony/stopwatch": "^6.4 || ^7.0", @@ -3200,7 +3093,7 @@ "symfony/var-exporter": "^6.4.1 || ^7.0.1", "symfony/web-profiler-bundle": "^6.4 || ^7.0", "symfony/yaml": "^6.4 || ^7.0", - "twig/twig": "^2.14.7 || ^3.0.4" + "twig/twig": "^2.13 || ^3.0.4" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -3245,7 +3138,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.1" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.16.1" }, "funding": [ { @@ -3261,32 +3154,32 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:42:10+00:00" + "time": "2025-09-05T15:24:53+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "3.7.0", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b" + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/1e380c6dd8ac8488217f39cff6b77e367f1a644b", - "reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/5a6ac7120c2924c4c070a869d08b11ccf9e277b9", + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "^2.4 || ^3.0", + "doctrine/doctrine-bundle": "^2.4", "doctrine/migrations": "^3.2", "php": "^7.2 || ^8.0", "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { "composer/semver": "^3.0", - "doctrine/coding-standard": "^12 || ^14", + "doctrine/coding-standard": "^12", "doctrine/orm": "^2.6 || ^3", "phpstan/phpstan": "^1.4 || ^2", "phpstan/phpstan-deprecation-rules": "^1 || ^2", @@ -3294,8 +3187,8 @@ "phpstan/phpstan-strict-rules": "^1.1 || ^2", "phpstan/phpstan-symfony": "^1.3 || ^2", "phpunit/phpunit": "^8.5 || ^9.5", - "symfony/phpunit-bridge": "^6.3 || ^7 || ^8", - "symfony/var-exporter": "^5.4 || ^6 || ^7 || ^8" + "symfony/phpunit-bridge": "^6.3 || ^7", + "symfony/var-exporter": "^5.4 || ^6 || ^7" }, "type": "symfony-bundle", "autoload": { @@ -3330,7 +3223,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.7.0" + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.4.2" }, "funding": [ { @@ -3346,7 +3239,7 @@ "type": "tidelift" } ], - "time": "2025-11-15T19:02:59+00:00" + "time": "2025-03-11T17:36:26+00:00" }, { "name": "doctrine/event-manager", @@ -3678,16 +3571,16 @@ }, { "name": "doctrine/migrations", - "version": "3.9.5", + "version": "3.9.4", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "1b823afbc40f932dae8272574faee53f2755eac5" + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/1b823afbc40f932dae8272574faee53f2755eac5", - "reference": "1b823afbc40f932dae8272574faee53f2755eac5", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", "shasum": "" }, "require": { @@ -3697,15 +3590,15 @@ "doctrine/event-manager": "^1.2 || ^2.0", "php": "^8.1", "psr/log": "^1.1.3 || ^2 || ^3", - "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", - "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0 || ^8.0", - "symfony/var-exporter": "^6.2 || ^7.0 || ^8.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.2 || ^7.0" }, "conflict": { "doctrine/orm": "<2.12 || >=4" }, "require-dev": { - "doctrine/coding-standard": "^14", + "doctrine/coding-standard": "^13", "doctrine/orm": "^2.13 || ^3", "doctrine/persistence": "^2 || ^3 || ^4", "doctrine/sql-formatter": "^1.0", @@ -3717,9 +3610,9 @@ "phpstan/phpstan-strict-rules": "^2", "phpstan/phpstan-symfony": "^2", "phpunit/phpunit": "^10.3 || ^11.0 || ^12.0", - "symfony/cache": "^5.4 || ^6.0 || ^7.0 || ^8.0", - "symfony/process": "^5.4 || ^6.0 || ^7.0 || ^8.0", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", @@ -3761,7 +3654,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.9.5" + "source": "https://github.com/doctrine/migrations/tree/3.9.4" }, "funding": [ { @@ -3777,20 +3670,20 @@ "type": "tidelift" } ], - "time": "2025-11-20T11:15:36+00:00" + "time": "2025-08-19T06:41:07+00:00" }, { "name": "doctrine/orm", - "version": "3.5.8", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "78dd074266e8b47a83bcf60ab5fe06c91a639168" + "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/78dd074266e8b47a83bcf60ab5fe06c91a639168", - "reference": "78dd074266e8b47a83bcf60ab5fe06c91a639168", + "url": "https://api.github.com/repos/doctrine/orm/zipball/5a541b8b3a327ab1ea5f93b1615b4ff67a34e109", + "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109", "shasum": "" }, "require": { @@ -3806,18 +3699,20 @@ "ext-ctype": "*", "php": "^8.1", "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", - "symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.3.9 || ^7.0" }, "require-dev": { - "doctrine/coding-standard": "^14.0", + "doctrine/coding-standard": "^13.0", "phpbench/phpbench": "^1.0", + "phpdocumentor/guides-cli": "^1.4", "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "2.1.23", + "phpstan/phpstan": "2.0.3", "phpstan/phpstan-deprecation-rules": "^2", - "phpunit/phpunit": "^10.5.0 || ^11.5", + "phpunit/phpunit": "^10.4.0", "psr/log": "^1 || ^2 || ^3", - "symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0" + "squizlabs/php_codesniffer": "3.12.0", + "symfony/cache": "^5.4 || ^6.2 || ^7.0" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", @@ -3863,22 +3758,22 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.5.8" + "source": "https://github.com/doctrine/orm/tree/3.5.2" }, - "time": "2025-11-29T23:11:02+00:00" + "time": "2025-08-08T17:00:40+00:00" }, { "name": "doctrine/persistence", - "version": "4.1.1", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "b9c49ad3558bb77ef973f4e173f2e9c2eca9be09" + "reference": "dcbdfe4b211ae09478e192289cae7ab0987b29a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/b9c49ad3558bb77ef973f4e173f2e9c2eca9be09", - "reference": "b9c49ad3558bb77ef973f4e173f2e9c2eca9be09", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/dcbdfe4b211ae09478e192289cae7ab0987b29a4", + "reference": "dcbdfe4b211ae09478e192289cae7ab0987b29a4", "shasum": "" }, "require": { @@ -3887,11 +3782,11 @@ "psr/cache": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { - "doctrine/coding-standard": "^14", - "phpstan/phpstan": "2.1.30", - "phpstan/phpstan-phpunit": "^2", - "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "^10.5.58 || ^12", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "1.12.7", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.6", + "phpunit/phpunit": "^9.6", "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0", "symfony/finder": "^4.4 || ^5.4 || ^6.0 || ^7.0" }, @@ -3942,7 +3837,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/4.1.1" + "source": "https://github.com/doctrine/persistence/tree/4.1.0" }, "funding": [ { @@ -3958,30 +3853,30 @@ "type": "tidelift" } ], - "time": "2025-10-16T20:13:18+00:00" + "time": "2025-08-21T16:00:31+00:00" }, { "name": "doctrine/sql-formatter", - "version": "1.5.3", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/doctrine/sql-formatter.git", - "reference": "a8af23a8e9d622505baa2997465782cbe8bb7fc7" + "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/a8af23a8e9d622505baa2997465782cbe8bb7fc7", - "reference": "a8af23a8e9d622505baa2997465782cbe8bb7fc7", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/d6d00aba6fd2957fe5216fe2b7673e9985db20c8", + "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^14", - "ergebnis/phpunit-slow-test-detector": "^2.20", - "phpstan/phpstan": "^2.1.31", - "phpunit/phpunit": "^10.5.58" + "doctrine/coding-standard": "^12", + "ergebnis/phpunit-slow-test-detector": "^2.14", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5" }, "bin": [ "bin/sql-formatter" @@ -4011,22 +3906,22 @@ ], "support": { "issues": "https://github.com/doctrine/sql-formatter/issues", - "source": "https://github.com/doctrine/sql-formatter/tree/1.5.3" + "source": "https://github.com/doctrine/sql-formatter/tree/1.5.2" }, - "time": "2025-10-26T09:35:14+00:00" + "time": "2025-01-24T11:45:48+00:00" }, { "name": "dompdf/dompdf", - "version": "v3.1.4", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "db712c90c5b9868df3600e64e68da62e78a34623" + "reference": "a51bd7a063a65499446919286fb18b518177155a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623", - "reference": "db712c90c5b9868df3600e64e68da62e78a34623", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/a51bd7a063a65499446919286fb18b518177155a", + "reference": "a51bd7a063a65499446919286fb18b518177155a", "shasum": "" }, "require": { @@ -4075,9 +3970,9 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v3.1.4" + "source": "https://github.com/dompdf/dompdf/tree/v3.1.0" }, - "time": "2025-10-29T12:43:30+00:00" + "time": "2025-01-15T14:09:04+00:00" }, { "name": "dompdf/php-font-lib", @@ -4756,16 +4651,16 @@ }, { "name": "hshn/base64-encoded-file", - "version": "v5.0.3", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/hshn/base64-encoded-file.git", - "reference": "74984c7e69fbed9378dbf1d64e632522cc1b6d95" + "reference": "49e38d27fcf01a2f5b142886d6ef20fa62132a2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hshn/base64-encoded-file/zipball/74984c7e69fbed9378dbf1d64e632522cc1b6d95", - "reference": "74984c7e69fbed9378dbf1d64e632522cc1b6d95", + "url": "https://api.github.com/repos/hshn/base64-encoded-file/zipball/49e38d27fcf01a2f5b142886d6ef20fa62132a2d", + "reference": "49e38d27fcf01a2f5b142886d6ef20fa62132a2d", "shasum": "" }, "require": { @@ -4812,9 +4707,9 @@ "description": "Provides handling base64 encoded files, and the integration of symfony/form", "support": { "issues": "https://github.com/hshn/base64-encoded-file/issues", - "source": "https://github.com/hshn/base64-encoded-file/tree/v5.0.3" + "source": "https://github.com/hshn/base64-encoded-file/tree/v5.0.2" }, - "time": "2025-10-06T10:34:52+00:00" + "time": "2025-07-06T05:52:34+00:00" }, { "name": "imagine/imagine", @@ -4940,24 +4835,24 @@ }, { "name": "jbtronics/dompdf-font-loader-bundle", - "version": "v1.1.6", + "version": "v1.1.4", "source": { "type": "git", "url": "https://github.com/jbtronics/dompdf-font-loader-bundle.git", - "reference": "5fb434f35544d5757292cd5471768dda3862c932" + "reference": "1b41014a2dd9e82ba6a62e61deeebe3cdc1eaf1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/dompdf-font-loader-bundle/zipball/5fb434f35544d5757292cd5471768dda3862c932", - "reference": "5fb434f35544d5757292cd5471768dda3862c932", + "url": "https://api.github.com/repos/jbtronics/dompdf-font-loader-bundle/zipball/1b41014a2dd9e82ba6a62e61deeebe3cdc1eaf1f", + "reference": "1b41014a2dd9e82ba6a62e61deeebe3cdc1eaf1f", "shasum": "" }, "require": { "dompdf/dompdf": "^1.0.0|^2.0.0|^3.0.0", "ext-json": "*", "php": "^8.1", - "symfony/finder": "^6.0|^7.0|^8.0", - "symfony/framework-bundle": "^6.0|^7.0|^8.0" + "symfony/finder": "^6.0|^7.0", + "symfony/framework-bundle": "^6.0|^7.0" }, "require-dev": { "phpunit/phpunit": "^9.5", @@ -4989,22 +4884,22 @@ ], "support": { "issues": "https://github.com/jbtronics/dompdf-font-loader-bundle/issues", - "source": "https://github.com/jbtronics/dompdf-font-loader-bundle/tree/v1.1.6" + "source": "https://github.com/jbtronics/dompdf-font-loader-bundle/tree/v1.1.4" }, - "time": "2025-11-30T22:19:12+00:00" + "time": "2025-07-07T20:39:34+00:00" }, { "name": "jbtronics/settings-bundle", - "version": "v3.1.2", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/jbtronics/settings-bundle.git", - "reference": "f16bce21b54d202baabfe05cb7c64a14d43b9671" + "reference": "9103bd7f78f0b223d1c7167feb824004fc2a9f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/settings-bundle/zipball/f16bce21b54d202baabfe05cb7c64a14d43b9671", - "reference": "f16bce21b54d202baabfe05cb7c64a14d43b9671", + "url": "https://api.github.com/repos/jbtronics/settings-bundle/zipball/9103bd7f78f0b223d1c7167feb824004fc2a9f07", + "reference": "9103bd7f78f0b223d1c7167feb824004fc2a9f07", "shasum": "" }, "require": { @@ -5012,11 +4907,11 @@ "ext-json": "*", "php": "^8.1", "symfony/deprecation-contracts": "^3.4", - "symfony/form": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/translation": "^7.0|^6.4|^8.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/translation": "^7.0|^6.4", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0", "symfony/var-exporter": "^6.4|^7.0" }, "require-dev": { @@ -5030,10 +4925,10 @@ "phpstan/phpstan-symfony": "^1.3", "phpunit/phpunit": "^9.5", "roave/security-advisories": "dev-latest", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/phpunit-bridge": "^6.4|^7.0|^8.0", - "symfony/security-csrf": "^7.0|^6.4|^8.0", - "symfony/twig-bridge": "^6.4|^7.0|^8.0" + "symfony/console": "^6.4|^7.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symfony/security-csrf": "^7.0|^6.4", + "symfony/twig-bridge": "^6.4|^7.0" }, "suggest": { "doctrine/doctrine-bundle": "To use the doctrine ORM storage", @@ -5065,7 +4960,7 @@ ], "support": { "issues": "https://github.com/jbtronics/settings-bundle/issues", - "source": "https://github.com/jbtronics/settings-bundle/tree/v3.1.2" + "source": "https://github.com/jbtronics/settings-bundle/tree/v3.0.1" }, "funding": [ { @@ -5077,7 +4972,7 @@ "type": "github" } ], - "time": "2025-11-30T22:22:49+00:00" + "time": "2025-08-24T21:20:15+00:00" }, { "name": "jfcherng/php-color-output", @@ -5376,32 +5271,31 @@ }, { "name": "knpuniversity/oauth2-client-bundle", - "version": "v2.20.0", + "version": "v2.18.4", "source": { "type": "git", "url": "https://github.com/knpuniversity/oauth2-client-bundle.git", - "reference": "cee929516df679473b42765ed3d50c5aa7e9a837" + "reference": "2f48e1ff7969ef0252482d0f6af874eca639ea2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/cee929516df679473b42765ed3d50c5aa7e9a837", - "reference": "cee929516df679473b42765ed3d50c5aa7e9a837", + "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/2f48e1ff7969ef0252482d0f6af874eca639ea2d", + "reference": "2f48e1ff7969ef0252482d0f6af874eca639ea2d", "shasum": "" }, "require": { "league/oauth2-client": "^2.0", "php": ">=8.1", - "symfony/dependency-injection": "^6.4|^7.3|^8.0", - "symfony/framework-bundle": "^6.4|^7.3|^8.0", - "symfony/http-foundation": "^6.4|^7.3|^8.0", - "symfony/routing": "^6.4|^7.3|^8.0", - "symfony/security-core": "^6.4|^7.3|^8.0", - "symfony/security-http": "^6.4|^7.3|^8.0" + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0" }, "require-dev": { "league/oauth2-facebook": "^1.1|^2.0", - "symfony/phpunit-bridge": "^7.3", - "symfony/yaml": "^6.4|^7.3|^8.0" + "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", + "symfony/security-guard": "^5.4", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "suggest": { "symfony/security-guard": "For integration with Symfony's Guard Security layer" @@ -5430,9 +5324,9 @@ ], "support": { "issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues", - "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.20.0" + "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.18.4" }, - "time": "2025-11-07T10:44:56+00:00" + "time": "2025-08-18T15:33:00+00:00" }, { "name": "lcobucci/clock", @@ -5500,22 +5394,22 @@ }, { "name": "lcobucci/jwt", - "version": "5.6.0", + "version": "5.5.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" + "reference": "a835af59b030d3f2967725697cf88300f579088e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", - "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a835af59b030d3f2967725697cf88300f579088e", + "reference": "a835af59b030d3f2967725697cf88300f579088e", "shasum": "" }, "require": { "ext-openssl": "*", "ext-sodium": "*", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "psr/clock": "^1.0" }, "require-dev": { @@ -5557,7 +5451,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.6.0" + "source": "https://github.com/lcobucci/jwt/tree/5.5.0" }, "funding": [ { @@ -5569,20 +5463,20 @@ "type": "patreon" } ], - "time": "2025-10-17T11:30:53+00:00" + "time": "2025-01-26T21:29:45+00:00" }, { "name": "league/commonmark", - "version": "2.8.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", "shasum": "" }, "require": { @@ -5619,7 +5513,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.9-dev" + "dev-main": "2.8-dev" } }, "autoload": { @@ -5676,7 +5570,7 @@ "type": "tidelift" } ], - "time": "2025-11-26T21:48:24+00:00" + "time": "2025-07-20T12:47:49+00:00" }, { "name": "league/config", @@ -5762,16 +5656,16 @@ }, { "name": "league/csv", - "version": "9.27.1", + "version": "9.24.1", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797" + "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797", - "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/e0221a3f16aa2a823047d59fab5809d552e29bc8", + "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8", "shasum": "" }, "require": { @@ -5787,7 +5681,7 @@ "phpstan/phpstan-deprecation-rules": "^1.2.1", "phpstan/phpstan-phpunit": "^1.4.2", "phpstan/phpstan-strict-rules": "^1.6.2", - "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.3.6", + "phpunit/phpunit": "^10.5.16 || ^11.5.22", "symfony/var-dumper": "^6.4.8 || ^7.3.0" }, "suggest": { @@ -5849,7 +5743,7 @@ "type": "github" } ], - "time": "2025-10-25T08:35:20+00:00" + "time": "2025-06-25T14:53:51+00:00" }, { "name": "league/html-to-markdown", @@ -5942,22 +5836,22 @@ }, { "name": "league/oauth2-client", - "version": "2.9.0", + "version": "2.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-client.git", - "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e" + "reference": "9df2924ca644736c835fc60466a3a60390d334f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/26e8c5da4f3d78cede7021e09b1330a0fc093d5e", - "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/9df2924ca644736c835fc60466a3a60390d334f9", + "reference": "9df2924ca644736c835fc60466a3a60390d334f9", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "php": "^7.1 || >=8.0.0 <8.6.0" + "php": "^7.1 || >=8.0.0 <8.5.0" }, "require-dev": { "mockery/mockery": "^1.3.5", @@ -6001,44 +5895,39 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-client/issues", - "source": "https://github.com/thephpleague/oauth2-client/tree/2.9.0" + "source": "https://github.com/thephpleague/oauth2-client/tree/2.8.1" }, - "time": "2025-11-25T22:17:17+00:00" + "time": "2025-02-26T04:37:30+00:00" }, { "name": "league/uri", - "version": "7.6.0", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "f625804987a0a9112d954f9209d91fec52182344" + "reference": "81fb5145d2644324614cc532b28efd0215bda430" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", - "reference": "f625804987a0a9112d954f9209d91fec52182344", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.6", - "php": "^8.1", - "psr/http-factory": "^1" + "league/uri-interfaces": "^7.5", + "php": "^8.1" }, "conflict": { "league/uri-schemes": "^1.0" }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", - "ext-dom": "to convert the URI into an HTML anchor tag", "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-uri": "to use the PHP native URI class", "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", "league/uri-components": "Needed to easily manipulate URI objects components", - "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -6066,7 +5955,6 @@ "description": "URI manipulation library", "homepage": "https://uri.thephpleague.com", "keywords": [ - "URN", "data-uri", "file-uri", "ftp", @@ -6079,11 +5967,9 @@ "psr-7", "query-string", "querystring", - "rfc2141", "rfc3986", "rfc3987", "rfc6570", - "rfc8141", "uri", "uri-template", "url", @@ -6093,7 +5979,7 @@ "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.6.0" + "source": "https://github.com/thephpleague/uri/tree/7.5.1" }, "funding": [ { @@ -6101,37 +5987,34 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2024-12-08T08:40:02+00:00" }, { "name": "league/uri-components", - "version": "7.6.0", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-components.git", - "reference": "ffa1215dbee72ee4b7bc08d983d25293812456c2" + "reference": "4aabf0e2f2f9421ffcacab35be33e4fb5e63c44f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/ffa1215dbee72ee4b7bc08d983d25293812456c2", - "reference": "ffa1215dbee72ee4b7bc08d983d25293812456c2", + "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/4aabf0e2f2f9421ffcacab35be33e4fb5e63c44f", + "reference": "4aabf0e2f2f9421ffcacab35be33e4fb5e63c44f", "shasum": "" }, "require": { - "league/uri": "^7.6", + "league/uri": "^7.5", "php": "^8.1" }, "suggest": { - "bakame/aide-uri": "A polyfill for PHP8.1 until PHP8.4 to add support to PHP Native URI parser", "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", - "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -6178,7 +6061,7 @@ "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.6.0" + "source": "https://github.com/thephpleague/uri-components/tree/7.5.1" }, "funding": [ { @@ -6186,25 +6069,26 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2024-12-08T08:40:02+00:00" }, { "name": "league/uri-interfaces", - "version": "7.6.0", + "version": "7.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", - "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", "shasum": "" }, "require": { "ext-filter": "*", "php": "^8.1", + "psr/http-factory": "^1", "psr/http-message": "^1.1 || ^2.0" }, "suggest": { @@ -6212,7 +6096,6 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -6237,7 +6120,7 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "description": "Common interfaces and classes for URI representation and interaction", "homepage": "https://uri.thephpleague.com", "keywords": [ "data-uri", @@ -6262,7 +6145,7 @@ "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.6.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" }, "funding": [ { @@ -6270,20 +6153,20 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2024-12-08T08:18:47+00:00" }, { "name": "liip/imagine-bundle", - "version": "2.15.0", + "version": "2.14.0", "source": { "type": "git", "url": "https://github.com/liip/LiipImagineBundle.git", - "reference": "f8c98a5a962806f26571db219412b64266c763d8" + "reference": "f80dc13e9a454682b8c2255b3487829d2f8a7fe4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/f8c98a5a962806f26571db219412b64266c763d8", - "reference": "f8c98a5a962806f26571db219412b64266c763d8", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/f80dc13e9a454682b8c2255b3487829d2f8a7fe4", + "reference": "f80dc13e9a454682b8c2255b3487829d2f8a7fe4", "shasum": "" }, "require": { @@ -6375,9 +6258,9 @@ ], "support": { "issues": "https://github.com/liip/LiipImagineBundle/issues", - "source": "https://github.com/liip/LiipImagineBundle/tree/2.15.0" + "source": "https://github.com/liip/LiipImagineBundle/tree/2.14.0" }, - "time": "2025-10-09T06:49:28+00:00" + "time": "2025-09-03T06:33:10+00:00" }, { "name": "lorenzo/pinky", @@ -6432,188 +6315,6 @@ }, "time": "2023-07-31T13:36:50+00:00" }, - { - "name": "maennchen/zipstream-php", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", - "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", - "shasum": "" - }, - "require": { - "myclabs/php-enum": "^1.5", - "php": ">= 7.1", - "psr/http-message": "^1.0", - "symfony/polyfill-mbstring": "^1.0" - }, - "require-dev": { - "ext-zip": "*", - "guzzlehttp/guzzle": ">= 6.3", - "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": ">= 7.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "ZipStream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paul Duncan", - "email": "pabs@pablotron.org" - }, - { - "name": "Jonatan Männchen", - "email": "jonatan@maennchen.ch" - }, - { - "name": "Jesse Donat", - "email": "donatj@gmail.com" - }, - { - "name": "András Kolesár", - "email": "kolesar@kolesar.hu" - } - ], - "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", - "keywords": [ - "stream", - "zip" - ], - "support": { - "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.1.0" - }, - "funding": [ - { - "url": "https://github.com/maennchen", - "type": "github" - }, - { - "url": "https://opencollective.com/zipstream", - "type": "open_collective" - } - ], - "time": "2020-05-30T13:11:16+00:00" - }, - { - "name": "markbaker/complex", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/MarkBaker/PHPComplex.git", - "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", - "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-master", - "phpcompatibility/php-compatibility": "^9.3", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "squizlabs/php_codesniffer": "^3.7" - }, - "type": "library", - "autoload": { - "psr-4": { - "Complex\\": "classes/src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mark Baker", - "email": "mark@lange.demon.co.uk" - } - ], - "description": "PHP Class for working with complex numbers", - "homepage": "https://github.com/MarkBaker/PHPComplex", - "keywords": [ - "complex", - "mathematics" - ], - "support": { - "issues": "https://github.com/MarkBaker/PHPComplex/issues", - "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" - }, - "time": "2022-12-06T16:21:08+00:00" - }, - { - "name": "markbaker/matrix", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/MarkBaker/PHPMatrix.git", - "reference": "728434227fe21be27ff6d86621a1b13107a2562c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", - "reference": "728434227fe21be27ff6d86621a1b13107a2562c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-master", - "phpcompatibility/php-compatibility": "^9.3", - "phpdocumentor/phpdocumentor": "2.*", - "phploc/phploc": "^4.0", - "phpmd/phpmd": "2.*", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "sebastian/phpcpd": "^4.0", - "squizlabs/php_codesniffer": "^3.7" - }, - "type": "library", - "autoload": { - "psr-4": { - "Matrix\\": "classes/src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mark Baker", - "email": "mark@demon-angel.eu" - } - ], - "description": "PHP Class for working with matrices", - "homepage": "https://github.com/MarkBaker/PHPMatrix", - "keywords": [ - "mathematics", - "matrix", - "vector" - ], - "support": { - "issues": "https://github.com/MarkBaker/PHPMatrix/issues", - "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" - }, - "time": "2022-12-02T22:17:43+00:00" - }, { "name": "masterminds/html5", "version": "2.10.0", @@ -6784,81 +6485,18 @@ ], "time": "2025-03-24T10:02:05+00:00" }, - { - "name": "myclabs/php-enum", - "version": "1.8.5", - "source": { - "type": "git", - "url": "https://github.com/myclabs/php-enum.git", - "reference": "e7be26966b7398204a234f8673fdad5ac6277802" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802", - "reference": "e7be26966b7398204a234f8673fdad5ac6277802", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "1.*", - "vimeo/psalm": "^4.6.2 || ^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "MyCLabs\\Enum\\": "src/" - }, - "classmap": [ - "stubs/Stringable.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP Enum contributors", - "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" - } - ], - "description": "PHP Enum implementation", - "homepage": "https://github.com/myclabs/php-enum", - "keywords": [ - "enum" - ], - "support": { - "issues": "https://github.com/myclabs/php-enum/issues", - "source": "https://github.com/myclabs/php-enum/tree/1.8.5" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", - "type": "tidelift" - } - ], - "time": "2025-01-14T11:49:03+00:00" - }, { "name": "nbgrp/onelogin-saml-bundle", - "version": "v2.1.0", + "version": "v2.0.2", "source": { "type": "git", "url": "https://github.com/nbgrp/onelogin-saml-bundle.git", - "reference": "087402c69ef87e0a34d9b708661deecd00fd190a" + "reference": "d2feeb7de6ab5b98e69deeea31ad0ceb20a1c4dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nbgrp/onelogin-saml-bundle/zipball/087402c69ef87e0a34d9b708661deecd00fd190a", - "reference": "087402c69ef87e0a34d9b708661deecd00fd190a", + "url": "https://api.github.com/repos/nbgrp/onelogin-saml-bundle/zipball/d2feeb7de6ab5b98e69deeea31ad0ceb20a1c4dc", + "reference": "d2feeb7de6ab5b98e69deeea31ad0ceb20a1c4dc", "shasum": "" }, "require": { @@ -6878,7 +6516,7 @@ }, "require-dev": { "doctrine/orm": "^2.3 || ^3", - "phpunit/phpunit": "^11", + "phpunit/phpunit": "^11.2", "symfony/event-dispatcher": "^7" }, "type": "symfony-bundle", @@ -6906,9 +6544,9 @@ ], "support": { "issues": "https://github.com/nbgrp/onelogin-saml-bundle/issues", - "source": "https://github.com/nbgrp/onelogin-saml-bundle/tree/v2.1.0" + "source": "https://github.com/nbgrp/onelogin-saml-bundle/tree/v2.0.2" }, - "time": "2025-09-26T08:45:17+00:00" + "time": "2024-09-01T22:16:27+00:00" }, { "name": "nelexa/zip", @@ -6985,28 +6623,25 @@ }, { "name": "nelmio/cors-bundle", - "version": "2.6.0", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioCorsBundle.git", - "reference": "530217472204881cacd3671909f634b960c7b948" + "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/530217472204881cacd3671909f634b960c7b948", - "reference": "530217472204881cacd3671909f634b960c7b948", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3a526fe025cd20e04a6a11370cf5ab28dbb5a544", + "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544", "shasum": "" }, "require": { "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "phpstan/phpstan": "^1.11.5", - "phpstan/phpstan-deprecation-rules": "^1.2.0", - "phpstan/phpstan-phpunit": "^1.4", - "phpstan/phpstan-symfony": "^1.4.4", - "phpunit/phpunit": "^8" + "mockery/mockery": "^1.3.6", + "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0" }, "type": "symfony-bundle", "extra": { @@ -7044,22 +6679,22 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", - "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.0" + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.5.0" }, - "time": "2025-10-23T06:57:22+00:00" + "time": "2024-06-24T21:25:28+00:00" }, { "name": "nelmio/security-bundle", - "version": "v3.6.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioSecurityBundle.git", - "reference": "f3a7bf628a0873788172a0d05d20c0224080f5eb" + "reference": "b1c5e323d71152bc1a61a4f8fbf7d88c6fa3e2e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/f3a7bf628a0873788172a0d05d20c0224080f5eb", - "reference": "f3a7bf628a0873788172a0d05d20c0224080f5eb", + "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/b1c5e323d71152bc1a61a4f8fbf7d88c6fa3e2e7", + "reference": "b1c5e323d71152bc1a61a4f8fbf7d88c6fa3e2e7", "shasum": "" }, "require": { @@ -7118,31 +6753,31 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioSecurityBundle/issues", - "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.6.0" + "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.5.1" }, - "time": "2025-09-19T08:24:46+00:00" + "time": "2025-03-13T09:17:16+00:00" }, { "name": "nette/schema", - "version": "v1.3.3", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.5" + "php": "8.1 - 8.4" }, "require-dev": { "nette/tester": "^2.5.2", - "phpstan/phpstan-nette": "^2.0@stable", + "phpstan/phpstan-nette": "^1.0", "tracy/tracy": "^2.8" }, "type": "library", @@ -7152,9 +6787,6 @@ } }, "autoload": { - "psr-4": { - "Nette\\": "src" - }, "classmap": [ "src/" ] @@ -7183,22 +6815,22 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.3" + "source": "https://github.com/nette/schema/tree/v1.3.2" }, - "time": "2025-10-30T22:57:59+00:00" + "time": "2024-10-06T23:10:23+00:00" }, { "name": "nette/utils", - "version": "v4.0.9", + "version": "v4.0.8", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "505a30ad386daa5211f08a318e47015b501cad30" + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/505a30ad386daa5211f08a318e47015b501cad30", - "reference": "505a30ad386daa5211f08a318e47015b501cad30", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", "shasum": "" }, "require": { @@ -7272,9 +6904,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.9" + "source": "https://github.com/nette/utils/tree/v4.0.8" }, - "time": "2025-10-31T00:45:47+00:00" + "time": "2025-08-06T21:43:34+00:00" }, { "name": "nikolaposa/version", @@ -7417,63 +7049,63 @@ }, { "name": "omines/datatables-bundle", - "version": "0.10.7", + "version": "0.10.3", "source": { "type": "git", "url": "https://github.com/omines/datatables-bundle.git", - "reference": "4cd6d27b12c79a1ed72b4953a86aedf289e8701d" + "reference": "d64e7d5c72303995ada7365b467166f3cdf4757c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/omines/datatables-bundle/zipball/4cd6d27b12c79a1ed72b4953a86aedf289e8701d", - "reference": "4cd6d27b12c79a1ed72b4953a86aedf289e8701d", + "url": "https://api.github.com/repos/omines/datatables-bundle/zipball/d64e7d5c72303995ada7365b467166f3cdf4757c", + "reference": "d64e7d5c72303995ada7365b467166f3cdf4757c", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/event-dispatcher": "^6.4|^7.3|^8.0", - "symfony/framework-bundle": "^6.4|^7.3|^8.0", - "symfony/options-resolver": "^6.4|^7.3|^8.0", + "symfony/event-dispatcher": "^6.4|^7.1", + "symfony/framework-bundle": "^6.4|^7.1", + "symfony/options-resolver": "^6.4|^7.1", "symfony/polyfill-mbstring": "^1.31.0", - "symfony/property-access": "^6.4|^7.3|^8.0", - "symfony/translation": "^6.4|^7.3|^8.0" + "symfony/property-access": "^6.4|^7.1", + "symfony/translation": "^6.4|^7.1" }, "conflict": { "doctrine/orm": "^3.0 <3.3" }, "require-dev": { "doctrine/common": "^3.5.0", - "doctrine/doctrine-bundle": "^2.18.1|^3.0.0@dev", - "doctrine/orm": "^2.19.3|^3.5.7@dev", - "doctrine/persistence": "^3.4.0|^4.1.1", + "doctrine/doctrine-bundle": "^2.15.0", + "doctrine/orm": "^2.19.3|^3.4.1", + "doctrine/persistence": "^3.4.0|^4.0.0", "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", "ext-mongodb": "*", "ext-pdo_sqlite": "*", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.90.0", - "mongodb/mongodb": "^1.20.0|^2.1.2", - "openspout/openspout": "^4.28.5", - "phpoffice/phpspreadsheet": "^2.3.3|^3.9.2|^4.5.0|^5.2.0", + "friendsofphp/php-cs-fixer": "^3.75.0", + "mongodb/mongodb": "^1.20.0|^2.1.0", + "ocramius/package-versions": "^2.9", + "openspout/openspout": "^4.23", + "phpoffice/phpspreadsheet": "^2.3.3|^3.9.2|^4.4.0", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.32", - "phpstan/phpstan-doctrine": "^2.0.11", - "phpstan/phpstan-phpunit": "^2.0.8", - "phpstan/phpstan-symfony": "^2.0.8", - "phpunit/phpunit": "^11.5.44|^12.4.4", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-doctrine": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.6", + "phpstan/phpstan-symfony": "^2.0.6", + "phpunit/phpunit": "^10.5.38|^11.5.24", "ruflin/elastica": "^7.3.2", - "symfony/browser-kit": "^6.4.13|^7.3|^8.0", - "symfony/css-selector": "^6.4.13|^7.3|^8.0", - "symfony/doctrine-bridge": "^6.4.13|^7.3|^8.0", - "symfony/dom-crawler": "^6.4.13|^7.3|^8.0", - "symfony/intl": "^6.4.13|^7.3|^8.0", - "symfony/mime": "^6.4.13|^7.3|^8.0", - "symfony/phpunit-bridge": "^7.3|^8.0", - "symfony/twig-bundle": "^6.4|^7.3|^8.0", - "symfony/var-dumper": "^6.4.13|^7.3|^8.0", - "symfony/var-exporter": "^v6.4.26|^7.3", - "symfony/yaml": "^6.4.13|^7.3|^8.0" + "symfony/browser-kit": "^6.4.13|^7.3", + "symfony/css-selector": "^6.4.13|^7.3", + "symfony/doctrine-bridge": "^6.4.13|^7.3", + "symfony/dom-crawler": "^6.4.13|^7.3", + "symfony/intl": "^6.4.13|^7.3", + "symfony/mime": "^6.4.13|^7.3", + "symfony/phpunit-bridge": "^7.3", + "symfony/twig-bundle": "^6.4|^7.3", + "symfony/var-dumper": "^6.4.13|^7.3", + "symfony/yaml": "^6.4.13|^7.3" }, "suggest": { "doctrine/doctrine-bundle": "For integrated access to Doctrine object managers", @@ -7525,7 +7157,7 @@ ], "support": { "issues": "https://github.com/omines/datatables-bundle/issues", - "source": "https://github.com/omines/datatables-bundle/tree/0.10.7" + "source": "https://github.com/omines/datatables-bundle/tree/0.10.3" }, "funding": [ { @@ -7533,7 +7165,7 @@ "type": "github" } ], - "time": "2025-11-28T21:20:14+00:00" + "time": "2025-07-24T19:50:46+00:00" }, { "name": "onelogin/php-saml", @@ -7601,26 +7233,24 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v3.1.3", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", - "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", "shasum": "" }, "require": { "php": "^8" }, "require-dev": { - "infection/infection": "^0", - "nikic/php-fuzzer": "^0", - "phpunit/phpunit": "^9|^10|^11", - "vimeo/psalm": "^4|^5|^6" + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" }, "type": "library", "autoload": { @@ -7666,7 +7296,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2025-09-24T15:06:41+00:00" + "time": "2024-05-08T12:36:18+00:00" }, { "name": "paragonie/random_compat", @@ -7720,16 +7350,16 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.23.0", + "version": "v1.21.1", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "b938a5c6844d222a26d46a6c7b80291e4cd8cfab" + "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/b938a5c6844d222a26d46a6c7b80291e4cd8cfab", - "reference": "b938a5c6844d222a26d46a6c7b80291e4cd8cfab", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bb312875dcdd20680419564fe42ba1d9564b9e37", + "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37", "shasum": "" }, "require": { @@ -7800,9 +7430,9 @@ ], "support": { "issues": "https://github.com/paragonie/sodium_compat/issues", - "source": "https://github.com/paragonie/sodium_compat/tree/v1.23.0" + "source": "https://github.com/paragonie/sodium_compat/tree/v1.21.1" }, - "time": "2025-10-06T08:53:07+00:00" + "time": "2024-04-22T22:05:04+00:00" }, { "name": "part-db/exchanger", @@ -8360,16 +7990,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.5", + "version": "5.6.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "90614c73d3800e187615e2dd236ad0e2a01bf761" + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/90614c73d3800e187615e2dd236ad0e2a01bf761", - "reference": "90614c73d3800e187615e2dd236ad0e2a01bf761", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", "shasum": "" }, "require": { @@ -8418,22 +8048,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.5" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" }, - "time": "2025-11-27T19:50:05+00:00" + "time": "2025-08-01T19:43:32+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.12.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", - "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { @@ -8476,115 +8106,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2025-11-21T15:09:14+00:00" - }, - { - "name": "phpoffice/phpspreadsheet", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/4d597c1aacdde1805a33c525b9758113ea0d90df", - "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df", - "shasum": "" - }, - "require": { - "composer/pcre": "^1||^2||^3", - "ext-ctype": "*", - "ext-dom": "*", - "ext-fileinfo": "*", - "ext-gd": "*", - "ext-iconv": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-xml": "*", - "ext-xmlreader": "*", - "ext-xmlwriter": "*", - "ext-zip": "*", - "ext-zlib": "*", - "maennchen/zipstream-php": "^2.1 || ^3.0", - "markbaker/complex": "^3.0", - "markbaker/matrix": "^3.0", - "php": "^8.1", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-main", - "dompdf/dompdf": "^2.0 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.2", - "mitoteam/jpgraph": "^10.5", - "mpdf/mpdf": "^8.1.1", - "phpcompatibility/php-compatibility": "^9.3", - "phpstan/phpstan": "^1.1 || ^2.0", - "phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0", - "phpstan/phpstan-phpunit": "^1.0 || ^2.0", - "phpunit/phpunit": "^10.5", - "squizlabs/php_codesniffer": "^3.7", - "tecnickcom/tcpdf": "^6.5" - }, - "suggest": { - "dompdf/dompdf": "Option for rendering PDF with PDF Writer", - "ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard", - "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", - "mpdf/mpdf": "Option for rendering PDF with PDF Writer", - "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Maarten Balliauw", - "homepage": "https://blog.maartenballiauw.be" - }, - { - "name": "Mark Baker", - "homepage": "https://markbakeruk.net" - }, - { - "name": "Franck Lefevre", - "homepage": "https://rootslabs.net" - }, - { - "name": "Erik Tilt" - }, - { - "name": "Adrien Crivelli" - } - ], - "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", - "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", - "keywords": [ - "OpenXML", - "excel", - "gnumeric", - "ods", - "php", - "spreadsheet", - "xls", - "xlsx" - ], - "support": { - "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.3.0" - }, - "time": "2025-11-24T15:47:10+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -8942,16 +8466,16 @@ }, { "name": "psr/http-message", - "version": "1.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { @@ -8960,7 +8484,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -8975,7 +8499,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -8989,9 +8513,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/1.1" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2023-04-04T09:50:52+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/link", @@ -9196,16 +8720,16 @@ }, { "name": "revolt/event-loop", - "version": "v1.0.8", + "version": "v1.0.7", "source": { "type": "git", "url": "https://github.com/revoltphp/event-loop.git", - "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", - "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", "shasum": "" }, "require": { @@ -9262,22 +8786,22 @@ ], "support": { "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" }, - "time": "2025-08-27T21:33:23+00:00" + "time": "2025-01-25T19:27:39+00:00" }, { "name": "rhukster/dom-sanitizer", - "version": "1.0.8", + "version": "1.0.7", "source": { "type": "git", "url": "https://github.com/rhukster/dom-sanitizer.git", - "reference": "757e4d6ac03afe9afa4f97cbef453fc5c25f0729" + "reference": "c2a98f27ad742668b254282ccc5581871d0fb601" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rhukster/dom-sanitizer/zipball/757e4d6ac03afe9afa4f97cbef453fc5c25f0729", - "reference": "757e4d6ac03afe9afa4f97cbef453fc5c25f0729", + "url": "https://api.github.com/repos/rhukster/dom-sanitizer/zipball/c2a98f27ad742668b254282ccc5581871d0fb601", + "reference": "c2a98f27ad742668b254282ccc5581871d0fb601", "shasum": "" }, "require": { @@ -9307,9 +8831,9 @@ "description": "A simple but effective DOM/SVG/MathML Sanitizer for PHP 7.4+", "support": { "issues": "https://github.com/rhukster/dom-sanitizer/issues", - "source": "https://github.com/rhukster/dom-sanitizer/tree/1.0.8" + "source": "https://github.com/rhukster/dom-sanitizer/tree/1.0.7" }, - "time": "2024-04-15T08:48:55+00:00" + "time": "2023-11-06T16:46:48+00:00" }, { "name": "robrichards/xmlseclibs", @@ -9353,6 +8877,58 @@ }, "time": "2024-11-20T21:13:56+00:00" }, + { + "name": "runtime/frankenphp-symfony", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-runtime/frankenphp-symfony.git", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-runtime/frankenphp-symfony/zipball/56822c3631d9522a3136a4c33082d006bdfe4bad", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/runtime": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Runtime\\FrankenPhpSymfony\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.dev" + } + ], + "description": "FrankenPHP runtime for Symfony", + "support": { + "issues": "https://github.com/php-runtime/frankenphp-symfony/issues", + "source": "https://github.com/php-runtime/frankenphp-symfony/tree/0.2.0" + }, + "funding": [ + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-12-12T12:06:11+00:00" + }, { "name": "s9e/regexp-builder", "version": "1.4.6", @@ -9443,16 +9019,16 @@ }, { "name": "s9e/text-formatter", - "version": "2.19.3", + "version": "2.19.0", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "aee579c12d05ca3053f9b9abdb8c479c0f2fbe69" + "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/aee579c12d05ca3053f9b9abdb8c479c0f2fbe69", - "reference": "aee579c12d05ca3053f9b9abdb8c479c0f2fbe69", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/d65a4f61cbe494937afb3150dc73b6e757d400d3", + "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3", "shasum": "" }, "require": { @@ -9480,7 +9056,7 @@ }, "type": "library", "extra": { - "version": "2.19.3" + "version": "2.19.0" }, "autoload": { "psr-4": { @@ -9512,9 +9088,9 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.19.3" + "source": "https://github.com/s9e/TextFormatter/tree/2.19.0" }, - "time": "2025-11-14T21:26:59+00:00" + "time": "2025-04-26T09:27:34+00:00" }, { "name": "sabberworm/php-css-parser", @@ -9584,20 +9160,20 @@ }, { "name": "scheb/2fa-backup-code", - "version": "v7.12.1", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-backup-code.git", - "reference": "35f1ace4be7be2c10158d2bb8284208499111db8" + "reference": "62c6099b179903db5ab03b8059068cdb28659294" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/35f1ace4be7be2c10158d2bb8284208499111db8", - "reference": "35f1ace4be7be2c10158d2bb8284208499111db8", + "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/62c6099b179903db5ab03b8059068cdb28659294", + "reference": "62c6099b179903db5ab03b8059068cdb28659294", "shasum": "" }, "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "scheb/2fa-bundle": "self.version" }, "type": "library", @@ -9627,27 +9203,27 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-backup-code/tree/v7.12.1" + "source": "https://github.com/scheb/2fa-backup-code/tree/v7.11.0" }, - "time": "2025-11-20T13:35:24+00:00" + "time": "2025-04-20T08:27:40+00:00" }, { "name": "scheb/2fa-bundle", - "version": "v7.12.1", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-bundle.git", - "reference": "2056c313e4ceff8098f970d99d428ddd2a3bfbf5" + "reference": "06a343d14dad8cdd1670157d384738f9cfba29e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/2056c313e4ceff8098f970d99d428ddd2a3bfbf5", - "reference": "2056c313e4ceff8098f970d99d428ddd2a3bfbf5", + "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/06a343d14dad8cdd1670157d384738f9cfba29e5", + "reference": "06a343d14dad8cdd1670157d384738f9cfba29e5", "shasum": "" }, "require": { "ext-json": "*", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "symfony/config": "^6.4 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0", "symfony/event-dispatcher": "^6.4 || ^7.0", @@ -9695,26 +9271,26 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-bundle/tree/v7.12.1" + "source": "https://github.com/scheb/2fa-bundle/tree/v7.11.0" }, - "time": "2025-11-25T15:24:27+00:00" + "time": "2025-06-27T12:14:20+00:00" }, { "name": "scheb/2fa-google-authenticator", - "version": "v7.12.1", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-google-authenticator.git", - "reference": "230cf3404d56f3311a6b2da0c161db33941dba2f" + "reference": "01a446eb68a76c3d0528a190029afa5e6ce5c384" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/230cf3404d56f3311a6b2da0c161db33941dba2f", - "reference": "230cf3404d56f3311a6b2da0c161db33941dba2f", + "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/01a446eb68a76c3d0528a190029afa5e6ce5c384", + "reference": "01a446eb68a76c3d0528a190029afa5e6ce5c384", "shasum": "" }, "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "scheb/2fa-bundle": "self.version", "spomky-labs/otphp": "^11.0" }, @@ -9745,28 +9321,28 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-google-authenticator/tree/v7.12.1" + "source": "https://github.com/scheb/2fa-google-authenticator/tree/v7.11.0" }, - "time": "2025-11-20T13:35:24+00:00" + "time": "2025-04-20T08:38:44+00:00" }, { "name": "scheb/2fa-trusted-device", - "version": "v7.12.1", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-trusted-device.git", - "reference": "e1026a977d9cdb794f349b828ab956e9341d7790" + "reference": "6ab98fdee3aa001ca6598eeb422d9abf2c85b5b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/e1026a977d9cdb794f349b828ab956e9341d7790", - "reference": "e1026a977d9cdb794f349b828ab956e9341d7790", + "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/6ab98fdee3aa001ca6598eeb422d9abf2c85b5b3", + "reference": "6ab98fdee3aa001ca6598eeb422d9abf2c85b5b3", "shasum": "" }, "require": { "lcobucci/clock": "^3.0", "lcobucci/jwt": "^5.0", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "scheb/2fa-bundle": "self.version" }, "type": "library", @@ -9796,9 +9372,9 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-trusted-device/tree/v7.12.1" + "source": "https://github.com/scheb/2fa-trusted-device/tree/v7.11.0" }, - "time": "2025-11-20T13:35:24+00:00" + "time": "2025-06-27T12:14:20+00:00" }, { "name": "shivas/versioning-bundle", @@ -9862,21 +9438,21 @@ }, { "name": "spatie/db-dumper", - "version": "3.8.1", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "e974cc7862b8de1bd3b7ea7d4839ba7167acb546" + "reference": "91e1fd4dc000aefc9753cda2da37069fc996baee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/e974cc7862b8de1bd3b7ea7d4839ba7167acb546", - "reference": "e974cc7862b8de1bd3b7ea7d4839ba7167acb546", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/91e1fd4dc000aefc9753cda2da37069fc996baee", + "reference": "91e1fd4dc000aefc9753cda2da37069fc996baee", "shasum": "" }, "require": { "php": "^8.0", - "symfony/process": "^5.0|^6.0|^7.0|^8.0" + "symfony/process": "^5.0|^6.0|^7.0" }, "require-dev": { "pestphp/pest": "^1.22" @@ -9909,7 +9485,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/db-dumper/tree/3.8.1" + "source": "https://github.com/spatie/db-dumper/tree/3.8.0" }, "funding": [ { @@ -9921,32 +9497,44 @@ "type": "github" } ], - "time": "2025-11-26T09:51:23+00:00" + "time": "2025-02-14T15:04:22+00:00" }, { "name": "spomky-labs/cbor-php", - "version": "3.2.2", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/cbor-php.git", - "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0" + "reference": "5404f3e21cbe72f5cf612aa23db2b922fd2f43bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/2a5fb86aacfe1004611370ead6caa2bfc88435d0", - "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/5404f3e21cbe72f5cf612aa23db2b922fd2f43bf", + "reference": "5404f3e21cbe72f5cf612aa23db2b922fd2f43bf", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14", + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13", "ext-mbstring": "*", "php": ">=8.0" }, "require-dev": { + "deptrac/deptrac": "^3.0", + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-json": "*", + "infection/infection": "^0.29", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0|^2.0", + "phpstan/phpstan-beberlei-assert": "^1.0|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.0|^2.0", + "phpstan/phpstan-strict-rules": "^1.0|^2.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0", + "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", - "symfony/error-handler": "^6.4|^7.1|^8.0", - "symfony/var-dumper": "^6.4|^7.1|^8.0" + "symfony/var-dumper": "^6.0|^7.0", + "symplify/easy-coding-standard": "^12.0" }, "suggest": { "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", @@ -9980,7 +9568,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/cbor-php/issues", - "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.2.2" + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.1.1" }, "funding": [ { @@ -9992,7 +9580,7 @@ "type": "patreon" } ], - "time": "2025-11-13T13:00:34+00:00" + "time": "2025-06-13T11:57:55+00:00" }, { "name": "spomky-labs/otphp", @@ -10078,20 +9666,20 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.4.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7" + "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/bf6f55a9d9eb25b7781640221cb54f5c727850d7", - "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae", + "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae", "shasum": "" }, "require": { - "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14", + "brick/math": "^0.10|^0.11|^0.12|^0.13", "ext-mbstring": "*", "php": ">=8.1" }, @@ -10099,7 +9687,7 @@ "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.28|^0.29|^0.31", + "infection/infection": "^0.28|^0.29", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.3|^2.0", "phpstan/phpstan": "^1.8|^2.0", @@ -10109,8 +9697,8 @@ "phpunit/phpunit": "^10.1|^11.0|^12.0", "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/string": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", "symplify/easy-coding-standard": "^12.0" }, "suggest": { @@ -10171,7 +9759,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.0" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0" }, "funding": [ { @@ -10183,7 +9771,7 @@ "type": "patreon" } ], - "time": "2025-10-22T08:24:34+00:00" + "time": "2025-06-13T08:35:04+00:00" }, { "name": "symfony/apache-pack", @@ -10213,16 +9801,16 @@ }, { "name": "symfony/asset", - "version": "v7.4.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "0f7bccb9ffa1f373cbd659774d90629b2773464f" + "reference": "56c4d9f759247c4e07d8549e3baf7493cb9c3e4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/0f7bccb9ffa1f373cbd659774d90629b2773464f", - "reference": "0f7bccb9ffa1f373cbd659774d90629b2773464f", + "url": "https://api.github.com/repos/symfony/asset/zipball/56c4d9f759247c4e07d8549e3baf7493cb9c3e4b", + "reference": "56c4d9f759247c4e07d8549e3baf7493cb9c3e4b", "shasum": "" }, "require": { @@ -10232,9 +9820,9 @@ "symfony/http-foundation": "<6.4" }, "require-dev": { - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0" + "symfony/http-client": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10262,7 +9850,7 @@ "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset/tree/v7.4.0" + "source": "https://github.com/symfony/asset/tree/v7.3.0" }, "funding": [ { @@ -10273,29 +9861,25 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-04T07:05:15+00:00" + "time": "2025-03-05T10:15:41+00:00" }, { "name": "symfony/cache", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "a7a1325a5de2e54ddb45fda002ff528162e48293" + "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/a7a1325a5de2e54ddb45fda002ff528162e48293", - "reference": "a7a1325a5de2e54ddb45fda002ff528162e48293", + "url": "https://api.github.com/repos/symfony/cache/zipball/6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", + "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", "shasum": "" }, "require": { @@ -10303,14 +9887,12 @@ "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^3.6", - "symfony/deprecation-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/var-exporter": "^6.4|^7.0" }, "conflict": { "doctrine/dbal": "<3.6", - "ext-redis": "<6.1", - "ext-relay": "<0.12.1", "symfony/dependency-injection": "<6.4", "symfony/http-kernel": "<6.4", "symfony/var-dumper": "<6.4" @@ -10325,13 +9907,13 @@ "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/filesystem": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10366,7 +9948,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.0" + "source": "https://github.com/symfony/cache/tree/v7.3.2" }, "funding": [ { @@ -10386,7 +9968,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2025-07-30T17:13:41+00:00" }, { "name": "symfony/cache-contracts", @@ -10466,16 +10048,16 @@ }, { "name": "symfony/clock", - "version": "v7.4.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", "shasum": "" }, "require": { @@ -10520,7 +10102,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.4.0" + "source": "https://github.com/symfony/clock/tree/v7.3.0" }, "funding": [ { @@ -10531,35 +10113,31 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/config", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "f76c74e93bce2b9285f2dad7fbd06fa8182a7a41" + "reference": "faef36e271bbeb74a9d733be4b56419b157762e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/f76c74e93bce2b9285f2dad7fbd06fa8182a7a41", - "reference": "f76c74e93bce2b9285f2dad7fbd06fa8182a7a41", + "url": "https://api.github.com/repos/symfony/config/zipball/faef36e271bbeb74a9d733be4b56419b157762e2", + "reference": "faef36e271bbeb74a9d733be4b56419b157762e2", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^7.1|^8.0", + "symfony/filesystem": "^7.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { @@ -10567,11 +10145,11 @@ "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10599,7 +10177,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.0" + "source": "https://github.com/symfony/config/tree/v7.3.2" }, "funding": [ { @@ -10619,20 +10197,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-07-26T13:55:06+00:00" }, { "name": "symfony/console", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8" + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", + "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", "shasum": "" }, "require": { @@ -10640,7 +10218,7 @@ "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" + "symfony/string": "^7.2" }, "conflict": { "symfony/dependency-injection": "<6.4", @@ -10654,16 +10232,16 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10697,7 +10275,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.0" + "source": "https://github.com/symfony/console/tree/v7.3.3" }, "funding": [ { @@ -10717,20 +10295,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", "shasum": "" }, "require": { @@ -10766,7 +10344,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.0" + "source": "https://github.com/symfony/css-selector/tree/v7.3.0" }, "funding": [ { @@ -10777,37 +10355,33 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-10-30T13:39:42+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "3972ca7bbd649467b21a54870721b9e9f3652f9b" + "reference": "ab6c38dad5da9b15b1f7afb2f5c5814112e70261" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/3972ca7bbd649467b21a54870721b9e9f3652f9b", - "reference": "3972ca7bbd649467b21a54870721b9e9f3652f9b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ab6c38dad5da9b15b1f7afb2f5c5814112e70261", + "reference": "ab6c38dad5da9b15b1f7afb2f5c5814112e70261", "shasum": "" }, "require": { "php": ">=8.2", "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^3.6", - "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4.20|^7.2.5" }, "conflict": { "ext-psr": "<1.1|>=2", @@ -10820,9 +10394,9 @@ "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10850,7 +10424,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.0" + "source": "https://github.com/symfony/dependency-injection/tree/v7.3.3" }, "funding": [ { @@ -10870,7 +10444,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-08-14T09:54:27+00:00" }, { "name": "symfony/deprecation-contracts", @@ -10941,16 +10515,16 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "7b511891a81ca14e993b6c88fd35d6bf656085f7" + "reference": "b371ded46da25415e1a3a7422e4acd2ec34214c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/7b511891a81ca14e993b6c88fd35d6bf656085f7", - "reference": "7b511891a81ca14e993b6c88fd35d6bf656085f7", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/b371ded46da25415e1a3a7422e4acd2ec34214c5", + "reference": "b371ded46da25415e1a3a7422e4acd2ec34214c5", "shasum": "" }, "require": { @@ -10977,7 +10551,7 @@ "symfony/property-info": "<6.4", "symfony/security-bundle": "<6.4", "symfony/security-core": "<6.4", - "symfony/validator": "<7.4" + "symfony/validator": "<6.4" }, "require-dev": { "doctrine/collections": "^1.8|^2.0", @@ -10985,24 +10559,24 @@ "doctrine/dbal": "^3.6|^4", "doctrine/orm": "^2.15|^3", "psr/log": "^1|^2|^3", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/doctrine-messenger": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/form": "^7.2|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", - "symfony/type-info": "^7.1.8|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", - "symfony/validator": "^7.4|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/doctrine-messenger": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4.6|^7.0.6", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/type-info": "^7.1.8", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "symfony-bridge", "autoload": { @@ -11030,7 +10604,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v7.4.0" + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.3.3" }, "funding": [ { @@ -11050,31 +10624,30 @@ "type": "tidelift" } ], - "time": "2025-11-04T03:05:49+00:00" + "time": "2025-08-18T13:10:53+00:00" }, { "name": "symfony/dom-crawler", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "8f3e7464fe7e77294686e935956a6a8ccf7442c4" + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8f3e7464fe7e77294686e935956a6a8ccf7442c4", - "reference": "8f3e7464fe7e77294686e935956a6a8ccf7442c4", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/efa076ea0eeff504383ff0dcf827ea5ce15690ba", + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba", "shasum": "" }, "require": { "masterminds/html5": "^2.6", "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/css-selector": "^6.4|^7.0|^8.0" + "symfony/css-selector": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -11102,7 +10675,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.0" + "source": "https://github.com/symfony/dom-crawler/tree/v7.3.3" }, "funding": [ { @@ -11122,20 +10695,20 @@ "type": "tidelift" } ], - "time": "2025-10-31T09:30:03+00:00" + "time": "2025-08-06T20:13:54+00:00" }, { "name": "symfony/dotenv", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "1658a4d34df028f3d93bcdd8e81f04423925a364" + "reference": "2192790a11f9e22cbcf9dc705a3ff22a5503923a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/1658a4d34df028f3d93bcdd8e81f04423925a364", - "reference": "1658a4d34df028f3d93bcdd8e81f04423925a364", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/2192790a11f9e22cbcf9dc705a3ff22a5503923a", + "reference": "2192790a11f9e22cbcf9dc705a3ff22a5503923a", "shasum": "" }, "require": { @@ -11146,8 +10719,8 @@ "symfony/process": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0" + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -11180,7 +10753,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v7.4.0" + "source": "https://github.com/symfony/dotenv/tree/v7.3.2" }, "funding": [ { @@ -11200,37 +10773,36 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2025-07-10T08:29:33+00:00" }, { "name": "symfony/error-handler", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3", "shasum": "" }, "require": { "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/polyfill-php85": "^1.32", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/var-dumper": "^6.4|^7.0" }, "conflict": { "symfony/deprecation-contracts": "<2.5", "symfony/http-kernel": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ @@ -11262,7 +10834,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.0" + "source": "https://github.com/symfony/error-handler/tree/v7.3.2" }, "funding": [ { @@ -11282,20 +10854,20 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:29:59+00:00" + "time": "2025-07-07T08:17:57+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", "shasum": "" }, "require": { @@ -11312,14 +10884,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -11347,7 +10918,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" }, "funding": [ { @@ -11367,7 +10938,7 @@ "type": "tidelift" } ], - "time": "2025-10-28T09:38:46+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -11447,21 +11018,21 @@ }, { "name": "symfony/expression-language", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "8b9bbbb8c71f79a09638f6ea77c531e511139efa" + "reference": "32d2d19c62e58767e6552166c32fb259975d2b23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/8b9bbbb8c71f79a09638f6ea77c531e511139efa", - "reference": "8b9bbbb8c71f79a09638f6ea77c531e511139efa", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/32d2d19c62e58767e6552166c32fb259975d2b23", + "reference": "32d2d19c62e58767e6552166c32fb259975d2b23", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/cache": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^2.5|^3" }, @@ -11491,7 +11062,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v7.4.0" + "source": "https://github.com/symfony/expression-language/tree/v7.3.2" }, "funding": [ { @@ -11511,20 +11082,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2025-07-10T08:29:33+00:00" }, { "name": "symfony/filesystem", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", "shasum": "" }, "require": { @@ -11533,7 +11104,7 @@ "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0|^8.0" + "symfony/process": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -11561,7 +11132,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.2" }, "funding": [ { @@ -11581,27 +11152,27 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-07-07T08:17:47+00:00" }, { "name": "symfony/finder", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0" + "symfony/filesystem": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -11629,7 +11200,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.0" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -11649,36 +11220,35 @@ "type": "tidelift" } ], - "time": "2025-11-05T05:42:40+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/flex", - "version": "v2.10.0", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "9cd384775973eabbf6e8b05784dda279fc67c28d" + "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/9cd384775973eabbf6e8b05784dda279fc67c28d", - "reference": "9cd384775973eabbf6e8b05784dda279fc67c28d", + "url": "https://api.github.com/repos/symfony/flex/zipball/f356aa35f3cf3d2f46c31d344c1098eb2d260426", + "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426", "shasum": "" }, "require": { "composer-plugin-api": "^2.1", - "php": ">=8.1" + "php": ">=8.0" }, "conflict": { - "composer/semver": "<1.7.2", - "symfony/dotenv": "<5.4" + "composer/semver": "<1.7.2" }, "require-dev": { "composer/composer": "^2.1", - "symfony/dotenv": "^6.4|^7.4|^8.0", - "symfony/filesystem": "^6.4|^7.4|^8.0", - "symfony/phpunit-bridge": "^6.4|^7.4|^8.0", - "symfony/process": "^6.4|^7.4|^8.0" + "symfony/dotenv": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" }, "type": "composer-plugin", "extra": { @@ -11702,7 +11272,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v2.10.0" + "source": "https://github.com/symfony/flex/tree/v2.8.2" }, "funding": [ { @@ -11722,31 +11292,31 @@ "type": "tidelift" } ], - "time": "2025-11-16T09:38:19+00:00" + "time": "2025-08-22T07:17:23+00:00" }, { "name": "symfony/form", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "00b8d61709b323749aef317950abd276f309597b" + "reference": "f151b4a027fa67769268b80111f7fdb63edb5e79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/00b8d61709b323749aef317950abd276f309597b", - "reference": "00b8d61709b323749aef317950abd276f309597b", + "url": "https://api.github.com/repos/symfony/form/zipball/f151b4a027fa67769268b80111f7fdb63edb5e79", + "reference": "f151b4a027fa67769268b80111f7fdb63edb5e79", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/options-resolver": "^7.3|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/options-resolver": "^7.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "^1.21", "symfony/polyfill-mbstring": "~1.0", - "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -11756,28 +11326,26 @@ "symfony/error-handler": "<6.4", "symfony/framework-bundle": "<6.4", "symfony/http-kernel": "<6.4", - "symfony/intl": "<7.4", "symfony/translation": "<6.4.3|>=7.0,<7.0.3", "symfony/translation-contracts": "<2.5", "symfony/twig-bridge": "<6.4" }, "require-dev": { "doctrine/collections": "^1.0|^2.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/html-sanitizer": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^7.4|^8.0", - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/security-csrf": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4.3|^7.0.3|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4.12|^7.1.5|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -11805,7 +11373,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v7.4.0" + "source": "https://github.com/symfony/form/tree/v7.3.3" }, "funding": [ { @@ -11825,39 +11393,38 @@ "type": "tidelift" } ], - "time": "2025-11-20T12:20:24+00:00" + "time": "2025-08-18T13:10:53+00:00" }, { "name": "symfony/framework-bundle", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "3c62a3437267ac55bcd40175689c74772250e943" + "reference": "19ec4ab6be90322ed190e041e2404a976ed22571" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/3c62a3437267ac55bcd40175689c74772250e943", - "reference": "3c62a3437267ac55bcd40175689c74772250e943", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/19ec4ab6be90322ed190e041e2404a976ed22571", + "reference": "19ec4ab6be90322ed190e041e2404a976ed22571", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", "php": ">=8.2", - "symfony/cache": "^6.4.12|^7.0|^8.0", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^7.3", + "symfony/dependency-injection": "^7.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^7.3|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/filesystem": "^7.1|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", + "symfony/error-handler": "^7.3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/filesystem": "^7.1", + "symfony/finder": "^6.4|^7.0", + "symfony/http-foundation": "^7.3", + "symfony/http-kernel": "^7.2", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php85": "^1.32", - "symfony/routing": "^7.4|^8.0" + "symfony/routing": "^6.4|^7.0" }, "conflict": { "doctrine/persistence": "<1.3", @@ -11869,12 +11436,14 @@ "symfony/console": "<6.4", "symfony/dom-crawler": "<6.4", "symfony/dotenv": "<6.4", - "symfony/form": "<7.4", + "symfony/form": "<6.4", "symfony/http-client": "<6.4", + "symfony/json-streamer": ">=7.4", "symfony/lock": "<6.4", "symfony/mailer": "<6.4", - "symfony/messenger": "<7.4", + "symfony/messenger": "<6.4", "symfony/mime": "<6.4", + "symfony/object-mapper": ">=7.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", @@ -11889,52 +11458,51 @@ "symfony/validator": "<6.4", "symfony/web-profiler-bundle": "<6.4", "symfony/webhook": "<7.2", - "symfony/workflow": "<7.4" + "symfony/workflow": "<7.3.0-beta2" }, "require-dev": { "doctrine/persistence": "^1.3|^2|^3", "dragonmantank/cron-expression": "^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "seld/jsonlint": "^1.10", - "symfony/asset": "^6.4|^7.0|^8.0", - "symfony/asset-mapper": "^6.4|^7.0|^8.0", - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dom-crawler": "^6.4|^7.0|^8.0", - "symfony/dotenv": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/form": "^7.4|^8.0", - "symfony/html-sanitizer": "^6.4|^7.0|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/json-streamer": "^7.3|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/mailer": "^6.4|^7.0|^8.0", - "symfony/messenger": "^7.4|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/notifier": "^6.4|^7.0|^8.0", - "symfony/object-mapper": "^7.3|^8.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/json-streamer": "7.3.*", + "symfony/lock": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0", + "symfony/object-mapper": "^v7.3.0-beta2", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0", - "symfony/runtime": "^6.4.13|^7.1.6|^8.0", - "symfony/scheduler": "^6.4.4|^7.0.4|^8.0", - "symfony/security-bundle": "^6.4|^7.0|^8.0", - "symfony/semaphore": "^6.4|^7.0|^8.0", - "symfony/serializer": "^7.2.5|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/translation": "^7.3|^8.0", - "symfony/twig-bundle": "^6.4|^7.0|^8.0", - "symfony/type-info": "^7.1.8|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", - "symfony/validator": "^7.4|^8.0", - "symfony/web-link": "^6.4|^7.0|^8.0", - "symfony/webhook": "^7.2|^8.0", - "symfony/workflow": "^7.4|^8.0", - "symfony/yaml": "^7.3|^8.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/scheduler": "^6.4.4|^7.0.4", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/semaphore": "^6.4|^7.0", + "symfony/serializer": "^7.2.5", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^7.3", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/type-info": "^7.1.8", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/webhook": "^7.2", + "symfony/workflow": "^7.3", + "symfony/yaml": "^6.4|^7.0", "twig/twig": "^3.12" }, "type": "symfony-bundle", @@ -11963,7 +11531,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.4.0" + "source": "https://github.com/symfony/framework-bundle/tree/v7.3.3" }, "funding": [ { @@ -11983,20 +11551,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-08-27T07:45:05+00:00" }, { "name": "symfony/http-client", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "ee5e0e0139ab506f6063a230e631bed677c650a4" + "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/ee5e0e0139ab506f6063a230e631bed677c650a4", - "reference": "ee5e0e0139ab506f6063a230e631bed677c650a4", + "url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", + "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", "shasum": "" }, "require": { @@ -12027,13 +11595,12 @@ "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12064,7 +11631,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.0" + "source": "https://github.com/symfony/http-client/tree/v7.3.3" }, "funding": [ { @@ -12084,7 +11651,7 @@ "type": "tidelift" } ], - "time": "2025-11-20T12:32:50+00:00" + "time": "2025-08-27T07:45:05+00:00" }, { "name": "symfony/http-client-contracts", @@ -12166,22 +11733,23 @@ }, { "name": "symfony/http-foundation", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "769c1720b68e964b13b58529c17d4a385c62167b" + "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/769c1720b68e964b13b58529c17d4a385c62167b", - "reference": "769c1720b68e964b13b58529c17d4a385c62167b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00", + "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "^1.1" + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" }, "conflict": { "doctrine/dbal": "<3.6", @@ -12190,13 +11758,13 @@ "require-dev": { "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4.12|^7.1.5|^8.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0" + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/clock": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12224,7 +11792,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.3" }, "funding": [ { @@ -12244,29 +11812,29 @@ "type": "tidelift" } ], - "time": "2025-11-13T08:49:24+00:00" + "time": "2025-08-20T08:04:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "7348193cd384495a755554382e4526f27c456085" + "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7348193cd384495a755554382e4526f27c456085", - "reference": "7348193cd384495a755554382e4526f27c456085", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/72c304de37e1a1cec6d5d12b81187ebd4850a17b", + "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b", "shasum": "" }, "require": { "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^7.3|^8.0", - "symfony/http-foundation": "^7.4|^8.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^7.3", + "symfony/http-foundation": "^7.3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -12276,7 +11844,6 @@ "symfony/console": "<6.4", "symfony/dependency-injection": "<6.4", "symfony/doctrine-bridge": "<6.4", - "symfony/flex": "<2.10", "symfony/form": "<6.4", "symfony/http-client": "<6.4", "symfony/http-client-contracts": "<2.5", @@ -12294,27 +11861,27 @@ }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/dom-crawler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-access": "^7.1|^8.0", - "symfony/routing": "^6.4|^7.0|^8.0", - "symfony/serializer": "^7.1|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", "twig/twig": "^3.12" }, "type": "library", @@ -12343,7 +11910,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.3" }, "funding": [ { @@ -12363,20 +11930,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:38:24+00:00" + "time": "2025-08-29T08:23:45+00:00" }, { "name": "symfony/intl", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "2fa074de6c7faa6b54f2891fc22708f42245ed5c" + "reference": "754d5ad02c889e380efc5a74fa3f6cfe56b7454d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/2fa074de6c7faa6b54f2891fc22708f42245ed5c", - "reference": "2fa074de6c7faa6b54f2891fc22708f42245ed5c", + "url": "https://api.github.com/repos/symfony/intl/zipball/754d5ad02c889e380efc5a74fa3f6cfe56b7454d", + "reference": "754d5ad02c889e380efc5a74fa3f6cfe56b7454d", "shasum": "" }, "require": { @@ -12387,8 +11954,8 @@ "symfony/string": "<7.1" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/filesystem": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12433,7 +12000,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v7.4.0" + "source": "https://github.com/symfony/intl/tree/v7.3.3" }, "funding": [ { @@ -12453,20 +12020,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-08-19T14:06:46+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd" + "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", + "url": "https://api.github.com/repos/symfony/mailer/zipball/a32f3f45f1990db8c4341d5122a7d3a381c7e575", + "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575", "shasum": "" }, "require": { @@ -12474,8 +12041,8 @@ "php": ">=8.2", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/mime": "^7.2|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^7.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -12486,10 +12053,10 @@ "symfony/twig-bridge": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/twig-bridge": "^6.4|^7.0|^8.0" + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12517,7 +12084,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.0" + "source": "https://github.com/symfony/mailer/tree/v7.3.3" }, "funding": [ { @@ -12537,25 +12104,24 @@ "type": "tidelift" } ], - "time": "2025-11-21T15:26:00+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/mime", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", + "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -12570,11 +12136,11 @@ "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4.3|^7.0.3|^8.0" + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", "autoload": { @@ -12606,7 +12172,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.0" + "source": "https://github.com/symfony/mime/tree/v7.3.2" }, "funding": [ { @@ -12626,27 +12192,26 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "189d16466ff83d9c51fad26382bf0beeb41bda21" + "reference": "6f3745e887659b46a8b7bb5ade8356a41700f095" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/189d16466ff83d9c51fad26382bf0beeb41bda21", - "reference": "189d16466ff83d9c51fad26382bf0beeb41bda21", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/6f3745e887659b46a8b7bb5ade8356a41700f095", + "reference": "6f3745e887659b46a8b7bb5ade8356a41700f095", "shasum": "" }, "require": { "monolog/monolog": "^3", "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -12655,13 +12220,13 @@ "symfony/security-core": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/mailer": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "symfony-bridge", "autoload": { @@ -12689,7 +12254,7 @@ "description": "Provides integration for Monolog with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v7.4.0" + "source": "https://github.com/symfony/monolog-bridge/tree/v7.3.3" }, "funding": [ { @@ -12709,43 +12274,48 @@ "type": "tidelift" } ], - "time": "2025-11-01T09:17:33+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/monolog-bundle", - "version": "v3.11.0", + "version": "v3.10.0", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bundle.git", - "reference": "e12eb92655b234cd50c21cda648088847a7ec777" + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/e12eb92655b234cd50c21cda648088847a7ec777", - "reference": "e12eb92655b234cd50c21cda648088847a7ec777", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", "shasum": "" }, "require": { - "composer-runtime-api": "^2.0", "monolog/monolog": "^1.25.1 || ^2.0 || ^3.0", - "php": ">=8.1", - "symfony/config": "^6.4 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0", - "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/monolog-bridge": "^6.4 || ^7.0", - "symfony/polyfill-php84": "^1.30" + "php": ">=7.2.5", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "symfony/console": "^6.4 || ^7.0", - "symfony/phpunit-bridge": "^7.3.3", - "symfony/yaml": "^6.4 || ^7.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^6.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Bundle\\MonologBundle\\": "src" - } + "Symfony\\Bundle\\MonologBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -12769,7 +12339,7 @@ ], "support": { "issues": "https://github.com/symfony/monolog-bundle/issues", - "source": "https://github.com/symfony/monolog-bundle/tree/v3.11.0" + "source": "https://github.com/symfony/monolog-bundle/tree/v3.10.0" }, "funding": [ { @@ -12780,29 +12350,25 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-11-27T09:16:19+00:00" + "time": "2023-11-06T17:08:13+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "b38026df55197f9e39a44f3215788edf83187b80" + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80", - "reference": "b38026df55197f9e39a44f3215788edf83187b80", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", "shasum": "" }, "require": { @@ -12840,7 +12406,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.4.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" }, "funding": [ { @@ -12860,20 +12426,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2025-08-05T10:16:07+00:00" }, { "name": "symfony/password-hasher", - "version": "v7.4.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "aa075ce6f54fe931f03c1e382597912f4fd94e1e" + "reference": "31fbe66af859582a20b803f38be96be8accdf2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/aa075ce6f54fe931f03c1e382597912f4fd94e1e", - "reference": "aa075ce6f54fe931f03c1e382597912f4fd94e1e", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/31fbe66af859582a20b803f38be96be8accdf2c3", + "reference": "31fbe66af859582a20b803f38be96be8accdf2c3", "shasum": "" }, "require": { @@ -12883,8 +12449,8 @@ "symfony/security-core": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/security-core": "^6.4|^7.0|^8.0" + "symfony/console": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12916,7 +12482,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v7.4.0" + "source": "https://github.com/symfony/password-hasher/tree/v7.3.0" }, "funding": [ { @@ -12927,16 +12493,12 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-13T16:46:49+00:00" + "time": "2025-02-04T08:22:58+00:00" }, { "name": "symfony/polyfill-ctype", @@ -13363,6 +12925,255 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php82", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "5d2ed36f7734637dacc025f179698031951b1692" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", + "reference": "5d2ed36f7734637dacc025f179698031951b1692", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-php83", "version": "v1.33.0", @@ -13523,86 +13334,6 @@ ], "time": "2025-06-24T13:30:11+00:00" }, - { - "name": "symfony/polyfill-php85", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php85\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-06-23T16:12:55+00:00" - }, { "name": "symfony/polyfill-uuid", "version": "v1.33.0", @@ -13688,16 +13419,16 @@ }, { "name": "symfony/process", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8" + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", + "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", "shasum": "" }, "require": { @@ -13729,7 +13460,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.0" + "source": "https://github.com/symfony/process/tree/v7.3.3" }, "funding": [ { @@ -13749,29 +13480,28 @@ "type": "tidelift" } ], - "time": "2025-10-16T11:21:06+00:00" + "time": "2025-08-18T09:42:54+00:00" }, { "name": "symfony/property-access", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "537626149d2910ca43eb9ce465654366bf4442f4" + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/537626149d2910ca43eb9ce465654366bf4442f4", - "reference": "537626149d2910ca43eb9ce465654366bf4442f4", + "url": "https://api.github.com/repos/symfony/property-access/zipball/4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/property-info": "^6.4|^7.0|^8.0" + "symfony/property-info": "^6.4|^7.0" }, "require-dev": { - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" + "symfony/cache": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -13810,7 +13540,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.4.0" + "source": "https://github.com/symfony/property-access/tree/v7.3.3" }, "funding": [ { @@ -13830,27 +13560,27 @@ "type": "tidelift" } ], - "time": "2025-09-08T21:14:32+00:00" + "time": "2025-08-04T15:15:28+00:00" }, { "name": "symfony/property-info", - "version": "v7.4.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "c3c686e3d3a33a99f6967e69d6d5832acb7c25a1" + "reference": "90586acbf2a6dd13bee4f09f09111c8bd4773970" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/c3c686e3d3a33a99f6967e69d6d5832acb7c25a1", - "reference": "c3c686e3d3a33a99f6967e69d6d5832acb7c25a1", + "url": "https://api.github.com/repos/symfony/property-info/zipball/90586acbf2a6dd13bee4f09f09111c8bd4773970", + "reference": "90586acbf2a6dd13bee4f09f09111c8bd4773970", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/type-info": "^7.3.5|^8.0" + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "~7.2.8|^7.3.1" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2", @@ -13862,9 +13592,9 @@ "require-dev": { "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0" + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -13900,7 +13630,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.4.0" + "source": "https://github.com/symfony/property-info/tree/v7.3.1" }, "funding": [ { @@ -13911,35 +13641,31 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-11-13T08:38:49+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v7.4.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "0101ff8bd0506703b045b1670960302d302a726c" + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/0101ff8bd0506703b045b1670960302d302a726c", - "reference": "0101ff8bd0506703b045b1670960302d302a726c", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", "shasum": "" }, "require": { "php": ">=8.2", "psr/http-message": "^1.0|^2.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0" + "symfony/http-foundation": "^6.4|^7.0" }, "conflict": { "php-http/discovery": "<1.15", @@ -13949,12 +13675,11 @@ "nyholm/psr7": "^1.1", "php-http/discovery": "^1.15", "psr/log": "^1.1.4|^2|^3", - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", - "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", - "symfony/runtime": "^6.4.13|^7.1.6|^8.0" + "symfony/browser-kit": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "symfony-bridge", "autoload": { @@ -13988,7 +13713,7 @@ "psr-7" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.0" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.3.0" }, "funding": [ { @@ -13999,38 +13724,34 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-11-13T08:38:49+00:00" + "time": "2024-09-26T08:57:56+00:00" }, { "name": "symfony/rate-limiter", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/rate-limiter.git", - "reference": "5c6df5bc10308505bb0fa8d1388bc6bd8a628ba8" + "reference": "7e855541d302ba752f86fb0e97932e7969fe9c04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/5c6df5bc10308505bb0fa8d1388bc6bd8a628ba8", - "reference": "5c6df5bc10308505bb0fa8d1388bc6bd8a628ba8", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/7e855541d302ba752f86fb0e97932e7969fe9c04", + "reference": "7e855541d302ba752f86fb0e97932e7969fe9c04", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/options-resolver": "^7.3|^8.0" + "symfony/options-resolver": "^7.3" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/lock": "^6.4|^7.0|^8.0" + "symfony/lock": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -14062,7 +13783,7 @@ "rate-limiter" ], "support": { - "source": "https://github.com/symfony/rate-limiter/tree/v7.4.0" + "source": "https://github.com/symfony/rate-limiter/tree/v7.3.2" }, "funding": [ { @@ -14082,20 +13803,20 @@ "type": "tidelift" } ], - "time": "2025-08-04T07:05:15+00:00" + "time": "2025-07-07T08:17:57+00:00" }, { "name": "symfony/routing", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "4720254cb2644a0b876233d258a32bf017330db7" + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/4720254cb2644a0b876233d258a32bf017330db7", - "reference": "4720254cb2644a0b876233d258a32bf017330db7", + "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", "shasum": "" }, "require": { @@ -14109,11 +13830,11 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -14147,7 +13868,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.0" + "source": "https://github.com/symfony/routing/tree/v7.3.2" }, "funding": [ { @@ -14167,20 +13888,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/runtime", - "version": "v7.4.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "e3dd6c0f46a6810b3245726e8452cee45754e628" + "reference": "9516056d432f8acdac9458eb41b80097da7a05c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/e3dd6c0f46a6810b3245726e8452cee45754e628", - "reference": "e3dd6c0f46a6810b3245726e8452cee45754e628", + "url": "https://api.github.com/repos/symfony/runtime/zipball/9516056d432f8acdac9458eb41b80097da7a05c9", + "reference": "9516056d432f8acdac9458eb41b80097da7a05c9", "shasum": "" }, "require": { @@ -14192,10 +13913,10 @@ }, "require-dev": { "composer/composer": "^2.6", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dotenv": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0" + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "composer-plugin", "extra": { @@ -14230,7 +13951,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v7.4.0" + "source": "https://github.com/symfony/runtime/tree/v7.3.1" }, "funding": [ { @@ -14241,46 +13962,41 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-11-04T03:05:49+00:00" + "time": "2025-06-13T07:48:40+00:00" }, { "name": "symfony/security-bundle", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "48a64e746857464a5e8fd7bab84b31c9ba967eb9" + "reference": "fbecca9a10af8d886e116f74e860e19b7583689c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/48a64e746857464a5e8fd7bab84b31c9ba967eb9", - "reference": "48a64e746857464a5e8fd7bab84b31c9ba967eb9", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/fbecca9a10af8d886e116f74e860e19b7583689c", + "reference": "fbecca9a10af8d886e116f74e860e19b7583689c", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", "php": ">=8.2", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^6.4.11|^7.1.4|^8.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", - "symfony/password-hasher": "^6.4|^7.0|^8.0", - "symfony/security-core": "^7.4|^8.0", - "symfony/security-csrf": "^6.4|^7.0|^8.0", - "symfony/security-http": "^7.4|^8.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^7.3", + "symfony/dependency-injection": "^6.4.11|^7.1.4", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/password-hasher": "^6.4|^7.0", + "symfony/security-core": "^7.3", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^7.3", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -14294,26 +14010,25 @@ "symfony/validator": "<6.4" }, "require-dev": { - "symfony/asset": "^6.4|^7.0|^8.0", - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dom-crawler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/form": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/ldap": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0", - "symfony/runtime": "^6.4.13|^7.1.6|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", - "symfony/twig-bridge": "^6.4|^7.0|^8.0", - "symfony/twig-bundle": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0", - "twig/twig": "^3.15", + "symfony/asset": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.12", "web-token/jwt-library": "^3.3.2|^4.0" }, "type": "symfony-bundle", @@ -14342,7 +14057,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v7.4.0" + "source": "https://github.com/symfony/security-bundle/tree/v7.3.3" }, "funding": [ { @@ -14362,27 +14077,27 @@ "type": "tidelift" } ], - "time": "2025-11-14T09:57:20+00:00" + "time": "2025-08-06T08:34:58+00:00" }, { "name": "symfony/security-core", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "fe4d25e5700a2f3b605bf23f520be57504ae5c51" + "reference": "4465a3b9cefbaebaeeeb98c2becfdb4b59d22488" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/fe4d25e5700a2f3b605bf23f520be57504ae5c51", - "reference": "fe4d25e5700a2f3b605bf23f520be57504ae5c51", + "url": "https://api.github.com/repos/symfony/security-core/zipball/4465a3b9cefbaebaeeeb98c2becfdb4b59d22488", + "reference": "4465a3b9cefbaebaeeeb98c2becfdb4b59d22488", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher-contracts": "^2.5|^3", - "symfony/password-hasher": "^6.4|^7.0|^8.0", + "symfony/password-hasher": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -14397,15 +14112,15 @@ "psr/cache": "^1.0|^2.0|^3.0", "psr/container": "^1.1|^2.0", "psr/log": "^1|^2|^3", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/ldap": "^6.4|^7.0|^8.0", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4.3|^7.0.3|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0" + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/validator": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -14433,7 +14148,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.4.0" + "source": "https://github.com/symfony/security-core/tree/v7.3.3" }, "funding": [ { @@ -14453,33 +14168,33 @@ "type": "tidelift" } ], - "time": "2025-11-21T15:26:00+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/security-csrf", - "version": "v7.4.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "ec41009e83589d0b3d86bd131d07e6fc8ecf35ab" + "reference": "2b4b0c46c901729e4e90719eacd980381f53e0a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/ec41009e83589d0b3d86bd131d07e6fc8ecf35ab", - "reference": "ec41009e83589d0b3d86bd131d07e6fc8ecf35ab", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/2b4b0c46c901729e4e90719eacd980381f53e0a3", + "reference": "2b4b0c46c901729e4e90719eacd980381f53e0a3", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/security-core": "^6.4|^7.0|^8.0" + "symfony/security-core": "^6.4|^7.0" }, "conflict": { "symfony/http-foundation": "<6.4" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0" + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -14507,7 +14222,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v7.4.0" + "source": "https://github.com/symfony/security-csrf/tree/v7.3.0" }, "funding": [ { @@ -14518,59 +14233,55 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-11-21T15:26:00+00:00" + "time": "2025-01-02T18:42:10+00:00" }, { "name": "symfony/security-http", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "92f9cc6494f3d29042ac35c2ee5209191bbbb781" + "reference": "1bf0dc10f27d4776c47f18f98236c619793a9260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/92f9cc6494f3d29042ac35c2ee5209191bbbb781", - "reference": "92f9cc6494f3d29042ac35c2ee5209191bbbb781", + "url": "https://api.github.com/repos/symfony/security-http/zipball/1bf0dc10f27d4776c47f18f98236c619793a9260", + "reference": "1bf0dc10f27d4776c47f18f98236c619793a9260", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/security-core": "^7.3|^8.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/security-core": "^7.3", "symfony/service-contracts": "^2.5|^3" }, "conflict": { "symfony/clock": "<6.4", + "symfony/event-dispatcher": "<6.4", "symfony/http-client-contracts": "<3.0", "symfony/security-bundle": "<6.4", "symfony/security-csrf": "<6.4" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/cache": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", "symfony/http-client-contracts": "^3.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0", - "symfony/routing": "^6.4|^7.0|^8.0", - "symfony/security-csrf": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", "web-token/jwt-library": "^3.3.2|^4.0" }, "type": "library", @@ -14599,7 +14310,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v7.4.0" + "source": "https://github.com/symfony/security-http/tree/v7.3.3" }, "funding": [ { @@ -14619,20 +14330,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/serializer", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "5a3bbf317b3f1025126b6d9debce53515601ab43" + "reference": "5608b04d8daaf29432d76ecc618b0fac169c2dfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/5a3bbf317b3f1025126b6d9debce53515601ab43", - "reference": "5a3bbf317b3f1025126b6d9debce53515601ab43", + "url": "https://api.github.com/repos/symfony/serializer/zipball/5608b04d8daaf29432d76ecc618b0fac169c2dfb", + "reference": "5608b04d8daaf29432d76ecc618b0fac169c2dfb", "shasum": "" }, "require": { @@ -14655,26 +14366,26 @@ "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^7.2|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/filesystem": "^6.4|^7.0|^8.0", - "symfony/form": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", + "symfony/error-handler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.1.8|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/type-info": "^7.1.8", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -14702,7 +14413,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.4.0" + "source": "https://github.com/symfony/serializer/tree/v7.3.3" }, "funding": [ { @@ -14722,20 +14433,20 @@ "type": "tidelift" } ], - "time": "2025-11-18T13:23:20+00:00" + "time": "2025-08-27T11:34:33+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -14789,7 +14500,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -14800,29 +14511,25 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/stimulus-bundle", - "version": "v2.31.0", + "version": "v2.30.0", "source": { "type": "git", "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "c5ea8ee2ccd45447b7f4b6b82f704ee5e76127f0" + "reference": "668b9efe9d0ab8b4e50091263171609e0459c0c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/c5ea8ee2ccd45447b7f4b6b82f704ee5e76127f0", - "reference": "c5ea8ee2ccd45447b7f4b6b82f704ee5e76127f0", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/668b9efe9d0ab8b4e50091263171609e0459c0c8", + "reference": "668b9efe9d0ab8b4e50091263171609e0459c0c8", "shasum": "" }, "require": { @@ -14862,7 +14569,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.31.0" + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.30.0" }, "funding": [ { @@ -14882,20 +14589,20 @@ "type": "tidelift" } ], - "time": "2025-09-24T13:27:42+00:00" + "time": "2025-08-27T15:25:48+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.4.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "8a24af0a2e8a872fb745047180649b8418303084" + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8a24af0a2e8a872fb745047180649b8418303084", - "reference": "8a24af0a2e8a872fb745047180649b8418303084", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", "shasum": "" }, "require": { @@ -14928,7 +14635,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.4.0" + "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" }, "funding": [ { @@ -14939,36 +14646,31 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-04T07:05:15+00:00" + "time": "2025-02-24T10:49:57+00:00" }, { "name": "symfony/string", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", + "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -14976,11 +14678,12 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -15019,7 +14722,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.0" + "source": "https://github.com/symfony/string/tree/v7.3.3" }, "funding": [ { @@ -15039,27 +14742,27 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/translation", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68" + "reference": "e0837b4cbcef63c754d89a4806575cada743a38d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", + "url": "https://api.github.com/repos/symfony/translation/zipball/e0837b4cbcef63c754d89a4806575cada743a38d", + "reference": "e0837b4cbcef63c754d89a4806575cada743a38d", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5.3|^3.3" + "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { "nikic/php-parser": "<5.0", @@ -15078,17 +14781,17 @@ "require-dev": { "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/routing": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -15119,7 +14822,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.0" + "source": "https://github.com/symfony/translation/tree/v7.3.3" }, "funding": [ { @@ -15139,20 +14842,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-08-01T21:02:37+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { @@ -15201,7 +14904,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -15212,29 +14915,25 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { "name": "symfony/twig-bridge", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "e96998da928007554b8b8c02e677861877daced9" + "reference": "33558f013b7f6ed72805527c8405cae0062e47c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/e96998da928007554b8b8c02e677861877daced9", - "reference": "e96998da928007554b8b8c02e677861877daced9", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/33558f013b7f6ed72805527c8405cae0062e47c5", + "reference": "33558f013b7f6ed72805527c8405cae0062e47c5", "shasum": "" }, "require": { @@ -15259,33 +14958,33 @@ "egulias/email-validator": "^2.1.10|^3|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^6.4|^7.0|^8.0", - "symfony/asset-mapper": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/emoji": "^7.1|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/form": "^6.4.20|^7.2.5|^8.0", - "symfony/html-sanitizer": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^7.3|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/emoji": "^7.1", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4.20|^7.2.5", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^7.3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/security-csrf": "^6.4|^7.0|^8.0", - "symfony/security-http": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4.3|^7.0.3|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0", - "symfony/web-link": "^6.4|^7.0|^8.0", - "symfony/workflow": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", "twig/cssinliner-extra": "^3", "twig/inky-extra": "^3", "twig/markdown-extra": "^3" @@ -15316,7 +15015,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.4.0" + "source": "https://github.com/symfony/twig-bridge/tree/v7.3.3" }, "funding": [ { @@ -15336,30 +15035,30 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:29:59+00:00" + "time": "2025-08-18T13:10:53+00:00" }, { "name": "symfony/twig-bundle", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "f83f530d00d1bbc6f7fafeb433077887c83326ef" + "reference": "5d85220df4d8d79e6a9ca57eea6f70004de39657" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/f83f530d00d1bbc6f7fafeb433077887c83326ef", - "reference": "f83f530d00d1bbc6f7fafeb433077887c83326ef", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/5d85220df4d8d79e6a9ca57eea6f70004de39657", + "reference": "5d85220df4d8d79e6a9ca57eea6f70004de39657", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "php": ">=8.2", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", - "symfony/twig-bridge": "^7.3|^8.0", + "symfony/config": "^7.3", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^7.3", "twig/twig": "^3.12" }, "conflict": { @@ -15367,17 +15066,16 @@ "symfony/translation": "<6.4" }, "require-dev": { - "symfony/asset": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/form": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", - "symfony/routing": "^6.4|^7.0|^8.0", - "symfony/runtime": "^6.4.13|^7.1.6", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", - "symfony/web-link": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/asset": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -15405,7 +15103,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v7.4.0" + "source": "https://github.com/symfony/twig-bundle/tree/v7.3.2" }, "funding": [ { @@ -15425,20 +15123,20 @@ "type": "tidelift" } ], - "time": "2025-10-02T07:41:02+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/type-info", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "7f9743e921abcce92a03fc693530209c59e73076" + "reference": "aa64b58ed04517d4d730202dd035895743c23273" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/7f9743e921abcce92a03fc693530209c59e73076", - "reference": "7f9743e921abcce92a03fc693530209c59e73076", + "url": "https://api.github.com/repos/symfony/type-info/zipball/aa64b58ed04517d4d730202dd035895743c23273", + "reference": "aa64b58ed04517d4d730202dd035895743c23273", "shasum": "" }, "require": { @@ -15488,7 +15186,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.4.0" + "source": "https://github.com/symfony/type-info/tree/v7.3.3" }, "funding": [ { @@ -15508,20 +15206,20 @@ "type": "tidelift" } ], - "time": "2025-11-07T09:36:46+00:00" + "time": "2025-08-28T09:38:04+00:00" }, { "name": "symfony/uid", - "version": "v7.4.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "2498e9f81b7baa206f44de583f2f48350b90142c" + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c", - "reference": "2498e9f81b7baa206f44de583f2f48350b90142c", + "url": "https://api.github.com/repos/symfony/uid/zipball/a69f69f3159b852651a6bf45a9fdd149520525bb", + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb", "shasum": "" }, "require": { @@ -15529,7 +15227,7 @@ "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0" + "symfony/console": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -15566,7 +15264,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.0" + "source": "https://github.com/symfony/uid/tree/v7.3.1" }, "funding": [ { @@ -15577,29 +15275,25 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-09-25T11:02:55+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/ux-translator", - "version": "v2.31.0", + "version": "v2.30.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-translator.git", - "reference": "b4b323fdc846d2d67feb7f8ca5ef5a05238f6639" + "reference": "9616091db206df4caa7d8dce2e48941512b1a94a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-translator/zipball/b4b323fdc846d2d67feb7f8ca5ef5a05238f6639", - "reference": "b4b323fdc846d2d67feb7f8ca5ef5a05238f6639", + "url": "https://api.github.com/repos/symfony/ux-translator/zipball/9616091db206df4caa7d8dce2e48941512b1a94a", + "reference": "9616091db206df4caa7d8dce2e48941512b1a94a", "shasum": "" }, "require": { @@ -15647,7 +15341,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/ux-translator/tree/v2.31.0" + "source": "https://github.com/symfony/ux-translator/tree/v2.30.0" }, "funding": [ { @@ -15667,20 +15361,20 @@ "type": "tidelift" } ], - "time": "2025-10-16T07:24:06+00:00" + "time": "2025-08-27T15:25:48+00:00" }, { "name": "symfony/ux-turbo", - "version": "v2.31.0", + "version": "v2.30.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "06d5e4cf4573efe4faf648f3810a28c63684c706" + "reference": "c5e88c7e16713e84a2a35f36276ccdb05c2c78d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/06d5e4cf4573efe4faf648f3810a28c63684c706", - "reference": "06d5e4cf4573efe4faf648f3810a28c63684c706", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/c5e88c7e16713e84a2a35f36276ccdb05c2c78d8", + "reference": "c5e88c7e16713e84a2a35f36276ccdb05c2c78d8", "shasum": "" }, "require": { @@ -15693,7 +15387,7 @@ "require-dev": { "dbrekelmans/bdi": "dev-main", "doctrine/doctrine-bundle": "^2.4.3", - "doctrine/orm": "^2.8|^3.0", + "doctrine/orm": "^2.8 | 3.0", "php-webdriver/webdriver": "^1.15", "phpstan/phpstan": "^2.1.17", "symfony/asset-mapper": "^6.4|^7.0|^8.0", @@ -15750,7 +15444,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.31.0" + "source": "https://github.com/symfony/ux-turbo/tree/v2.30.0" }, "funding": [ { @@ -15770,20 +15464,20 @@ "type": "tidelift" } ], - "time": "2025-10-16T07:24:06+00:00" + "time": "2025-08-27T15:25:48+00:00" }, { "name": "symfony/validator", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "829d4acbecc6a9c097ca9cb118d7f96f46d33da9" + "reference": "a2f26d7c122393db75a2d41435ad8251250f8bc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/829d4acbecc6a9c097ca9cb118d7f96f46d33da9", - "reference": "829d4acbecc6a9c097ca9cb118d7f96f46d33da9", + "url": "https://api.github.com/repos/symfony/validator/zipball/a2f26d7c122393db75a2d41435ad8251250f8bc6", + "reference": "a2f26d7c122393db75a2d41435ad8251250f8bc6", "shasum": "" }, "require": { @@ -15803,29 +15497,27 @@ "symfony/intl": "<6.4", "symfony/property-info": "<6.4", "symfony/translation": "<6.4.3|>=7.0,<7.0.3", - "symfony/var-exporter": "<6.4.25|>=7.0,<7.3.3", "symfony/yaml": "<6.4" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4.3|^7.0.3|^8.0", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", "symfony/type-info": "^7.1.8", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -15854,7 +15546,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.4.0" + "source": "https://github.com/symfony/validator/tree/v7.3.3" }, "funding": [ { @@ -15874,20 +15566,20 @@ "type": "tidelift" } ], - "time": "2025-11-18T13:23:20+00:00" + "time": "2025-08-27T11:34:33+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece" + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41fd6c4ae28c38b294b42af6db61446594a0dece", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", "shasum": "" }, "require": { @@ -15899,10 +15591,10 @@ "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", "twig/twig": "^3.12" }, "bin": [ @@ -15941,7 +15633,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.3" }, "funding": [ { @@ -15961,20 +15653,20 @@ "type": "tidelift" } ], - "time": "2025-10-27T20:36:44+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "03a60f169c79a28513a78c967316fbc8bf17816f" + "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/03a60f169c79a28513a78c967316fbc8bf17816f", - "reference": "03a60f169c79a28513a78c967316fbc8bf17816f", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", + "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", "shasum": "" }, "require": { @@ -15982,9 +15674,9 @@ "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -16022,7 +15714,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.4.0" + "source": "https://github.com/symfony/var-exporter/tree/v7.3.3" }, "funding": [ { @@ -16042,20 +15734,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:15:23+00:00" + "time": "2025-08-18T13:10:53+00:00" }, { "name": "symfony/web-link", - "version": "v7.4.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/web-link.git", - "reference": "c62edd6b52e31cf2f6f38fd3386725f364f19942" + "reference": "7697f74fce67555665339423ce453cc8216a98ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-link/zipball/c62edd6b52e31cf2f6f38fd3386725f364f19942", - "reference": "c62edd6b52e31cf2f6f38fd3386725f364f19942", + "url": "https://api.github.com/repos/symfony/web-link/zipball/7697f74fce67555665339423ce453cc8216a98ff", + "reference": "7697f74fce67555665339423ce453cc8216a98ff", "shasum": "" }, "require": { @@ -16069,7 +15761,7 @@ "psr/link-implementation": "1.0|2.0" }, "require-dev": { - "symfony/http-kernel": "^6.4|^7.0|^8.0" + "symfony/http-kernel": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -16109,7 +15801,7 @@ "push" ], "support": { - "source": "https://github.com/symfony/web-link/tree/v7.4.0" + "source": "https://github.com/symfony/web-link/tree/v7.3.0" }, "funding": [ { @@ -16120,29 +15812,25 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-04T07:05:15+00:00" + "time": "2025-05-19T13:28:18+00:00" }, { "name": "symfony/webpack-encore-bundle", - "version": "v2.4.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/symfony/webpack-encore-bundle.git", - "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e" + "reference": "7ae70d44c24c3b913f308af8396169b5c6d9e0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e", - "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/7ae70d44c24c3b913f308af8396169b5c6d9e0f5", + "reference": "7ae70d44c24c3b913f308af8396169b5c6d9e0f5", "shasum": "" }, "require": { @@ -16185,7 +15873,7 @@ "description": "Integration of your Symfony app with Webpack Encore", "support": { "issues": "https://github.com/symfony/webpack-encore-bundle/issues", - "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.4.0" + "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.3.0" }, "funding": [ { @@ -16205,32 +15893,32 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:41:46+00:00" + "time": "2025-08-05T11:43:32+00:00" }, { "name": "symfony/yaml", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810" + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6c84a4b55aee4cd02034d1c528e83f69ddf63810", - "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0" + "symfony/console": "^6.4|^7.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -16261,7 +15949,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.0" + "source": "https://github.com/symfony/yaml/tree/v7.3.3" }, "funding": [ { @@ -16281,20 +15969,20 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2025-08-27T11:34:33+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "12.6.2", + "version": "12.5.24", "source": { "type": "git", "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "7a6798aa424f0ecafb1542b6f5207c5a99704d3d" + "reference": "4b90f2b6efed9508000968eac2397ac7aff34354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/7a6798aa424f0ecafb1542b6f5207c5a99704d3d", - "reference": "7a6798aa424f0ecafb1542b6f5207c5a99704d3d", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/4b90f2b6efed9508000968eac2397ac7aff34354", + "reference": "4b90f2b6efed9508000968eac2397ac7aff34354", "shasum": "" }, "require": { @@ -16330,7 +16018,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.6.2" + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.5.24" }, "funding": [ { @@ -16342,20 +16030,20 @@ "type": "github" } ], - "time": "2025-10-29T08:51:50+00:00" + "time": "2025-08-21T06:57:14+00:00" }, { "name": "tecnickcom/tc-lib-barcode", - "version": "2.4.11", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-barcode.git", - "reference": "c6d1060abaa9b540d7cd86ced827653196541e84" + "reference": "f238ffd120d98a34df6573590e7ed02f766a91c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/c6d1060abaa9b540d7cd86ced827653196541e84", - "reference": "c6d1060abaa9b540d7cd86ced827653196541e84", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/f238ffd120d98a34df6573590e7ed02f766a91c4", + "reference": "f238ffd120d98a34df6573590e7ed02f766a91c4", "shasum": "" }, "require": { @@ -16369,8 +16057,8 @@ "require-dev": { "pdepend/pdepend": "2.16.2", "phpmd/phpmd": "2.15.0", - "phpunit/phpunit": "12.4.4 || 11.5.44 || 10.5.58", - "squizlabs/php_codesniffer": "4.0.1" + "phpunit/phpunit": "12.2.0 || 11.5.7 || 10.5.40", + "squizlabs/php_codesniffer": "3.13.0" }, "type": "library", "autoload": { @@ -16434,7 +16122,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/tc-lib-barcode/issues", - "source": "https://github.com/tecnickcom/tc-lib-barcode/tree/2.4.11" + "source": "https://github.com/tecnickcom/tc-lib-barcode/tree/2.4.8" }, "funding": [ { @@ -16442,20 +16130,20 @@ "type": "custom" } ], - "time": "2025-11-28T18:43:32+00:00" + "time": "2025-06-06T11:35:02+00:00" }, { "name": "tecnickcom/tc-lib-color", - "version": "2.2.16", + "version": "2.2.13", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-color.git", - "reference": "f11b2fd7f72ac9d49642a7af2ec854dd09a76b62" + "reference": "85d1366fb33813aa521d30e3d7c7d7d82a8103a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/f11b2fd7f72ac9d49642a7af2ec854dd09a76b62", - "reference": "f11b2fd7f72ac9d49642a7af2ec854dd09a76b62", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/85d1366fb33813aa521d30e3d7c7d7d82a8103a6", + "reference": "85d1366fb33813aa521d30e3d7c7d7d82a8103a6", "shasum": "" }, "require": { @@ -16465,8 +16153,8 @@ "require-dev": { "pdepend/pdepend": "2.16.2", "phpmd/phpmd": "2.15.0", - "phpunit/phpunit": "12.4.4 || 11.5.44 || 10.5.58", - "squizlabs/php_codesniffer": "4.0.1" + "phpunit/phpunit": "12.2.0 || 11.5.7 || 10.5.40", + "squizlabs/php_codesniffer": "3.13.0" }, "type": "library", "autoload": { @@ -16503,7 +16191,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/tc-lib-color/issues", - "source": "https://github.com/tecnickcom/tc-lib-color/tree/2.2.16" + "source": "https://github.com/tecnickcom/tc-lib-color/tree/2.2.13" }, "funding": [ { @@ -16511,7 +16199,7 @@ "type": "custom" } ], - "time": "2025-11-28T18:42:01+00:00" + "time": "2025-06-06T11:33:19+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -16570,16 +16258,16 @@ }, { "name": "twig/cssinliner-extra", - "version": "v3.22.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/cssinliner-extra.git", - "reference": "9bcbf04ca515e98fcde479fdceaa1d9d9e76173e" + "reference": "378d29b61d6406c456e3a4afbd15bbeea0b72ea8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/9bcbf04ca515e98fcde479fdceaa1d9d9e76173e", - "reference": "9bcbf04ca515e98fcde479fdceaa1d9d9e76173e", + "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/378d29b61d6406c456e3a4afbd15bbeea0b72ea8", + "reference": "378d29b61d6406c456e3a4afbd15bbeea0b72ea8", "shasum": "" }, "require": { @@ -16623,7 +16311,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.22.0" + "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.21.0" }, "funding": [ { @@ -16635,30 +16323,30 @@ "type": "tidelift" } ], - "time": "2025-07-29T08:07:07+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/extra-bundle", - "version": "v3.22.1", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "b6534bc925bec930004facca92fccebd0c809247" + "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/b6534bc925bec930004facca92fccebd0c809247", - "reference": "b6534bc925bec930004facca92fccebd0c809247", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/62d1cf47a1aa009cbd07b21045b97d3d5cb79896", + "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896", "shasum": "" }, "require": { "php": ">=8.1.0", - "symfony/framework-bundle": "^5.4|^6.4|^7.0|^8.0", - "symfony/twig-bundle": "^5.4|^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0", "twig/twig": "^3.2|^4.0" }, "require-dev": { - "league/commonmark": "^2.7", + "league/commonmark": "^1.0|^2.0", "symfony/phpunit-bridge": "^6.4|^7.0", "twig/cache-extra": "^3.0", "twig/cssinliner-extra": "^3.0", @@ -16697,7 +16385,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.1" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.21.0" }, "funding": [ { @@ -16709,26 +16397,26 @@ "type": "tidelift" } ], - "time": "2025-11-02T11:00:49+00:00" + "time": "2025-02-19T14:29:33+00:00" }, { "name": "twig/html-extra", - "version": "v3.22.1", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "d56d33315bce2b19ed815f8feedce85448736568" + "reference": "5442dd707601c83b8cd4233e37bb10ab8489a90f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/d56d33315bce2b19ed815f8feedce85448736568", - "reference": "d56d33315bce2b19ed815f8feedce85448736568", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/5442dd707601c83b8cd4233e37bb10ab8489a90f", + "reference": "5442dd707601c83b8cd4233e37bb10ab8489a90f", "shasum": "" }, "require": { "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/mime": "^5.4|^6.4|^7.0|^8.0", + "symfony/mime": "^5.4|^6.4|^7.0", "twig/twig": "^3.13|^4.0" }, "require-dev": { @@ -16765,7 +16453,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.22.1" + "source": "https://github.com/twigphp/html-extra/tree/v3.21.0" }, "funding": [ { @@ -16777,20 +16465,20 @@ "type": "tidelift" } ], - "time": "2025-11-02T11:00:49+00:00" + "time": "2025-02-19T14:29:33+00:00" }, { "name": "twig/inky-extra", - "version": "v3.22.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/inky-extra.git", - "reference": "631f42c7123240d9c2497903679ec54bb25f2f52" + "reference": "aacd79d94534b4a7fd6533cb5c33c4ee97239a0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/631f42c7123240d9c2497903679ec54bb25f2f52", - "reference": "631f42c7123240d9c2497903679ec54bb25f2f52", + "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/aacd79d94534b4a7fd6533cb5c33c4ee97239a0d", + "reference": "aacd79d94534b4a7fd6533cb5c33c4ee97239a0d", "shasum": "" }, "require": { @@ -16835,7 +16523,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/inky-extra/tree/v3.22.0" + "source": "https://github.com/twigphp/inky-extra/tree/v3.21.0" }, "funding": [ { @@ -16847,25 +16535,25 @@ "type": "tidelift" } ], - "time": "2025-07-29T08:07:07+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/intl-extra", - "version": "v3.22.1", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/intl-extra.git", - "reference": "93ac31e53cdd3f2e541f42690cd0c54ca8138ab1" + "reference": "05bc5d46b9df9e62399eae53e7c0b0633298b146" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/93ac31e53cdd3f2e541f42690cd0c54ca8138ab1", - "reference": "93ac31e53cdd3f2e541f42690cd0c54ca8138ab1", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/05bc5d46b9df9e62399eae53e7c0b0633298b146", + "reference": "05bc5d46b9df9e62399eae53e7c0b0633298b146", "shasum": "" }, "require": { "php": ">=8.1.0", - "symfony/intl": "^5.4|^6.4|^7.0|^8.0", + "symfony/intl": "^5.4|^6.4|^7.0", "twig/twig": "^3.13|^4.0" }, "require-dev": { @@ -16899,7 +16587,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/intl-extra/tree/v3.22.1" + "source": "https://github.com/twigphp/intl-extra/tree/v3.21.0" }, "funding": [ { @@ -16911,20 +16599,20 @@ "type": "tidelift" } ], - "time": "2025-11-02T11:00:49+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/markdown-extra", - "version": "v3.22.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "fb6f952082e3a7d62a75c8be2c8c47242d3925fb" + "reference": "f4616e1dd375209dacf6026f846e6b537d036ce4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/fb6f952082e3a7d62a75c8be2c8c47242d3925fb", - "reference": "fb6f952082e3a7d62a75c8be2c8c47242d3925fb", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/f4616e1dd375209dacf6026f846e6b537d036ce4", + "reference": "f4616e1dd375209dacf6026f846e6b537d036ce4", "shasum": "" }, "require": { @@ -16934,7 +16622,7 @@ }, "require-dev": { "erusev/parsedown": "dev-master as 1.x-dev", - "league/commonmark": "^2.7", + "league/commonmark": "^1.0|^2.0", "league/html-to-markdown": "^4.8|^5.0", "michelf/php-markdown": "^1.8|^2.0", "symfony/phpunit-bridge": "^6.4|^7.0" @@ -16971,7 +16659,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.22.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.21.0" }, "funding": [ { @@ -16983,25 +16671,25 @@ "type": "tidelift" } ], - "time": "2025-09-15T05:57:37+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/string-extra", - "version": "v3.22.1", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/string-extra.git", - "reference": "d5f16e0bec548bc96cce255b5f43d90492b8ce13" + "reference": "4b3337544ac8f76c280def94e32b53acfaec0589" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/string-extra/zipball/d5f16e0bec548bc96cce255b5f43d90492b8ce13", - "reference": "d5f16e0bec548bc96cce255b5f43d90492b8ce13", + "url": "https://api.github.com/repos/twigphp/string-extra/zipball/4b3337544ac8f76c280def94e32b53acfaec0589", + "reference": "4b3337544ac8f76c280def94e32b53acfaec0589", "shasum": "" }, "require": { "php": ">=8.1.0", - "symfony/string": "^5.4|^6.4|^7.0|^8.0", + "symfony/string": "^5.4|^6.4|^7.0", "symfony/translation-contracts": "^1.1|^2|^3", "twig/twig": "^3.13|^4.0" }, @@ -17038,7 +16726,7 @@ "unicode" ], "support": { - "source": "https://github.com/twigphp/string-extra/tree/v3.22.1" + "source": "https://github.com/twigphp/string-extra/tree/v3.21.0" }, "funding": [ { @@ -17050,20 +16738,20 @@ "type": "tidelift" } ], - "time": "2025-11-02T11:00:49+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/twig", - "version": "v3.22.1", + "version": "v3.21.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "1de2ec1fc43ab58a4b7e80b214b96bfc895750f3" + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/1de2ec1fc43ab58a4b7e80b214b96bfc895750f3", - "reference": "1de2ec1fc43ab58a4b7e80b214b96bfc895750f3", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", "shasum": "" }, "require": { @@ -17117,7 +16805,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.22.1" + "source": "https://github.com/twigphp/Twig/tree/v3.21.1" }, "funding": [ { @@ -17129,7 +16817,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T16:01:12+00:00" + "time": "2025-05-03T07:21:55+00:00" }, { "name": "ua-parser/uap-php", @@ -17446,28 +17134,28 @@ }, { "name": "webmozart/assert", - "version": "1.12.1", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { "ext-ctype": "*", - "ext-date": "*", - "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "suggest": { - "ext-intl": "", - "ext-simplexml": "", - "ext-spl": "" + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" }, "type": "library", "extra": { @@ -17498,9 +17186,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.12.1" + "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, - "time": "2025-10-29T15:56:20+00:00" + "time": "2022-06-03T18:03:27+00:00" }, { "name": "willdurand/negotiation", @@ -17562,25 +17250,25 @@ "packages-dev": [ { "name": "dama/doctrine-test-bundle", - "version": "v8.4.0", + "version": "v8.3.1", "source": { "type": "git", "url": "https://github.com/dmaicher/doctrine-test-bundle.git", - "reference": "ce7cd44126c36694e2f2d92c4aedd4fc5b0874f2" + "reference": "9bc47e02a0d67cbfef6773837249f71e65c95bf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/ce7cd44126c36694e2f2d92c4aedd4fc5b0874f2", - "reference": "ce7cd44126c36694e2f2d92c4aedd4fc5b0874f2", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/9bc47e02a0d67cbfef6773837249f71e65c95bf6", + "reference": "9bc47e02a0d67cbfef6773837249f71e65c95bf6", "shasum": "" }, "require": { "doctrine/dbal": "^3.3 || ^4.0", - "doctrine/doctrine-bundle": "^2.11.0 || ^3.0", + "doctrine/doctrine-bundle": "^2.11.0", "php": ">= 8.1", "psr/cache": "^2.0 || ^3.0", - "symfony/cache": "^6.4 || ^7.3 || ^8.0", - "symfony/framework-bundle": "^6.4 || ^7.3 || ^8.0" + "symfony/cache": "^6.4 || ^7.2 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.2 || ^8.0" }, "conflict": { "phpunit/phpunit": "<10.0" @@ -17589,9 +17277,9 @@ "behat/behat": "^3.0", "friendsofphp/php-cs-fixer": "^3.27", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^10.5.57 || ^11.5.41|| ^12.3.14", - "symfony/dotenv": "^6.4 || ^7.3 || ^8.0", - "symfony/process": "^6.4 || ^7.3 || ^8.0" + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", + "symfony/process": "^6.4 || ^7.2 || ^8.0", + "symfony/yaml": "^6.4 || ^7.2 || ^8.0" }, "type": "symfony-bundle", "extra": { @@ -17625,27 +17313,27 @@ ], "support": { "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues", - "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.4.0" + "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.3.1" }, - "time": "2025-10-11T15:24:02+00:00" + "time": "2025-08-05T17:55:02+00:00" }, { "name": "doctrine/doctrine-fixtures-bundle", - "version": "4.3.0", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineFixturesBundle.git", - "reference": "11941deb6f2899b91e8b8680b07ffe63899d864b" + "reference": "a06db6b81ff20a2980bf92063d80c013bb8b4b7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/11941deb6f2899b91e8b8680b07ffe63899d864b", - "reference": "11941deb6f2899b91e8b8680b07ffe63899d864b", + "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/a06db6b81ff20a2980bf92063d80c013bb8b4b7c", + "reference": "a06db6b81ff20a2980bf92063d80c013bb8b4b7c", "shasum": "" }, "require": { - "doctrine/data-fixtures": "^2.2", - "doctrine/doctrine-bundle": "^2.2 || ^3.0", + "doctrine/data-fixtures": "^2.0", + "doctrine/doctrine-bundle": "^2.2", "doctrine/orm": "^2.14.0 || ^3.0", "doctrine/persistence": "^2.4 || ^3.0 || ^4.0", "php": "^8.1", @@ -17661,7 +17349,7 @@ "doctrine/dbal": "< 3" }, "require-dev": { - "doctrine/coding-standard": "14.0.0", + "doctrine/coding-standard": "13.0.0", "phpstan/phpstan": "2.1.11", "phpunit/phpunit": "^10.5.38 || 11.4.14" }, @@ -17697,7 +17385,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues", - "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/4.3.0" + "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/4.1.0" }, "funding": [ { @@ -17713,7 +17401,7 @@ "type": "tidelift" } ], - "time": "2025-10-20T06:18:40+00:00" + "time": "2025-03-26T10:56:26+00:00" }, { "name": "ekino/phpstan-banned-code", @@ -17783,27 +17471,27 @@ }, { "name": "jbtronics/translation-editor-bundle", - "version": "v1.1.3", + "version": "v1.1.2", "source": { "type": "git", "url": "https://github.com/jbtronics/translation-editor-bundle.git", - "reference": "36bfb256e11d231d185bc2491323b041ba731257" + "reference": "bab5dd6ef41e87ba3d60c6363793e1cdf5cb6249" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/translation-editor-bundle/zipball/36bfb256e11d231d185bc2491323b041ba731257", - "reference": "36bfb256e11d231d185bc2491323b041ba731257", + "url": "https://api.github.com/repos/jbtronics/translation-editor-bundle/zipball/bab5dd6ef41e87ba3d60c6363793e1cdf5cb6249", + "reference": "bab5dd6ef41e87ba3d60c6363793e1cdf5cb6249", "shasum": "" }, "require": { "ext-json": "*", "php": "^8.1", "symfony/deprecation-contracts": "^3.4", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/translation": "^7.0|^6.4|^8.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/translation": "^7.0|^6.4", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/twig-bundle": "^7.0|^6.4|^8.0", - "symfony/web-profiler-bundle": "^7.0|^6.4|^8.0" + "symfony/twig-bundle": "^7.0|^6.4", + "symfony/web-profiler-bundle": "^7.0|^6.4" }, "require-dev": { "ekino/phpstan-banned-code": "^1.0", @@ -17837,7 +17525,7 @@ ], "support": { "issues": "https://github.com/jbtronics/translation-editor-bundle/issues", - "source": "https://github.com/jbtronics/translation-editor-bundle/tree/v1.1.3" + "source": "https://github.com/jbtronics/translation-editor-bundle/tree/v1.1.2" }, "funding": [ { @@ -17849,7 +17537,7 @@ "type": "github" } ], - "time": "2025-11-30T22:23:47+00:00" + "time": "2025-07-28T09:19:13+00:00" }, { "name": "myclabs/deep-copy", @@ -17913,16 +17601,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -17965,9 +17653,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", @@ -18137,11 +17825,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.32", + "version": "2.1.22", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4" + }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", - "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4", + "reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4", "shasum": "" }, "require": { @@ -18186,20 +17879,20 @@ "type": "github" } ], - "time": "2025-11-11T15:18:17+00:00" + "time": "2025-08-04T19:17:37+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "2.0.11", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "368ad1c713a6d95763890bc2292694a603ece7c8" + "reference": "eeff19808f8ae3a6f7c4e43e388a2848eb2b0865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/368ad1c713a6d95763890bc2292694a603ece7c8", - "reference": "368ad1c713a6d95763890bc2292694a603ece7c8", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/eeff19808f8ae3a6f7c4e43e388a2848eb2b0865", + "reference": "eeff19808f8ae3a6f7c4e43e388a2848eb2b0865", "shasum": "" }, "require": { @@ -18229,12 +17922,11 @@ "nesbot/carbon": "^2.49", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-deprecation-rules": "^2.0.2", - "phpstan/phpstan-phpunit": "^2.0.8", + "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6.20", "ramsey/uuid": "^4.2", - "symfony/cache": "^5.4", - "symfony/uid": "^5.4 || ^6.4 || ^7.3" + "symfony/cache": "^5.4" }, "type": "phpstan-extension", "extra": { @@ -18257,27 +17949,27 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.11" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.5" }, - "time": "2025-11-04T09:55:35+00:00" + "time": "2025-09-07T11:52:30+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.7", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538" + "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538", - "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/f9f77efa9de31992a832ff77ea52eb42d675b094", + "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.29" + "phpstan/phpstan": "^2.0.4" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -18305,22 +17997,22 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.6" }, - "time": "2025-09-26T11:19:08+00:00" + "time": "2025-07-21T12:19:29+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "2.0.9", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "24d8c157aa483141b0579d705ef0aac9e1b95436" + "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/24d8c157aa483141b0579d705ef0aac9e1b95436", - "reference": "24d8c157aa483141b0579d705ef0aac9e1b95436", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/8820c22d785c235f69bb48da3d41e688bc8a1796", + "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796", "shasum": "" }, "require": { @@ -18333,7 +18025,7 @@ }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^2.0.8", + "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6", "psr/container": "1.1.2", @@ -18376,9 +18068,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/2.0.9" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.8" }, - "time": "2025-11-29T11:17:28+00:00" + "time": "2025-09-07T06:55:50+00:00" }, { "name": "phpunit/php-code-coverage", @@ -18717,16 +18409,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.44", + "version": "11.5.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c346885c95423eda3f65d85a194aaa24873cda82" + "reference": "264a87c7ef68b1ab9af7172357740dc266df5957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c346885c95423eda3f65d85a194aaa24873cda82", - "reference": "c346885c95423eda3f65d85a194aaa24873cda82", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/264a87c7ef68b1ab9af7172357740dc266df5957", + "reference": "264a87c7ef68b1ab9af7172357740dc266df5957", "shasum": "" }, "require": { @@ -18750,7 +18442,7 @@ "sebastian/comparator": "^6.3.2", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.2", + "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", "sebastian/type": "^5.1.3", @@ -18798,7 +18490,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.44" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.36" }, "funding": [ { @@ -18822,25 +18514,25 @@ "type": "tidelift" } ], - "time": "2025-11-13T07:17:35+00:00" + "time": "2025-09-03T06:24:17+00:00" }, { "name": "rector/rector", - "version": "2.2.9", + "version": "2.1.6", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "0b8e49ec234877b83244d2ecd0df7a4c16471f05" + "reference": "729aabc0ec66e700ef164e26454a1357f222a2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/0b8e49ec234877b83244d2ecd0df7a4c16471f05", - "reference": "0b8e49ec234877b83244d2ecd0df7a4c16471f05", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/729aabc0ec66e700ef164e26454a1357f222a2f3", + "reference": "729aabc0ec66e700ef164e26454a1357f222a2f3", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.32" + "phpstan/phpstan": "^2.1.18" }, "conflict": { "rector/rector-doctrine": "*", @@ -18874,7 +18566,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.2.9" + "source": "https://github.com/rectorphp/rector/tree/2.1.6" }, "funding": [ { @@ -18882,7 +18574,7 @@ "type": "github" } ], - "time": "2025-11-28T14:21:22+00:00" + "time": "2025-09-05T15:43:08+00:00" }, { "name": "roave/security-advisories", @@ -18890,18 +18582,18 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "3f393e137e490ecb2ac77989a692129c31192de7" + "reference": "dc5c4ede5c331ae21fb68947ff89672df9b7cc7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/3f393e137e490ecb2ac77989a692129c31192de7", - "reference": "3f393e137e490ecb2ac77989a692129c31192de7", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/dc5c4ede5c331ae21fb68947ff89672df9b7cc7d", + "reference": "dc5c4ede5c331ae21fb68947ff89672df9b7cc7d", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", "adaptcms/adaptcms": "<=1.3", - "admidio/admidio": "<=4.3.16", + "admidio/admidio": "<4.3.12", "adodb/adodb-php": "<=5.22.9", "aheinze/cockpit": "<2.2", "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", @@ -18914,7 +18606,6 @@ "akaunting/akaunting": "<2.1.13", "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", "alextselegidis/easyappointments": "<1.5.2.0-beta1", - "alt-design/alt-redirect": "<1.6.4", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", "amazing/media2click": ">=1,<1.3.3", "ameos/ameos_tarteaucitron": "<1.2.23", @@ -18938,22 +18629,22 @@ "athlon1600/php-proxy-app": "<=3", "athlon1600/youtube-downloader": "<=4", "austintoddj/canvas": "<=3.4.2", - "auth0/auth0-php": ">=3.3,<=8.16", - "auth0/login": "<=7.18", - "auth0/symfony": "<=5.4.1", - "auth0/wordpress": "<=5.3", + "auth0/auth0-php": ">=8.0.0.0-beta1,<8.14", + "auth0/login": "<7.17", + "auth0/symfony": "<5.4", + "auth0/wordpress": "<5.3", "automad/automad": "<2.0.0.0-alpha5", "automattic/jetpack": "<9.8", "awesome-support/awesome-support": "<=6.0.7", "aws/aws-sdk-php": "<3.288.1", "azuracast/azuracast": "<0.18.3", "b13/seo_basics": "<0.8.2", - "backdrop/backdrop": "<=1.32", + "backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2", "backpack/crud": "<3.4.9", "backpack/filemanager": "<2.0.2|>=3,<3.0.9", "bacula-web/bacula-web": "<9.7.1", "badaso/core": "<=2.9.11", - "bagisto/bagisto": "<=2.3.7", + "bagisto/bagisto": "<2.1", "barrelstrength/sprout-base-email": "<1.2.7", "barrelstrength/sprout-forms": "<3.9", "barryvdh/laravel-translation-manager": "<0.6.8", @@ -19004,14 +18695,12 @@ "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", "co-stack/fal_sftp": "<0.2.6", "cockpit-hq/cockpit": "<2.11.4", - "code16/sharp": "<9.11.1", "codeception/codeception": "<3.1.3|>=4,<4.1.22", "codeigniter/framework": "<3.1.10", "codeigniter4/framework": "<4.6.2", "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9", - "codingms/modules": "<4.3.11|>=5,<5.7.4|>=6,<6.4.2|>=7,<7.5.5", "commerceteam/commerce": ">=0.9.6,<0.9.9", "components/jquery": ">=1.0.3,<3.5", "composer/composer": "<1.10.27|>=2,<2.2.24|>=2.3,<2.7.7", @@ -19021,7 +18710,7 @@ "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.13.56|>=5,<5.3.38|>=5.4.0.0-RC1-dev,<5.6.1", "contao/core": "<3.5.39", - "contao/core-bundle": "<4.13.57|>=5,<5.3.42|>=5.4,<5.6.5", + "contao/core-bundle": "<4.13.56|>=5,<5.3.38|>=5.4,<5.6.1", "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", "contao/managed-edition": "<=1.5", "corveda/phpsandbox": "<1.3.5", @@ -19036,7 +18725,6 @@ "dapphp/securimage": "<3.6.6", "darylldoyle/safe-svg": "<1.9.10", "datadog/dd-trace": ">=0.30,<0.30.2", - "datahihi1/tiny-env": "<1.0.3|>=1.0.9,<1.0.11", "datatables/datatables": "<1.10.10", "david-garcia/phpwhois": "<=4.3.1", "dbrisinajumi/d2files": "<1", @@ -19045,7 +18733,6 @@ "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", "desperado/xml-bundle": "<=0.1.7", "dev-lancer/minecraft-motd-parser": "<=1.0.5", - "devcode-it/openstamanager": "<=2.9.4", "devgroup/dotplant": "<2020.09.14-dev", "digimix/wp-svg-upload": "<=1", "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", @@ -19061,45 +18748,33 @@ "doctrine/mongodb-odm": "<1.0.2", "doctrine/mongodb-odm-bundle": "<3.0.1", "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<21.0.3", + "dolibarr/dolibarr": "<=21.0.2", "dompdf/dompdf": "<2.0.4", "doublethreedigital/guest-entries": "<3.1.2", - "drupal-pattern-lab/unified-twig-extensions": "<=0.1", - "drupal/access_code": "<2.0.5", - "drupal/acquia_dam": "<1.1.5", "drupal/admin_audit_trail": "<1.0.5", "drupal/ai": "<1.0.5", "drupal/alogin": "<2.0.6", "drupal/cache_utility": "<1.2.1", - "drupal/civictheme": "<1.12", "drupal/commerce_alphabank_redirect": "<1.0.3", "drupal/commerce_eurobank_redirect": "<2.1.1", "drupal/config_split": "<1.10|>=2,<2.0.2", - "drupal/core": ">=6,<6.38|>=7,<7.102|>=8,<10.4.9|>=10.5,<10.5.6|>=11,<11.1.9|>=11.2,<11.2.8", + "drupal/core": ">=6,<6.38|>=7,<7.102|>=8,<10.3.14|>=10.4,<10.4.5|>=11,<11.0.13|>=11.1,<11.1.5", "drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", - "drupal/currency": "<3.5", "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", - "drupal/email_tfa": "<2.0.6", "drupal/formatter_suite": "<2.1", "drupal/gdpr": "<3.0.1|>=3.1,<3.1.2", "drupal/google_tag": "<1.8|>=2,<2.0.8", "drupal/ignition": "<1.0.4", - "drupal/json_field": "<1.5", "drupal/lightgallery": "<1.6", "drupal/link_field_display_mode_formatter": "<1.6", "drupal/matomo": "<1.24", "drupal/oauth2_client": "<4.1.3", "drupal/oauth2_server": "<2.1", "drupal/obfuscate": "<2.0.1", - "drupal/plausible_tracking": "<1.0.2", "drupal/quick_node_block": "<2", "drupal/rapidoc_elements_field_formatter": "<1.0.1", - "drupal/reverse_proxy_header": "<1.1.2", - "drupal/simple_multistep": "<2", - "drupal/simple_oauth": ">=6,<6.0.7", "drupal/spamspan": "<3.2.1", "drupal/tfa": "<1.10", - "drupal/umami_analytics": "<1.0.1", "duncanmcclean/guest-entries": "<3.1.2", "dweeves/magmi": "<=0.7.24", "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", @@ -19124,7 +18799,7 @@ "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.39|>=3.3,<3.3.39", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.38|>=3.3,<3.3.39", "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1|>=5.3.0.0-beta1,<5.3.5", "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", "ezsystems/ezplatform-http-cache": "<2.3.16", @@ -19186,9 +18861,9 @@ "genix/cms": "<=1.1.11", "georgringer/news": "<1.3.3", "geshi/geshi": "<=1.0.9.1", - "getformwork/formwork": "<2.2", + "getformwork/formwork": "<1.13.1|>=2.0.0.0-beta1,<2.0.0.0-beta4", "getgrav/grav": "<1.7.46", - "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1|>=5,<5.1.4", + "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", "getkirby/panel": "<2.5.14", "getkirby/starterkit": "<=3.7.0.2", @@ -19199,7 +18874,6 @@ "gogentooss/samlbase": "<1.2.7", "google/protobuf": "<3.4", "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", - "gp247/core": "<1.1.24", "gree/jose": "<2.2.1", "gregwar/rst": "<1.0.3", "grumpydictator/firefly-iii": "<6.1.17", @@ -19218,15 +18892,15 @@ "hov/jobfair": "<1.0.13|>=2,<2.0.2", "httpsoft/http-message": "<1.0.12", "hyn/multi-tenant": ">=5.6,<5.7.2", - "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.25|>=5,<5.0.3", + "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.21", "ibexa/admin-ui-assets": ">=4.6.0.0-alpha1,<4.6.21", "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", - "ibexa/fieldtype-richtext": ">=4.6,<4.6.25|>=5,<5.0.3", + "ibexa/fieldtype-richtext": ">=4.6,<4.6.21", "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", "ibexa/http-cache": ">=4.6,<4.6.14", "ibexa/post-install": "<1.0.16|>=4.6,<4.6.14", "ibexa/solr": ">=4.5,<4.5.4", - "ibexa/user": ">=4,<4.4.3|>=5,<5.0.3", + "ibexa/user": ">=4,<4.4.3", "icecoder/icecoder": "<=8.1", "idno/known": "<=1.3.1", "ilicmiljan/secure-props": ">=1.2,<1.2.2", @@ -19262,7 +18936,7 @@ "joomla/archive": "<1.1.12|>=2,<2.0.1", "joomla/database": ">=1,<2.2|>=3,<3.4", "joomla/filesystem": "<1.6.2|>=2,<2.0.1", - "joomla/filter": "<2.0.6|>=3,<3.0.5|==4", + "joomla/filter": "<1.4.4|>=2,<2.0.1", "joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12", "joomla/input": ">=2,<2.0.2", "joomla/joomla-cms": "<3.9.12|>=4,<4.4.13|>=5,<5.2.6", @@ -19301,7 +18975,6 @@ "laravel/socialite": ">=1,<2.0.10", "latte/latte": "<2.10.8", "lavalite/cms": "<=9|==10.1", - "lavitto/typo3-form-to-database": "<2.2.5|>=3,<3.2.2|>=4,<4.2.3|>=5,<5.0.2", "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", "league/commonmark": "<2.7", "league/flysystem": "<1.1.4|>=2,<2.1.1", @@ -19323,18 +18996,17 @@ "luyadev/yii-helpers": "<1.2.1", "macropay-solutions/laravel-crud-wizard-free": "<3.4.17", "maestroerror/php-heic-to-jpg": "<1.0.5", - "magento/community-edition": "<2.4.6.0-patch13|>=2.4.7.0-beta1,<2.4.7.0-patch8|>=2.4.8.0-beta1,<2.4.8.0-patch3|>=2.4.9.0-alpha1,<2.4.9.0-alpha3|==2.4.9", + "magento/community-edition": "<2.4.5.0-patch14|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch12|>=2.4.7.0-beta1,<2.4.7.0-patch7|>=2.4.8.0-beta1,<2.4.8.0-patch1", "magento/core": "<=1.9.4.5", "magento/magento1ce": "<1.9.4.3-dev", "magento/magento1ee": ">=1,<1.14.4.3-dev", "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1", "magento/project-community-edition": "<=2.0.2", "magneto/core": "<1.9.4.4-dev", - "mahocommerce/maho": "<25.9", "maikuolan/phpmussel": ">=1,<1.6", "mainwp/mainwp": "<=4.4.3.3", "manogi/nova-tiptap": "<=3.2.6", - "mantisbt/mantisbt": "<2.27.2", + "mantisbt/mantisbt": "<=2.26.3", "marcwillmann/turn": "<0.3.3", "marshmallow/nova-tiptap": "<5.7", "matomo/matomo": "<1.11", @@ -19344,16 +19016,14 @@ "maximebf/debugbar": "<1.19", "mdanter/ecc": "<2", "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", - "mediawiki/cargo": "<3.8.3", + "mediawiki/cargo": "<3.6.1", "mediawiki/core": "<1.39.5|==1.40", "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", "mehrwert/phpmyadmin": "<3.2", "melisplatform/melis-asset-manager": "<5.0.1", - "melisplatform/melis-cms": "<5.3.4", - "melisplatform/melis-cms-slider": "<5.3.1", - "melisplatform/melis-core": "<5.3.11", + "melisplatform/melis-cms": "<5.0.1", "melisplatform/melis-front": "<5.0.1", "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", "mgallegos/laravel-jqgrid": "<=1.3", @@ -19368,17 +19038,17 @@ "modx/revolution": "<=3.1", "mojo42/jirafeau": "<4.4", "mongodb/mongodb": ">=1,<1.9.2", - "mongodb/mongodb-extension": "<1.21.2", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.4.11|>=4.5.0.0-beta,<4.5.7|>=5.0.0.0-beta,<5.0.3", + "moodle/moodle": "<4.3.12|>=4.4,<4.4.8|>=4.5.0.0-beta,<4.5.4", "moonshine/moonshine": "<=3.12.5", "mos/cimage": "<0.7.19", "movim/moxl": ">=0.8,<=0.10", "movingbytes/social-network": "<=1.2.1", "mpdf/mpdf": "<=7.1.7", - "munkireport/comment": "<4", + "munkireport/comment": "<4.1", "munkireport/managedinstalls": "<2.6", "munkireport/munki_facts": "<1.5", + "munkireport/munkireport": ">=2.5.3,<5.6.3", "munkireport/reportdata": "<3.5", "munkireport/softwareupdate": "<1.6", "mustache/mustache": ">=2,<2.14.1", @@ -19404,7 +19074,6 @@ "notrinos/notrinos-erp": "<=0.7", "noumo/easyii": "<=0.9", "novaksolutions/infusionsoft-php-sdk": "<1", - "novosga/novosga": "<=2.2.12", "nukeviet/nukeviet": "<4.5.02", "nyholm/psr7": "<1.6.1", "nystudio107/craft-seomatic": "<3.4.12", @@ -19419,10 +19088,10 @@ "omeka/omeka-s": "<4.0.3", "onelogin/php-saml": "<2.10.4", "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", - "open-web-analytics/open-web-analytics": "<1.8.1", + "open-web-analytics/open-web-analytics": "<1.7.4", "opencart/opencart": ">=0", "openid/php-openid": "<2.3", - "openmage/magento-lts": "<20.16", + "openmage/magento-lts": "<20.12.3", "opensolutions/vimbadmin": "<=3.0.15", "opensource-workshop/connect-cms": "<1.8.7|>=2,<2.4.7", "orchid/platform": ">=8,<14.43", @@ -19463,12 +19132,11 @@ "phpmailer/phpmailer": "<6.5", "phpmussel/phpmussel": ">=1,<1.6", "phpmyadmin/phpmyadmin": "<5.2.2", - "phpmyfaq/phpmyfaq": "<=4.0.13", + "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5|>=3.2.10,<=4.0.1", "phpoffice/common": "<0.2.9", "phpoffice/math": "<=0.2", "phpoffice/phpexcel": "<=1.8.2", "phpoffice/phpspreadsheet": "<1.30|>=2,<2.1.12|>=2.2,<2.4|>=3,<3.10|>=4,<5", - "phppgadmin/phppgadmin": "<=7.13", "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", "phpservermon/phpservermon": "<3.6", "phpsysinfo/phpsysinfo": "<3.4.3", @@ -19499,13 +19167,12 @@ "prestashop/gamification": "<2.3.2", "prestashop/prestashop": "<8.2.3", "prestashop/productcomments": "<5.0.2", - "prestashop/ps_checkout": "<4.4.1|>=5,<5.0.5", "prestashop/ps_contactinfo": "<=3.3.2", "prestashop/ps_emailsubscription": "<2.6.1", "prestashop/ps_facetedsearch": "<3.4.1", "prestashop/ps_linklist": "<3.1", - "privatebin/privatebin": "<1.4|>=1.5,<1.7.4|>=1.7.7,<2.0.3", - "processwire/processwire": "<=3.0.246", + "privatebin/privatebin": "<1.4|>=1.5,<1.7.4", + "processwire/processwire": "<=3.0.229", "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", "pterodactyl/panel": "<=1.11.10", @@ -19526,7 +19193,7 @@ "rap2hpoutre/laravel-log-viewer": "<0.13", "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", - "redaxo/source": "<5.20.1", + "redaxo/source": "<5.18.3", "remdex/livehelperchat": "<4.29", "renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1", "reportico-web/reportico": "<=8.1", @@ -19537,7 +19204,7 @@ "roundcube/roundcubemail": "<1.5.10|>=1.6,<1.6.11", "rudloff/alltube": "<3.0.3", "rudloff/rtmpdump-bin": "<=2.3.1", - "s-cart/core": "<=9.0.5", + "s-cart/core": "<6.9", "s-cart/s-cart": "<6.9", "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9", @@ -19548,10 +19215,10 @@ "setasign/fpdi": "<2.6.4", "sfroemken/url_redirect": "<=1.2.1", "sheng/yiicms": "<1.2.1", - "shopware/core": "<6.6.10.9-dev|>=6.7,<6.7.4.1-dev", - "shopware/platform": "<6.6.10.7-dev|>=6.7,<6.7.3.1-dev", + "shopware/core": "<6.5.8.18-dev|>=6.6,<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev", + "shopware/platform": "<=6.6.10.4|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev", "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<=5.7.17|>=6.7,<6.7.2.1-dev", + "shopware/shopware": "<=5.7.17", "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev", "shopxo/shopxo": "<=6.4", "showdoc/showdoc": "<2.10.4", @@ -19593,7 +19260,7 @@ "slim/slim": "<2.6", "slub/slub-events": "<3.0.3", "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<=8.3.4", + "snipe/snipe-it": "<8.1", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", "solspace/craft-freeform": ">=5,<5.10.16", @@ -19602,16 +19269,14 @@ "spatie/image-optimizer": "<1.7.3", "spencer14420/sp-php-email-handler": "<1", "spipu/html2pdf": "<5.2.8", - "spiral/roadrunner": "<2025.1", "spoon/library": "<1.4.1", "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", "ssddanbrown/bookstack": "<24.05.1", - "starcitizentools/citizen-skin": ">=1.9.4,<3.9", + "starcitizentools/citizen-skin": ">=1.9.4,<3.4", "starcitizentools/short-description": ">=4,<4.0.1", "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", - "starcitizenwiki/embedvideo": "<=4", - "statamic/cms": "<=5.22", + "statamic/cms": "<=5.16", "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<=2.1.64", "studiomitte/friendlycaptcha": "<0.1.4", @@ -19642,7 +19307,7 @@ "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", - "symfony/http-foundation": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", + "symfony/http-foundation": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", @@ -19661,7 +19326,7 @@ "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", + "symfony/symfony": "<5.4.47|>=6,<6.4.15|>=7,<7.1.8", "symfony/translation": ">=2,<2.0.17", "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", "symfony/ux-autocomplete": "<2.11.2", @@ -19685,7 +19350,7 @@ "thelia/thelia": ">=2.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<=4.0.13", + "thorsten/phpmyfaq": "<=4.0.1", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", "tinymce/tinymce": "<7.2", @@ -19696,19 +19361,19 @@ "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", "topthink/think": "<=6.1.1", "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", - "torrentpier/torrentpier": "<=2.8.8", + "torrentpier/torrentpier": "<=2.4.3", "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", "tribalsystems/zenario": "<=9.7.61188", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", - "twbs/bootstrap": "<3.4.1|>=4,<4.3.1", + "twbs/bootstrap": "<=3.4.1|>=4,<=4.6.2", "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", + "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<10.4.46|>=11,<11.5.40|>=12,<=12.4.30|>=13,<=13.4.11", "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", - "typo3/cms-beuser": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", - "typo3/cms-core": "<=8.7.56|>=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", - "typo3/cms-dashboard": ">=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", + "typo3/cms-beuser": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-core": "<=8.7.56|>=9,<=9.5.50|>=10,<=10.4.49|>=11,<=11.5.43|>=12,<=12.4.30|>=13,<=13.4.11", + "typo3/cms-dashboard": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-felogin": ">=4.2,<4.2.3", @@ -19718,13 +19383,10 @@ "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2", "typo3/cms-lowlevel": ">=11,<=11.5.41", - "typo3/cms-recordlist": ">=11,<11.5.48", - "typo3/cms-recycler": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", "typo3/cms-scheduler": ">=11,<=11.5.41", "typo3/cms-setup": ">=9,<=9.5.50|>=10,<=10.4.49|>=11,<=11.5.43|>=12,<=12.4.30|>=13,<=13.4.11", "typo3/cms-webhooks": ">=12,<=12.4.30|>=13,<=13.4.11", - "typo3/cms-workspaces": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", @@ -19765,7 +19427,6 @@ "webklex/laravel-imap": "<5.3", "webklex/php-imap": "<5.3", "webpa/webpa": "<3.1.2", - "webreinvent/vaahcms": "<=2.3.1", "wikibase/wikibase": "<=1.39.3", "wikimedia/parsoid": "<0.12.2", "willdurand/js-translation-bundle": "<2.1.1", @@ -19786,7 +19447,7 @@ "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", - "yeswiki/yeswiki": "<=4.5.4", + "yeswiki/yeswiki": "<4.5.4", "yetiforce/yetiforce-crm": "<6.5", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", @@ -19878,7 +19539,7 @@ "type": "tidelift" } ], - "time": "2025-11-26T00:22:38+00:00" + "time": "2025-09-04T20:05:35+00:00" }, { "name": "sebastian/cli-parser", @@ -20345,16 +20006,16 @@ }, { "name": "sebastian/exporter", - "version": "6.3.2", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", - "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", "shasum": "" }, "require": { @@ -20368,7 +20029,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.3-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -20411,27 +20072,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" } ], - "time": "2025-09-24T06:12:51+00:00" + "time": "2024-12-05T09:17:50+00:00" }, { "name": "sebastian/global-state", @@ -20920,28 +20569,27 @@ }, { "name": "symfony/browser-kit", - "version": "v7.4.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "3bb26dafce31633b1f699894c86379eefc8af5bb" + "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/3bb26dafce31633b1f699894c86379eefc8af5bb", - "reference": "3bb26dafce31633b1f699894c86379eefc8af5bb", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f0b889b73a845cddef1d25fe207b37fd04cb5419", + "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/dom-crawler": "^6.4|^7.0|^8.0" + "symfony/dom-crawler": "^6.4|^7.0" }, "require-dev": { - "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0" + "symfony/css-selector": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -20969,7 +20617,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v7.4.0" + "source": "https://github.com/symfony/browser-kit/tree/v7.3.2" }, "funding": [ { @@ -20989,34 +20637,34 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:29:59+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/debug-bundle", - "version": "v7.4.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/debug-bundle.git", - "reference": "329383fb895353e3c8ab792cc35c4a7e7b17881b" + "reference": "781acc90f31f5fe18915f9276890864ebbbe3da8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/329383fb895353e3c8ab792cc35c4a7e7b17881b", - "reference": "329383fb895353e3c8ab792cc35c4a7e7b17881b", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/781acc90f31f5fe18915f9276890864ebbbe3da8", + "reference": "781acc90f31f5fe18915f9276890864ebbbe3da8", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", "php": ">=8.2", - "symfony/config": "^7.3|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/twig-bridge": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/config": "^7.3", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "require-dev": { - "symfony/web-profiler-bundle": "^6.4|^7.0|^8.0" + "symfony/web-profiler-bundle": "^6.4|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -21044,7 +20692,7 @@ "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug-bundle/tree/v7.4.0" + "source": "https://github.com/symfony/debug-bundle/tree/v7.3.0" }, "funding": [ { @@ -21055,44 +20703,40 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-10-24T13:56:35+00:00" + "time": "2025-05-04T13:21:13+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.65.0", + "version": "v1.64.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "9a0276d7486b29cae641b4a0a85d5e5cc149bff2" + "reference": "c86da84640b0586e92aee2b276ee3638ef2f425a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/9a0276d7486b29cae641b4a0a85d5e5cc149bff2", - "reference": "9a0276d7486b29cae641b4a0a85d5e5cc149bff2", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/c86da84640b0586e92aee2b276ee3638ef2f425a", + "reference": "c86da84640b0586e92aee2b276ee3638ef2f425a", "shasum": "" }, "require": { "doctrine/inflector": "^2.0", "nikic/php-parser": "^5.0", "php": ">=8.1", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.2|^3", - "symfony/filesystem": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0" + "symfony/filesystem": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" }, "conflict": { "doctrine/doctrine-bundle": "<2.10", @@ -21100,14 +20744,13 @@ }, "require-dev": { "composer/semver": "^3.0", - "doctrine/doctrine-bundle": "^2.5.0|^3.0.0", + "doctrine/doctrine-bundle": "^2.5.0", "doctrine/orm": "^2.15|^3", - "doctrine/persistence": "^3.1|^4.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/phpunit-bridge": "^6.4.1|^7.0|^8.0", - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/security-http": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/phpunit-bridge": "^6.4.1|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", "twig/twig": "^3.0|^4.x-dev" }, "type": "symfony-bundle", @@ -21142,7 +20785,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.65.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.64.0" }, "funding": [ { @@ -21153,37 +20796,37 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-11-24T15:41:51+00:00" + "time": "2025-06-23T16:12:08+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "059b051b38f2138ef104dd848fa48f0cbbb7d78b" + "reference": "7954e563ed14f924593169f6c4645d58d9d9ac77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/059b051b38f2138ef104dd848fa48f0cbbb7d78b", - "reference": "059b051b38f2138ef104dd848fa48f0cbbb7d78b", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/7954e563ed14f924593169f6c4645d58d9d9ac77", + "reference": "7954e563ed14f924593169f6c4645d58d9d9ac77", "shasum": "" }, "require": { - "php": ">=8.1.0" + "php": ">=7.2.5" + }, + "conflict": { + "phpunit/phpunit": "<7.5|9.1.2" }, "require-dev": { - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.4.3|^7.0.3|^8.0" + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.4|^7.0", + "symfony/polyfill-php81": "^1.27" }, "bin": [ "bin/simple-phpunit" @@ -21227,7 +20870,7 @@ "testing" ], "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.4.0" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.3.3" }, "funding": [ { @@ -21247,32 +20890,32 @@ "type": "tidelift" } ], - "time": "2025-10-28T22:44:23+00:00" + "time": "2025-08-04T15:15:28+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v7.4.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "dcd955ca9c60f2942194854518049f8ae4dbd696" + "reference": "6ee224d6e9de787a47622b9ad4880e205ef16ad1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/dcd955ca9c60f2942194854518049f8ae4dbd696", - "reference": "dcd955ca9c60f2942194854518049f8ae4dbd696", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/6ee224d6e9de787a47622b9ad4880e205ef16ad1", + "reference": "6ee224d6e9de787a47622b9ad4880e205ef16ad1", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "php": ">=8.2", - "symfony/config": "^7.3|^8.0", + "symfony/config": "^7.3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", - "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", - "symfony/routing": "^6.4|^7.0|^8.0", - "symfony/twig-bundle": "^6.4|^7.0|^8.0", - "twig/twig": "^3.15" + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "twig/twig": "^3.12" }, "conflict": { "symfony/form": "<6.4", @@ -21282,11 +20925,10 @@ "symfony/workflow": "<7.3" }, "require-dev": { - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/runtime": "^6.4.13|^7.1.6|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -21317,7 +20959,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.0" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.3.3" }, "funding": [ { @@ -21337,20 +20979,20 @@ "type": "tidelift" } ], - "time": "2025-11-19T14:48:01+00:00" + "time": "2025-08-19T13:44:55+00:00" }, { "name": "theseer/tokenizer", - "version": "1.3.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -21379,7 +21021,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -21387,7 +21029,7 @@ "type": "github" } ], - "time": "2025-11-17T20:03:58+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], @@ -21407,9 +21049,9 @@ "ext-json": "*", "ext-mbstring": "*" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.2.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/config/packages/doctrine.php b/config/packages/doctrine.php deleted file mode 100644 index 47584ed7..00000000 --- a/config/packages/doctrine.php +++ /dev/null @@ -1,33 +0,0 @@ -. - */ - -declare(strict_types=1); - -/** - * This class extends the default doctrine ORM configuration to enable native lazy objects on PHP 8.4+. - * We have to do this in a PHP file, because the yaml file does not support conditionals on PHP version. - */ - -return static function(\Symfony\Config\DoctrineConfig $doctrine) { - //On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies. - if (PHP_VERSION_ID >= 80400) { - $doctrine->orm()->enableNativeLazyObjects(true); - } -}; diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml index 387d71ad..725ebd7c 100644 --- a/config/packages/monolog.yaml +++ b/config/packages/monolog.yaml @@ -10,6 +10,14 @@ when@dev: path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug channels: ["!event"] + # uncomment to get logging in your browser + # you may have to allow bigger header sizes in your Web server configuration + #firephp: + # type: firephp + # level: info + #chromephp: + # type: chromephp + # level: info console: type: console process_psr_3_messages: false @@ -37,7 +45,6 @@ when@prod: action_level: error handler: nested excluded_http_codes: [404, 405] - channels: ["!deprecation"] buffer_size: 50 # How many messages should be saved? Prevent memory leaks nested: type: stream diff --git a/config/packages/nelmio_security.yaml b/config/packages/nelmio_security.yaml index 6b2b7337..c283cd8e 100644 --- a/config/packages/nelmio_security.yaml +++ b/config/packages/nelmio_security.yaml @@ -20,6 +20,12 @@ nelmio_security: - 'digikey.com' - 'nexar.com' + # forces Microsoft's XSS-Protection with + # its block mode + xss_protection: + enabled: true + mode_block: true + # Send a full URL in the `Referer` header when performing a same-origin request, # only send the origin of the document to secure destination (HTTPS->HTTPS), # and send no header to a less secure destination (HTTPS->HTTP). diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index a3f529e3..cbc1cd7e 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -1,7 +1,7 @@ framework: default_locale: 'en' # Just enable the locales we need for performance reasons. - enabled_locale: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] + enabled_locale: '%partdb.locale_menu%' translator: default_path: '%kernel.project_dir%/translations' fallbacks: diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 95ae4f3b..674aa317 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -1,6 +1,6 @@ twig: default_path: '%kernel.project_dir%/templates' - form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig', 'form/synonyms_collection.html.twig'] + form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig'] paths: '%kernel.project_dir%/assets/css': css @@ -20,4 +20,4 @@ twig: when@test: twig: - strict_variables: true + strict_variables: true \ No newline at end of file diff --git a/config/parameters.yaml b/config/parameters.yaml index b79e2b88..154fbd8a 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -8,9 +8,9 @@ parameters: # This is used as workaround for places where we can not access the settings directly (like the 2FA application names) partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage) - partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl', 'hu'] # The languages that are shown in user drop down menu + partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu - partdb.default_uri: '%env(addSlash: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.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. @@ -104,9 +104,3 @@ parameters: env(SAML_ROLE_MAPPING): '{}' env(DATABASE_EMULATE_NATURAL_SORT): 0 - - ###################################################################################################################### - # Bulk Info Provider Import Configuration - ###################################################################################################################### - partdb.bulk_import.batch_size: 20 # Number of parts to process in each batch during bulk operations - partdb.bulk_import.max_parts_per_operation: 1000 # Maximum number of parts allowed per bulk import operation diff --git a/config/permissions.yaml b/config/permissions.yaml index 8c6a145e..8cbd60c3 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -18,13 +18,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co parts: # e.g. this maps to perms_parts in User/Group database group: "data" - label: "[[Part]]" + label: "perm.parts" operations: # Here are all possible operations are listed => the op name is mapped to bit value read: label: "perm.read" # If a part can be read by a user, he can also see all the datastructures (except devices) alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read', - 'currencies.read', 'attachment_types.read', 'measurement_units.read', 'part_custom_states.read'] + 'currencies.read', 'attachment_types.read', 'measurement_units.read'] apiTokenRole: ROLE_API_READ_ONLY edit: label: "perm.edit" @@ -71,7 +71,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co storelocations: &PART_CONTAINING - label: "[[Storage_location]]" + label: "perm.storelocations" group: "data" operations: read: @@ -103,39 +103,35 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co footprints: <<: *PART_CONTAINING - label: "[[Footprint]]" + label: "perm.part.footprints" categories: <<: *PART_CONTAINING - label: "[[Category]]" + label: "perm.part.categories" suppliers: <<: *PART_CONTAINING - label: "[[Supplier]]" + label: "perm.part.supplier" manufacturers: <<: *PART_CONTAINING - label: "[[Manufacturer]]" + label: "perm.part.manufacturers" projects: <<: *PART_CONTAINING - label: "[[Project]]" + label: "perm.projects" attachment_types: <<: *PART_CONTAINING - label: "[[Attachment_type]]" + label: "perm.part.attachment_types" currencies: <<: *PART_CONTAINING - label: "[[Currency]]" + label: "perm.currencies" measurement_units: <<: *PART_CONTAINING - label: "[[Measurement_unit]]" - - part_custom_states: - <<: *PART_CONTAINING - label: "[[Part_custom_state]]" + label: "perm.measurement_units" tools: label: "perm.part.tools" @@ -377,4 +373,4 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co manage_tokens: label: "perm.api.manage_tokens" alsoSet: ['access_api'] - apiTokenRole: ROLE_API_FULL + apiTokenRole: ROLE_API_FULL \ No newline at end of file diff --git a/config/reference.php b/config/reference.php deleted file mode 100644 index 6ea52419..00000000 --- a/config/reference.php +++ /dev/null @@ -1,2896 +0,0 @@ - [ - * 'App\\' => [ - * 'resource' => '../src/', - * ], - * ], - * ]); - * ``` - * - * @psalm-type ImportsConfig = list - * @psalm-type ParametersConfig = array|null>|null> - * @psalm-type ArgumentsType = list|array - * @psalm-type CallType = array|array{0:string, 1?:ArgumentsType, 2?:bool}|array{method:string, arguments?:ArgumentsType, returns_clone?:bool} - * @psalm-type TagsType = list>> // arrays inside the list must have only one element, with the tag name as the key - * @psalm-type CallbackType = string|array{0:string|ReferenceConfigurator,1:string}|\Closure|ReferenceConfigurator|ExpressionConfigurator - * @psalm-type DeprecationType = array{package: string, version: string, message?: string} - * @psalm-type DefaultsType = array{ - * public?: bool, - * tags?: TagsType, - * resource_tags?: TagsType, - * autowire?: bool, - * autoconfigure?: bool, - * bind?: array, - * } - * @psalm-type InstanceofType = array{ - * shared?: bool, - * lazy?: bool|string, - * public?: bool, - * properties?: array, - * configurator?: CallbackType, - * calls?: list, - * tags?: TagsType, - * resource_tags?: TagsType, - * autowire?: bool, - * bind?: array, - * constructor?: string, - * } - * @psalm-type DefinitionType = array{ - * class?: string, - * file?: string, - * parent?: string, - * shared?: bool, - * synthetic?: bool, - * lazy?: bool|string, - * public?: bool, - * abstract?: bool, - * deprecated?: DeprecationType, - * factory?: CallbackType, - * configurator?: CallbackType, - * arguments?: ArgumentsType, - * properties?: array, - * calls?: list, - * tags?: TagsType, - * resource_tags?: TagsType, - * decorates?: string, - * decoration_inner_name?: string, - * decoration_priority?: int, - * decoration_on_invalid?: 'exception'|'ignore'|null, - * autowire?: bool, - * autoconfigure?: bool, - * bind?: array, - * constructor?: string, - * from_callable?: CallbackType, - * } - * @psalm-type AliasType = string|array{ - * alias: string, - * public?: bool, - * deprecated?: DeprecationType, - * } - * @psalm-type PrototypeType = array{ - * resource: string, - * namespace?: string, - * exclude?: string|list, - * parent?: string, - * shared?: bool, - * lazy?: bool|string, - * public?: bool, - * abstract?: bool, - * deprecated?: DeprecationType, - * factory?: CallbackType, - * arguments?: ArgumentsType, - * properties?: array, - * configurator?: CallbackType, - * calls?: list, - * tags?: TagsType, - * resource_tags?: TagsType, - * autowire?: bool, - * autoconfigure?: bool, - * bind?: array, - * constructor?: string, - * } - * @psalm-type StackType = array{ - * stack: list>, - * public?: bool, - * deprecated?: DeprecationType, - * } - * @psalm-type ServicesConfig = array{ - * _defaults?: DefaultsType, - * _instanceof?: InstanceofType, - * ... - * } - * @psalm-type ExtensionType = array - * @psalm-type FrameworkConfig = array{ - * secret?: scalar|null, - * http_method_override?: bool, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false - * allowed_http_method_override?: list|null, - * trust_x_sendfile_type_header?: scalar|null, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%" - * ide?: scalar|null, // Default: "%env(default::SYMFONY_IDE)%" - * test?: bool, - * default_locale?: scalar|null, // Default: "en" - * set_locale_from_accept_language?: bool, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false - * set_content_language_from_locale?: bool, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false - * enabled_locales?: list, - * trusted_hosts?: list, - * trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"] - * trusted_headers?: list, - * error_controller?: scalar|null, // Default: "error_controller" - * handle_all_throwables?: bool, // HttpKernel will handle all kinds of \Throwable. // Default: true - * csrf_protection?: bool|array{ - * enabled?: scalar|null, // Default: null - * stateless_token_ids?: list, - * check_header?: scalar|null, // Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. // Default: false - * cookie_name?: scalar|null, // The name of the cookie to use when using stateless protection. // Default: "csrf-token" - * }, - * form?: bool|array{ // Form configuration - * enabled?: bool, // Default: true - * csrf_protection?: array{ - * enabled?: scalar|null, // Default: null - * token_id?: scalar|null, // Default: null - * field_name?: scalar|null, // Default: "_token" - * field_attr?: array, - * }, - * }, - * http_cache?: bool|array{ // HTTP cache configuration - * enabled?: bool, // Default: false - * debug?: bool, // Default: "%kernel.debug%" - * trace_level?: "none"|"short"|"full", - * trace_header?: scalar|null, - * default_ttl?: int, - * private_headers?: list, - * skip_response_headers?: list, - * allow_reload?: bool, - * allow_revalidate?: bool, - * stale_while_revalidate?: int, - * stale_if_error?: int, - * terminate_on_cache_hit?: bool, - * }, - * esi?: bool|array{ // ESI configuration - * enabled?: bool, // Default: false - * }, - * ssi?: bool|array{ // SSI configuration - * enabled?: bool, // Default: false - * }, - * fragments?: bool|array{ // Fragments configuration - * enabled?: bool, // Default: false - * hinclude_default_template?: scalar|null, // Default: null - * path?: scalar|null, // Default: "/_fragment" - * }, - * profiler?: bool|array{ // Profiler configuration - * enabled?: bool, // Default: false - * collect?: bool, // Default: true - * collect_parameter?: scalar|null, // The name of the parameter to use to enable or disable collection on a per request basis. // Default: null - * only_exceptions?: bool, // Default: false - * only_main_requests?: bool, // Default: false - * dsn?: scalar|null, // Default: "file:%kernel.cache_dir%/profiler" - * collect_serializer_data?: bool, // Enables the serializer data collector and profiler panel. // Default: false - * }, - * workflows?: bool|array{ - * enabled?: bool, // Default: false - * workflows?: array, - * definition_validators?: list, - * support_strategy?: scalar|null, - * initial_marking?: list, - * events_to_dispatch?: list|null, - * places?: list, - * }>, - * transitions: list, - * to?: list, - * weight?: int, // Default: 1 - * metadata?: list, - * }>, - * metadata?: list, - * }>, - * }, - * router?: bool|array{ // Router configuration - * enabled?: bool, // Default: false - * resource: scalar|null, - * type?: scalar|null, - * cache_dir?: scalar|null, // Deprecated: Setting the "framework.router.cache_dir.cache_dir" configuration option is deprecated. It will be removed in version 8.0. // Default: "%kernel.build_dir%" - * default_uri?: scalar|null, // The default URI used to generate URLs in a non-HTTP context. // Default: null - * http_port?: scalar|null, // Default: 80 - * https_port?: scalar|null, // Default: 443 - * strict_requirements?: scalar|null, // set to true to throw an exception when a parameter does not match the requirements set to false to disable exceptions when a parameter does not match the requirements (and return null instead) set to null to disable parameter checks against requirements 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production // Default: true - * utf8?: bool, // Default: true - * }, - * session?: bool|array{ // Session configuration - * enabled?: bool, // Default: false - * storage_factory_id?: scalar|null, // Default: "session.storage.factory.native" - * handler_id?: scalar|null, // Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null. - * name?: scalar|null, - * cookie_lifetime?: scalar|null, - * cookie_path?: scalar|null, - * cookie_domain?: scalar|null, - * cookie_secure?: true|false|"auto", // Default: "auto" - * cookie_httponly?: bool, // Default: true - * cookie_samesite?: null|"lax"|"strict"|"none", // Default: "lax" - * use_cookies?: bool, - * gc_divisor?: scalar|null, - * gc_probability?: scalar|null, - * gc_maxlifetime?: scalar|null, - * save_path?: scalar|null, // Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null. - * metadata_update_threshold?: int, // Seconds to wait between 2 session metadata updates. // Default: 0 - * sid_length?: int, // Deprecated: Setting the "framework.session.sid_length.sid_length" configuration option is deprecated. It will be removed in version 8.0. No alternative is provided as PHP 8.4 has deprecated the related option. - * sid_bits_per_character?: int, // Deprecated: Setting the "framework.session.sid_bits_per_character.sid_bits_per_character" configuration option is deprecated. It will be removed in version 8.0. No alternative is provided as PHP 8.4 has deprecated the related option. - * }, - * request?: bool|array{ // Request configuration - * enabled?: bool, // Default: false - * formats?: array>, - * }, - * assets?: bool|array{ // Assets configuration - * enabled?: bool, // Default: true - * strict_mode?: bool, // Throw an exception if an entry is missing from the manifest.json. // Default: false - * version_strategy?: scalar|null, // Default: null - * version?: scalar|null, // Default: null - * version_format?: scalar|null, // Default: "%%s?%%s" - * json_manifest_path?: scalar|null, // Default: null - * base_path?: scalar|null, // Default: "" - * base_urls?: list, - * packages?: array, - * }>, - * }, - * asset_mapper?: bool|array{ // Asset Mapper configuration - * enabled?: bool, // Default: false - * paths?: array, - * excluded_patterns?: list, - * exclude_dotfiles?: bool, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true - * server?: bool, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true - * public_prefix?: scalar|null, // The public path where the assets will be written to (and served from when "server" is true). // Default: "/assets/" - * missing_import_mode?: "strict"|"warn"|"ignore", // Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import './non-existent.js'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is. // Default: "warn" - * extensions?: array, - * importmap_path?: scalar|null, // The path of the importmap.php file. // Default: "%kernel.project_dir%/importmap.php" - * importmap_polyfill?: scalar|null, // The importmap name that will be used to load the polyfill. Set to false to disable. // Default: "es-module-shims" - * importmap_script_attributes?: array, - * vendor_dir?: scalar|null, // The directory to store JavaScript vendors. // Default: "%kernel.project_dir%/assets/vendor" - * precompress?: bool|array{ // Precompress assets with Brotli, Zstandard and gzip. - * enabled?: bool, // Default: false - * formats?: list, - * extensions?: list, - * }, - * }, - * translator?: bool|array{ // Translator configuration - * enabled?: bool, // Default: true - * fallbacks?: list, - * logging?: bool, // Default: false - * formatter?: scalar|null, // Default: "translator.formatter.default" - * cache_dir?: scalar|null, // Default: "%kernel.cache_dir%/translations" - * default_path?: scalar|null, // The default path used to load translations. // Default: "%kernel.project_dir%/translations" - * paths?: list, - * pseudo_localization?: bool|array{ - * enabled?: bool, // Default: false - * accents?: bool, // Default: true - * expansion_factor?: float, // Default: 1.0 - * brackets?: bool, // Default: true - * parse_html?: bool, // Default: false - * localizable_html_attributes?: list, - * }, - * providers?: array, - * locales?: list, - * }>, - * globals?: array, - * domain?: string, - * }>, - * }, - * validation?: bool|array{ // Validation configuration - * enabled?: bool, // Default: true - * cache?: scalar|null, // Deprecated: Setting the "framework.validation.cache.cache" configuration option is deprecated. It will be removed in version 8.0. - * enable_attributes?: bool, // Default: true - * static_method?: list, - * translation_domain?: scalar|null, // Default: "validators" - * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|"loose", // Default: "html5" - * mapping?: array{ - * paths?: list, - * }, - * not_compromised_password?: bool|array{ - * enabled?: bool, // When disabled, compromised passwords will be accepted as valid. // Default: true - * endpoint?: scalar|null, // API endpoint for the NotCompromisedPassword Validator. // Default: null - * }, - * disable_translation?: bool, // Default: false - * auto_mapping?: array, - * }>, - * }, - * annotations?: bool|array{ - * enabled?: bool, // Default: false - * }, - * serializer?: bool|array{ // Serializer configuration - * enabled?: bool, // Default: true - * enable_attributes?: bool, // Default: true - * name_converter?: scalar|null, - * circular_reference_handler?: scalar|null, - * max_depth_handler?: scalar|null, - * mapping?: array{ - * paths?: list, - * }, - * default_context?: list, - * named_serializers?: array, - * include_built_in_normalizers?: bool, // Whether to include the built-in normalizers // Default: true - * include_built_in_encoders?: bool, // Whether to include the built-in encoders // Default: true - * }>, - * }, - * property_access?: bool|array{ // Property access configuration - * enabled?: bool, // Default: true - * magic_call?: bool, // Default: false - * magic_get?: bool, // Default: true - * magic_set?: bool, // Default: true - * throw_exception_on_invalid_index?: bool, // Default: false - * throw_exception_on_invalid_property_path?: bool, // Default: true - * }, - * type_info?: bool|array{ // Type info configuration - * enabled?: bool, // Default: true - * aliases?: array, - * }, - * property_info?: bool|array{ // Property info configuration - * enabled?: bool, // Default: true - * with_constructor_extractor?: bool, // Registers the constructor extractor. - * }, - * cache?: array{ // Cache configuration - * prefix_seed?: scalar|null, // Used to namespace cache keys when using several apps with the same shared backend. // Default: "_%kernel.project_dir%.%kernel.container_class%" - * app?: scalar|null, // App related cache pools configuration. // Default: "cache.adapter.filesystem" - * system?: scalar|null, // System related cache pools configuration. // Default: "cache.adapter.system" - * directory?: scalar|null, // Default: "%kernel.share_dir%/pools/app" - * default_psr6_provider?: scalar|null, - * default_redis_provider?: scalar|null, // Default: "redis://localhost" - * default_valkey_provider?: scalar|null, // Default: "valkey://localhost" - * default_memcached_provider?: scalar|null, // Default: "memcached://localhost" - * default_doctrine_dbal_provider?: scalar|null, // Default: "database_connection" - * default_pdo_provider?: scalar|null, // Default: null - * pools?: array, - * tags?: scalar|null, // Default: null - * public?: bool, // Default: false - * default_lifetime?: scalar|null, // Default lifetime of the pool. - * provider?: scalar|null, // Overwrite the setting from the default provider for this adapter. - * early_expiration_message_bus?: scalar|null, - * clearer?: scalar|null, - * }>, - * }, - * php_errors?: array{ // PHP errors handling configuration - * log?: mixed, // Use the application logger instead of the PHP logger for logging PHP errors. // Default: true - * throw?: bool, // Throw PHP errors as \ErrorException instances. // Default: true - * }, - * exceptions?: array, - * web_link?: bool|array{ // Web links configuration - * enabled?: bool, // Default: true - * }, - * lock?: bool|string|array{ // Lock configuration - * enabled?: bool, // Default: false - * resources?: array>, - * }, - * semaphore?: bool|string|array{ // Semaphore configuration - * enabled?: bool, // Default: false - * resources?: array, - * }, - * messenger?: bool|array{ // Messenger configuration - * enabled?: bool, // Default: false - * routing?: array, - * }>, - * serializer?: array{ - * default_serializer?: scalar|null, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" - * symfony_serializer?: array{ - * format?: scalar|null, // Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default). // Default: "json" - * context?: array, - * }, - * }, - * transports?: array, - * failure_transport?: scalar|null, // Transport name to send failed messages to (after all retries have failed). // Default: null - * retry_strategy?: string|array{ - * service?: scalar|null, // Service id to override the retry strategy entirely. // Default: null - * max_retries?: int, // Default: 3 - * delay?: int, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 - * multiplier?: float, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2 - * max_delay?: int, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 - * jitter?: float, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1 - * }, - * rate_limiter?: scalar|null, // Rate limiter name to use when processing messages. // Default: null - * }>, - * failure_transport?: scalar|null, // Transport name to send failed messages to (after all retries have failed). // Default: null - * stop_worker_on_signals?: list, - * default_bus?: scalar|null, // Default: null - * buses?: array, - * }>, - * }>, - * }, - * scheduler?: bool|array{ // Scheduler configuration - * enabled?: bool, // Default: false - * }, - * disallow_search_engine_index?: bool, // Enabled by default when debug is enabled. // Default: true - * http_client?: bool|array{ // HTTP Client configuration - * enabled?: bool, // Default: true - * max_host_connections?: int, // The maximum number of connections to a single host. - * default_options?: array{ - * headers?: array, - * vars?: list, - * max_redirects?: int, // The maximum number of redirects to follow. - * http_version?: scalar|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. - * resolve?: array, - * proxy?: scalar|null, // The URL of the proxy to pass requests through or null for automatic detection. - * no_proxy?: scalar|null, // A comma separated list of hosts that do not require a proxy to be reached. - * timeout?: float, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. - * max_duration?: float, // The maximum execution time for the request+response as a whole. - * bindto?: scalar|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. - * verify_peer?: bool, // Indicates if the peer should be verified in a TLS context. - * verify_host?: bool, // Indicates if the host should exist as a certificate common name. - * cafile?: scalar|null, // A certificate authority file. - * capath?: scalar|null, // A directory that contains multiple certificate authority files. - * local_cert?: scalar|null, // A PEM formatted certificate file. - * local_pk?: scalar|null, // A private key file. - * passphrase?: scalar|null, // The passphrase used to encrypt the "local_pk" file. - * ciphers?: scalar|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...) - * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). - * sha1?: mixed, - * pin-sha256?: mixed, - * md5?: mixed, - * }, - * crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. - * extra?: list, - * rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null - * caching?: bool|array{ // Caching configuration. - * enabled?: bool, // Default: false - * cache_pool?: string, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" - * shared?: bool, // Indicates whether the cache is shared (public) or private. // Default: true - * max_ttl?: int, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null - * }, - * retry_failed?: bool|array{ - * enabled?: bool, // Default: false - * retry_strategy?: scalar|null, // service id to override the retry strategy. // Default: null - * http_codes?: array, - * }>, - * max_retries?: int, // Default: 3 - * delay?: int, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 - * multiplier?: float, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 - * max_delay?: int, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 - * jitter?: float, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 - * }, - * }, - * mock_response_factory?: scalar|null, // The id of the service that should generate mock responses. It should be either an invokable or an iterable. - * scoped_clients?: array, - * headers?: array, - * max_redirects?: int, // The maximum number of redirects to follow. - * http_version?: scalar|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. - * resolve?: array, - * proxy?: scalar|null, // The URL of the proxy to pass requests through or null for automatic detection. - * no_proxy?: scalar|null, // A comma separated list of hosts that do not require a proxy to be reached. - * timeout?: float, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. - * max_duration?: float, // The maximum execution time for the request+response as a whole. - * bindto?: scalar|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. - * verify_peer?: bool, // Indicates if the peer should be verified in a TLS context. - * verify_host?: bool, // Indicates if the host should exist as a certificate common name. - * cafile?: scalar|null, // A certificate authority file. - * capath?: scalar|null, // A directory that contains multiple certificate authority files. - * local_cert?: scalar|null, // A PEM formatted certificate file. - * local_pk?: scalar|null, // A private key file. - * passphrase?: scalar|null, // The passphrase used to encrypt the "local_pk" file. - * ciphers?: scalar|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...). - * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). - * sha1?: mixed, - * pin-sha256?: mixed, - * md5?: mixed, - * }, - * crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. - * extra?: list, - * rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null - * caching?: bool|array{ // Caching configuration. - * enabled?: bool, // Default: false - * cache_pool?: string, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" - * shared?: bool, // Indicates whether the cache is shared (public) or private. // Default: true - * max_ttl?: int, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null - * }, - * retry_failed?: bool|array{ - * enabled?: bool, // Default: false - * retry_strategy?: scalar|null, // service id to override the retry strategy. // Default: null - * http_codes?: array, - * }>, - * max_retries?: int, // Default: 3 - * delay?: int, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 - * multiplier?: float, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 - * max_delay?: int, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 - * jitter?: float, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 - * }, - * }>, - * }, - * mailer?: bool|array{ // Mailer configuration - * enabled?: bool, // Default: true - * message_bus?: scalar|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null - * dsn?: scalar|null, // Default: null - * transports?: array, - * envelope?: array{ // Mailer Envelope configuration - * sender?: scalar|null, - * recipients?: list, - * allowed_recipients?: list, - * }, - * headers?: array, - * dkim_signer?: bool|array{ // DKIM signer configuration - * enabled?: bool, // Default: false - * key?: scalar|null, // Key content, or path to key (in PEM format with the `file://` prefix) // Default: "" - * domain?: scalar|null, // Default: "" - * select?: scalar|null, // Default: "" - * passphrase?: scalar|null, // The private key passphrase // Default: "" - * options?: array, - * }, - * smime_signer?: bool|array{ // S/MIME signer configuration - * enabled?: bool, // Default: false - * key?: scalar|null, // Path to key (in PEM format) // Default: "" - * certificate?: scalar|null, // Path to certificate (in PEM format without the `file://` prefix) // Default: "" - * passphrase?: scalar|null, // The private key passphrase // Default: null - * extra_certificates?: scalar|null, // Default: null - * sign_options?: int, // Default: null - * }, - * smime_encrypter?: bool|array{ // S/MIME encrypter configuration - * enabled?: bool, // Default: false - * repository?: scalar|null, // S/MIME certificate repository service. This service shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`. // Default: "" - * cipher?: int, // A set of algorithms used to encrypt the message // Default: null - * }, - * }, - * secrets?: bool|array{ - * enabled?: bool, // Default: true - * vault_directory?: scalar|null, // Default: "%kernel.project_dir%/config/secrets/%kernel.runtime_environment%" - * local_dotenv_file?: scalar|null, // Default: "%kernel.project_dir%/.env.%kernel.runtime_environment%.local" - * decryption_env_var?: scalar|null, // Default: "base64:default::SYMFONY_DECRYPTION_SECRET" - * }, - * notifier?: bool|array{ // Notifier configuration - * enabled?: bool, // Default: false - * message_bus?: scalar|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null - * chatter_transports?: array, - * texter_transports?: array, - * notification_on_failed_messages?: bool, // Default: false - * channel_policy?: array>, - * admin_recipients?: list, - * }, - * rate_limiter?: bool|array{ // Rate limiter configuration - * enabled?: bool, // Default: true - * limiters?: array, - * limit?: int, // The maximum allowed hits in a fixed interval or burst. - * interval?: scalar|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). - * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". - * interval?: scalar|null, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). - * amount?: int, // Amount of tokens to add each interval. // Default: 1 - * }, - * }>, - * }, - * uid?: bool|array{ // Uid configuration - * enabled?: bool, // Default: true - * default_uuid_version?: 7|6|4|1, // Default: 7 - * name_based_uuid_version?: 5|3, // Default: 5 - * name_based_uuid_namespace?: scalar|null, - * time_based_uuid_version?: 7|6|1, // Default: 7 - * time_based_uuid_node?: scalar|null, - * }, - * html_sanitizer?: bool|array{ // HtmlSanitizer configuration - * enabled?: bool, // Default: false - * sanitizers?: array, - * block_elements?: list, - * drop_elements?: list, - * allow_attributes?: array, - * drop_attributes?: array, - * force_attributes?: array>, - * force_https_urls?: bool, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false - * allowed_link_schemes?: list, - * allowed_link_hosts?: list|null, - * allow_relative_links?: bool, // Allows relative URLs to be used in links href attributes. // Default: false - * allowed_media_schemes?: list, - * allowed_media_hosts?: list|null, - * allow_relative_medias?: bool, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false - * with_attribute_sanitizers?: list, - * without_attribute_sanitizers?: list, - * max_input_length?: int, // The maximum length allowed for the sanitized input. // Default: 0 - * }>, - * }, - * webhook?: bool|array{ // Webhook configuration - * enabled?: bool, // Default: false - * message_bus?: scalar|null, // The message bus to use. // Default: "messenger.default_bus" - * routing?: array, - * }, - * remote-event?: bool|array{ // RemoteEvent configuration - * enabled?: bool, // Default: false - * }, - * json_streamer?: bool|array{ // JSON streamer configuration - * enabled?: bool, // Default: false - * }, - * } - * @psalm-type DoctrineConfig = array{ - * dbal?: array{ - * default_connection?: scalar|null, - * types?: array, - * driver_schemes?: array, - * connections?: array, - * mapping_types?: array, - * default_table_options?: array, - * schema_manager_factory?: scalar|null, // Default: "doctrine.dbal.default_schema_manager_factory" - * result_cache?: scalar|null, - * slaves?: array, - * replicas?: array, - * }>, - * }, - * orm?: array{ - * default_entity_manager?: scalar|null, - * auto_generate_proxy_classes?: scalar|null, // Auto generate mode possible values are: "NEVER", "ALWAYS", "FILE_NOT_EXISTS", "EVAL", "FILE_NOT_EXISTS_OR_CHANGED", this option is ignored when the "enable_native_lazy_objects" option is true // Default: false - * enable_lazy_ghost_objects?: bool, // Enables the new implementation of proxies based on lazy ghosts instead of using the legacy implementation // Default: true - * enable_native_lazy_objects?: bool, // Enables the new native implementation of PHP lazy objects instead of generated proxies // Default: false - * proxy_dir?: scalar|null, // Configures the path where generated proxy classes are saved when using non-native lazy objects, this option is ignored when the "enable_native_lazy_objects" option is true // Default: "%kernel.build_dir%/doctrine/orm/Proxies" - * proxy_namespace?: scalar|null, // Defines the root namespace for generated proxy classes when using non-native lazy objects, this option is ignored when the "enable_native_lazy_objects" option is true // Default: "Proxies" - * controller_resolver?: bool|array{ - * enabled?: bool, // Default: true - * auto_mapping?: bool|null, // Set to false to disable using route placeholders as lookup criteria when the primary key doesn't match the argument name // Default: null - * evict_cache?: bool, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false - * }, - * entity_managers?: array, - * }>, - * }>, - * }, - * connection?: scalar|null, - * class_metadata_factory_name?: scalar|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" - * default_repository_class?: scalar|null, // Default: "Doctrine\\ORM\\EntityRepository" - * auto_mapping?: scalar|null, // Default: false - * naming_strategy?: scalar|null, // Default: "doctrine.orm.naming_strategy.default" - * quote_strategy?: scalar|null, // Default: "doctrine.orm.quote_strategy.default" - * typed_field_mapper?: scalar|null, // Default: "doctrine.orm.typed_field_mapper.default" - * entity_listener_resolver?: scalar|null, // Default: null - * fetch_mode_subselect_batch_size?: scalar|null, - * repository_factory?: scalar|null, // Default: "doctrine.orm.container_repository_factory" - * schema_ignore_classes?: list, - * report_fields_where_declared?: bool, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.16 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/10455. // Default: true - * validate_xml_mapping?: bool, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14. See https://github.com/doctrine/orm/pull/6728. // Default: false - * second_level_cache?: array{ - * region_cache_driver?: string|array{ - * type?: scalar|null, // Default: null - * id?: scalar|null, - * pool?: scalar|null, - * }, - * region_lock_lifetime?: scalar|null, // Default: 60 - * log_enabled?: bool, // Default: true - * region_lifetime?: scalar|null, // Default: 3600 - * enabled?: bool, // Default: true - * factory?: scalar|null, - * regions?: array, - * loggers?: array, - * }, - * hydrators?: array, - * mappings?: array, - * dql?: array{ - * string_functions?: array, - * numeric_functions?: array, - * datetime_functions?: array, - * }, - * filters?: array, - * }>, - * identity_generation_preferences?: array, - * }>, - * resolve_target_entities?: array, - * }, - * } - * @psalm-type DoctrineMigrationsConfig = array{ - * enable_service_migrations?: bool, // Whether to enable fetching migrations from the service container. // Default: false - * migrations_paths?: array, - * services?: array, - * factories?: array, - * storage?: array{ // Storage to use for migration status metadata. - * table_storage?: array{ // The default metadata storage, implemented as a table in the database. - * table_name?: scalar|null, // Default: null - * version_column_name?: scalar|null, // Default: null - * version_column_length?: scalar|null, // Default: null - * executed_at_column_name?: scalar|null, // Default: null - * execution_time_column_name?: scalar|null, // Default: null - * }, - * }, - * migrations?: list, - * connection?: scalar|null, // Connection name to use for the migrations database. // Default: null - * em?: scalar|null, // Entity manager name to use for the migrations database (available when doctrine/orm is installed). // Default: null - * all_or_nothing?: scalar|null, // Run all migrations in a transaction. // Default: false - * check_database_platform?: scalar|null, // Adds an extra check in the generated migrations to allow execution only on the same platform as they were initially generated on. // Default: true - * custom_template?: scalar|null, // Custom template path for generated migration classes. // Default: null - * organize_migrations?: scalar|null, // Organize migrations mode. Possible values are: "BY_YEAR", "BY_YEAR_AND_MONTH", false // Default: false - * enable_profiler?: bool, // Whether or not to enable the profiler collector to calculate and visualize migration status. This adds some queries overhead. // Default: false - * transactional?: bool, // Whether or not to wrap migrations in a single transaction. // Default: true - * } - * @psalm-type SecurityConfig = array{ - * access_denied_url?: scalar|null, // Default: null - * session_fixation_strategy?: "none"|"migrate"|"invalidate", // Default: "migrate" - * hide_user_not_found?: bool, // Deprecated: The "hide_user_not_found" option is deprecated and will be removed in 8.0. Use the "expose_security_errors" option instead. - * expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All, // Default: "none" - * erase_credentials?: bool, // Default: true - * access_decision_manager?: array{ - * strategy?: "affirmative"|"consensus"|"unanimous"|"priority", - * service?: scalar|null, - * strategy_service?: scalar|null, - * allow_if_all_abstain?: bool, // Default: false - * allow_if_equal_granted_denied?: bool, // Default: true - * }, - * password_hashers?: array, - * hash_algorithm?: scalar|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" - * key_length?: scalar|null, // Default: 40 - * ignore_case?: bool, // Default: false - * encode_as_base64?: bool, // Default: true - * iterations?: scalar|null, // Default: 5000 - * cost?: int, // Default: null - * memory_cost?: scalar|null, // Default: null - * time_cost?: scalar|null, // Default: null - * id?: scalar|null, - * }>, - * providers?: array, - * }, - * entity?: array{ - * class: scalar|null, // The full entity class name of your user class. - * property?: scalar|null, // Default: null - * manager_name?: scalar|null, // Default: null - * }, - * memory?: array{ - * users?: array, - * }>, - * }, - * ldap?: array{ - * service: scalar|null, - * base_dn: scalar|null, - * search_dn?: scalar|null, // Default: null - * search_password?: scalar|null, // Default: null - * extra_fields?: list, - * default_roles?: list, - * role_fetcher?: scalar|null, // Default: null - * uid_key?: scalar|null, // Default: "sAMAccountName" - * filter?: scalar|null, // Default: "({uid_key}={user_identifier})" - * password_attribute?: scalar|null, // Default: null - * }, - * saml?: array{ - * user_class: scalar|null, - * default_roles?: list, - * }, - * }>, - * firewalls: array, - * security?: bool, // Default: true - * user_checker?: scalar|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" - * request_matcher?: scalar|null, - * access_denied_url?: scalar|null, - * access_denied_handler?: scalar|null, - * entry_point?: scalar|null, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface". - * provider?: scalar|null, - * stateless?: bool, // Default: false - * lazy?: bool, // Default: false - * context?: scalar|null, - * logout?: array{ - * enable_csrf?: bool|null, // Default: null - * csrf_token_id?: scalar|null, // Default: "logout" - * csrf_parameter?: scalar|null, // Default: "_csrf_token" - * csrf_token_manager?: scalar|null, - * path?: scalar|null, // Default: "/logout" - * target?: scalar|null, // Default: "/" - * invalidate_session?: bool, // Default: true - * clear_site_data?: list<"*"|"cache"|"cookies"|"storage"|"executionContexts">, - * delete_cookies?: array, - * }, - * switch_user?: array{ - * provider?: scalar|null, - * parameter?: scalar|null, // Default: "_switch_user" - * role?: scalar|null, // Default: "ROLE_ALLOWED_TO_SWITCH" - * target_route?: scalar|null, // Default: null - * }, - * required_badges?: list, - * custom_authenticators?: list, - * login_throttling?: array{ - * limiter?: scalar|null, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface". - * max_attempts?: int, // Default: 5 - * interval?: scalar|null, // Default: "1 minute" - * lock_factory?: scalar|null, // The service ID of the lock factory used by the login rate limiter (or null to disable locking). // Default: null - * cache_pool?: string, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter" - * storage_service?: string, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null - * }, - * two_factor?: array{ - * check_path?: scalar|null, // Default: "/2fa_check" - * post_only?: bool, // Default: true - * auth_form_path?: scalar|null, // Default: "/2fa" - * always_use_default_target_path?: bool, // Default: false - * default_target_path?: scalar|null, // Default: "/" - * success_handler?: scalar|null, // Default: null - * failure_handler?: scalar|null, // Default: null - * authentication_required_handler?: scalar|null, // Default: null - * auth_code_parameter_name?: scalar|null, // Default: "_auth_code" - * trusted_parameter_name?: scalar|null, // Default: "_trusted" - * remember_me_sets_trusted?: scalar|null, // Default: false - * multi_factor?: bool, // Default: false - * prepare_on_login?: bool, // Default: false - * prepare_on_access_denied?: bool, // Default: false - * enable_csrf?: scalar|null, // Default: false - * csrf_parameter?: scalar|null, // Default: "_csrf_token" - * csrf_token_id?: scalar|null, // Default: "two_factor" - * csrf_header?: scalar|null, // Default: null - * csrf_token_manager?: scalar|null, // Default: "scheb_two_factor.csrf_token_manager" - * provider?: scalar|null, // Default: null - * }, - * webauthn?: array{ - * user_provider?: scalar|null, // Default: null - * options_storage?: scalar|null, // Deprecated: The child node "options_storage" at path "security.firewalls..webauthn.options_storage" is deprecated. Please use the root option "options_storage" instead. // Default: null - * success_handler?: scalar|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultSuccessHandler" - * failure_handler?: scalar|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultFailureHandler" - * secured_rp_ids?: array, - * authentication?: bool|array{ - * enabled?: bool, // Default: true - * profile?: scalar|null, // Default: "default" - * options_builder?: scalar|null, // Default: null - * routes?: array{ - * host?: scalar|null, // Default: null - * options_method?: scalar|null, // Default: "POST" - * options_path?: scalar|null, // Default: "/login/options" - * result_method?: scalar|null, // Default: "POST" - * result_path?: scalar|null, // Default: "/login" - * }, - * options_handler?: scalar|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultRequestOptionsHandler" - * }, - * registration?: bool|array{ - * enabled?: bool, // Default: false - * profile?: scalar|null, // Default: "default" - * options_builder?: scalar|null, // Default: null - * routes?: array{ - * host?: scalar|null, // Default: null - * options_method?: scalar|null, // Default: "POST" - * options_path?: scalar|null, // Default: "/register/options" - * result_method?: scalar|null, // Default: "POST" - * result_path?: scalar|null, // Default: "/register" - * }, - * options_handler?: scalar|null, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultCreationOptionsHandler" - * }, - * }, - * x509?: array{ - * provider?: scalar|null, - * user?: scalar|null, // Default: "SSL_CLIENT_S_DN_Email" - * credentials?: scalar|null, // Default: "SSL_CLIENT_S_DN" - * user_identifier?: scalar|null, // Default: "emailAddress" - * }, - * remote_user?: array{ - * provider?: scalar|null, - * user?: scalar|null, // Default: "REMOTE_USER" - * }, - * saml?: array{ - * provider?: scalar|null, - * remember_me?: bool, // Default: true - * success_handler?: scalar|null, // Default: "Nbgrp\\OneloginSamlBundle\\Security\\Http\\Authentication\\SamlAuthenticationSuccessHandler" - * failure_handler?: scalar|null, - * check_path?: scalar|null, // Default: "/login_check" - * use_forward?: bool, // Default: false - * login_path?: scalar|null, // Default: "/login" - * identifier_attribute?: scalar|null, // Default: null - * use_attribute_friendly_name?: bool, // Default: false - * user_factory?: scalar|null, // Default: null - * token_factory?: scalar|null, // Default: null - * persist_user?: bool, // Default: false - * always_use_default_target_path?: bool, // Default: false - * default_target_path?: scalar|null, // Default: "/" - * target_path_parameter?: scalar|null, // Default: "_target_path" - * use_referer?: bool, // Default: false - * failure_path?: scalar|null, // Default: null - * failure_forward?: bool, // Default: false - * failure_path_parameter?: scalar|null, // Default: "_failure_path" - * }, - * login_link?: array{ - * check_route: scalar|null, // Route that will validate the login link - e.g. "app_login_link_verify". - * check_post_only?: scalar|null, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false - * signature_properties: list, - * lifetime?: int, // The lifetime of the login link in seconds. // Default: 600 - * max_uses?: int, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null - * used_link_cache?: scalar|null, // Cache service id used to expired links of max_uses is set. - * success_handler?: scalar|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. - * failure_handler?: scalar|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. - * provider?: scalar|null, // The user provider to load users from. - * secret?: scalar|null, // Default: "%kernel.secret%" - * always_use_default_target_path?: bool, // Default: false - * default_target_path?: scalar|null, // Default: "/" - * login_path?: scalar|null, // Default: "/login" - * target_path_parameter?: scalar|null, // Default: "_target_path" - * use_referer?: bool, // Default: false - * failure_path?: scalar|null, // Default: null - * failure_forward?: bool, // Default: false - * failure_path_parameter?: scalar|null, // Default: "_failure_path" - * }, - * form_login?: array{ - * provider?: scalar|null, - * remember_me?: bool, // Default: true - * success_handler?: scalar|null, - * failure_handler?: scalar|null, - * check_path?: scalar|null, // Default: "/login_check" - * use_forward?: bool, // Default: false - * login_path?: scalar|null, // Default: "/login" - * username_parameter?: scalar|null, // Default: "_username" - * password_parameter?: scalar|null, // Default: "_password" - * csrf_parameter?: scalar|null, // Default: "_csrf_token" - * csrf_token_id?: scalar|null, // Default: "authenticate" - * enable_csrf?: bool, // Default: false - * post_only?: bool, // Default: true - * form_only?: bool, // Default: false - * always_use_default_target_path?: bool, // Default: false - * default_target_path?: scalar|null, // Default: "/" - * target_path_parameter?: scalar|null, // Default: "_target_path" - * use_referer?: bool, // Default: false - * failure_path?: scalar|null, // Default: null - * failure_forward?: bool, // Default: false - * failure_path_parameter?: scalar|null, // Default: "_failure_path" - * }, - * form_login_ldap?: array{ - * provider?: scalar|null, - * remember_me?: bool, // Default: true - * success_handler?: scalar|null, - * failure_handler?: scalar|null, - * check_path?: scalar|null, // Default: "/login_check" - * use_forward?: bool, // Default: false - * login_path?: scalar|null, // Default: "/login" - * username_parameter?: scalar|null, // Default: "_username" - * password_parameter?: scalar|null, // Default: "_password" - * csrf_parameter?: scalar|null, // Default: "_csrf_token" - * csrf_token_id?: scalar|null, // Default: "authenticate" - * enable_csrf?: bool, // Default: false - * post_only?: bool, // Default: true - * form_only?: bool, // Default: false - * always_use_default_target_path?: bool, // Default: false - * default_target_path?: scalar|null, // Default: "/" - * target_path_parameter?: scalar|null, // Default: "_target_path" - * use_referer?: bool, // Default: false - * failure_path?: scalar|null, // Default: null - * failure_forward?: bool, // Default: false - * failure_path_parameter?: scalar|null, // Default: "_failure_path" - * service?: scalar|null, // Default: "ldap" - * dn_string?: scalar|null, // Default: "{user_identifier}" - * query_string?: scalar|null, - * search_dn?: scalar|null, // Default: "" - * search_password?: scalar|null, // Default: "" - * }, - * json_login?: array{ - * provider?: scalar|null, - * remember_me?: bool, // Default: true - * success_handler?: scalar|null, - * failure_handler?: scalar|null, - * check_path?: scalar|null, // Default: "/login_check" - * use_forward?: bool, // Default: false - * login_path?: scalar|null, // Default: "/login" - * username_path?: scalar|null, // Default: "username" - * password_path?: scalar|null, // Default: "password" - * }, - * json_login_ldap?: array{ - * provider?: scalar|null, - * remember_me?: bool, // Default: true - * success_handler?: scalar|null, - * failure_handler?: scalar|null, - * check_path?: scalar|null, // Default: "/login_check" - * use_forward?: bool, // Default: false - * login_path?: scalar|null, // Default: "/login" - * username_path?: scalar|null, // Default: "username" - * password_path?: scalar|null, // Default: "password" - * service?: scalar|null, // Default: "ldap" - * dn_string?: scalar|null, // Default: "{user_identifier}" - * query_string?: scalar|null, - * search_dn?: scalar|null, // Default: "" - * search_password?: scalar|null, // Default: "" - * }, - * access_token?: array{ - * provider?: scalar|null, - * remember_me?: bool, // Default: true - * success_handler?: scalar|null, - * failure_handler?: scalar|null, - * realm?: scalar|null, // Default: null - * token_extractors?: list, - * token_handler: string|array{ - * id?: scalar|null, - * oidc_user_info?: string|array{ - * base_uri: scalar|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). - * discovery?: array{ // Enable the OIDC discovery. - * cache?: array{ - * id: scalar|null, // Cache service id to use to cache the OIDC discovery configuration. - * }, - * }, - * claim?: scalar|null, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" - * client?: scalar|null, // HttpClient service id to use to call the OIDC server. - * }, - * oidc?: array{ - * discovery?: array{ // Enable the OIDC discovery. - * base_uri: list, - * cache?: array{ - * id: scalar|null, // Cache service id to use to cache the OIDC discovery configuration. - * }, - * }, - * claim?: scalar|null, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" - * audience: scalar|null, // Audience set in the token, for validation purpose. - * issuers: list, - * algorithm?: array, - * algorithms: list, - * key?: scalar|null, // Deprecated: The "key" option is deprecated and will be removed in 8.0. Use the "keyset" option instead. // JSON-encoded JWK used to sign the token (must contain a "kty" key). - * keyset?: scalar|null, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). - * encryption?: bool|array{ - * enabled?: bool, // Default: false - * enforce?: bool, // When enabled, the token shall be encrypted. // Default: false - * algorithms: list, - * keyset: scalar|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). - * }, - * }, - * cas?: array{ - * validation_url: scalar|null, // CAS server validation URL - * prefix?: scalar|null, // CAS prefix // Default: "cas" - * http_client?: scalar|null, // HTTP Client service // Default: null - * }, - * oauth2?: scalar|null, - * }, - * }, - * http_basic?: array{ - * provider?: scalar|null, - * realm?: scalar|null, // Default: "Secured Area" - * }, - * http_basic_ldap?: array{ - * provider?: scalar|null, - * realm?: scalar|null, // Default: "Secured Area" - * service?: scalar|null, // Default: "ldap" - * dn_string?: scalar|null, // Default: "{user_identifier}" - * query_string?: scalar|null, - * search_dn?: scalar|null, // Default: "" - * search_password?: scalar|null, // Default: "" - * }, - * remember_me?: array{ - * secret?: scalar|null, // Default: "%kernel.secret%" - * service?: scalar|null, - * user_providers?: list, - * catch_exceptions?: bool, // Default: true - * signature_properties?: list, - * token_provider?: string|array{ - * service?: scalar|null, // The service ID of a custom remember-me token provider. - * doctrine?: bool|array{ - * enabled?: bool, // Default: false - * connection?: scalar|null, // Default: null - * }, - * }, - * token_verifier?: scalar|null, // The service ID of a custom rememberme token verifier. - * name?: scalar|null, // Default: "REMEMBERME" - * lifetime?: int, // Default: 31536000 - * path?: scalar|null, // Default: "/" - * domain?: scalar|null, // Default: null - * secure?: true|false|"auto", // Default: null - * httponly?: bool, // Default: true - * samesite?: null|"lax"|"strict"|"none", // Default: "lax" - * always_remember_me?: bool, // Default: false - * remember_me_parameter?: scalar|null, // Default: "_remember_me" - * }, - * }>, - * access_control?: list, - * attributes?: array, - * route?: scalar|null, // Default: null - * methods?: list, - * allow_if?: scalar|null, // Default: null - * roles?: list, - * }>, - * role_hierarchy?: array>, - * } - * @psalm-type TwigConfig = array{ - * form_themes?: list, - * globals?: array, - * autoescape_service?: scalar|null, // Default: null - * autoescape_service_method?: scalar|null, // Default: null - * base_template_class?: scalar|null, // Deprecated: The child node "base_template_class" at path "twig.base_template_class" is deprecated. - * cache?: scalar|null, // Default: true - * charset?: scalar|null, // Default: "%kernel.charset%" - * debug?: bool, // Default: "%kernel.debug%" - * strict_variables?: bool, // Default: "%kernel.debug%" - * auto_reload?: scalar|null, - * optimizations?: int, - * default_path?: scalar|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates" - * file_name_pattern?: list, - * paths?: array, - * date?: array{ // The default format options used by the date filter. - * format?: scalar|null, // Default: "F j, Y H:i" - * interval_format?: scalar|null, // Default: "%d days" - * timezone?: scalar|null, // The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used. // Default: null - * }, - * number_format?: array{ // The default format options for the number_format filter. - * decimals?: int, // Default: 0 - * decimal_point?: scalar|null, // Default: "." - * thousands_separator?: scalar|null, // Default: "," - * }, - * mailer?: array{ - * html_to_text_converter?: scalar|null, // A service implementing the "Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface". // Default: null - * }, - * } - * @psalm-type WebProfilerConfig = array{ - * toolbar?: bool|array{ // Profiler toolbar configuration - * enabled?: bool, // Default: false - * ajax_replace?: bool, // Replace toolbar on AJAX requests // Default: false - * }, - * intercept_redirects?: bool, // Default: false - * excluded_ajax_paths?: scalar|null, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt" - * } - * @psalm-type MonologConfig = array{ - * use_microseconds?: scalar|null, // Default: true - * channels?: list, - * handlers?: array, - * excluded_http_codes?: list, - * }>, - * accepted_levels?: list, - * min_level?: scalar|null, // Default: "DEBUG" - * max_level?: scalar|null, // Default: "EMERGENCY" - * buffer_size?: scalar|null, // Default: 0 - * flush_on_overflow?: bool, // Default: false - * handler?: scalar|null, - * url?: scalar|null, - * exchange?: scalar|null, - * exchange_name?: scalar|null, // Default: "log" - * room?: scalar|null, - * message_format?: scalar|null, // Default: "text" - * api_version?: scalar|null, // Default: null - * channel?: scalar|null, // Default: null - * bot_name?: scalar|null, // Default: "Monolog" - * use_attachment?: scalar|null, // Default: true - * use_short_attachment?: scalar|null, // Default: false - * include_extra?: scalar|null, // Default: false - * icon_emoji?: scalar|null, // Default: null - * webhook_url?: scalar|null, - * exclude_fields?: list, - * team?: scalar|null, - * notify?: scalar|null, // Default: false - * nickname?: scalar|null, // Default: "Monolog" - * token?: scalar|null, - * region?: scalar|null, - * source?: scalar|null, - * use_ssl?: bool, // Default: true - * user?: mixed, - * title?: scalar|null, // Default: null - * host?: scalar|null, // Default: null - * port?: scalar|null, // Default: 514 - * config?: list, - * members?: list, - * connection_string?: scalar|null, - * timeout?: scalar|null, - * time?: scalar|null, // Default: 60 - * deduplication_level?: scalar|null, // Default: 400 - * store?: scalar|null, // Default: null - * connection_timeout?: scalar|null, - * persistent?: bool, - * dsn?: scalar|null, - * hub_id?: scalar|null, // Default: null - * client_id?: scalar|null, // Default: null - * auto_log_stacks?: scalar|null, // Default: false - * release?: scalar|null, // Default: null - * environment?: scalar|null, // Default: null - * message_type?: scalar|null, // Default: 0 - * parse_mode?: scalar|null, // Default: null - * disable_webpage_preview?: bool|null, // Default: null - * disable_notification?: bool|null, // Default: null - * split_long_messages?: bool, // Default: false - * delay_between_messages?: bool, // Default: false - * topic?: int, // Default: null - * factor?: int, // Default: 1 - * tags?: list, - * console_formater_options?: mixed, // Deprecated: "monolog.handlers..console_formater_options.console_formater_options" is deprecated, use "monolog.handlers..console_formater_options.console_formatter_options" instead. - * console_formatter_options?: mixed, // Default: [] - * formatter?: scalar|null, - * nested?: bool, // Default: false - * publisher?: string|array{ - * id?: scalar|null, - * hostname?: scalar|null, - * port?: scalar|null, // Default: 12201 - * chunk_size?: scalar|null, // Default: 1420 - * encoder?: "json"|"compressed_json", - * }, - * mongo?: string|array{ - * id?: scalar|null, - * host?: scalar|null, - * port?: scalar|null, // Default: 27017 - * user?: scalar|null, - * pass?: scalar|null, - * database?: scalar|null, // Default: "monolog" - * collection?: scalar|null, // Default: "logs" - * }, - * mongodb?: string|array{ - * id?: scalar|null, // ID of a MongoDB\Client service - * uri?: scalar|null, - * username?: scalar|null, - * password?: scalar|null, - * database?: scalar|null, // Default: "monolog" - * collection?: scalar|null, // Default: "logs" - * }, - * elasticsearch?: string|array{ - * id?: scalar|null, - * hosts?: list, - * host?: scalar|null, - * port?: scalar|null, // Default: 9200 - * transport?: scalar|null, // Default: "Http" - * user?: scalar|null, // Default: null - * password?: scalar|null, // Default: null - * }, - * index?: scalar|null, // Default: "monolog" - * document_type?: scalar|null, // Default: "logs" - * ignore_error?: scalar|null, // Default: false - * redis?: string|array{ - * id?: scalar|null, - * host?: scalar|null, - * password?: scalar|null, // Default: null - * port?: scalar|null, // Default: 6379 - * database?: scalar|null, // Default: 0 - * key_name?: scalar|null, // Default: "monolog_redis" - * }, - * predis?: string|array{ - * id?: scalar|null, - * host?: scalar|null, - * }, - * from_email?: scalar|null, - * to_email?: list, - * subject?: scalar|null, - * content_type?: scalar|null, // Default: null - * headers?: list, - * mailer?: scalar|null, // Default: null - * email_prototype?: string|array{ - * id: scalar|null, - * method?: scalar|null, // Default: null - * }, - * lazy?: bool, // Default: true - * verbosity_levels?: array{ - * VERBOSITY_QUIET?: scalar|null, // Default: "ERROR" - * VERBOSITY_NORMAL?: scalar|null, // Default: "WARNING" - * VERBOSITY_VERBOSE?: scalar|null, // Default: "NOTICE" - * VERBOSITY_VERY_VERBOSE?: scalar|null, // Default: "INFO" - * VERBOSITY_DEBUG?: scalar|null, // Default: "DEBUG" - * }, - * channels?: string|array{ - * type?: scalar|null, - * elements?: list, - * }, - * }>, - * } - * @psalm-type DebugConfig = array{ - * max_items?: int, // Max number of displayed items past the first level, -1 means no limit. // Default: 2500 - * min_depth?: int, // Minimum tree depth to clone all the items, 1 is default. // Default: 1 - * max_string_length?: int, // Max length of displayed strings, -1 means no limit. // Default: -1 - * dump_destination?: scalar|null, // A stream URL where dumps should be written to. // Default: null - * theme?: "dark"|"light", // Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light". // Default: "dark" - * } - * @psalm-type MakerConfig = array{ - * root_namespace?: scalar|null, // Default: "App" - * generate_final_classes?: bool, // Default: true - * generate_final_entities?: bool, // Default: false - * } - * @psalm-type WebpackEncoreConfig = array{ - * output_path: scalar|null, // The path where Encore is building the assets - i.e. Encore.setOutputPath() - * crossorigin?: false|"anonymous"|"use-credentials", // crossorigin value when Encore.enableIntegrityHashes() is used, can be false (default), anonymous or use-credentials // Default: false - * preload?: bool, // preload all rendered script and link tags automatically via the http2 Link header. // Default: false - * cache?: bool, // Enable caching of the entry point file(s) // Default: false - * strict_mode?: bool, // Throw an exception if the entrypoints.json file is missing or an entry is missing from the data // Default: true - * builds?: array, - * script_attributes?: array, - * link_attributes?: array, - * } - * @psalm-type DatatablesConfig = array{ - * language_from_cdn?: bool, // Load i18n data from DataTables CDN or locally // Default: true - * persist_state?: "none"|"query"|"fragment"|"local"|"session", // Where to persist the current table state automatically // Default: "fragment" - * method?: "GET"|"POST", // Default HTTP method to be used for callbacks // Default: "POST" - * options?: array, - * renderer?: scalar|null, // Default service used to render templates, built-in TwigRenderer uses global Twig environment // Default: "Omines\\DataTablesBundle\\Twig\\TwigRenderer" - * template?: scalar|null, // Default template to be used for DataTables HTML // Default: "@DataTables/datatable_html.html.twig" - * template_parameters?: array{ // Default parameters to be passed to the template - * className?: scalar|null, // Default class attribute to apply to the root table elements // Default: "table table-bordered" - * columnFilter?: "thead"|"tfoot"|"both"|null, // If and where to enable the DataTables Filter module // Default: null - * ... - * }, - * translation_domain?: scalar|null, // Default translation domain to be used // Default: "messages" - * } - * @psalm-type LiipImagineConfig = array{ - * resolvers?: array, - * get_options?: array, - * put_options?: array, - * proxies?: array, - * }, - * flysystem?: array{ - * filesystem_service: scalar|null, - * cache_prefix?: scalar|null, // Default: "" - * root_url: scalar|null, - * visibility?: "public"|"private"|"noPredefinedVisibility", // Default: "public" - * }, - * }>, - * loaders?: array, - * allow_unresolvable_data_roots?: bool, // Default: false - * bundle_resources?: array{ - * enabled?: bool, // Default: false - * access_control_type?: "blacklist"|"whitelist", // Sets the access control method applied to bundle names in "access_control_list" into a blacklist or whitelist. // Default: "blacklist" - * access_control_list?: list, - * }, - * }, - * flysystem?: array{ - * filesystem_service: scalar|null, - * }, - * chain?: array{ - * loaders: list, - * }, - * }>, - * driver?: scalar|null, // Default: "gd" - * cache?: scalar|null, // Default: "default" - * cache_base_path?: scalar|null, // Default: "" - * data_loader?: scalar|null, // Default: "default" - * default_image?: scalar|null, // Default: null - * default_filter_set_settings?: array{ - * quality?: scalar|null, // Default: 100 - * jpeg_quality?: scalar|null, // Default: null - * png_compression_level?: scalar|null, // Default: null - * png_compression_filter?: scalar|null, // Default: null - * format?: scalar|null, // Default: null - * animated?: bool, // Default: false - * cache?: scalar|null, // Default: null - * data_loader?: scalar|null, // Default: null - * default_image?: scalar|null, // Default: null - * filters?: array>, - * post_processors?: array>, - * }, - * controller?: array{ - * filter_action?: scalar|null, // Default: "Liip\\ImagineBundle\\Controller\\ImagineController::filterAction" - * filter_runtime_action?: scalar|null, // Default: "Liip\\ImagineBundle\\Controller\\ImagineController::filterRuntimeAction" - * redirect_response_code?: int, // Default: 302 - * }, - * filter_sets?: array>, - * post_processors?: array>, - * }>, - * twig?: array{ - * mode?: "none"|"lazy"|"legacy", // Twig mode: none/lazy/legacy (default) // Default: "legacy" - * assets_version?: scalar|null, // Default: null - * }, - * enqueue?: bool, // Enables integration with enqueue if set true. Allows resolve image caches in background by sending messages to MQ. // Default: false - * messenger?: bool|array{ // Enables integration with symfony/messenger if set true. Warmup image caches in background by sending messages to MQ. - * enabled?: bool, // Default: false - * }, - * templating?: bool, // Enables integration with symfony/templating component // Default: true - * webp?: array{ - * generate?: bool, // Default: false - * quality?: int, // Default: 100 - * cache?: scalar|null, // Default: null - * data_loader?: scalar|null, // Default: null - * post_processors?: array>, - * }, - * } - * @psalm-type TwigExtraConfig = array{ - * cache?: bool|array{ - * enabled?: bool, // Default: false - * }, - * html?: bool|array{ - * enabled?: bool, // Default: true - * }, - * markdown?: bool|array{ - * enabled?: bool, // Default: true - * }, - * intl?: bool|array{ - * enabled?: bool, // Default: true - * }, - * cssinliner?: bool|array{ - * enabled?: bool, // Default: true - * }, - * inky?: bool|array{ - * enabled?: bool, // Default: true - * }, - * string?: bool|array{ - * enabled?: bool, // Default: true - * }, - * commonmark?: array{ - * renderer?: array{ // Array of options for rendering HTML. - * block_separator?: scalar|null, - * inner_separator?: scalar|null, - * soft_break?: scalar|null, - * }, - * html_input?: "strip"|"allow"|"escape", // How to handle HTML input. - * allow_unsafe_links?: bool, // Remove risky link and image URLs by setting this to false. // Default: true - * max_nesting_level?: int, // The maximum nesting level for blocks. // Default: 9223372036854775807 - * max_delimiters_per_line?: int, // The maximum number of strong/emphasis delimiters per line. // Default: 9223372036854775807 - * slug_normalizer?: array{ // Array of options for configuring how URL-safe slugs are created. - * instance?: mixed, - * max_length?: int, // Default: 255 - * unique?: mixed, - * }, - * commonmark?: array{ // Array of options for configuring the CommonMark core extension. - * enable_em?: bool, // Default: true - * enable_strong?: bool, // Default: true - * use_asterisk?: bool, // Default: true - * use_underscore?: bool, // Default: true - * unordered_list_markers?: list, - * }, - * ... - * }, - * } - * @psalm-type GregwarCaptchaConfig = array{ - * length?: scalar|null, // Default: 5 - * width?: scalar|null, // Default: 130 - * height?: scalar|null, // Default: 50 - * font?: scalar|null, // Default: "C:\\Users\\mail\\Documents\\PHP\\Part-DB-server\\vendor\\gregwar\\captcha-bundle\\DependencyInjection/../Generator/Font/captcha.ttf" - * keep_value?: scalar|null, // Default: false - * charset?: scalar|null, // Default: "abcdefhjkmnprstuvwxyz23456789" - * as_file?: scalar|null, // Default: false - * as_url?: scalar|null, // Default: false - * reload?: scalar|null, // Default: false - * image_folder?: scalar|null, // Default: "captcha" - * web_path?: scalar|null, // Default: "%kernel.project_dir%/public" - * gc_freq?: scalar|null, // Default: 100 - * expiration?: scalar|null, // Default: 60 - * quality?: scalar|null, // Default: 50 - * invalid_message?: scalar|null, // Default: "Bad code value" - * bypass_code?: scalar|null, // Default: null - * whitelist_key?: scalar|null, // Default: "captcha_whitelist_key" - * humanity?: scalar|null, // Default: 0 - * distortion?: scalar|null, // Default: true - * max_front_lines?: scalar|null, // Default: null - * max_behind_lines?: scalar|null, // Default: null - * interpolation?: scalar|null, // Default: true - * text_color?: list, - * background_color?: list, - * background_images?: list, - * disabled?: scalar|null, // Default: false - * ignore_all_effects?: scalar|null, // Default: false - * session_key?: scalar|null, // Default: "captcha" - * } - * @psalm-type FlorianvSwapConfig = array{ - * cache?: array{ - * ttl?: int, // Default: 3600 - * type?: scalar|null, // A cache type or service id // Default: null - * }, - * providers?: array{ - * apilayer_fixer?: array{ - * priority?: int, // Default: 0 - * api_key: scalar|null, - * }, - * apilayer_currency_data?: array{ - * priority?: int, // Default: 0 - * api_key: scalar|null, - * }, - * apilayer_exchange_rates_data?: array{ - * priority?: int, // Default: 0 - * api_key: scalar|null, - * }, - * abstract_api?: array{ - * priority?: int, // Default: 0 - * api_key: scalar|null, - * }, - * fixer?: array{ - * priority?: int, // Default: 0 - * access_key: scalar|null, - * enterprise?: bool, // Default: false - * }, - * cryptonator?: array{ - * priority?: int, // Default: 0 - * }, - * exchange_rates_api?: array{ - * priority?: int, // Default: 0 - * access_key: scalar|null, - * enterprise?: bool, // Default: false - * }, - * webservicex?: array{ - * priority?: int, // Default: 0 - * }, - * central_bank_of_czech_republic?: array{ - * priority?: int, // Default: 0 - * }, - * central_bank_of_republic_turkey?: array{ - * priority?: int, // Default: 0 - * }, - * european_central_bank?: array{ - * priority?: int, // Default: 0 - * }, - * national_bank_of_romania?: array{ - * priority?: int, // Default: 0 - * }, - * russian_central_bank?: array{ - * priority?: int, // Default: 0 - * }, - * frankfurter?: array{ - * priority?: int, // Default: 0 - * }, - * fawazahmed_currency_api?: array{ - * priority?: int, // Default: 0 - * }, - * bulgarian_national_bank?: array{ - * priority?: int, // Default: 0 - * }, - * national_bank_of_ukraine?: array{ - * priority?: int, // Default: 0 - * }, - * currency_data_feed?: array{ - * priority?: int, // Default: 0 - * api_key: scalar|null, - * }, - * currency_layer?: array{ - * priority?: int, // Default: 0 - * access_key: scalar|null, - * enterprise?: bool, // Default: false - * }, - * forge?: array{ - * priority?: int, // Default: 0 - * api_key: scalar|null, - * }, - * open_exchange_rates?: array{ - * priority?: int, // Default: 0 - * app_id: scalar|null, - * enterprise?: bool, // Default: false - * }, - * xignite?: array{ - * priority?: int, // Default: 0 - * token: scalar|null, - * }, - * xchangeapi?: array{ - * priority?: int, // Default: 0 - * api_key: scalar|null, - * }, - * currency_converter?: array{ - * priority?: int, // Default: 0 - * access_key: scalar|null, - * enterprise?: bool, // Default: false - * }, - * array?: array{ - * priority?: int, // Default: 0 - * latestRates: mixed, - * historicalRates?: mixed, - * }, - * }, - * } - * @psalm-type NelmioSecurityConfig = array{ - * signed_cookie?: array{ - * names?: list, - * secret?: scalar|null, // Default: "%kernel.secret%" - * hash_algo?: scalar|null, - * legacy_hash_algo?: scalar|null, // Fallback algorithm to allow for frictionless hash algorithm upgrades. Use with caution and as a temporary measure as it allows for downgrade attacks. // Default: null - * separator?: scalar|null, // Default: "." - * }, - * clickjacking?: array{ - * hosts?: list, - * paths?: array, - * content_types?: list, - * }, - * external_redirects?: array{ - * abort?: bool, // Default: false - * override?: scalar|null, // Default: null - * forward_as?: scalar|null, // Default: null - * log?: bool, // Default: false - * allow_list?: list, - * }, - * flexible_ssl?: bool|array{ - * enabled?: bool, // Default: false - * cookie_name?: scalar|null, // Default: "auth" - * unsecured_logout?: bool, // Default: false - * }, - * forced_ssl?: bool|array{ - * enabled?: bool, // Default: false - * hsts_max_age?: scalar|null, // Default: null - * hsts_subdomains?: bool, // Default: false - * hsts_preload?: bool, // Default: false - * allow_list?: list, - * hosts?: list, - * redirect_status_code?: scalar|null, // Default: 302 - * }, - * content_type?: array{ - * nosniff?: bool, // Default: false - * }, - * xss_protection?: array{ // Deprecated: The "xss_protection" option is deprecated, use Content Security Policy without allowing "unsafe-inline" scripts instead. - * enabled?: bool, // Default: false - * mode_block?: bool, // Default: false - * report_uri?: scalar|null, // Default: null - * }, - * csp?: bool|array{ - * enabled?: bool, // Default: true - * request_matcher?: scalar|null, // Default: null - * hosts?: list, - * content_types?: list, - * report_endpoint?: array{ - * log_channel?: scalar|null, // Default: null - * log_formatter?: scalar|null, // Default: "nelmio_security.csp_report.log_formatter" - * log_level?: "alert"|"critical"|"debug"|"emergency"|"error"|"info"|"notice"|"warning", // Default: "notice" - * filters?: array{ - * domains?: bool, // Default: true - * schemes?: bool, // Default: true - * browser_bugs?: bool, // Default: true - * injected_scripts?: bool, // Default: true - * }, - * dismiss?: list>, - * }, - * compat_headers?: bool, // Default: true - * report_logger_service?: scalar|null, // Default: "logger" - * hash?: array{ - * algorithm?: "sha256"|"sha384"|"sha512", // The algorithm to use for hashes // Default: "sha256" - * }, - * report?: array{ - * level1_fallback?: bool, // Provides CSP Level 1 fallback when using hash or nonce (CSP level 2) by adding 'unsafe-inline' source. See https://www.w3.org/TR/CSP2/#directive-script-src and https://www.w3.org/TR/CSP2/#directive-style-src // Default: true - * browser_adaptive?: bool|array{ // Do not send directives that browser do not support - * enabled?: bool, // Default: false - * parser?: scalar|null, // Default: "nelmio_security.ua_parser.ua_php" - * }, - * default-src?: list, - * base-uri?: list, - * block-all-mixed-content?: bool, // Default: false - * child-src?: list, - * connect-src?: list, - * font-src?: list, - * form-action?: list, - * frame-ancestors?: list, - * frame-src?: list, - * img-src?: list, - * manifest-src?: list, - * media-src?: list, - * object-src?: list, - * plugin-types?: list, - * script-src?: list, - * style-src?: list, - * upgrade-insecure-requests?: bool, // Default: false - * report-uri?: list, - * worker-src?: list, - * prefetch-src?: list, - * report-to?: scalar|null, - * }, - * enforce?: array{ - * level1_fallback?: bool, // Provides CSP Level 1 fallback when using hash or nonce (CSP level 2) by adding 'unsafe-inline' source. See https://www.w3.org/TR/CSP2/#directive-script-src and https://www.w3.org/TR/CSP2/#directive-style-src // Default: true - * browser_adaptive?: bool|array{ // Do not send directives that browser do not support - * enabled?: bool, // Default: false - * parser?: scalar|null, // Default: "nelmio_security.ua_parser.ua_php" - * }, - * default-src?: list, - * base-uri?: list, - * block-all-mixed-content?: bool, // Default: false - * child-src?: list, - * connect-src?: list, - * font-src?: list, - * form-action?: list, - * frame-ancestors?: list, - * frame-src?: list, - * img-src?: list, - * manifest-src?: list, - * media-src?: list, - * object-src?: list, - * plugin-types?: list, - * script-src?: list, - * style-src?: list, - * upgrade-insecure-requests?: bool, // Default: false - * report-uri?: list, - * worker-src?: list, - * prefetch-src?: list, - * report-to?: scalar|null, - * }, - * }, - * referrer_policy?: bool|array{ - * enabled?: bool, // Default: false - * policies?: list, - * }, - * permissions_policy?: bool|array{ - * enabled?: bool, // Default: false - * policies?: array{ - * accelerometer?: mixed, // Default: null - * ambient_light_sensor?: mixed, // Default: null - * attribution_reporting?: mixed, // Default: null - * autoplay?: mixed, // Default: null - * bluetooth?: mixed, // Default: null - * browsing_topics?: mixed, // Default: null - * camera?: mixed, // Default: null - * captured_surface_control?: mixed, // Default: null - * compute_pressure?: mixed, // Default: null - * cross_origin_isolated?: mixed, // Default: null - * deferred_fetch?: mixed, // Default: null - * deferred_fetch_minimal?: mixed, // Default: null - * display_capture?: mixed, // Default: null - * encrypted_media?: mixed, // Default: null - * fullscreen?: mixed, // Default: null - * gamepad?: mixed, // Default: null - * geolocation?: mixed, // Default: null - * gyroscope?: mixed, // Default: null - * hid?: mixed, // Default: null - * identity_credentials_get?: mixed, // Default: null - * idle_detection?: mixed, // Default: null - * interest_cohort?: mixed, // Default: null - * language_detector?: mixed, // Default: null - * local_fonts?: mixed, // Default: null - * magnetometer?: mixed, // Default: null - * microphone?: mixed, // Default: null - * midi?: mixed, // Default: null - * otp_credentials?: mixed, // Default: null - * payment?: mixed, // Default: null - * picture_in_picture?: mixed, // Default: null - * publickey_credentials_create?: mixed, // Default: null - * publickey_credentials_get?: mixed, // Default: null - * screen_wake_lock?: mixed, // Default: null - * serial?: mixed, // Default: null - * speaker_selection?: mixed, // Default: null - * storage_access?: mixed, // Default: null - * summarizer?: mixed, // Default: null - * translator?: mixed, // Default: null - * usb?: mixed, // Default: null - * web_share?: mixed, // Default: null - * window_management?: mixed, // Default: null - * xr_spatial_tracking?: mixed, // Default: null - * }, - * }, - * } - * @psalm-type TurboConfig = array{ - * broadcast?: bool|array{ - * enabled?: bool, // Default: true - * entity_template_prefixes?: list, - * doctrine_orm?: bool|array{ // Enable the Doctrine ORM integration - * enabled?: bool, // Default: true - * }, - * }, - * default_transport?: scalar|null, // Default: "default" - * } - * @psalm-type TfaWebauthnConfig = array{ - * enabled?: scalar|null, // Default: false - * timeout?: int, // Default: 60000 - * rpID?: scalar|null, // Default: null - * rpName?: scalar|null, // Default: "Webauthn Application" - * rpIcon?: scalar|null, // Default: null - * template?: scalar|null, // Default: "@TFAWebauthn/Authentication/form.html.twig" - * U2FAppID?: scalar|null, // Default: null - * } - * @psalm-type SchebTwoFactorConfig = array{ - * persister?: scalar|null, // Default: "scheb_two_factor.persister.doctrine" - * model_manager_name?: scalar|null, // Default: null - * security_tokens?: list, - * ip_whitelist?: list, - * ip_whitelist_provider?: scalar|null, // Default: "scheb_two_factor.default_ip_whitelist_provider" - * two_factor_token_factory?: scalar|null, // Default: "scheb_two_factor.default_token_factory" - * two_factor_provider_decider?: scalar|null, // Default: "scheb_two_factor.default_provider_decider" - * two_factor_condition?: scalar|null, // Default: null - * code_reuse_cache?: scalar|null, // Default: null - * code_reuse_cache_duration?: int, // Default: 60 - * code_reuse_default_handler?: scalar|null, // Default: null - * trusted_device?: bool|array{ - * enabled?: scalar|null, // Default: false - * manager?: scalar|null, // Default: "scheb_two_factor.default_trusted_device_manager" - * lifetime?: int, // Default: 5184000 - * extend_lifetime?: bool, // Default: false - * key?: scalar|null, // Default: null - * cookie_name?: scalar|null, // Default: "trusted_device" - * cookie_secure?: true|false|"auto", // Default: "auto" - * cookie_domain?: scalar|null, // Default: null - * cookie_path?: scalar|null, // Default: "/" - * cookie_same_site?: scalar|null, // Default: "lax" - * }, - * backup_codes?: bool|array{ - * enabled?: scalar|null, // Default: false - * manager?: scalar|null, // Default: "scheb_two_factor.default_backup_code_manager" - * }, - * google?: bool|array{ - * enabled?: scalar|null, // Default: false - * form_renderer?: scalar|null, // Default: null - * issuer?: scalar|null, // Default: null - * server_name?: scalar|null, // Default: null - * template?: scalar|null, // Default: "@SchebTwoFactor/Authentication/form.html.twig" - * digits?: int, // Default: 6 - * leeway?: int, // Default: 0 - * }, - * } - * @psalm-type WebauthnConfig = array{ - * fake_credential_generator?: scalar|null, // A service that implements the FakeCredentialGenerator to generate fake credentials for preventing username enumeration. // Default: "Webauthn\\SimpleFakeCredentialGenerator" - * clock?: scalar|null, // PSR-20 Clock service. // Default: "webauthn.clock.default" - * options_storage?: scalar|null, // Service responsible of the options/user entity storage during the ceremony // Default: "Webauthn\\Bundle\\Security\\Storage\\SessionStorage" - * event_dispatcher?: scalar|null, // PSR-14 Event Dispatcher service. // Default: "Psr\\EventDispatcher\\EventDispatcherInterface" - * http_client?: scalar|null, // A Symfony HTTP client. // Default: "webauthn.http_client.default" - * logger?: scalar|null, // A PSR-3 logger to receive logs during the processes // Default: "webauthn.logger.default" - * credential_repository?: scalar|null, // This repository is responsible of the credential storage // Default: "Webauthn\\Bundle\\Repository\\DummyPublicKeyCredentialSourceRepository" - * user_repository?: scalar|null, // This repository is responsible of the user storage // Default: "Webauthn\\Bundle\\Repository\\DummyPublicKeyCredentialUserEntityRepository" - * allowed_origins?: array, - * allow_subdomains?: bool, // Default: false - * secured_rp_ids?: array, - * counter_checker?: scalar|null, // This service will check if the counter is valid. By default it throws an exception (recommended). // Default: "Webauthn\\Counter\\ThrowExceptionIfInvalid" - * top_origin_validator?: scalar|null, // For cross origin (e.g. iframe), this service will be in charge of verifying the top origin. // Default: null - * creation_profiles?: array, - * public_key_credential_parameters?: list, - * attestation_conveyance?: scalar|null, // Default: "none" - * }>, - * request_profiles?: array, - * }>, - * metadata?: bool|array{ // Enable the support of the Metadata Statements. Please read the documentation for this feature. - * enabled?: bool, // Default: false - * mds_repository: scalar|null, // The Metadata Statement repository. - * status_report_repository: scalar|null, // The Status Report repository. - * certificate_chain_checker?: scalar|null, // A Certificate Chain checker. // Default: "Webauthn\\MetadataService\\CertificateChain\\PhpCertificateChainValidator" - * }, - * controllers?: bool|array{ - * enabled?: bool, // Default: false - * creation?: array, - * allow_subdomains?: bool, // Default: false - * secured_rp_ids?: array, - * }>, - * request?: array, - * allow_subdomains?: bool, // Default: false - * secured_rp_ids?: array, - * }>, - * }, - * } - * @psalm-type NbgrpOneloginSamlConfig = array{ // nb:group OneLogin PHP Symfony Bundle configuration - * onelogin_settings?: array/saml/" - * strict?: bool, - * debug?: bool, - * idp: array{ - * entityId: scalar|null, - * singleSignOnService: array{ - * url: scalar|null, - * binding?: scalar|null, - * }, - * singleLogoutService?: array{ - * url?: scalar|null, - * responseUrl?: scalar|null, - * binding?: scalar|null, - * }, - * x509cert?: scalar|null, - * certFingerprint?: scalar|null, - * certFingerprintAlgorithm?: "sha1"|"sha256"|"sha384"|"sha512", - * x509certMulti?: array{ - * signing?: list, - * encryption?: list, - * }, - * }, - * sp?: array{ - * entityId?: scalar|null, // Default: "/saml/metadata" - * assertionConsumerService?: array{ - * url?: scalar|null, // Default: "/saml/acs" - * binding?: scalar|null, - * }, - * attributeConsumingService?: array{ - * serviceName?: scalar|null, - * serviceDescription?: scalar|null, - * requestedAttributes?: list, - * }>, - * }, - * singleLogoutService?: array{ - * url?: scalar|null, // Default: "/saml/logout" - * binding?: scalar|null, - * }, - * NameIDFormat?: scalar|null, - * x509cert?: scalar|null, - * privateKey?: scalar|null, - * x509certNew?: scalar|null, - * }, - * compress?: array{ - * requests?: bool, - * responses?: bool, - * }, - * security?: array{ - * nameIdEncrypted?: bool, - * authnRequestsSigned?: bool, - * logoutRequestSigned?: bool, - * logoutResponseSigned?: bool, - * signMetadata?: bool, - * wantMessagesSigned?: bool, - * wantAssertionsEncrypted?: bool, - * wantAssertionsSigned?: bool, - * wantNameId?: bool, - * wantNameIdEncrypted?: bool, - * requestedAuthnContext?: mixed, - * requestedAuthnContextComparison?: "exact"|"minimum"|"maximum"|"better", - * wantXMLValidation?: bool, - * relaxDestinationValidation?: bool, - * destinationStrictlyMatches?: bool, - * allowRepeatAttributeName?: bool, - * rejectUnsolicitedResponsesWithInResponseTo?: bool, - * signatureAlgorithm?: "http:\/\/www.w3.org\/2000\/09\/xmldsig#rsa-sha1"|"http:\/\/www.w3.org\/2000\/09\/xmldsig#dsa-sha1"|"http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha256"|"http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha384"|"http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha512", - * digestAlgorithm?: "http:\/\/www.w3.org\/2000\/09\/xmldsig#sha1"|"http:\/\/www.w3.org\/2001\/04\/xmlenc#sha256"|"http:\/\/www.w3.org\/2001\/04\/xmldsig-more#sha384"|"http:\/\/www.w3.org\/2001\/04\/xmlenc#sha512", - * encryption_algorithm?: "http:\/\/www.w3.org\/2001\/04\/xmlenc#tripledes-cbc"|"http:\/\/www.w3.org\/2001\/04\/xmlenc#aes128-cbc"|"http:\/\/www.w3.org\/2001\/04\/xmlenc#aes192-cbc"|"http:\/\/www.w3.org\/2001\/04\/xmlenc#aes256-cbc"|"http:\/\/www.w3.org\/2009\/xmlenc11#aes128-gcm"|"http:\/\/www.w3.org\/2009\/xmlenc11#aes192-gcm"|"http:\/\/www.w3.org\/2009\/xmlenc11#aes256-gcm", - * lowercaseUrlencoding?: bool, - * }, - * contactPerson?: array{ - * technical?: array{ - * givenName: scalar|null, - * emailAddress: scalar|null, - * }, - * support?: array{ - * givenName: scalar|null, - * emailAddress: scalar|null, - * }, - * administrative?: array{ - * givenName: scalar|null, - * emailAddress: scalar|null, - * }, - * billing?: array{ - * givenName: scalar|null, - * emailAddress: scalar|null, - * }, - * other?: array{ - * givenName: scalar|null, - * emailAddress: scalar|null, - * }, - * }, - * organization?: list, - * }>, - * use_proxy_vars?: bool, // Default: false - * idp_parameter_name?: scalar|null, // Default: "idp" - * entity_manager_name?: scalar|null, - * authn_request?: array{ - * parameters?: list, - * forceAuthn?: bool, // Default: false - * isPassive?: bool, // Default: false - * setNameIdPolicy?: bool, // Default: true - * nameIdValueReq?: scalar|null, // Default: null - * }, - * } - * @psalm-type StimulusConfig = array{ - * controller_paths?: list, - * controllers_json?: scalar|null, // Default: "%kernel.project_dir%/assets/controllers.json" - * } - * @psalm-type UxTranslatorConfig = array{ - * dump_directory?: scalar|null, // Default: "%kernel.project_dir%/var/translations" - * domains?: string|array{ // List of domains to include/exclude from the generated translations. Prefix with a `!` to exclude a domain. - * type?: scalar|null, - * elements?: list, - * }, - * } - * @psalm-type DompdfFontLoaderConfig = array{ - * autodiscovery?: bool|array{ - * paths?: list, - * exclude_patterns?: list, - * file_pattern?: scalar|null, // Default: "/\\.(ttf)$/" - * enabled?: bool, // Default: true - * }, - * auto_install?: bool, // Default: false - * fonts?: list, - * } - * @psalm-type KnpuOauth2ClientConfig = array{ - * http_client?: scalar|null, // Service id of HTTP client to use (must implement GuzzleHttp\ClientInterface) // Default: null - * http_client_options?: array{ - * timeout?: int, - * proxy?: scalar|null, - * verify?: bool, // Use only with proxy option set - * }, - * clients?: array>, - * } - * @psalm-type NelmioCorsConfig = array{ - * defaults?: array{ - * allow_credentials?: bool, // Default: false - * allow_origin?: list, - * allow_headers?: list, - * allow_methods?: list, - * allow_private_network?: bool, // Default: false - * expose_headers?: list, - * max_age?: scalar|null, // Default: 0 - * hosts?: list, - * origin_regex?: bool, // Default: false - * forced_allow_origin_value?: scalar|null, // Default: null - * skip_same_as_origin?: bool, // Default: true - * }, - * paths?: array, - * allow_headers?: list, - * allow_methods?: list, - * allow_private_network?: bool, - * expose_headers?: list, - * max_age?: scalar|null, // Default: 0 - * hosts?: list, - * origin_regex?: bool, - * forced_allow_origin_value?: scalar|null, // Default: null - * skip_same_as_origin?: bool, - * }>, - * } - * @psalm-type JbtronicsSettingsConfig = array{ - * search_paths?: list, - * proxy_dir?: scalar|null, // Default: "%kernel.cache_dir%/jbtronics_settings/proxies" - * proxy_namespace?: scalar|null, // Default: "Jbtronics\\SettingsBundle\\Proxies" - * default_storage_adapter?: scalar|null, // Default: null - * save_after_migration?: bool, // Default: true - * file_storage?: array{ - * storage_directory?: scalar|null, // Default: "%kernel.project_dir%/var/jbtronics_settings/" - * default_filename?: scalar|null, // Default: "settings" - * }, - * orm_storage?: array{ - * default_entity_class?: scalar|null, // Default: null - * prefetch_all?: bool, // Default: true - * }, - * cache?: array{ - * service?: scalar|null, // Default: "cache.app.taggable" - * default_cacheable?: bool, // Default: false - * ttl?: int, // Default: 0 - * invalidate_on_env_change?: bool, // Default: true - * }, - * } - * @psalm-type JbtronicsTranslationEditorConfig = array{ - * translations_path?: scalar|null, // Default: "%translator.default_path%" - * format?: scalar|null, // Default: "xlf" - * xliff_version?: scalar|null, // Default: "2.0" - * use_intl_icu_format?: bool, // Default: false - * writer_options?: list, - * } - * @psalm-type ApiPlatformConfig = array{ - * title?: scalar|null, // The title of the API. // Default: "" - * description?: scalar|null, // The description of the API. // Default: "" - * version?: scalar|null, // The version of the API. // Default: "0.0.0" - * show_webby?: bool, // If true, show Webby on the documentation page // Default: true - * use_symfony_listeners?: bool, // Uses Symfony event listeners instead of the ApiPlatform\Symfony\Controller\MainController. // Default: false - * name_converter?: scalar|null, // Specify a name converter to use. // Default: null - * asset_package?: scalar|null, // Specify an asset package name to use. // Default: null - * path_segment_name_generator?: scalar|null, // Specify a path name generator to use. // Default: "api_platform.metadata.path_segment_name_generator.underscore" - * inflector?: scalar|null, // Specify an inflector to use. // Default: "api_platform.metadata.inflector" - * validator?: array{ - * serialize_payload_fields?: mixed, // Set to null to serialize all payload fields when a validation error is thrown, or set the fields you want to include explicitly. // Default: [] - * query_parameter_validation?: bool, // Deprecated: Will be removed in API Platform 5.0. // Default: true - * }, - * eager_loading?: bool|array{ - * enabled?: bool, // Default: true - * fetch_partial?: bool, // Fetch only partial data according to serialization groups. If enabled, Doctrine ORM entities will not work as expected if any of the other fields are used. // Default: false - * max_joins?: int, // Max number of joined relations before EagerLoading throws a RuntimeException // Default: 30 - * force_eager?: bool, // Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode. // Default: true - * }, - * handle_symfony_errors?: bool, // Allows to handle symfony exceptions. // Default: false - * enable_swagger?: bool, // Enable the Swagger documentation and export. // Default: true - * enable_json_streamer?: bool, // Enable json streamer. // Default: false - * enable_swagger_ui?: bool, // Enable Swagger UI // Default: true - * enable_re_doc?: bool, // Enable ReDoc // Default: true - * enable_entrypoint?: bool, // Enable the entrypoint // Default: true - * enable_docs?: bool, // Enable the docs // Default: true - * enable_profiler?: bool, // Enable the data collector and the WebProfilerBundle integration. // Default: true - * enable_phpdoc_parser?: bool, // Enable resource metadata collector using PHPStan PhpDocParser. // Default: true - * enable_link_security?: bool, // Enable security for Links (sub resources) // Default: false - * collection?: array{ - * exists_parameter_name?: scalar|null, // The name of the query parameter to filter on nullable field values. // Default: "exists" - * order?: scalar|null, // The default order of results. // Default: "ASC" - * order_parameter_name?: scalar|null, // The name of the query parameter to order results. // Default: "order" - * order_nulls_comparison?: "nulls_smallest"|"nulls_largest"|"nulls_always_first"|"nulls_always_last"|null, // The nulls comparison strategy. // Default: null - * pagination?: bool|array{ - * enabled?: bool, // Default: true - * page_parameter_name?: scalar|null, // The default name of the parameter handling the page number. // Default: "page" - * enabled_parameter_name?: scalar|null, // The name of the query parameter to enable or disable pagination. // Default: "pagination" - * items_per_page_parameter_name?: scalar|null, // The name of the query parameter to set the number of items per page. // Default: "itemsPerPage" - * partial_parameter_name?: scalar|null, // The name of the query parameter to enable or disable partial pagination. // Default: "partial" - * }, - * }, - * mapping?: array{ - * imports?: list, - * paths?: list, - * }, - * resource_class_directories?: list, - * serializer?: array{ - * hydra_prefix?: bool, // Use the "hydra:" prefix. // Default: false - * }, - * doctrine?: bool|array{ - * enabled?: bool, // Default: true - * }, - * doctrine_mongodb_odm?: bool|array{ - * enabled?: bool, // Default: false - * }, - * oauth?: bool|array{ - * enabled?: bool, // Default: false - * clientId?: scalar|null, // The oauth client id. // Default: "" - * clientSecret?: scalar|null, // The OAuth client secret. Never use this parameter in your production environment. It exposes crucial security information. This feature is intended for dev/test environments only. Enable "oauth.pkce" instead // Default: "" - * pkce?: bool, // Enable the oauth PKCE. // Default: false - * type?: scalar|null, // The oauth type. // Default: "oauth2" - * flow?: scalar|null, // The oauth flow grant type. // Default: "application" - * tokenUrl?: scalar|null, // The oauth token url. // Default: "" - * authorizationUrl?: scalar|null, // The oauth authentication url. // Default: "" - * refreshUrl?: scalar|null, // The oauth refresh url. // Default: "" - * scopes?: list, - * }, - * graphql?: bool|array{ - * enabled?: bool, // Default: false - * default_ide?: scalar|null, // Default: "graphiql" - * graphiql?: bool|array{ - * enabled?: bool, // Default: false - * }, - * introspection?: bool|array{ - * enabled?: bool, // Default: true - * }, - * max_query_depth?: int, // Default: 20 - * graphql_playground?: array, - * max_query_complexity?: int, // Default: 500 - * nesting_separator?: scalar|null, // The separator to use to filter nested fields. // Default: "_" - * collection?: array{ - * pagination?: bool|array{ - * enabled?: bool, // Default: true - * }, - * }, - * }, - * swagger?: array{ - * persist_authorization?: bool, // Persist the SwaggerUI Authorization in the localStorage. // Default: false - * versions?: list, - * api_keys?: array, - * http_auth?: array, - * swagger_ui_extra_configuration?: mixed, // To pass extra configuration to Swagger UI, like docExpansion or filter. // Default: [] - * }, - * http_cache?: array{ - * public?: bool|null, // To make all responses public by default. // Default: null - * invalidation?: bool|array{ // Enable the tags-based cache invalidation system. - * enabled?: bool, // Default: false - * varnish_urls?: list, - * urls?: list, - * scoped_clients?: list, - * max_header_length?: int, // Max header length supported by the cache server. // Default: 7500 - * request_options?: mixed, // To pass options to the client charged with the request. // Default: [] - * purger?: scalar|null, // Specify a purger to use (available values: "api_platform.http_cache.purger.varnish.ban", "api_platform.http_cache.purger.varnish.xkey", "api_platform.http_cache.purger.souin"). // Default: "api_platform.http_cache.purger.varnish" - * xkey?: array{ // Deprecated: The "xkey" configuration is deprecated, use your own purger to customize surrogate keys or the appropriate paramters. - * glue?: scalar|null, // xkey glue between keys // Default: " " - * }, - * }, - * }, - * mercure?: bool|array{ - * enabled?: bool, // Default: false - * hub_url?: scalar|null, // The URL sent in the Link HTTP header. If not set, will default to the URL for MercureBundle's default hub. // Default: null - * include_type?: bool, // Always include @type in updates (including delete ones). // Default: false - * }, - * messenger?: bool|array{ - * enabled?: bool, // Default: false - * }, - * elasticsearch?: bool|array{ - * enabled?: bool, // Default: false - * hosts?: list, - * }, - * openapi?: array{ - * contact?: array{ - * name?: scalar|null, // The identifying name of the contact person/organization. // Default: null - * url?: scalar|null, // The URL pointing to the contact information. MUST be in the format of a URL. // Default: null - * email?: scalar|null, // The email address of the contact person/organization. MUST be in the format of an email address. // Default: null - * }, - * termsOfService?: scalar|null, // A URL to the Terms of Service for the API. MUST be in the format of a URL. // Default: null - * tags?: list, - * license?: array{ - * name?: scalar|null, // The license name used for the API. // Default: null - * url?: scalar|null, // URL to the license used for the API. MUST be in the format of a URL. // Default: null - * identifier?: scalar|null, // An SPDX license expression for the API. The identifier field is mutually exclusive of the url field. // Default: null - * }, - * swagger_ui_extra_configuration?: mixed, // To pass extra configuration to Swagger UI, like docExpansion or filter. // Default: [] - * overrideResponses?: bool, // Whether API Platform adds automatic responses to the OpenAPI documentation. // Default: true - * error_resource_class?: scalar|null, // The class used to represent errors in the OpenAPI documentation. // Default: null - * validation_error_resource_class?: scalar|null, // The class used to represent validation errors in the OpenAPI documentation. // Default: null - * }, - * maker?: bool|array{ - * enabled?: bool, // Default: true - * }, - * exception_to_status?: array, - * formats?: array, - * }>, - * patch_formats?: array, - * }>, - * docs_formats?: array, - * }>, - * error_formats?: array, - * }>, - * jsonschema_formats?: list, - * defaults?: array{ - * uri_template?: mixed, - * short_name?: mixed, - * description?: mixed, - * types?: mixed, - * operations?: mixed, - * formats?: mixed, - * input_formats?: mixed, - * output_formats?: mixed, - * uri_variables?: mixed, - * route_prefix?: mixed, - * defaults?: mixed, - * requirements?: mixed, - * options?: mixed, - * stateless?: mixed, - * sunset?: mixed, - * accept_patch?: mixed, - * status?: mixed, - * host?: mixed, - * schemes?: mixed, - * condition?: mixed, - * controller?: mixed, - * class?: mixed, - * url_generation_strategy?: mixed, - * deprecation_reason?: mixed, - * headers?: mixed, - * cache_headers?: mixed, - * normalization_context?: mixed, - * denormalization_context?: mixed, - * collect_denormalization_errors?: mixed, - * hydra_context?: mixed, - * openapi?: mixed, - * validation_context?: mixed, - * filters?: mixed, - * mercure?: mixed, - * messenger?: mixed, - * input?: mixed, - * output?: mixed, - * order?: mixed, - * fetch_partial?: mixed, - * force_eager?: mixed, - * pagination_client_enabled?: mixed, - * pagination_client_items_per_page?: mixed, - * pagination_client_partial?: mixed, - * pagination_via_cursor?: mixed, - * pagination_enabled?: mixed, - * pagination_fetch_join_collection?: mixed, - * pagination_use_output_walkers?: mixed, - * pagination_items_per_page?: mixed, - * pagination_maximum_items_per_page?: mixed, - * pagination_partial?: mixed, - * pagination_type?: mixed, - * security?: mixed, - * security_message?: mixed, - * security_post_denormalize?: mixed, - * security_post_denormalize_message?: mixed, - * security_post_validation?: mixed, - * security_post_validation_message?: mixed, - * composite_identifier?: mixed, - * exception_to_status?: mixed, - * query_parameter_validation_enabled?: mixed, - * links?: mixed, - * graph_ql_operations?: mixed, - * provider?: mixed, - * processor?: mixed, - * state_options?: mixed, - * rules?: mixed, - * policy?: mixed, - * middleware?: mixed, - * parameters?: mixed, - * strict_query_parameter_validation?: mixed, - * hide_hydra_operation?: mixed, - * json_stream?: mixed, - * extra_properties?: mixed, - * map?: mixed, - * route_name?: mixed, - * errors?: mixed, - * read?: mixed, - * deserialize?: mixed, - * validate?: mixed, - * write?: mixed, - * serialize?: mixed, - * priority?: mixed, - * name?: mixed, - * allow_create?: mixed, - * item_uri_template?: mixed, - * ... - * }, - * } - * @psalm-type DamaDoctrineTestConfig = array{ - * enable_static_connection?: mixed, // Default: true - * enable_static_meta_data_cache?: bool, // Default: true - * enable_static_query_cache?: bool, // Default: true - * connection_keys?: list, - * } - * @psalm-type ConfigType = array{ - * imports?: ImportsConfig, - * parameters?: ParametersConfig, - * services?: ServicesConfig, - * framework?: FrameworkConfig, - * doctrine?: DoctrineConfig, - * doctrine_migrations?: DoctrineMigrationsConfig, - * security?: SecurityConfig, - * twig?: TwigConfig, - * monolog?: MonologConfig, - * webpack_encore?: WebpackEncoreConfig, - * datatables?: DatatablesConfig, - * liip_imagine?: LiipImagineConfig, - * twig_extra?: TwigExtraConfig, - * gregwar_captcha?: GregwarCaptchaConfig, - * florianv_swap?: FlorianvSwapConfig, - * nelmio_security?: NelmioSecurityConfig, - * turbo?: TurboConfig, - * tfa_webauthn?: TfaWebauthnConfig, - * scheb_two_factor?: SchebTwoFactorConfig, - * webauthn?: WebauthnConfig, - * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, - * stimulus?: StimulusConfig, - * ux_translator?: UxTranslatorConfig, - * dompdf_font_loader?: DompdfFontLoaderConfig, - * knpu_oauth2_client?: KnpuOauth2ClientConfig, - * nelmio_cors?: NelmioCorsConfig, - * jbtronics_settings?: JbtronicsSettingsConfig, - * api_platform?: ApiPlatformConfig, - * "when@dev"?: array{ - * imports?: ImportsConfig, - * parameters?: ParametersConfig, - * services?: ServicesConfig, - * framework?: FrameworkConfig, - * doctrine?: DoctrineConfig, - * doctrine_migrations?: DoctrineMigrationsConfig, - * security?: SecurityConfig, - * twig?: TwigConfig, - * web_profiler?: WebProfilerConfig, - * monolog?: MonologConfig, - * debug?: DebugConfig, - * maker?: MakerConfig, - * webpack_encore?: WebpackEncoreConfig, - * datatables?: DatatablesConfig, - * liip_imagine?: LiipImagineConfig, - * twig_extra?: TwigExtraConfig, - * gregwar_captcha?: GregwarCaptchaConfig, - * florianv_swap?: FlorianvSwapConfig, - * nelmio_security?: NelmioSecurityConfig, - * turbo?: TurboConfig, - * tfa_webauthn?: TfaWebauthnConfig, - * scheb_two_factor?: SchebTwoFactorConfig, - * webauthn?: WebauthnConfig, - * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, - * stimulus?: StimulusConfig, - * ux_translator?: UxTranslatorConfig, - * dompdf_font_loader?: DompdfFontLoaderConfig, - * knpu_oauth2_client?: KnpuOauth2ClientConfig, - * nelmio_cors?: NelmioCorsConfig, - * jbtronics_settings?: JbtronicsSettingsConfig, - * jbtronics_translation_editor?: JbtronicsTranslationEditorConfig, - * api_platform?: ApiPlatformConfig, - * }, - * "when@docker"?: array{ - * imports?: ImportsConfig, - * parameters?: ParametersConfig, - * services?: ServicesConfig, - * framework?: FrameworkConfig, - * doctrine?: DoctrineConfig, - * doctrine_migrations?: DoctrineMigrationsConfig, - * security?: SecurityConfig, - * twig?: TwigConfig, - * monolog?: MonologConfig, - * webpack_encore?: WebpackEncoreConfig, - * datatables?: DatatablesConfig, - * liip_imagine?: LiipImagineConfig, - * twig_extra?: TwigExtraConfig, - * gregwar_captcha?: GregwarCaptchaConfig, - * florianv_swap?: FlorianvSwapConfig, - * nelmio_security?: NelmioSecurityConfig, - * turbo?: TurboConfig, - * tfa_webauthn?: TfaWebauthnConfig, - * scheb_two_factor?: SchebTwoFactorConfig, - * webauthn?: WebauthnConfig, - * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, - * stimulus?: StimulusConfig, - * ux_translator?: UxTranslatorConfig, - * dompdf_font_loader?: DompdfFontLoaderConfig, - * knpu_oauth2_client?: KnpuOauth2ClientConfig, - * nelmio_cors?: NelmioCorsConfig, - * jbtronics_settings?: JbtronicsSettingsConfig, - * api_platform?: ApiPlatformConfig, - * }, - * "when@prod"?: array{ - * imports?: ImportsConfig, - * parameters?: ParametersConfig, - * services?: ServicesConfig, - * framework?: FrameworkConfig, - * doctrine?: DoctrineConfig, - * doctrine_migrations?: DoctrineMigrationsConfig, - * security?: SecurityConfig, - * twig?: TwigConfig, - * monolog?: MonologConfig, - * webpack_encore?: WebpackEncoreConfig, - * datatables?: DatatablesConfig, - * liip_imagine?: LiipImagineConfig, - * twig_extra?: TwigExtraConfig, - * gregwar_captcha?: GregwarCaptchaConfig, - * florianv_swap?: FlorianvSwapConfig, - * nelmio_security?: NelmioSecurityConfig, - * turbo?: TurboConfig, - * tfa_webauthn?: TfaWebauthnConfig, - * scheb_two_factor?: SchebTwoFactorConfig, - * webauthn?: WebauthnConfig, - * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, - * stimulus?: StimulusConfig, - * ux_translator?: UxTranslatorConfig, - * dompdf_font_loader?: DompdfFontLoaderConfig, - * knpu_oauth2_client?: KnpuOauth2ClientConfig, - * nelmio_cors?: NelmioCorsConfig, - * jbtronics_settings?: JbtronicsSettingsConfig, - * api_platform?: ApiPlatformConfig, - * }, - * "when@test"?: array{ - * imports?: ImportsConfig, - * parameters?: ParametersConfig, - * services?: ServicesConfig, - * framework?: FrameworkConfig, - * doctrine?: DoctrineConfig, - * doctrine_migrations?: DoctrineMigrationsConfig, - * security?: SecurityConfig, - * twig?: TwigConfig, - * web_profiler?: WebProfilerConfig, - * monolog?: MonologConfig, - * debug?: DebugConfig, - * webpack_encore?: WebpackEncoreConfig, - * datatables?: DatatablesConfig, - * liip_imagine?: LiipImagineConfig, - * dama_doctrine_test?: DamaDoctrineTestConfig, - * twig_extra?: TwigExtraConfig, - * gregwar_captcha?: GregwarCaptchaConfig, - * florianv_swap?: FlorianvSwapConfig, - * nelmio_security?: NelmioSecurityConfig, - * turbo?: TurboConfig, - * tfa_webauthn?: TfaWebauthnConfig, - * scheb_two_factor?: SchebTwoFactorConfig, - * webauthn?: WebauthnConfig, - * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, - * stimulus?: StimulusConfig, - * ux_translator?: UxTranslatorConfig, - * dompdf_font_loader?: DompdfFontLoaderConfig, - * knpu_oauth2_client?: KnpuOauth2ClientConfig, - * nelmio_cors?: NelmioCorsConfig, - * jbtronics_settings?: JbtronicsSettingsConfig, - * api_platform?: ApiPlatformConfig, - * }, - * ..., - * }> - * } - */ -final class App -{ - /** - * @param ConfigType $config - * - * @psalm-return ConfigType - */ - public static function config(array $config): array - { - return AppReference::config($config); - } -} - -namespace Symfony\Component\Routing\Loader\Configurator; - -/** - * This class provides array-shapes for configuring the routes of an application. - * - * Example: - * - * ```php - * // config/routes.php - * namespace Symfony\Component\Routing\Loader\Configurator; - * - * return Routes::config([ - * 'controllers' => [ - * 'resource' => 'routing.controllers', - * ], - * ]); - * ``` - * - * @psalm-type RouteConfig = array{ - * path: string|array, - * controller?: string, - * methods?: string|list, - * requirements?: array, - * defaults?: array, - * options?: array, - * host?: string|array, - * schemes?: string|list, - * condition?: string, - * locale?: string, - * format?: string, - * utf8?: bool, - * stateless?: bool, - * } - * @psalm-type ImportConfig = array{ - * resource: string, - * type?: string, - * exclude?: string|list, - * prefix?: string|array, - * name_prefix?: string, - * trailing_slash_on_root?: bool, - * controller?: string, - * methods?: string|list, - * requirements?: array, - * defaults?: array, - * options?: array, - * host?: string|array, - * schemes?: string|list, - * condition?: string, - * locale?: string, - * format?: string, - * utf8?: bool, - * stateless?: bool, - * } - * @psalm-type AliasConfig = array{ - * alias: string, - * deprecated?: array{package:string, version:string, message?:string}, - * } - * @psalm-type RoutesConfig = array{ - * "when@dev"?: array, - * "when@docker"?: array, - * "when@prod"?: array, - * "when@test"?: array, - * ... - * } - */ -final class Routes -{ - /** - * @param RoutesConfig $config - * - * @psalm-return RoutesConfig - */ - public static function config(array $config): array - { - return $config; - } -} diff --git a/config/routes.yaml b/config/routes.yaml index 4830b774..8b38fa71 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -1,12 +1,3 @@ -# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json - -# This file is the entry point to configure the routes of your app. -# Methods with the #[Route] attribute are automatically imported. -# See also https://symfony.com/doc/current/routing.html - -# To list all registered routes, run the following command: -# bin/console debug:router - # Redirect every url without an locale to the locale of the user/the global base locale scan_qr: @@ -25,4 +16,4 @@ redirector: url: ".*" controller: App\Controller\RedirectController::addLocalePart # Dont match localized routes (no redirection loop, if no root with that name exists) or API prefixed routes - condition: "not (request.getPathInfo() matches '/^\\\\/([a-z]{2}(_[A-Z]{2})?|api)\\\\//')" + condition: "not (request.getPathInfo() matches '/^\\\\/([a-z]{2}(_[A-Z]{2})?|api)\\\\//')" \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 4ba33456..17611cea 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,8 +1,5 @@ -# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json - # This file is the entry point to configure your own services. # Files in the packages/ subdirectory configure your dependencies. -# See also https://symfony.com/doc/current/service_container/import.html # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration @@ -32,9 +29,6 @@ services: # this creates a service per class whose id is the fully-qualified class name App\: resource: '../src/' - exclude: - - '../src/DataFixtures/' - - '../src/Doctrine/Purger/' # controllers are imported separately to make sure services can be injected # as action arguments even if you don't extend any base controller class @@ -233,15 +227,9 @@ services: arguments: $enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%' - App\Repository\PartRepository: - arguments: - $translator: '@translator' - tags: ['doctrine.repository_service'] - - App\EventSubscriber\UserSystem\PartUniqueIpnSubscriber: + App\Doctrine\Purger\ResetAutoIncrementPurgerFactory: tags: - - { name: doctrine.event_listener, event: onFlush, connection: default } - + - { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' } # We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container. App\Services\UserSystem\PermissionPresetsHelper: @@ -274,21 +262,8 @@ services: tags: - { name: monolog.processor } -when@test: &test +when@test: services: - - App\DataFixtures\: - resource: '../src/DataFixtures/' - autoconfigure: true - autowire: true - - App\Doctrine\Purger\: - resource: '../src/Doctrine/Purger/' - - App\Doctrine\Purger\ResetAutoIncrementPurgerFactory: - tags: - - { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' } - # Decorate the doctrine fixtures load command to use our custom purger by default doctrine.fixtures_load_command.custom: decorates: doctrine.fixtures_load_command @@ -297,6 +272,3 @@ when@test: &test - '@doctrine.fixtures.loader' - '@doctrine' - { default: '@App\Doctrine\Purger\DoNotUsePurgerFactory' } - -when@dev: - *test diff --git a/docs/api/intro.md b/docs/api/intro.md index 283afbe6..78a8d2c1 100644 --- a/docs/api/intro.md +++ b/docs/api/intro.md @@ -17,7 +17,7 @@ This allows external applications to interact with Part-DB, extend it or integra > Some features might be missing or not working yet. > Also be aware, that there might be security issues in the API, which could allow attackers to access or edit data via > the API, which -> they normally should not be able to access. So currently you should only use the API with trusted users and trusted +> they normally should be able to access. So currently you should only use the API with trusted users and trusted > applications. Part-DB uses [API Platform](https://api-platform.com/) to provide the API, which allows for easy creation of REST APIs @@ -46,7 +46,7 @@ See [Authentication chapter]({% link api/authentication.md %}) for more details. The API is split into different endpoints, which are reachable under the `/api/` path of your Part-DB instance ( e.g. `https://your-part-db.local/api/`). -There are various endpoints for each entity type (like `parts`, `manufacturers`, etc.), which allow you to read and write data, and some special endpoints like `search` or `statistics`. +There are various endpoints for each entity type (like `part`, `manufacturer`, etc.), which allow you to read and write data, and some special endpoints like `search` or `statistics`. For example, all API endpoints for managing categories are available under `/api/categories/`. Depending on the exact path and the HTTP method used, you can read, create, update or delete categories. @@ -56,7 +56,7 @@ For most entities, there are endpoints like this: * **POST**: `/api/categories/` - Create a new category * **GET**: `/api/categories/{id}` - Get a specific category by its ID * **DELETE**: `/api/categories/{id}` - Delete a specific category by its ID -* **PATCH**: `/api/categories/{id}` - Update a specific category by its ID. Only the fields which are sent in the +* **UPDATE**: `/api/categories/{id}` - Update a specific category by its ID. Only the fields which are sent in the request are updated, all other fields are left unchanged. Be aware that you have to set the [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386) content type header (`Content-Type: application/merge-patch+json`) for this to work. @@ -106,11 +106,11 @@ This is a great way to test the API and see how it works, without having to writ By default, all list endpoints are paginated, which means only a certain number of results is returned per request. To get another page of the results, you have to use the `page` query parameter, which contains the page number you want -to get (e.g. `/api/categories/?page=2`). +to get (e.g. `/api/categoues/?page=2`). When using JSONLD, the links to the next page are also included in the `hydra:view` property of the response. To change the size of the pages (the number of items in a single page) use the `itemsPerPage` query parameter ( -e.g. `/api/categories/?itemsPerPage=50`). +e.g. `/api/categoues/?itemsPerPage=50`). See [API Platform docs](https://api-platform.com/docs/core/pagination) for more infos. diff --git a/docs/assets/usage/import_export/part_import_example.csv b/docs/assets/usage/import_export/part_import_example.csv index 14d4500f..08701426 100644 --- a/docs/assets/usage/import_export/part_import_example.csv +++ b/docs/assets/usage/import_export/part_import_example.csv @@ -1,7 +1,4 @@ -name;description;category;notes;footprint;tags;quantity;storage_location;mass;ipn;mpn;manufacturing_status;manufacturer;supplier;spn;price;favorite;needs_review;minamount;partUnit;eda_info.reference_prefix;eda_info.value;eda_info.visibility;eda_info.exclude_from_bom;eda_info.exclude_from_board;eda_info.exclude_from_sim;eda_info.kicad_symbol;eda_info.kicad_footprint -"MLCC; 0603; 0.22uF";Multilayer ceramic capacitor;Electrical Components->Passive Components->Capacitors_SMD;High quality MLCC;0603;Capacitor,SMD,MLCC,0603;500;Room 1->Shelf 1->Box 2;0.1;CL10B224KO8NNNC;CL10B224KO8NNNC;active;Samsung;LCSC;C160828;0.0023;0;0;1;pcs;C;0.22uF;1;0;0;0;Device:C;Capacitor_SMD:C_0603_1608Metric -"MLCC; 0402; 10pF";Small MLCC for high frequency;Electrical Components->Passive Components->Capacitors_SMD;;0402;Capacitor,SMD,MLCC,0402;500;Room 1->Shelf 1->Box 3;0.05;FCC0402N100J500AT;FCC0402N100J500AT;active;Fenghua;LCSC;C5137557;0.0015;0;0;1;pcs;C;10pF;1;0;0;0;Device:C;Capacitor_SMD:C_0402_1005Metric -"Diode; 1N4148W";Fast switching diode;Electrical Components->Semiconductors->Diodes;Fast recovery time;Diode_SMD:D_SOD-123;Diode,SMD,Schottky;100;Room 2->Box 1;0.2;1N4148W;1N4148W;active;Vishay;LCSC;C917030;0.008;0;0;1;pcs;D;1N4148W;1;0;0;0;Device:D;Diode_SMD:D_SOD-123 -BC547;NPN transistor;Transistors->NPN;very important notes;TO->TO-92;NPN,Transistor;5;Room 1->Shelf 1->Box 2;10;BC547;BC547;active;Generic;LCSC;BC547C;2.3;0;0;1;pcs;Q;BC547;1;0;0;0;Device:Q_NPN_EBC;TO_SOT_Packages_SMD:TO-92_HandSolder -BC557;PNP transistor;Transistors->PNP;PNP complement to BC547;TO->TO-92;PNP,Transistor;10;Room 2->Box 3;10;BC557;BC557;active;Generic;LCSC;BC557C;2.1;0;0;1;pcs;Q;BC557;1;0;0;0;Device:Q_PNP_EBC;TO_SOT_Packages_SMD:TO-92_HandSolder -Copper Wire;Bare copper wire;Wire->Copper;For prototyping;Wire;Wire,Copper;50;Room 3->Spool Rack;0.5;CW-22AWG;CW-22AWG;active;Generic;Local Supplier;LS-CW-22;0.15;0;0;1;Meter;W;22AWG;1;0;0;0;Device:Wire;Connector_PinHeader_2.54mm:PinHeader_1x01_P2.54mm_Vertical +name;description;category;notes;footprint;tags;quantity;storage_location;mass;ipn;mpn;manufacturing_status;manufacturer;supplier;spn;price;favorite;needs_review;minamount;partUnit;manufacturing_status +BC547;NPN transistor;Transistors -> NPN;very important notes;TO -> TO-92;NPN,Transistor;5;Room 1 -> Shelf 1 -> Box 2;10;;;Manufacturer;;You need to fill this line, to use spn and price;BC547C;2,3;0;;;; +BC557;PNP transistor;HTML;;TO -> TO-92;PNP,Transistor;10;Room 2-> Box 3;;Internal1234;;;;;;;;1;;;active +Copper Wire;;Wire;;;;;;;;;;;;;;;;;Meter; \ No newline at end of file diff --git a/docs/concepts.md b/docs/concepts.md index 8a3551bd..ddf38633 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -23,14 +23,14 @@ each other so that it does not matter which one of your 1000 things of Part you A part entity has many fields, which can be used to describe it better. Most of the fields are optional: * **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer-provided name, or a - name you thought of yourself. Each name needs to be unique and must exist in a single category only. + name you thought of yourself. Each name needs to be unique and must exist in a single category. * **Description**: A short (single-line) description of what this part is/does. For longer information, you should use the comment field or the specifications * **Category** (Required): The category (see there) to which this part belongs to. * **Tags**: The list of tags this part belongs to. Tags can be used to group parts logically (similar to the category), - but tags are much less strict and formal (they don't have to be defined beforehand) and you can assign multiple tags to + but tags are much less strict and formal (they don't have to be defined forehands) and you can assign multiple tags to a part. When clicking on a tag, a list with all parts which have the same tag, is shown. -* **Min Instock**: *Not fully implemented yet*. Parts where the total instock is below this value will show up for +* **Min Instock**: *Not really implemented yet*. Parts where the total instock is below this value, will show up for ordering. * **Footprint**: See there. Useful especially for electronic parts, which have one of the common electronic footprints ( like DIP8, SMD0805 or similar). If a part has no explicitly defined preview picture, the preview picture of its @@ -48,9 +48,9 @@ A part entity has many fields, which can be used to describe it better. Most of completely trustworthy. * **Favorite**: Parts with this flag are highlighted in parts lists * **Mass**: The mass of a single piece of this part (so of a single transistor). Given in grams. -* **Internal Part Number** (IPN): Each part is automatically assigned a numerical ID that identifies a part in the - database. This ID depends on when a part was created and cannot be changed. If you want to assign your own unique - identifiers, or sync parts identifiers with the identifiers of another database, you can use this field. +* **Internal Part number** (IPN): Each part is automatically assigned a numerical ID that identifies a part in the + database. This ID depends on when a part was created and can not be changed. If you want to assign your own unique + identifiers, or sync parts identifiers with the identifiers of another database you can use this field. ### Stock / Part lot @@ -99,12 +99,12 @@ possible category tree could look like this: ### Supplier -A supplier is a vendor/distributor where you can buy/order parts. Price information of parts is associated with a +A Supplier is a vendor/distributor where you can buy/order parts. Price information of parts is associated with a supplier. ### Manufacturer -A manufacturer represents the company that manufactures/builds various parts (not necessarily sells them). If the +A manufacturer represents the company that manufacturers/builds various parts (not necessarily sell them). If the manufacturer also sells the parts, you have to create a supplier for that. ### Storage location diff --git a/docs/configuration.md b/docs/configuration.md index 709c39b3..d4b21781 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -6,11 +6,11 @@ nav_order: 5 # Configuration -Part-DB's behavior can be configured to your needs. There are different kinds of configuration options: Options that are +Part-DBs behavior can be configured to your needs. There are different kinds of configuration options: Options, which are user-changeable (changeable dynamically via frontend), options that can be configured by environment variables, and options that are only configurable via Symfony config files. -## User configuration +## User configruation The following things can be changed for every user and a user can change it for himself (if he has the correct permission for it). Configuration is either possible via the user's own settings page (where you can also change the password) or via @@ -40,10 +40,10 @@ The following configuration options can only be changed by the server administra variables, changing the `.env.local` file or setting env for your docker container. Here are just the most important options listed, see `.env` file for the full list of possible env variables. -Environment variables allow you to overwrite settings in the web interface. This is useful if you want to enforce certain -settings to be unchangeable by users, or if you want to configure settings in a central place in a deployed environment. +Environment variables allow to overwrite settings in the web interface. This is useful, if you want to enforce certain +settings to be unchangable by users, or if you want to configure settings in a central place in a deployed environment. On the settings page, you can hover over a setting to see, which environment variable can be used to overwrite it, it -is shown as tooltip. API keys or similar sensitive data which is overwritten by env variables, are redacted on the web +is shown as tooltip. API keys or similar sensitve data which is overwritten by env variables, are redacted on the web interface, so that even administrators cannot see them (only the last 2 characters and the length). For technical and security reasons some settings can only be configured via environment variables and not via the web @@ -105,27 +105,17 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept * `part_delete`: Delete operation of an existing part * `part_create`: Creation of a new part * `part_stock_operation`: Stock operation on a part (therefore withdraw, add or move stock) - * `datastructure_edit`: Edit operation of an existing data structure (e.g. category, manufacturer, ...) - * `datastructure_delete`: Delete operation of an existing data structure (e.g. category, manufacturer, ...) - * `datastructure_create`: Creation of a new data structure (e.g. category, manufacturer, ...) -* `CHECK_FOR_UPDATES` (default `1`): Set this to 0 if you do not want Part-DB to connect to GitHub to check for new - versions, or if your server cannot connect to the internet. + * `datastructure_edit`: Edit operation of an existing datastructure (e.g. category, manufacturer, ...) + * `datastructure_delete`: Delete operation of a existing datastructure (e.g. category, manufacturer, ...) + * `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...) +* `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new + versions, or if your server can not connect to the internet. * `APP_SECRET` (env only): This variable is a configuration parameter used for various security-related purposes, particularly for securing and protecting various aspects of your application. It's a secret key that is used for cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this value should be handled as confidential data and not shared publicly. * `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the part image gallery -* `IPN_SUGGEST_REGEX`: A global regular expression, that part IPNs have to fulfill. Enforce your own format for your users. -* `IPN_SUGGEST_REGEX_HELP`: Define your own user help text for the Regex format specification. -* `IPN_AUTO_APPEND_SUFFIX`: When enabled, an incremental suffix will be added to the user input when entering an existing -* IPN again upon saving. -* `IPN_SUGGEST_PART_DIGITS`: Defines the fixed number of digits used as the increment at the end of an IPN (Internal Part Number). - IPN prefixes, maintained within part categories and their hierarchy, form the foundation for suggesting complete IPNs. - These suggestions become accessible during IPN input of a part. The constant specifies the digits used to calculate and assign - unique increments for parts within a category hierarchy, ensuring consistency and uniqueness in IPN generation. -* `IPN_USE_DUPLICATE_DESCRIPTION`: When enabled, the part’s description is used to find existing parts with the same - description and to determine the next available IPN by incrementing their numeric suffix for the suggestion list. ### E-Mail settings (all env only) @@ -146,7 +136,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept * `TABLE_PARTS_DEFAULT_COLUMNS`: The columns in parts tables, which are visible by default (when loading table for first time). Also specify the default order of the columns. This is a comma separated list of column names. Available columns - are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `partCustomState`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`. + are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`. ### History/Eventlog-related settings @@ -262,10 +252,10 @@ markdown (and even some subset of HTML) syntax to format the text. ## parameters.yaml -You can also configure some options via the `config/parameters.yaml` file. This should normally not be needed, -and you should know what you are doing when you change something here. You should expect that you will have to do some -manual merges when you have changed something here and update to a newer version of Part-DB. It is possible that -configuration options here will change or be completely removed in future versions of Part-DB. +You can also configure some options via the `config/parameters.yaml` file. This should normally not need, +and you should know what you are doing, when you change something here. You should expect, that you will have to do some +manual merge, when you have changed something here and update to a newer version of Part-DB. It is possible that +configuration options here will change or be completely removed in future versions of Part-DB. If you change something here, you have to clear the cache, before the changes will take effect with the command `bin/console cache:clear`. diff --git a/docs/index.md b/docs/index.md index c2128946..d732f31d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,7 +18,8 @@ It is installed on a web server and so can be accessed with any browser without > You can log in with username: **user** and password: **user**, to change/create data. > > Every change to the master branch gets automatically deployed, so it represents the current development progress and -> may not be completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading +> is +> maybe not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading > the page > for the first time. @@ -27,32 +28,32 @@ It is installed on a web server and so can be accessed with any browser without * Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer, and multiple store locations and price information. Parts can be grouped using tags. You can associate various files like datasheets or pictures with the parts. -* Multi-language support (currently German, English, Russian, Japanese, French, Czech, Danish, and Chinese) -* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the built-in barcode scanner -* User system with groups and detailed (fine-grained) permissions. +* Multi-language support (currently German, English, Russian, Japanese and French (experimental)) +* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner +* User system with groups and detailed (fine granular) permissions. Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. - Password reset via email can be set up. + Password reset via email can be setup. * Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server) * Import/Export system * Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB -* Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older +* 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. +* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface. * 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) * Easy migration from an existing PartKeepr instance (see [here]({%link partkeepr_migration.md %})) -* Use cloud providers (like Octopart, Digikey, Farnell, Mouser, or TME) to automatically get part information, datasheets, and +* Use cloud providers (like Octopart, Digikey, Farnell or TME) to automatically get part information, datasheets and prices for parts (see [here]({% link usage/information_provider_system.md %})) * API to access Part-DB from other applications/scripts -* [Integration with KiCad]({%link usage/eda_integration.md %}): Use Part-DB as the central datasource for your - KiCad and see available parts from Part-DB directly inside KiCad. +* [Integration with KiCad]({%link usage/eda_integration.md %}): Use Part-DB as central datasource for your + KiCad and see available parts from Part-DB directly inside KiCad. -With these features, Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory, -or makerspaces, where many users should have (controlled) access to the shared inventory. +With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory, +or makerspaces, where many users have should have (controlled) access to the shared inventory. Part-DB is also used by small companies and universities for managing their inventory. @@ -67,11 +68,11 @@ See [LICENSE](https://github.com/Part-DB/Part-DB-symfony/blob/master/LICENSE) fo ## Donate for development If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name). -There you will find various methods to support development on a monthly or a one-time basis. +There you will find various methods to support development on a monthly or a one time base. ## Built with -* [Symfony 6](https://symfony.com/): The main framework used for the serverside PHP +* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP * [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme * [Fontawesome](https://fontawesome.com/): Used as icon set * [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend diff --git a/docs/installation/choosing_database.md b/docs/installation/choosing_database.md index 8a070120..cd9657d4 100644 --- a/docs/installation/choosing_database.md +++ b/docs/installation/choosing_database.md @@ -38,7 +38,7 @@ you have started creating data**. So you should choose the database type for you * **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. -* **Emulated natural sorting**: SQLite does not support natural sorting natively. Part-DB can emulate it, but it 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. @@ -131,7 +131,7 @@ The host (here 127.0.0.1) and port should also be specified according to your My 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 connection, you can specify the socket path in the `unix_socket` parameter. +If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `unix_socket` parameter. ```shell DATABASE_URL="mysql://user:password@localhost/database?serverVersion=8.0.37&unix_socket=/var/run/mysqld/mysqld.sock" ``` @@ -150,7 +150,7 @@ In the `serverVersion` parameter you can specify the version of the PostgreSQL s The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly. -If you want to use a unix socket for the connection instead of a TCP connection, you can specify the socket path in the `host` parameter. +If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `host` parameter. ```shell DATABASE_URL="postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql" ``` @@ -177,6 +177,6 @@ In natural sorting, it would be sorted as: 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 explicitly enabled by setting the +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/docs/installation/email.md b/docs/installation/email.md index 0418fb4a..c9feaba6 100644 --- a/docs/installation/email.md +++ b/docs/installation/email.md @@ -19,7 +19,7 @@ automatic mail providers (like MailChimp or SendGrid). If you want to use one of Mailer documentation for more information. We will only cover the configuration of an SMTP provider here, which is sufficient for most use-cases. -You will need an email account, which you can use to send emails from via password-based SMTP authentication, this account +You will need an email account, which you can use send emails from via password-bases SMTP authentication, this account should be dedicated to Part-DB. To configure the SMTP provider, you have to set the following environment variables: diff --git a/docs/installation/index.md b/docs/installation/index.md index aa12e582..217f702a 100644 --- a/docs/installation/index.md +++ b/docs/installation/index.md @@ -8,4 +8,4 @@ has_children: true # Installation Below you can find some guides to install Part-DB. -For hobbyists without much experience, we recommend the Docker installation or direct installation on Debian. \ No newline at end of file +For the hobbyists without much experience, we recommend the docker installation or direct installation on debian. \ No newline at end of file diff --git a/docs/installation/installation_docker.md b/docs/installation/installation_docker.md index 347c2451..232633ab 100644 --- a/docs/installation/installation_docker.md +++ b/docs/installation/installation_docker.md @@ -136,18 +136,18 @@ services: # In docker env logs will be redirected to stderr - APP_ENV=docker - # Uncomment this, if you want to use the automatic database migration feature. With this you do not have to + # Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to # run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ # folder (under .automigration-backup), so you can restore it, if the migration fails. # This feature is currently experimental, so use it at your own risk! # - DB_AUTOMIGRATE=true # You can configure Part-DB using the webUI or environment variables - # However you can add any other environment configuration you want here + # However you can add add any other environment configuration you want here # See .env file for all available options or https://docs.part-db.de/configuration.html - # Override value if you want to show a given text on homepage. - # When this is commented out the webUI can be used to configure the banner + # Override value if you want to show to show a given text on homepage. + # When this is outcommented the webUI can be used to configure the banner #- BANNER=This is a test banner
    with a line break database: diff --git a/docs/installation/nginx.md b/docs/installation/nginx.md index db209d92..84305975 100644 --- a/docs/installation/nginx.md +++ b/docs/installation/nginx.md @@ -7,7 +7,7 @@ nav_order: 10 # Nginx -You can also use [nginx](https://www.nginx.com/) as webserver for Part-DB. Setting up Part-DB with Apache is a bit easier, so +You can also use [nginx](https://www.nginx.com/) as webserver for Part-DB. Setup Part-DB with apache is a bit easier, so this is the method shown in the guides. This guide assumes that you already have a working nginx installation with PHP configured. diff --git a/docs/installation/saml_sso.md b/docs/installation/saml_sso.md index f9752546..d2e65e7f 100644 --- a/docs/installation/saml_sso.md +++ b/docs/installation/saml_sso.md @@ -21,7 +21,7 @@ LDAP or Active Directory server. {: .warning } > This feature is currently in beta. Please report any bugs you find. -> So far it has only been tested with Keycloak, but it should work with any SAML 2.0 compatible identity provider. +> So far it has only tested with Keycloak, but it should work with any SAML 2.0 compatible identity provider. This guide will show you how to configure Part-DB with [Keycloak](https://www.keycloak.org/) as the SAML identity provider, but it should work with any SAML 2.0 compatible identity provider. @@ -75,8 +75,8 @@ the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/gettin ### Configure Part-DB to use SAML -1. Open the `.env.local` file of Part-DB (or the docker-compose.yaml) for editing -2. Set the `SAML_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the +1. Open the `.env.local` file of Part-DB (or the docker-compose.yaml) for edit +2. Set the `SAMLP_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the previous step. It should start with `MIEE` and end with `=`. 3. Set the `SAML_SP_X509_CERT` environment variable to the content of the Certificate field shown in the `Keys` tab of the SAML client in Keycloak. It should start with `MIIC` and end with `=`. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 74f9875e..f20a7f22 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -9,7 +9,7 @@ Sometimes things go wrong and Part-DB shows an error message. This page should h ## Error messages -When a common, easily fixable error occurs (like a non-up-to-date database), Part-DB will show you some short instructions +When a common, easy fixable error occurs (like a non-up-to-date database), Part-DB will show you some short instructions on how to fix the problem. If you have a problem that is not listed here, please open an issue on GitHub. ## General procedure @@ -28,9 +28,9 @@ php bin/console cache:clear php bin/console doctrine:migrations:migrate ``` -If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-server). +If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony). -## Search for a user and reset the password +## Search for the user and reset the password: You can list all users with the following command: `php bin/console partdb:users:list` To reset the password of a user you can use the following @@ -52,4 +52,4 @@ Please include the error logs in your issue on GitHub, if you open an issue. ## Report Issue -If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-server). +If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony). diff --git a/docs/upgrade/1_to_2.md b/docs/upgrade/1_to_2.md index ef0f4575..f5b3b085 100644 --- a/docs/upgrade/1_to_2.md +++ b/docs/upgrade/1_to_2.md @@ -27,7 +27,7 @@ about the requirements at all. ## Changes * Configuration is now preferably done via a web settings interface. You can still use environment variables, these overwrite -the settings in the web interface. Existing configuration will still work, but you should consider migrating them to the +the settings in the web interface. Existing configuration will still work, but you should consider migriting them to the web interface as described below. * The `config/banner.md` file that could been used to customize the banner text, was removed. You can now set the banner text directly in the admin interface, or by setting the `BANNER` environment variable. If you want to keep your existing @@ -43,20 +43,19 @@ The upgrade process works very similar to a normal (minor release) upgrade. ### Direct installation -**Be sure to execute the following steps as the user that owns the Part-DB files (e.g. `www-data`, or your webserver user). So prepend a `sudo -u www-data` where necessary.** +**Be sure to execute the following steps as the user that owns the Part-DB files (e.g. `www-data`, or your webserver user). So prepend a `sudo -u wwww-data` where necessary.** 1. Make a backup of your existing Part-DB installation, including the database, data directories and the configuration files and `.env.local` file. The `php bin/console partdb:backup` command can help you with this. 2. Pull the v2 version. For git installation you can do this with `git checkout v2.0.0` (or newer version) -3. Remove the `var/cache/` directory inside the Part-DB installation to ensure that no old cache files remain. -4. Run `composer install --no-dev -o` to update the dependencies. -5. Run `yarn install` and `yarn build` to update the frontend assets. -6. Run `php bin/console doctrine:migrations:migrate` to update the database schema. -7. Clear the cache with `php bin/console cache:clear`. -8. Open your Part-DB instance in the browser and log in as an admin user. -9. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration"). -10. You can now go to the settings page (under "System" and "Settings") and check if all settings are correct. -11. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface. +3. Run `composer install --no-dev -o` to update the dependencies. +4. Run `yarn install` and `yarn build` to update the frontend assets. +5. Rund `php bin/console doctrine:migrations:migrate` to update the database schema. +6. Clear the cache with `php bin/console cache:clear`. +7. Open your Part-DB instance in the browser and log in as an admin user. +8. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration"). +9. You can now go to the settings page (under "System" and "Settings") and check if all settings are correct. +10. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface. If you want to change them, you must migrate them to the settings interface as described below. ### Docker installation @@ -79,7 +78,7 @@ To change it, you must migrate your environment variable configuration to the ne For this there is the new console command `settings:migrate-env-to-settings`, which reads in all environment variables used to overwrite settings and write them to the database, so that you can safely delete them from your environment variable configuration afterwards, without -losing your configuration. +loosing your configuration. To run the command, execute `php bin/console settings:migrate-env-to-settings --all` as webserver user (or run `docker exec --user=www-data -it partdb php bin/console settings:migrate-env-to-settings --all` for docker containers). It will list you all environment variables, it found and ask you for confirmation to migrate them. Answer with `yes` to migrate them and hit enter. @@ -88,15 +87,3 @@ After the migration run successfully, the contents of your environment variables Go through the environment variables listed by the command and remove them from your environment variable configuration (e.g. `.env.local` file or docker compose file), or just comment them out for now. If you want to keep some environment variables, just leave them as they are, they will still work as before, the migration command only affects the settings stored in the database. - - -## Troubleshooting - -### cache:clear fails: You have requested a non-existent parameter "jbtronics.settings.proxy_dir". -If you receive an error like -``` -In App_KernelProdContainer.php line 2839: -You have requested a non-existent parameter "jbtronics.settings.proxy_dir". -``` -when running `php bin/console cache:clear` or `composer install`. You have to manually delete the `var/cache/` -directory inside your Part-DB installation and try again. diff --git a/docs/upgrade/index.md b/docs/upgrade/index.md index 4462f0dd..95a9cc33 100644 --- a/docs/upgrade/index.md +++ b/docs/upgrade/index.md @@ -5,7 +5,5 @@ nav_order: 7 has_children: true --- -# Upgrade - This section provides information on how to upgrade Part-DB to the latest version. -This is intended for major release upgrades, where requirements or things change significantly. +This is intended for major release upgrades, where requirements or things changes significantly. diff --git a/docs/upgrade/upgrade_legacy.md b/docs/upgrade/upgrade_legacy.md index b83661f3..4dd29e4d 100644 --- a/docs/upgrade/upgrade_legacy.md +++ b/docs/upgrade/upgrade_legacy.md @@ -24,7 +24,7 @@ sections carefully before proceeding to upgrade. also more sensitive stuff like database migration works via CLI now, so you should have console access on your server. * Markdown/HTML is now used instead of BBCode for rich text in description and command fields. It is possible to migrate your existing BBCode to Markdown - via `php bin/console partdb:migrations:convert-bbcode`. + via `php bin/console php bin/console partdb:migrations:convert-bbcode`. * Server exceptions are not logged into event log anymore. For security reasons (exceptions can contain sensitive information) exceptions are only logged to server log (by default under './var/log'), so only the server admins can access it. * Profile labels are now saved in the database (before they were saved in a separate JSON file). **The profiles of legacy diff --git a/docs/usage/backup_restore.md b/docs/usage/backup_restore.md index c4444d24..bef3792d 100644 --- a/docs/usage/backup_restore.md +++ b/docs/usage/backup_restore.md @@ -6,7 +6,7 @@ parent: Usage # Backup and Restore Data -When working productively, you should back up the data and configuration of Part-DB regularly to prevent data loss. This +When working productively you should back up the data and configuration of Part-DB regularly to prevent data loss. This is also useful if you want to migrate your Part-DB instance from one server to another. In that case, you just have to back up the data on server 1, move the backup to server 2, install Part-DB on server 2, and restore the backup. @@ -27,7 +27,7 @@ for more info about these options. ## Backup (manual) -Three parts have to be backed up: The configuration files, which contain the instance-specific options, the +3 parts have to be backup-ed: The configuration files, which contain the instance-specific options, the uploaded files of attachments, and the database containing the most data of Part-DB. Everything else like thumbnails and cache files, are recreated automatically when needed. @@ -44,7 +44,7 @@ You have to recursively copy the `uploads/` folder and the `public/media` folder #### SQLite -If you are using SQLite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`) +If you are using sqlite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`) to your backup location. #### MySQL / MariaDB @@ -56,7 +56,7 @@ interface (`mysqldump -uBACKUP -pPASSWORD DATABASE`) ## Restore Install Part-DB as usual as described in the installation section, except for the database creation/migration part. You -have to use the same database type (SQLite or MySQL) as on the backed up server instance. +have to use the same database type (SQLite or MySQL) as on the backuped server instance. ### Restore configuration @@ -71,7 +71,7 @@ Copy the `uploads/` and the `public/media/` folder from your backup into your ne #### SQLite -Copy the backed up `app.db` into the database folder normally `var/app.db` in Part-DB root folder. +Copy the backup-ed `app.db` into the database folder normally `var/app.db` in Part-DB root folder. #### MySQL / MariaDB diff --git a/docs/usage/console_commands.md b/docs/usage/console_commands.md index 2ca35ec4..173f7b78 100644 --- a/docs/usage/console_commands.md +++ b/docs/usage/console_commands.md @@ -8,7 +8,7 @@ parent: Usage Part-DB provides some console commands to display various information or perform some tasks. The commands are invoked from the main directory of Part-DB with the command `php bin/console [command]` in the context -of the web server user (so usually the webserver user), so you may have to use `sudo` or `su` to execute the commands: +of the database user (so usually the webserver user), so you maybe have to use `sudo` or `su` to execute the commands: ```bash sudo -u www-data php bin/console [command] @@ -17,8 +17,8 @@ sudo -u www-data php bin/console [command] You can get help for every command with the parameter `--help`. See `php bin/console` for a list of all available commands. -If you are running Part-DB in a Docker container, you must either execute the commands from a shell inside the container, -or use the `docker exec` command to execute the command directly inside the container. For example, if your Docker container +If you are running Part-DB in a docker container, you must either execute the commands from a shell inside a container, +or use the `docker exec` command to execute the command directly inside the container. For example if you docker container is named `partdb`, you can execute the command `php bin/console cache:clear` with the following command: ```bash @@ -61,7 +61,7 @@ docker exec --user=www-data partdb php bin/console cache:clear * `partdb:attachments:clean-unused`: Remove all attachments which are not used by any database entry (e.g. orphaned attachments) * `partdb:cache:clear`: Clears all caches, so the next page load will be slower, but the cache will be rebuilt. This can - maybe fix some issues when the cache was corrupted. This command is also needed after changing things in + maybe fix some issues, when the cache were corrupted. This command is also needed after changing things in the `parameters.yaml` file or upgrading Part-DB. * `partdb:migrations:import-partkeepr`: Imports a mysqldump XML dump of a PartKeepr database into Part-DB. This is only needed for users, which want to migrate from PartKeepr to Part-DB. *All existing data in the Part-DB database is @@ -76,6 +76,6 @@ The value of the environment variable is copied to the settings database, so the ## Attachment commands -* `php bin/console partdb:attachments:download`: Download all attachments that are not already downloaded to the - local filesystem. This is useful to create local backups of the attachments, no matter what happens on the remote, and - also makes picture thumbnails available for the frontend for them. +* `php bin/console partdb:attachments:download`: Download all attachments, which are not already downloaded, to the + local filesystem. This is useful to create local backups of the attachments, no matter what happens on the remote and + also makes pictures thumbnails available for the frontend for them diff --git a/docs/usage/eda_integration.md b/docs/usage/eda_integration.md index f8988763..9444e55f 100644 --- a/docs/usage/eda_integration.md +++ b/docs/usage/eda_integration.md @@ -17,14 +17,14 @@ This also allows to configure available and usable parts and their properties in ## KiCad Setup {: .important } -> Part-DB uses the HTTP library feature of KiCad, which was experimental in earlier versions. If you want to use this feature, you need to install KiCad 8 or newer. +> Part-DB uses the HTTP library feature of KiCad, which is experimental and not part of the stable KiCad 7 releases. If you want to use this feature, you need to install a KiCad nightly build (7.99 version). This feature will most likely also be part of KiCad 8. -Part-DB should be accessible from the PCs with KiCad. The URL should be stable (so no dynamically changing IP). -You require a user account in Part-DB, which has permission to access the Part-DB API and create API tokens. Every user can have their own account, or you set up a shared read-only account. +Part-DB should be accessible from the PCs with KiCAD. The URL should be stable (so no dynamically changing IP). +You require a user account in Part-DB, which has permission to access Part-DB API and create API tokens. Every user can have its own account, or you set up a shared read-only account. To connect KiCad with Part-DB do the following steps: -1. Create an API token on the user settings page for the KiCad application and copy/save it when it is shown. Currently, KiCad can only read the Part-DB database, so a token with a read-only scope is enough. +1. Create an API token on the user settings page for the KiCAD application and copy/save it, when it is shown. Currently, KiCad can only read Part-DB database, so a token with a read-only scope is enough. 2. Add some EDA metadata to parts, categories, or footprints. Only parts with usable info will show up in KiCad. See below for more info. 3. Create a file `partd.kicad_httplib` (or similar, only the extension is important) with the following content: ``` @@ -54,18 +54,18 @@ Part-DB doesn't save any concrete footprints or symbols for the part. Instead, P You can define this on a per-part basis using the KiCad symbol and KiCad footprint field in the EDA tab of the part editor. Or you can define it at a category (symbol) or footprint level, to assign this value to all parts with this category and footprint. -For example, to configure the values for a BC547 transistor you would put `Transistor_BJT:BC547` in the part's KiCad symbol field to give it the right schematic symbol in Eeschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in Pcbnew. +For example, to configure the values for a BC547 transistor you would put `Transistor_BJT:BC547` on the parts Kicad symbol to give it the right schematic symbol in EEschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in PcbNew. If you type in a character, you will get an autocomplete list of all symbols and footprints available in the KiCad standard library. You can also input your own value. ### Parts and category visibility -Only parts and their categories on which there is any kind of EDA metadata defined show up in KiCad. So if you want to see parts in KiCad, +Only parts and their categories, on which there is any kind of EDA metadata are defined show up in KiCad. So if you want to see parts in KiCad, you need to define at least a symbol, footprint, reference prefix, or value on a part, category or footprint. You can use the "Force visibility" checkbox on a part or category to override this behavior and force parts to be visible or hidden in KiCad. -*Please note that KiCad caches the library categories. So if you change something that would change the visible categories in KiCad, you have to reload Eeschema to see the changes.* +*Please note that KiCad caches the library categories. So if you change something, which would change the visible categories in KiCad, you have to reload EEschema to see the changes.* ### Category depth in KiCad diff --git a/docs/usage/getting_started.md b/docs/usage/getting_started.md index 8772130c..4b9a809a 100644 --- a/docs/usage/getting_started.md +++ b/docs/usage/getting_started.md @@ -6,7 +6,7 @@ nav_order: 4 # Getting started -After installing Part-DB, you should begin with customizing the settings and setting up the basic structures. +After Part-DB you should begin with customizing the settings, and setting up the basic structures. Before starting, it's useful to read a bit about the [concepts of Part-DB]({% link concepts.md %}). 1. TOC @@ -14,8 +14,8 @@ Before starting, it's useful to read a bit about the [concepts of Part-DB]({% li ## Customize system settings -Before starting creating data structures, you should check the system settings to ensure that they fit your needs. -After logging in as an administrator, you can find the settings in the sidebar under `Tools -> System -> Settings`. +Before starting creating datastructures, you should check the system settings to ensure that they fit your needs. +After login as an administrator, you can find the settings in the sidebar under `Tools -> System -> Settings`. ![image]({% link assets/getting_started/system_settings.png %}) Here you can change various settings, like the name of your Part-DB instance (which is shown in the title bar of the @@ -35,9 +35,9 @@ the navigation bar drop-down with the user symbol). ![image]({% link assets/getting_started/change_password.png %}) -There you can also find the option to set up Two-Factor Authentication methods like Google Authenticator. Using this is +There you can also find the option, to set up Two-Factor Authentication methods like Google Authenticator. Using this is highly recommended (especially if you have admin permissions) to increase the security of your account. (Two-factor authentication -can even be enforced for all members of a user group) +even can be enforced for all members of a user group) In the user settings panel, you can change account info like your username, your first and last name (which will be shown alongside your username to identify you better), department information, and your email address. The email address @@ -64,7 +64,7 @@ $E=mc^2$) or `$$` (like `$$E=mc^2$$`) which will be rendered as a block, like so When logged in as administrator, you can open the users menu in the `Tools` section of the sidebar under `System -> Users`. On this page you can create new users, change their passwords and settings, and change their permissions. -For each user who should use Part-DB, you should set up their own account so that tracking of what each user did works +For each user who should use Part-DB you should set up their own account so that tracking of what user did works properly. ![image]({% link assets/getting_started/user_admin.png %}) @@ -207,7 +207,7 @@ You have to enter at least a name for the part and choose a category for it, the However, it is recommended to fill out as much information as possible, as this will make it easier to find the part later. -You can choose from your created data structures to add manufacturer information, supplier information, etc. to the part. -You can also create new data structures on the fly if you want to add additional information to the part, by typing the -name of the new data structure in the field and selecting the "New ..." option in the dropdown menu. See [tips]({% link +You can choose from your created datastructures to add manufacturer information, supplier information, etc. to the part. +You can also create new datastructures on the fly, if you want to add additional information to the part, by typing the +name of the new datastructure in the field and select the "New ..." option in the dropdown menu. See [tips]({% link usage/tips_tricks.md %}) for more information. diff --git a/docs/usage/import_export.md b/docs/usage/import_export.md index f4d8d91c..0534221f 100644 --- a/docs/usage/import_export.md +++ b/docs/usage/import_export.md @@ -20,7 +20,7 @@ Part-DB. Data can also be exported from Part-DB into various formats. > individually in the permissions settings. If you want to import data from PartKeepr you might want to look into the [PartKeepr migration guide]({% link -partkeepr_migration.md %}). +upgrade/upgrade_legacy.md %}). ### Import parts @@ -47,9 +47,9 @@ You can upload the file that should be imported here and choose various options the import file (or the export will error, if no category is specified). * **Mark all imported parts as "Needs review"**: If this is selected, all imported parts will be marked as "Needs review" after the import. This can be useful if you want to review all imported parts before using them. -* **Create unknown data structures**: If this is selected, Part-DB will create new data structures (like categories, - manufacturers, etc.) if no data structure(s) with the same name and path already exist. If this is not selected, only - existing data structures will be used, and if no matching data structure is found, the imported parts field will be empty. +* **Create unknown data structures**: If this is selected Part-DB will create new data structures (like categories, + manufacturers, etc.) if no data structure(s) with the same name and path already exists. If this is not selected, only + existing data structures will be used and if no matching data strucure is found, the imported parts field will be empty. * **Path delimiter**: Part-DB allows you to create/select nested data structures (like categories, manufacturers, etc.) by using a path (e.g. `Category 1->Category 1.1`, which will select/create the `Category 1.1` whose parent is `Category 1`). This path is separated by the path delimiter. If you want to use a different path delimiter than the @@ -142,9 +142,6 @@ You can select between the following export formats: efficiently. * **YAML** (Yet Another Markup Language): Very similar to JSON * **XML** (Extensible Markup Language): Good support with nested data structures. Similar use cases as JSON and YAML. -* **Excel**: Similar to CSV, but in a native Excel format. Can be opened in Excel and LibreOffice Calc. Does not support nested - data structures or sub-data (like parameters, attachments, etc.), very well (many columns are generated, as every - possible sub-data is exported as a separate column). Also, you can select between the following export levels: diff --git a/docs/usage/information_provider_system.md b/docs/usage/information_provider_system.md index c6d4c83f..953db409 100644 --- a/docs/usage/information_provider_system.md +++ b/docs/usage/information_provider_system.md @@ -68,17 +68,10 @@ If you already have attachment types for images and datasheets and want the info can add the alternative names "Datasheet" and "Image" to the alternative names field of the attachment types. -## Bulk import - -If you want to update the information of multiple parts, you can use the bulk import system: Go to a part table and select -the parts you want to update. In the bulk actions dropdown select "Bulk info provider import" and click "Apply". -You will be redirected to a page, where you can select how part fields should be mapped to info provider fields, and the -results will be shown. - ## Data providers The system tries to be as flexible as possible, so many different information sources can be used. -Each information source is called an "info provider" and handles the communication with the external source. +Each information source is called am "info provider" and handles the communication with the external source. The providers are just a driver that handles the communication with the different external sources and converts them into a common format Part-DB understands. That way it is pretty easy to create new providers as they just need to do very little work. @@ -157,7 +150,7 @@ again, to establish a new connection. ### TME -The TME provider uses the API of [TME](https://www.tme.eu/) to search for parts and get shopping information from +The TME provider uses the API of [TME](https://www.tme.eu/) to search for parts and getting shopping information from them. To use it you have to create an account at TME and get an API key on the [TME API page](https://developers.tme.eu/en/). You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see @@ -176,10 +169,10 @@ The following env configuration options are available: ### Farnell / Element14 / Newark -The Farnell provider uses the [Farnell API](https://partner.element14.com/) to search for parts and get shopping +The Farnell provider uses the [Farnell API](https://partner.element14.com/) to search for parts and getting shopping information from [Farnell](https://www.farnell.com/). You have to create an account at Farnell and get an API key on the [Farnell API page](https://partner.element14.com/). -Register a new application there (settings do not matter, as long as you select the "Product Search API") and you will +Register a new application there (settings does not matter, as long as you select the "Product Search API") and you will get an API key. The following env configuration options are available: @@ -191,13 +184,17 @@ The following env configuration options are available: ### Mouser -The Mouser provider uses the [Mouser API](https://www.mouser.de/api-home/) to search for parts and get shopping +The Mouser provider uses the [Mouser API](https://www.mouser.de/api-home/) to search for parts and getting shopping information from [Mouser](https://www.mouser.com/). You have to create an account at Mouser and register for an API key for the Search API on the [Mouser API page](https://www.mouser.de/api-home/). You will receive an API token, which you have to put in the Part-DB env configuration (see below): At the registration you choose a country, language, and currency in which you want to get the results. +*Attention*: Currently (January 2024) the mouser API seems to be somewhat broken, in the way that it does not return any +information about datasheets and part specifications. Therefore Part-DB can not retrieve them, even if they are shown +at the mouser page. See [issue #503](https://github.com/Part-DB/Part-DB-server/issues/503) for more info. + Following env configuration options are available: * `PROVIDER_MOUSER_KEY`: The API key you got from Mouser (mandatory) @@ -213,7 +210,7 @@ Following env configuration options are available: webshop uses an internal JSON based API to render the page. Part-DB can use this inofficial API to get part information from LCSC. -**Please note that the use of this internal API is not intended or endorsed by LCSC and it could break at any time. So use it at your own risk.** +**Please note, that the use of this internal API is not intended or endorsed by LCS and it could break at any time. So use it at your own risk.** An API key is not required, it is enough to enable the provider using the following env configuration options: @@ -222,7 +219,7 @@ An API key is not required, it is enough to enable the provider using the follow ### OEMsecrets -The oemsecrets provider uses the [oemsecrets API](https://www.oemsecrets.com/) to search for parts and get shopping +The oemsecrets provider uses the [oemsecrets API](https://www.oemsecrets.com/) to search for parts and getting shopping information from them. Similar to octopart it aggregates offers from different distributors. You can apply for a free API key on the [oemsecrets API page](https://www.oemsecrets.com/api/) and put the key you get diff --git a/docs/usage/labels.md b/docs/usage/labels.md index 4c3f8b32..e84f4d7f 100644 --- a/docs/usage/labels.md +++ b/docs/usage/labels.md @@ -6,10 +6,10 @@ parent: Usage # Labels -Part-DB supports the generation and printing of labels for parts, part lots and storage locations. +Part-DB support the generation and printing of labels for parts, part lots and storage locations. You can use the "Tools -> Label generator" menu entry to create labels or click the label generation link on the part. -You can define label templates by creating label profiles. This way you can create many similar-looking labels for +You can define label templates by creating Label profiles. This way you can create many similar-looking labels with for many parts. The content of the labels is defined by the template's content field. You can use the WYSIWYG editor to create and style diff --git a/docs/usage/tips_tricks.md b/docs/usage/tips_tricks.md index cab05620..6eda718d 100644 --- a/docs/usage/tips_tricks.md +++ b/docs/usage/tips_tricks.md @@ -65,7 +65,7 @@ $$E=mc^2$$ ## Update currency exchange rates automatically Part-DB can update the currency exchange rates of all defined currencies programmatically -by calling `php bin/console partdb:currencies:update-exchange-rates`. +by calling the `php bin/console partdb:currencies:update-exchange-rates`. If you call this command regularly (e.g. with a cronjob), you can keep the exchange rates up-to-date. @@ -88,9 +88,9 @@ the user as "owner" of a part lot. This way, only he is allowed to add or remove ## Update notifications -Part-DB can show you a notification that there is a newer version than currently installed. The notification +Part-DB can show you a notification that there is a newer version than currently installed available. The notification will be shown on the homepage and the server info page. -It is only shown to users which have the `Show available Part-DB updates` permission. +It is only be shown to users which has the `Show available Part-DB updates` permission. For the notification to work, Part-DB queries the GitHub API every 2 days to check for new releases. No data is sent to GitHub besides the metadata required for the connection (so the public IP address of your computer running Part-DB). @@ -98,6 +98,6 @@ If you don't want Part-DB to query the GitHub API, or if your server can not rea update notifications by setting the `CHECK_FOR_UPDATES` option to `false`. ## Internet access via proxy -If your server running Part-DB does not have direct access to the internet, but has to use a proxy server, you can configure +If you server running Part-DB does not have direct access to the internet, but has to use a proxy server, you can configure the proxy settings in the `.env.local` file (or docker env config). You can set the `HTTP_PROXY` and `HTTPS_PROXY` environment variables to the URL of your proxy server. If your proxy server requires authentication, you can include the username and password in the URL. diff --git a/makefile b/makefile index bc4d0bf3..9041ba0f 100644 --- a/makefile +++ b/makefile @@ -1,91 +1,112 @@ # PartDB Makefile for Test Environment Management -.PHONY: help deps-install lint format format-check test coverage pre-commit all test-typecheck \ -test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run test-reset \ -section-dev dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset +.PHONY: help test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset deps-install # Default target -help: ## Show this help - @awk 'BEGIN {FS = ":.*##"}; /^[a-zA-Z0-9][a-zA-Z0-9_-]+:.*##/ {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) +help: + @echo "PartDB Test Environment Management" + @echo "==================================" + @echo "" + @echo "Available targets:" + @echo " deps-install - Install PHP dependencies with unlimited memory" + @echo "" + @echo "Development Environment:" + @echo " dev-setup - Complete development environment setup (clean, create DB, migrate, warmup)" + @echo " dev-clean - Clean development cache and database files" + @echo " dev-db-create - Create development database (if not exists)" + @echo " dev-db-migrate - Run database migrations for development environment" + @echo " dev-cache-clear - Clear development cache" + @echo " dev-warmup - Warm up development cache" + @echo " dev-reset - Quick development reset (clean + migrate)" + @echo "" + @echo "Test Environment:" + @echo " test-setup - Complete test environment setup (clean, create DB, migrate, load fixtures)" + @echo " test-clean - Clean test cache and database files" + @echo " test-db-create - Create test database (if not exists)" + @echo " test-db-migrate - Run database migrations for test environment" + @echo " test-cache-clear- Clear test cache" + @echo " test-fixtures - Load test fixtures" + @echo " test-run - Run PHPUnit tests" + @echo "" + @echo " help - Show this help message" -# Dependencies -deps-install: ## Install PHP dependencies with unlimited memory +# Install PHP dependencies with unlimited memory +deps-install: @echo "📦 Installing PHP dependencies..." COMPOSER_MEMORY_LIMIT=-1 composer install - yarn install @echo "✅ Dependencies installed" # Complete test environment setup -test-setup: test-clean test-db-create test-db-migrate test-fixtures ## Complete test setup (clean, create DB, migrate, fixtures) +test-setup: deps-install test-clean test-db-create test-db-migrate test-fixtures @echo "✅ Test environment setup complete!" # Clean test environment -test-clean: ## Clean test cache and database files +test-clean: @echo "🧹 Cleaning test environment..." rm -rf var/cache/test rm -f var/app_test.db @echo "✅ Test environment cleaned" # Create test database -test-db-create: ## Create test database (if not exists) +test-db-create: @echo "🗄️ Creating test database..." -php bin/console doctrine:database:create --if-not-exists --env test || echo "⚠️ Database creation failed (expected for SQLite) - continuing..." # Run database migrations for test environment -test-db-migrate: ## Run database migrations for test environment +test-db-migrate: @echo "🔄 Running database migrations..." - COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env test + php -d memory_limit=1G bin/console doctrine:migrations:migrate -n --env test # Clear test cache -test-cache-clear: ## Clear test cache +test-cache-clear: @echo "🗑️ Clearing test cache..." rm -rf var/cache/test @echo "✅ Test cache cleared" # Load test fixtures -test-fixtures: ## Load test fixtures +test-fixtures: @echo "📦 Loading test fixtures..." php bin/console partdb:fixtures:load -n --env test # Run PHPUnit tests -test-run: ## Run PHPUnit tests +test-run: @echo "🧪 Running tests..." php bin/phpunit +test-typecheck: + @echo "🧪 Running type checks..." + COMPOSER_MEMORY_LIMIT=-1 composer phpstan + # Quick test reset (clean + migrate + fixtures, skip DB creation) test-reset: test-cache-clear test-db-migrate test-fixtures @echo "✅ Test environment reset complete!" -test-typecheck: ## Run static analysis (PHPStan) - @echo "🧪 Running type checks..." - COMPOSER_MEMORY_LIMIT=-1 composer phpstan - # Development helpers -dev-setup: dev-clean dev-db-create dev-db-migrate dev-warmup ## Complete development setup (clean, create DB, migrate, warmup) +dev-setup: deps-install dev-clean dev-db-create dev-db-migrate dev-warmup @echo "✅ Development environment setup complete!" -dev-clean: ## Clean development cache and database files +dev-clean: @echo "🧹 Cleaning development environment..." rm -rf var/cache/dev rm -f var/app_dev.db @echo "✅ Development environment cleaned" -dev-db-create: ## Create development database (if not exists) +dev-db-create: @echo "🗄️ Creating development database..." -php bin/console doctrine:database:create --if-not-exists --env dev || echo "⚠️ Database creation failed (expected for SQLite) - continuing..." -dev-db-migrate: ## Run database migrations for development environment +dev-db-migrate: @echo "🔄 Running database migrations..." - COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env dev + php -d memory_limit=1G bin/console doctrine:migrations:migrate -n --env dev -dev-cache-clear: ## Clear development cache +dev-cache-clear: @echo "🗑️ Clearing development cache..." - rm -rf var/cache/dev + php -d memory_limit=1G bin/console cache:clear --env dev -n @echo "✅ Development cache cleared" -dev-warmup: ## Warm up development cache +dev-warmup: @echo "🔥 Warming up development cache..." - COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G bin/console cache:warmup --env dev -n + php -d memory_limit=1G bin/console cache:warmup --env dev -n -dev-reset: dev-cache-clear dev-db-migrate ## Quick development reset (cache clear + migrate) +dev-reset: dev-cache-clear dev-db-migrate @echo "✅ Development environment reset complete!" \ No newline at end of file diff --git a/migrations/Version20250321075747.php b/migrations/Version20250321075747.php deleted file mode 100644 index 14bcb8a9..00000000 --- a/migrations/Version20250321075747.php +++ /dev/null @@ -1,605 +0,0 @@ -addSql(<<<'SQL' - CREATE TABLE part_custom_states ( - id INT AUTO_INCREMENT NOT NULL, - parent_id INT DEFAULT NULL, - id_preview_attachment INT DEFAULT NULL, - name VARCHAR(255) NOT NULL, - comment LONGTEXT NOT NULL, - not_selectable TINYINT(1) NOT NULL, - alternative_names LONGTEXT DEFAULT NULL, - last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - INDEX IDX_F552745D727ACA70 (parent_id), - INDEX IDX_F552745DEA7100A1 (id_preview_attachment), - INDEX part_custom_state_name (name), - PRIMARY KEY(id) - ) - DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES part_custom_states (id) - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745DEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON DELETE SET NULL - SQL); - - $this->addSql(<<<'SQL' - ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL AFTER id_part_unit - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES part_custom_states (id) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state) - SQL); - } - - public function mySQLDown(Schema $schema): void - { - $this->addSql(<<<'SQL' - ALTER TABLE parts DROP FOREIGN KEY FK_6940A7FEA3ED1215 - SQL); - $this->addSql(<<<'SQL' - DROP INDEX IDX_6940A7FEA3ED1215 ON parts - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE parts DROP id_part_custom_state - SQL); - - $this->addSql(<<<'SQL' - ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745D727ACA70 - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745DEA7100A1 - SQL); - $this->addSql(<<<'SQL' - DROP TABLE part_custom_states - SQL); - } - - public function sqLiteUp(Schema $schema): void - { - $this->addSql(<<<'SQL' - CREATE TABLE "part_custom_states" ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - parent_id INTEGER DEFAULT NULL, - id_preview_attachment INTEGER DEFAULT NULL, - name VARCHAR(255) NOT NULL, - comment CLOB NOT NULL, - not_selectable BOOLEAN NOT NULL, - alternative_names CLOB DEFAULT NULL, - last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE - ) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX part_custom_state_name ON "part_custom_states" (name) - SQL); - - $this->addSql(<<<'SQL' - CREATE TEMPORARY TABLE __temp__parts AS - SELECT - id, - id_preview_attachment, - id_category, - id_footprint, - id_part_unit, - id_manufacturer, - order_orderdetails_id, - built_project_id, - datetime_added, - name, - last_modified, - needs_review, - tags, - mass, - description, - comment, - visible, - favorite, - minamount, - manufacturer_product_url, - manufacturer_product_number, - manufacturing_status, - order_quantity, - manual_order, - ipn, - provider_reference_provider_key, - provider_reference_provider_id, - provider_reference_provider_url, - provider_reference_last_updated, - eda_info_reference_prefix, - eda_info_value, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol, - eda_info_kicad_footprint - FROM parts - SQL); - - $this->addSql(<<<'SQL' - DROP TABLE parts - SQL); - - $this->addSql(<<<'SQL' - CREATE TABLE parts ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - id_preview_attachment INTEGER DEFAULT NULL, - id_category INTEGER NOT NULL, - id_footprint INTEGER DEFAULT NULL, - id_part_unit INTEGER DEFAULT NULL, - id_manufacturer INTEGER DEFAULT NULL, - id_part_custom_state INTEGER DEFAULT NULL, - order_orderdetails_id INTEGER DEFAULT NULL, - built_project_id INTEGER DEFAULT NULL, - datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - name VARCHAR(255) NOT NULL, - last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - needs_review BOOLEAN NOT NULL, - tags CLOB NOT NULL, - mass DOUBLE PRECISION DEFAULT NULL, - description CLOB NOT NULL, - comment CLOB NOT NULL, - visible BOOLEAN NOT NULL, - favorite BOOLEAN NOT NULL, - minamount DOUBLE PRECISION NOT NULL, - manufacturer_product_url CLOB NOT NULL, - manufacturer_product_number VARCHAR(255) NOT NULL, - manufacturing_status VARCHAR(255) DEFAULT NULL, - order_quantity INTEGER NOT NULL, - manual_order BOOLEAN NOT NULL, - ipn VARCHAR(100) DEFAULT 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 DATETIME 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, - CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE - ) - SQL); - - $this->addSql(<<<'SQL' - INSERT INTO parts ( - id, - id_preview_attachment, - id_category, - id_footprint, - id_part_unit, - id_manufacturer, - order_orderdetails_id, - built_project_id, - datetime_added, - name, - last_modified, - needs_review, - tags, - mass, - description, - comment, - visible, - favorite, - minamount, - manufacturer_product_url, - manufacturer_product_number, - manufacturing_status, - order_quantity, - manual_order, - ipn, - provider_reference_provider_key, - provider_reference_provider_id, - provider_reference_provider_url, - provider_reference_last_updated, - eda_info_reference_prefix, - eda_info_value, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol, - eda_info_kicad_footprint) - SELECT - id, - id_preview_attachment, - id_category, - id_footprint, - id_part_unit, - id_manufacturer, - order_orderdetails_id, - built_project_id, - datetime_added, - name, - last_modified, - needs_review, - tags, - mass, - description, - comment, - visible, - favorite, - minamount, - manufacturer_product_url, - manufacturer_product_number, - manufacturing_status, - order_quantity, - manual_order, - ipn, - provider_reference_provider_key, - provider_reference_provider_id, - provider_reference_provider_url, - provider_reference_last_updated, - eda_info_reference_prefix, - eda_info_value, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol, - eda_info_kicad_footprint - FROM __temp__parts - SQL); - - $this->addSql(<<<'SQL' - DROP TABLE __temp__parts - SQL); - - $this->addSql(<<<'SQL' - CREATE INDEX parts_idx_name ON parts (name) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX parts_idx_ipn ON parts (ipn) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review) - SQL); - $this->addSql(<<<'SQL' - CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id) - SQL); - $this->addSql(<<<'SQL' - CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id) - SQL); - $this->addSql(<<<'SQL' - CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state) - SQL); - } - - public function sqLiteDown(Schema $schema): void - { - $this->addSql(<<<'SQL' - CREATE TEMPORARY TABLE __temp__parts AS - SELECT - id, - id_preview_attachment, - id_category, - id_footprint, - id_part_unit, - id_manufacturer, - order_orderdetails_id, - built_project_id, - datetime_added, - name, - last_modified, - needs_review, - tags, - mass, - description, - comment, - visible, - favorite, - minamount, - manufacturer_product_url, - manufacturer_product_number, - manufacturing_status, - order_quantity, - manual_order, - ipn, - provider_reference_provider_key, - provider_reference_provider_id, - provider_reference_provider_url, - provider_reference_last_updated, - eda_info_reference_prefix, - eda_info_value, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol, - eda_info_kicad_footprint - FROM "parts" - SQL); - $this->addSql(<<<'SQL' - DROP TABLE "parts" - SQL); - $this->addSql(<<<'SQL' - CREATE TABLE "parts" ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - id_preview_attachment INTEGER DEFAULT NULL, - id_category INTEGER NOT NULL, - id_footprint INTEGER DEFAULT NULL, - id_part_unit INTEGER DEFAULT NULL, - id_manufacturer INTEGER DEFAULT NULL, - order_orderdetails_id INTEGER DEFAULT NULL, - built_project_id INTEGER DEFAULT NULL, - datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - name VARCHAR(255) NOT NULL, - last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - needs_review BOOLEAN NOT NULL, - tags CLOB NOT NULL, - mass DOUBLE PRECISION DEFAULT NULL, - description CLOB NOT NULL, - comment CLOB NOT NULL, - visible BOOLEAN NOT NULL, - favorite BOOLEAN NOT NULL, - minamount DOUBLE PRECISION NOT NULL, - manufacturer_product_url CLOB NOT NULL, - manufacturer_product_number VARCHAR(255) NOT NULL, - manufacturing_status VARCHAR(255) DEFAULT NULL, - order_quantity INTEGER NOT NULL, - manual_order BOOLEAN NOT NULL, - ipn VARCHAR(100) DEFAULT 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 DATETIME 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, - CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE - ) - SQL); - $this->addSql(<<<'SQL' - INSERT INTO "parts" ( - id, - id_preview_attachment, - id_category, - id_footprint, - id_part_unit, - id_manufacturer, - order_orderdetails_id, - built_project_id, - datetime_added, - name, - last_modified, - needs_review, - tags, - mass, - description, - comment, - visible, - favorite, - minamount, - manufacturer_product_url, - manufacturer_product_number, - manufacturing_status, - order_quantity, - manual_order, - ipn, - provider_reference_provider_key, - provider_reference_provider_id, - provider_reference_provider_url, - provider_reference_last_updated, - eda_info_reference_prefix, - eda_info_value, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol, - eda_info_kicad_footprint - ) SELECT - id, - id_preview_attachment, - id_category, - id_footprint, - id_part_unit, - id_manufacturer, - order_orderdetails_id, - built_project_id, - datetime_added, - name, - last_modified, - needs_review, - tags, - mass, - description, - comment, - visible, - favorite, - minamount, - manufacturer_product_url, - manufacturer_product_number, - manufacturing_status, - order_quantity, - manual_order, - ipn, - provider_reference_provider_key, - provider_reference_provider_id, - provider_reference_provider_url, - provider_reference_last_updated, - eda_info_reference_prefix, - eda_info_value, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol, - eda_info_kicad_footprint - FROM __temp__parts - SQL); - - $this->addSql(<<<'SQL' - DROP TABLE __temp__parts - SQL); - $this->addSql(<<<'SQL' - CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer) - SQL); - $this->addSql(<<<'SQL' - CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id) - SQL); - $this->addSql(<<<'SQL' - CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX parts_idx_name ON "parts" (name) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX parts_idx_ipn ON "parts" (ipn) - SQL); - - $this->addSql(<<<'SQL' - DROP TABLE "part_custom_states" - SQL); - } - - public function postgreSQLUp(Schema $schema): void - { - $this->addSql(<<<'SQL' - CREATE TABLE "part_custom_states" ( - id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - parent_id INT DEFAULT NULL, - id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id), - name VARCHAR(255) NOT NULL, - comment TEXT NOT NULL, - not_selectable BOOLEAN NOT NULL, - alternative_names 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 - ) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_F552745DEA7100A1 ON "part_custom_states" (id_preview_attachment) - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE "part_custom_states" - ADD CONSTRAINT FK_F552745D727ACA70 - FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE "part_custom_states" - ADD CONSTRAINT FK_F552745DEA7100A1 - FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE - SQL); - - - $this->addSql(<<<'SQL' - ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state) - SQL); - } - - public function postgreSQLDown(Schema $schema): void - { - $this->addSql(<<<'SQL' - ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEA3ED1215 - SQL); - $this->addSql(<<<'SQL' - DROP INDEX IDX_6940A7FEA3ED1215 - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE "parts" DROP id_part_custom_state - SQL); - - $this->addSql(<<<'SQL' - ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745D727ACA70 - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745DEA7100A1 - SQL); - $this->addSql(<<<'SQL' - DROP TABLE "part_custom_states" - SQL); - } -} diff --git a/migrations/Version20250325073036.php b/migrations/Version20250325073036.php deleted file mode 100644 index 3bae80ab..00000000 --- a/migrations/Version20250325073036.php +++ /dev/null @@ -1,307 +0,0 @@ -addSql(<<<'SQL' - ALTER TABLE categories ADD COLUMN part_ipn_prefix VARCHAR(255) NOT NULL DEFAULT '' - SQL); - } - - public function mySQLDown(Schema $schema): void - { - $this->addSql(<<<'SQL' - ALTER TABLE categories DROP part_ipn_prefix - SQL); - } - - public function sqLiteUp(Schema $schema): void - { - $this->addSql(<<<'SQL' - CREATE TEMPORARY TABLE __temp__categories AS - SELECT - id, - parent_id, - id_preview_attachment, - partname_hint, - partname_regex, - disable_footprints, - disable_manufacturers, - disable_autodatasheets, - disable_properties, - default_description, - default_comment, - comment, - not_selectable, - name, - last_modified, - datetime_added, - alternative_names, - eda_info_reference_prefix, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol - FROM categories - SQL); - - $this->addSql('DROP TABLE categories'); - - $this->addSql(<<<'SQL' - CREATE TABLE categories ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - parent_id INTEGER DEFAULT NULL, - id_preview_attachment INTEGER DEFAULT NULL, - partname_hint CLOB NOT NULL, - partname_regex CLOB NOT NULL, - part_ipn_prefix VARCHAR(255) DEFAULT '' NOT NULL, - disable_footprints BOOLEAN NOT NULL, - disable_manufacturers BOOLEAN NOT NULL, - disable_autodatasheets BOOLEAN NOT NULL, - disable_properties BOOLEAN NOT NULL, - default_description CLOB NOT NULL, - default_comment CLOB 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, - 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, - CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE - ) - SQL); - - $this->addSql(<<<'SQL' - INSERT INTO categories ( - id, - parent_id, - id_preview_attachment, - partname_hint, - partname_regex, - disable_footprints, - disable_manufacturers, - disable_autodatasheets, - disable_properties, - default_description, - default_comment, - comment, - not_selectable, - name, - last_modified, - datetime_added, - alternative_names, - eda_info_reference_prefix, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol - ) SELECT - id, - parent_id, - id_preview_attachment, - partname_hint, - partname_regex, - disable_footprints, - disable_manufacturers, - disable_autodatasheets, - disable_properties, - default_description, - default_comment, - comment, - not_selectable, - name, - last_modified, - datetime_added, - alternative_names, - eda_info_reference_prefix, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol - FROM __temp__categories - SQL); - - $this->addSql('DROP TABLE __temp__categories'); - - $this->addSql(<<<'SQL' - CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX category_idx_name ON categories (name) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX category_idx_parent_name ON categories (parent_id, name) - SQL); - } - - public function sqLiteDown(Schema $schema): void - { - $this->addSql(<<<'SQL' - CREATE TEMPORARY TABLE __temp__categories AS - SELECT - id, - parent_id, - id_preview_attachment, - partname_hint, - partname_regex, - disable_footprints, - disable_manufacturers, - disable_autodatasheets, - disable_properties, - default_description, - default_comment, - comment, - not_selectable, - name, - last_modified, - datetime_added, - alternative_names, - eda_info_reference_prefix, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol - FROM categories - SQL); - - $this->addSql('DROP TABLE categories'); - - $this->addSql(<<<'SQL' - CREATE TABLE categories ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - parent_id INTEGER DEFAULT NULL, - id_preview_attachment INTEGER DEFAULT NULL, - partname_hint CLOB NOT NULL, - partname_regex CLOB NOT NULL, - disable_footprints BOOLEAN NOT NULL, - disable_manufacturers BOOLEAN NOT NULL, - disable_autodatasheets BOOLEAN NOT NULL, - disable_properties BOOLEAN NOT NULL, - default_description CLOB NOT NULL, - default_comment CLOB 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, - 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, - CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, - CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE - ) - SQL); - - $this->addSql(<<<'SQL' - INSERT INTO categories ( - id, - parent_id, - id_preview_attachment, - partname_hint, - partname_regex, - disable_footprints, - disable_manufacturers, - disable_autodatasheets, - disable_properties, - default_description, - default_comment, - comment, - not_selectable, - name, - last_modified, - datetime_added, - alternative_names, - eda_info_reference_prefix, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol - ) SELECT - id, - parent_id, - id_preview_attachment, - partname_hint, - partname_regex, - disable_footprints, - disable_manufacturers, - disable_autodatasheets, - disable_properties, - default_description, - default_comment, - comment, - not_selectable, - name, - last_modified, - datetime_added, - alternative_names, - eda_info_reference_prefix, - eda_info_invisible, - eda_info_exclude_from_bom, - eda_info_exclude_from_board, - eda_info_exclude_from_sim, - eda_info_kicad_symbol - FROM __temp__categories - SQL); - - $this->addSql('DROP TABLE __temp__categories'); - - $this->addSql(<<<'SQL' - CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX category_idx_name ON categories (name) - SQL); - $this->addSql(<<<'SQL' - CREATE INDEX category_idx_parent_name ON categories (parent_id, name) - SQL); - } - - public function postgreSQLUp(Schema $schema): void - { - $this->addSql(<<<'SQL' - ALTER TABLE categories ADD part_ipn_prefix VARCHAR(255) DEFAULT '' NOT NULL - SQL); - } - - public function postgreSQLDown(Schema $schema): void - { - $this->addSql(<<<'SQL' - ALTER TABLE "categories" DROP part_ipn_prefix - SQL); - } -} diff --git a/migrations/Version20250802205143.php b/migrations/Version20250802205143.php deleted file mode 100644 index 5eb09a77..00000000 --- a/migrations/Version20250802205143.php +++ /dev/null @@ -1,70 +0,0 @@ -addSql('CREATE TABLE bulk_info_provider_import_jobs (id INT AUTO_INCREMENT NOT NULL, name LONGTEXT NOT NULL, field_mappings LONGTEXT NOT NULL, search_results LONGTEXT NOT NULL, status VARCHAR(20) NOT NULL, created_at DATETIME NOT NULL, completed_at DATETIME DEFAULT NULL, prefetch_details TINYINT(1) NOT NULL, created_by_id INT NOT NULL, CONSTRAINT FK_7F58C1EDB03A8386 FOREIGN KEY (created_by_id) REFERENCES `users` (id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); - $this->addSql('CREATE INDEX IDX_7F58C1EDB03A8386 ON bulk_info_provider_import_jobs (created_by_id)'); - - $this->addSql('CREATE TABLE bulk_info_provider_import_job_parts (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(20) NOT NULL, reason LONGTEXT DEFAULT NULL, completed_at DATETIME DEFAULT NULL, job_id INT NOT NULL, part_id INT NOT NULL, CONSTRAINT FK_CD93F28FBE04EA9 FOREIGN KEY (job_id) REFERENCES bulk_info_provider_import_jobs (id), CONSTRAINT FK_CD93F28F4CE34BEC FOREIGN KEY (part_id) REFERENCES `parts` (id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); - $this->addSql('CREATE INDEX IDX_CD93F28FBE04EA9 ON bulk_info_provider_import_job_parts (job_id)'); - $this->addSql('CREATE INDEX IDX_CD93F28F4CE34BEC ON bulk_info_provider_import_job_parts (part_id)'); - $this->addSql('CREATE UNIQUE INDEX unique_job_part ON bulk_info_provider_import_job_parts (job_id, part_id)'); - } - - public function mySQLDown(Schema $schema): void - { - $this->addSql('DROP TABLE bulk_info_provider_import_job_parts'); - $this->addSql('DROP TABLE bulk_info_provider_import_jobs'); - } - - public function sqLiteUp(Schema $schema): void - { - $this->addSql('CREATE TABLE bulk_info_provider_import_jobs (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name CLOB NOT NULL, field_mappings CLOB NOT NULL, search_results CLOB NOT NULL, status VARCHAR(20) NOT NULL, created_at DATETIME NOT NULL, completed_at DATETIME DEFAULT NULL, prefetch_details BOOLEAN NOT NULL, created_by_id INTEGER NOT NULL, CONSTRAINT FK_7F58C1EDB03A8386 FOREIGN KEY (created_by_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('CREATE INDEX IDX_7F58C1EDB03A8386 ON bulk_info_provider_import_jobs (created_by_id)'); - - $this->addSql('CREATE TABLE bulk_info_provider_import_job_parts (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, status VARCHAR(20) NOT NULL, reason CLOB DEFAULT NULL, completed_at DATETIME DEFAULT NULL, job_id INTEGER NOT NULL, part_id INTEGER NOT NULL, CONSTRAINT FK_CD93F28FBE04EA9 FOREIGN KEY (job_id) REFERENCES bulk_info_provider_import_jobs (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_CD93F28F4CE34BEC FOREIGN KEY (part_id) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('CREATE INDEX IDX_CD93F28FBE04EA9 ON bulk_info_provider_import_job_parts (job_id)'); - $this->addSql('CREATE INDEX IDX_CD93F28F4CE34BEC ON bulk_info_provider_import_job_parts (part_id)'); - $this->addSql('CREATE UNIQUE INDEX unique_job_part ON bulk_info_provider_import_job_parts (job_id, part_id)'); - } - - public function sqLiteDown(Schema $schema): void - { - $this->addSql('DROP TABLE bulk_info_provider_import_job_parts'); - $this->addSql('DROP TABLE bulk_info_provider_import_jobs'); - } - - public function postgreSQLUp(Schema $schema): void - { - $this->addSql('CREATE TABLE bulk_info_provider_import_jobs (id SERIAL PRIMARY KEY NOT NULL, name TEXT NOT NULL, field_mappings TEXT NOT NULL, search_results TEXT NOT NULL, status VARCHAR(20) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, completed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, prefetch_details BOOLEAN NOT NULL, created_by_id INT NOT NULL, CONSTRAINT FK_7F58C1EDB03A8386 FOREIGN KEY (created_by_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('CREATE INDEX IDX_7F58C1EDB03A8386 ON bulk_info_provider_import_jobs (created_by_id)'); - - $this->addSql('CREATE TABLE bulk_info_provider_import_job_parts (id SERIAL PRIMARY KEY NOT NULL, status VARCHAR(20) NOT NULL, reason TEXT DEFAULT NULL, completed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, job_id INT NOT NULL, part_id INT NOT NULL, CONSTRAINT FK_CD93F28FBE04EA9 FOREIGN KEY (job_id) REFERENCES bulk_info_provider_import_jobs (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_CD93F28F4CE34BEC FOREIGN KEY (part_id) REFERENCES parts (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('CREATE INDEX IDX_CD93F28FBE04EA9 ON bulk_info_provider_import_job_parts (job_id)'); - $this->addSql('CREATE INDEX IDX_CD93F28F4CE34BEC ON bulk_info_provider_import_job_parts (part_id)'); - $this->addSql('CREATE UNIQUE INDEX unique_job_part ON bulk_info_provider_import_job_parts (job_id, part_id)'); - } - - public function postgreSQLDown(Schema $schema): void - { - $this->addSql('DROP TABLE bulk_info_provider_import_job_parts'); - $this->addSql('DROP TABLE bulk_info_provider_import_jobs'); - } -} diff --git a/migrations/Version20251204215443.php b/migrations/Version20251204215443.php deleted file mode 100644 index 3cee0035..00000000 --- a/migrations/Version20251204215443.php +++ /dev/null @@ -1,156 +0,0 @@ -addSql('ALTER TABLE attachments CHANGE external_path external_path VARCHAR(2048) DEFAULT NULL'); - $this->addSql('ALTER TABLE manufacturers CHANGE website website VARCHAR(2048) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(2048) NOT NULL'); - $this->addSql('ALTER TABLE parts CHANGE provider_reference_provider_url provider_reference_provider_url VARCHAR(2048) DEFAULT NULL'); - $this->addSql('ALTER TABLE suppliers CHANGE website website VARCHAR(2048) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(2048) NOT NULL'); - } - - public function mySQLDown(Schema $schema): void - { - $this->addSql('ALTER TABLE `attachments` CHANGE external_path external_path VARCHAR(255) DEFAULT NULL'); - $this->addSql('ALTER TABLE `manufacturers` CHANGE website website VARCHAR(255) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(255) NOT NULL'); - $this->addSql('ALTER TABLE `parts` CHANGE provider_reference_provider_url provider_reference_provider_url VARCHAR(255) DEFAULT NULL'); - $this->addSql('ALTER TABLE `suppliers` CHANGE website website VARCHAR(255) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(255) NOT NULL'); - } - - public function sqLiteUp(Schema $schema): void - { - $this->addSql('CREATE TEMPORARY TABLE __temp__attachments AS SELECT id, type_id, original_filename, show_in_table, name, last_modified, datetime_added, class_name, element_id, internal_path, external_path FROM attachments'); - $this->addSql('DROP TABLE attachments'); - $this->addSql('CREATE TABLE attachments (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, type_id INTEGER NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, show_in_table BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INTEGER NOT NULL, internal_path VARCHAR(255) DEFAULT NULL, external_path VARCHAR(2048) DEFAULT NULL, CONSTRAINT FK_47C4FAD6C54C8C93 FOREIGN KEY (type_id) REFERENCES attachment_types (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('INSERT INTO attachments (id, type_id, original_filename, show_in_table, name, last_modified, datetime_added, class_name, element_id, internal_path, external_path) SELECT id, type_id, original_filename, show_in_table, name, last_modified, datetime_added, class_name, element_id, internal_path, external_path FROM __temp__attachments'); - $this->addSql('DROP TABLE __temp__attachments'); - $this->addSql('CREATE INDEX attachment_element_idx ON attachments (class_name, element_id)'); - $this->addSql('CREATE INDEX attachment_name_idx ON attachments (name)'); - $this->addSql('CREATE INDEX attachments_idx_class_name_id ON attachments (class_name, id)'); - $this->addSql('CREATE INDEX attachments_idx_id_element_id_class_name ON attachments (id, element_id, class_name)'); - $this->addSql('CREATE INDEX IDX_47C4FAD6C54C8C93 ON attachments (type_id)'); - $this->addSql('CREATE INDEX IDX_47C4FAD61F1F2A24 ON attachments (element_id)'); - $this->addSql('CREATE TEMPORARY TABLE __temp__manufacturers AS SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM manufacturers'); - $this->addSql('DROP TABLE manufacturers'); - $this->addSql('CREATE TABLE manufacturers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER 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(2048) NOT NULL, auto_product_url VARCHAR(2048) 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_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('INSERT INTO manufacturers (id, parent_id, id_preview_attachment, 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, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__manufacturers'); - $this->addSql('DROP TABLE __temp__manufacturers'); - $this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON manufacturers (id_preview_attachment)'); - $this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON manufacturers (parent_id)'); - $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 TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint FROM parts'); - $this->addSql('DROP TABLE parts'); - $this->addSql('CREATE TABLE parts (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, id_part_custom_state INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, ipn VARCHAR(100) DEFAULT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(2048) DEFAULT NULL, provider_reference_last_updated DATETIME 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, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES part_custom_states (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('INSERT INTO parts (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint FROM __temp__parts'); - $this->addSql('DROP TABLE __temp__parts'); - $this->addSql('CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)'); - $this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)'); - $this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)'); - $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_6940A7FEEA7100A1 ON parts (id_preview_attachment)'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)'); - $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_ipn ON parts (ipn)'); - $this->addSql('CREATE INDEX parts_idx_name ON parts (name)'); - $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(2048) NOT NULL, auto_product_url VARCHAR(2048) 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_AC28B95CECD792C0 ON suppliers (default_currency_id)'); - $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)'); - $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 INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)'); - } - - public function sqLiteDown(Schema $schema): void - { - $this->addSql('CREATE TEMPORARY TABLE __temp__attachments AS SELECT id, name, last_modified, datetime_added, original_filename, internal_path, external_path, show_in_table, type_id, class_name, element_id FROM "attachments"'); - $this->addSql('DROP TABLE "attachments"'); - $this->addSql('CREATE TABLE "attachments" (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, original_filename VARCHAR(255) DEFAULT NULL, internal_path VARCHAR(255) DEFAULT NULL, external_path VARCHAR(255) DEFAULT NULL, show_in_table BOOLEAN NOT NULL, type_id INTEGER NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INTEGER NOT NULL, CONSTRAINT FK_47C4FAD6C54C8C93 FOREIGN KEY (type_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('INSERT INTO "attachments" (id, name, last_modified, datetime_added, original_filename, internal_path, external_path, show_in_table, type_id, class_name, element_id) SELECT id, name, last_modified, datetime_added, original_filename, internal_path, external_path, show_in_table, type_id, class_name, element_id FROM __temp__attachments'); - $this->addSql('DROP TABLE __temp__attachments'); - $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 TEMPORARY TABLE __temp__manufacturers AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, parent_id, id_preview_attachment FROM "manufacturers"'); - $this->addSql('DROP TABLE "manufacturers"'); - $this->addSql('CREATE TABLE "manufacturers" (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, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('INSERT INTO "manufacturers" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, parent_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, parent_id, id_preview_attachment FROM __temp__manufacturers'); - $this->addSql('DROP TABLE __temp__manufacturers'); - $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 TEMPORARY TABLE __temp__parts AS SELECT id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id FROM "parts"'); - $this->addSql('DROP TABLE "parts"'); - $this->addSql('CREATE TABLE "parts" (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, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER 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 DATETIME 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 INTEGER DEFAULT NULL, id_part_custom_state INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('INSERT INTO "parts" (id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id) SELECT id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id FROM __temp__parts'); - $this->addSql('DROP TABLE __temp__parts'); - $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_6940A7FEA3ED1215 ON "parts" (id_part_custom_state)'); - $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 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, 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)'); - } - - public function postgreSQLUp(Schema $schema): void - { - // this up() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE attachments ALTER external_path TYPE VARCHAR(2048)'); - $this->addSql('ALTER TABLE manufacturers ALTER website TYPE VARCHAR(2048)'); - $this->addSql('ALTER TABLE manufacturers ALTER auto_product_url TYPE VARCHAR(2048)'); - $this->addSql('ALTER TABLE parts ALTER provider_reference_provider_url TYPE VARCHAR(2048)'); - $this->addSql('ALTER TABLE suppliers ALTER website TYPE VARCHAR(2048)'); - $this->addSql('ALTER TABLE suppliers ALTER auto_product_url TYPE VARCHAR(2048)'); - } - - public function postgreSQLDown(Schema $schema): void - { - $this->addSql('ALTER TABLE "attachments" ALTER external_path TYPE VARCHAR(255)'); - $this->addSql('ALTER TABLE "manufacturers" ALTER website TYPE VARCHAR(255)'); - $this->addSql('ALTER TABLE "manufacturers" ALTER auto_product_url TYPE VARCHAR(255)'); - $this->addSql('ALTER TABLE "parts" ALTER provider_reference_provider_url TYPE VARCHAR(255)'); - $this->addSql('ALTER TABLE "suppliers" ALTER website TYPE VARCHAR(255)'); - $this->addSql('ALTER TABLE "suppliers" ALTER auto_product_url TYPE VARCHAR(255)'); - } -} diff --git a/package.json b/package.json index a58b3aa4..7a3efaa4 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "@symfony/stimulus-bridge": "^4.0.0", "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets", - "@symfony/webpack-encore": "^5.1.0", + "@symfony/webpack-encore": "^5.0.0", "bootstrap": "^5.1.3", - "core-js": "^3.38.0", + "core-js": "^3.23.0", "intl-messageformat": "^10.2.5", "jquery": "^3.5.1", "popper.js": "^1.14.7", @@ -50,7 +50,7 @@ "bootbox": "^6.0.0", "bootswatch": "^5.1.3", "bs-custom-file-input": "^1.3.4", - "ckeditor5": "^47.0.0", + "ckeditor5": "^46.0.0", "clipboard": "^2.0.4", "compression-webpack-plugin": "^11.1.0", "datatables.net": "^2.0.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3feb4940..4a37b420 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,7 +4,7 @@ xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" colors="true" - failOnDeprecation="false" + failOnDeprecation="true" failOnNotice="true" failOnWarning="true" bootstrap="tests/bootstrap.php" diff --git a/public/img/calculator/ratio.png b/public/img/calculator/ratio.png new file mode 100644 index 00000000..d6decff3 Binary files /dev/null and b/public/img/calculator/ratio.png differ diff --git a/public/img/calculator/v1.png b/public/img/calculator/v1.png new file mode 100644 index 00000000..c98d3ad4 Binary files /dev/null and b/public/img/calculator/v1.png differ diff --git a/public/img/calculator/v2.png b/public/img/calculator/v2.png new file mode 100644 index 00000000..081386fe Binary files /dev/null and b/public/img/calculator/v2.png differ diff --git a/public/img/labels/100.png b/public/img/labels/100.png new file mode 100644 index 00000000..f68a23a9 Binary files /dev/null and b/public/img/labels/100.png differ diff --git a/public/img/labels/1001.png b/public/img/labels/1001.png new file mode 100644 index 00000000..c87e4ceb Binary files /dev/null and b/public/img/labels/1001.png differ diff --git a/public/img/labels/1002.png b/public/img/labels/1002.png new file mode 100644 index 00000000..68b6594c Binary files /dev/null and b/public/img/labels/1002.png differ diff --git a/public/img/labels/1003.png b/public/img/labels/1003.png new file mode 100644 index 00000000..2abbd616 Binary files /dev/null and b/public/img/labels/1003.png differ diff --git a/public/img/labels/100R.png b/public/img/labels/100R.png new file mode 100644 index 00000000..34fb8fa8 Binary files /dev/null and b/public/img/labels/100R.png differ diff --git a/public/img/labels/101.png b/public/img/labels/101.png new file mode 100644 index 00000000..dd07aa39 Binary files /dev/null and b/public/img/labels/101.png differ diff --git a/public/img/labels/102.png b/public/img/labels/102.png new file mode 100644 index 00000000..a54e16b7 Binary files /dev/null and b/public/img/labels/102.png differ diff --git a/public/img/labels/10R2.png b/public/img/labels/10R2.png new file mode 100644 index 00000000..2b57f7d4 Binary files /dev/null and b/public/img/labels/10R2.png differ diff --git a/public/img/labels/220.png b/public/img/labels/220.png new file mode 100644 index 00000000..28ede43d Binary files /dev/null and b/public/img/labels/220.png differ diff --git a/public/img/labels/221K.png b/public/img/labels/221K.png new file mode 100644 index 00000000..1dbb0c61 Binary files /dev/null and b/public/img/labels/221K.png differ diff --git a/public/img/labels/246-20.png b/public/img/labels/246-20.png new file mode 100644 index 00000000..590f7c5d Binary files /dev/null and b/public/img/labels/246-20.png differ diff --git a/public/img/labels/3F3.png b/public/img/labels/3F3.png new file mode 100644 index 00000000..ce85ae97 Binary files /dev/null and b/public/img/labels/3F3.png differ diff --git a/public/img/labels/R10.png b/public/img/labels/R10.png new file mode 100644 index 00000000..60a90182 Binary files /dev/null and b/public/img/labels/R10.png differ diff --git a/public/img/labels/template-c-elko-alu.png b/public/img/labels/template-c-elko-alu.png new file mode 100644 index 00000000..24d68d91 Binary files /dev/null and b/public/img/labels/template-c-elko-alu.png differ diff --git a/public/img/labels/template-c-elko.png b/public/img/labels/template-c-elko.png new file mode 100644 index 00000000..97e3c1ef Binary files /dev/null and b/public/img/labels/template-c-elko.png differ diff --git a/public/img/labels/template-c-tantal.png b/public/img/labels/template-c-tantal.png new file mode 100644 index 00000000..3e49efee Binary files /dev/null and b/public/img/labels/template-c-tantal.png differ diff --git a/public/img/labels/template-l.png b/public/img/labels/template-l.png new file mode 100644 index 00000000..7e5afd92 Binary files /dev/null and b/public/img/labels/template-l.png differ diff --git a/public/img/labels/template-r.png b/public/img/labels/template-r.png new file mode 100644 index 00000000..554d2a08 Binary files /dev/null and b/public/img/labels/template-r.png differ diff --git a/public/img/partdb/alldatasheet.png b/public/img/partdb/alldatasheet.png new file mode 100644 index 00000000..d7c1d40f Binary files /dev/null and b/public/img/partdb/alldatasheet.png differ diff --git a/public/img/partdb/dc.png b/public/img/partdb/dc.png new file mode 100644 index 00000000..4a9403af Binary files /dev/null and b/public/img/partdb/dc.png differ diff --git a/public/img/partdb/dummytn.png b/public/img/partdb/dummytn.png new file mode 100644 index 00000000..e63c9248 Binary files /dev/null and b/public/img/partdb/dummytn.png differ diff --git a/public/img/partdb/favicon.ico b/public/img/partdb/favicon.ico new file mode 100644 index 00000000..1d838794 Binary files /dev/null and b/public/img/partdb/favicon.ico differ diff --git a/public/img/partdb/file_all.svg b/public/img/partdb/file_all.svg new file mode 100644 index 00000000..bb4b4248 --- /dev/null +++ b/public/img/partdb/file_all.svg @@ -0,0 +1,131 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/partdb/file_dc.svg b/public/img/partdb/file_dc.svg new file mode 100644 index 00000000..f0039881 --- /dev/null +++ b/public/img/partdb/file_dc.svg @@ -0,0 +1,90 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + DC + diff --git a/public/img/partdb/file_google.svg b/public/img/partdb/file_google.svg new file mode 100644 index 00000000..20ea96bf --- /dev/null +++ b/public/img/partdb/file_google.svg @@ -0,0 +1,5 @@ + + +google + + diff --git a/public/img/partdb/file_octo.svg b/public/img/partdb/file_octo.svg new file mode 100644 index 00000000..307439a5 --- /dev/null +++ b/public/img/partdb/file_octo.svg @@ -0,0 +1,5 @@ + + +cog + + diff --git a/public/img/partdb/file_reichelt.svg b/public/img/partdb/file_reichelt.svg new file mode 100644 index 00000000..488dafaa --- /dev/null +++ b/public/img/partdb/file_reichelt.svg @@ -0,0 +1,98 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/public/img/partdb/help.png b/public/img/partdb/help.png new file mode 100644 index 00000000..7cb04978 Binary files /dev/null and b/public/img/partdb/help.png differ diff --git a/public/img/partdb/partdb.png b/public/img/partdb/partdb.png new file mode 100644 index 00000000..53f51afb Binary files /dev/null and b/public/img/partdb/partdb.png differ diff --git a/public/img/partdb/reichelt.png b/public/img/partdb/reichelt.png new file mode 100644 index 00000000..fcfcfd49 Binary files /dev/null and b/public/img/partdb/reichelt.png differ diff --git a/public/img/partdb/template-pdf.png b/public/img/partdb/template-pdf.png new file mode 100644 index 00000000..211bf5a4 Binary files /dev/null and b/public/img/partdb/template-pdf.png differ diff --git a/src/Command/Migrations/ImportPartKeeprCommand.php b/src/Command/Migrations/ImportPartKeeprCommand.php index 429f018d..aee71afe 100644 --- a/src/Command/Migrations/ImportPartKeeprCommand.php +++ b/src/Command/Migrations/ImportPartKeeprCommand.php @@ -121,11 +121,6 @@ class ImportPartKeeprCommand extends Command $count = $this->datastructureImporter->importPartUnits($data); $io->success('Imported '.$count.' measurement units.'); - //Import the custom states - $io->info('Importing custom states...'); - $count = $this->datastructureImporter->importPartCustomStates($data); - $io->success('Imported '.$count.' custom states.'); - //Import manufacturers $io->info('Importing manufacturers...'); $count = $this->datastructureImporter->importManufacturers($data); diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index e7dd7421..edc5917a 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -232,7 +232,6 @@ abstract class BaseAdminController extends AbstractController 'timeTravel' => $timeTravel_timestamp, 'repo' => $repo, 'partsContainingElement' => $repo instanceof PartsContainingRepositoryInterface, - 'showParameters' => !($this instanceof PartCustomStateController), ]); } @@ -383,7 +382,6 @@ abstract class BaseAdminController extends AbstractController 'import_form' => $import_form, 'mass_creation_form' => $mass_creation_form, 'route_base' => $this->route_base, - 'showParameters' => !($this instanceof PartCustomStateController), ]); } diff --git a/src/Controller/AdminPages/PartCustomStateController.php b/src/Controller/AdminPages/PartCustomStateController.php deleted file mode 100644 index 60f63abf..00000000 --- a/src/Controller/AdminPages/PartCustomStateController.php +++ /dev/null @@ -1,83 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Controller\AdminPages; - -use App\Entity\Attachments\PartCustomStateAttachment; -use App\Entity\Parameters\PartCustomStateParameter; -use App\Entity\Parts\PartCustomState; -use App\Form\AdminPages\PartCustomStateAdminForm; -use App\Services\ImportExportSystem\EntityExporter; -use App\Services\ImportExportSystem\EntityImporter; -use App\Services\Trees\StructuralElementRecursionHelper; -use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; - -/** - * @see \App\Tests\Controller\AdminPages\PartCustomStateControllerTest - */ -#[Route(path: '/part_custom_state')] -class PartCustomStateController extends BaseAdminController -{ - protected string $entity_class = PartCustomState::class; - protected string $twig_template = 'admin/part_custom_state_admin.html.twig'; - protected string $form_class = PartCustomStateAdminForm::class; - protected string $route_base = 'part_custom_state'; - protected string $attachment_class = PartCustomStateAttachment::class; - protected ?string $parameter_class = PartCustomStateParameter::class; - - #[Route(path: '/{id}', name: 'part_custom_state_delete', methods: ['DELETE'])] - public function delete(Request $request, PartCustomState $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse - { - return $this->_delete($request, $entity, $recursionHelper); - } - - #[Route(path: '/{id}/edit/{timestamp}', name: 'part_custom_state_edit', requirements: ['id' => '\d+'])] - #[Route(path: '/{id}', requirements: ['id' => '\d+'])] - public function edit(PartCustomState $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response - { - return $this->_edit($entity, $request, $em, $timestamp); - } - - #[Route(path: '/new', name: 'part_custom_state_new')] - #[Route(path: '/{id}/clone', name: 'part_custom_state_clone')] - #[Route(path: '/')] - public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?PartCustomState $entity = null): Response - { - return $this->_new($request, $em, $importer, $entity); - } - - #[Route(path: '/export', name: 'part_custom_state_export_all')] - public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response - { - return $this->_exportAll($em, $exporter, $request); - } - - #[Route(path: '/{id}/export', name: 'part_custom_state_export')] - public function exportEntity(PartCustomState $entity, EntityExporter $exporter, Request $request): Response - { - return $this->_exportEntity($entity, $exporter, $request); - } -} diff --git a/src/Controller/BulkInfoProviderImportController.php b/src/Controller/BulkInfoProviderImportController.php deleted file mode 100644 index 2d3dd7f6..00000000 --- a/src/Controller/BulkInfoProviderImportController.php +++ /dev/null @@ -1,588 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Controller; - -use App\Entity\InfoProviderSystem\BulkImportJobStatus; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; -use App\Entity\Parts\Part; -use App\Entity\Parts\Supplier; -use App\Entity\UserSystem\User; -use App\Form\InfoProviderSystem\GlobalFieldMappingType; -use App\Services\InfoProviderSystem\BulkInfoProviderService; -use App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO; -use App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultsDTO; -use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO; -use Doctrine\ORM\EntityManagerInterface; -use Psr\Log\LoggerInterface; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\DependencyInjection\Attribute\Autowire; -use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; - -#[Route('/tools/bulk_info_provider_import')] -class BulkInfoProviderImportController extends AbstractController -{ - public function __construct( - private readonly BulkInfoProviderService $bulkService, - private readonly EntityManagerInterface $entityManager, - private readonly LoggerInterface $logger, - #[Autowire(param: 'partdb.bulk_import.batch_size')] - private readonly int $bulkImportBatchSize, - #[Autowire(param: 'partdb.bulk_import.max_parts_per_operation')] - private readonly int $bulkImportMaxParts - ) { - } - - /** - * Convert field mappings from array format to FieldMappingDTO[]. - * - * @param array $fieldMappings Array of field mapping arrays - * @return BulkSearchFieldMappingDTO[] Array of FieldMappingDTO objects - */ - private function convertFieldMappingsToDto(array $fieldMappings): array - { - $dtos = []; - foreach ($fieldMappings as $mapping) { - $dtos[] = new BulkSearchFieldMappingDTO(field: $mapping['field'], providers: $mapping['providers'], priority: $mapping['priority'] ?? 1); - } - return $dtos; - } - - private function createErrorResponse(string $message, int $statusCode = 400, array $context = []): JsonResponse - { - $this->logger->warning('Bulk import operation failed', array_merge([ - 'error' => $message, - 'user' => $this->getUser()?->getUserIdentifier(), - ], $context)); - - return $this->json([ - 'success' => false, - 'error' => $message - ], $statusCode); - } - - private function validateJobAccess(int $jobId): ?BulkInfoProviderImportJob - { - $this->denyAccessUnlessGranted('@info_providers.create_parts'); - - $job = $this->entityManager->getRepository(BulkInfoProviderImportJob::class)->find($jobId); - - if (!$job) { - return null; - } - - if ($job->getCreatedBy() !== $this->getUser()) { - return null; - } - - return $job; - } - - private function updatePartSearchResults(BulkInfoProviderImportJob $job, ?BulkSearchPartResultsDTO $newResults): void - { - if ($newResults === null) { - return; - } - - // Only deserialize and update if we have new results - $allResults = $job->getSearchResults($this->entityManager); - - // Find and update the results for this specific part - $allResults = $allResults->replaceResultsForPart($newResults); - - // Save updated results back to job - $job->setSearchResults($allResults); - } - - #[Route('/step1', name: 'bulk_info_provider_step1')] - public function step1(Request $request): Response - { - $this->denyAccessUnlessGranted('@info_providers.create_parts'); - - set_time_limit(600); - - $ids = $request->query->get('ids'); - if (!$ids) { - $this->addFlash('error', 'No parts selected for bulk import'); - return $this->redirectToRoute('homepage'); - } - - $partIds = explode(',', $ids); - $partRepository = $this->entityManager->getRepository(Part::class); - $parts = $partRepository->getElementsFromIDArray($partIds); - - if (empty($parts)) { - $this->addFlash('error', 'No valid parts found for bulk import'); - return $this->redirectToRoute('homepage'); - } - - // Validate against configured maximum - if (count($parts) > $this->bulkImportMaxParts) { - $this->addFlash('error', sprintf( - 'Too many parts selected (%d). Maximum allowed is %d parts per operation.', - count($parts), - $this->bulkImportMaxParts - )); - return $this->redirectToRoute('homepage'); - } - - if (count($parts) > ($this->bulkImportMaxParts / 2)) { - $this->addFlash('warning', 'Processing ' . count($parts) . ' parts may take several minutes and could timeout. Consider processing smaller batches.'); - } - - // Generate field choices - $fieldChoices = [ - 'info_providers.bulk_search.field.mpn' => 'mpn', - 'info_providers.bulk_search.field.name' => 'name', - ]; - - // Add dynamic supplier fields - $suppliers = $this->entityManager->getRepository(Supplier::class)->findAll(); - foreach ($suppliers as $supplier) { - $supplierKey = strtolower(str_replace([' ', '-', '_'], '_', $supplier->getName())); - $fieldChoices["Supplier: " . $supplier->getName() . " (SPN)"] = $supplierKey . '_spn'; - } - - // Initialize form with useful default mappings - $initialData = [ - 'field_mappings' => [ - ['field' => 'mpn', 'providers' => [], 'priority' => 1] - ], - 'prefetch_details' => false - ]; - - $form = $this->createForm(GlobalFieldMappingType::class, $initialData, [ - 'field_choices' => $fieldChoices - ]); - $form->handleRequest($request); - - $searchResults = null; - - if ($form->isSubmitted() && $form->isValid()) { - $formData = $form->getData(); - $fieldMappingDtos = $this->convertFieldMappingsToDto($formData['field_mappings']); - $prefetchDetails = $formData['prefetch_details'] ?? false; - - $user = $this->getUser(); - if (!$user instanceof User) { - throw new \RuntimeException('User must be authenticated and of type User'); - } - - // Validate part count against configuration limit - if (count($parts) > $this->bulkImportMaxParts) { - $this->addFlash('error', "Too many parts selected. Maximum allowed: {$this->bulkImportMaxParts}"); - $partIds = array_map(fn($part) => $part->getId(), $parts); - return $this->redirectToRoute('bulk_info_provider_step1', ['ids' => implode(',', $partIds)]); - } - - // Create and save the job - $job = new BulkInfoProviderImportJob(); - $job->setFieldMappings($fieldMappingDtos); - $job->setPrefetchDetails($prefetchDetails); - $job->setCreatedBy($user); - - foreach ($parts as $part) { - $jobPart = new BulkInfoProviderImportJobPart($job, $part); - $job->addJobPart($jobPart); - } - - $this->entityManager->persist($job); - $this->entityManager->flush(); - - try { - $searchResultsDto = $this->bulkService->performBulkSearch($parts, $fieldMappingDtos, $prefetchDetails); - - // Save search results to job - $job->setSearchResults($searchResultsDto); - $job->markAsInProgress(); - $this->entityManager->flush(); - - // Prefetch details if requested - if ($prefetchDetails) { - $this->bulkService->prefetchDetailsForResults($searchResultsDto); - } - - return $this->redirectToRoute('bulk_info_provider_step2', ['jobId' => $job->getId()]); - - } catch (\Exception $e) { - $this->logger->error('Critical error during bulk import search', [ - 'job_id' => $job->getId(), - 'error' => $e->getMessage(), - 'exception' => $e - ]); - - $this->entityManager->remove($job); - $this->entityManager->flush(); - - $this->addFlash('error', 'Search failed due to an error: ' . $e->getMessage()); - $partIds = array_map(fn($part) => $part->getId(), $parts); - return $this->redirectToRoute('bulk_info_provider_step1', ['ids' => implode(',', $partIds)]); - } - } - - // Get existing in-progress jobs for current user - $existingJobs = $this->entityManager->getRepository(BulkInfoProviderImportJob::class) - ->findBy(['createdBy' => $this->getUser(), 'status' => BulkImportJobStatus::IN_PROGRESS], ['createdAt' => 'DESC'], 10); - - return $this->render('info_providers/bulk_import/step1.html.twig', [ - 'form' => $form, - 'parts' => $parts, - 'search_results' => $searchResults, - 'existing_jobs' => $existingJobs, - 'fieldChoices' => $fieldChoices - ]); - } - - #[Route('/manage', name: 'bulk_info_provider_manage')] - public function manageBulkJobs(): Response - { - $this->denyAccessUnlessGranted('@info_providers.create_parts'); - - // Get all jobs for current user - $allJobs = $this->entityManager->getRepository(BulkInfoProviderImportJob::class) - ->findBy([], ['createdAt' => 'DESC']); - - // Check and auto-complete jobs that should be completed - // Also clean up jobs with no results (failed searches) - $updatedJobs = false; - $jobsToDelete = []; - - foreach ($allJobs as $job) { - if ($job->isAllPartsCompleted() && !$job->isCompleted()) { - $job->markAsCompleted(); - $updatedJobs = true; - } - - // Mark jobs with no results for deletion (failed searches) - if ($job->getResultCount() === 0 && $job->isInProgress()) { - $jobsToDelete[] = $job; - } - } - - // Delete failed jobs - foreach ($jobsToDelete as $job) { - $this->entityManager->remove($job); - $updatedJobs = true; - } - - // Flush changes if any jobs were updated - if ($updatedJobs) { - $this->entityManager->flush(); - - if (!empty($jobsToDelete)) { - $this->addFlash('info', 'Cleaned up ' . count($jobsToDelete) . ' failed job(s) with no results.'); - } - } - - return $this->render('info_providers/bulk_import/manage.html.twig', [ - 'jobs' => $this->entityManager->getRepository(BulkInfoProviderImportJob::class) - ->findBy([], ['createdAt' => 'DESC']) // Refetch after cleanup - ]); - } - - #[Route('/job/{jobId}/delete', name: 'bulk_info_provider_delete', methods: ['DELETE'])] - public function deleteJob(int $jobId): Response - { - $job = $this->validateJobAccess($jobId); - if (!$job) { - return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); - } - - // Only allow deletion of completed, failed, or stopped jobs - if (!$job->isCompleted() && !$job->isFailed() && !$job->isStopped()) { - return $this->json(['error' => 'Cannot delete active job'], 400); - } - - $this->entityManager->remove($job); - $this->entityManager->flush(); - - return $this->json(['success' => true]); - } - - #[Route('/job/{jobId}/stop', name: 'bulk_info_provider_stop', methods: ['POST'])] - public function stopJob(int $jobId): Response - { - $job = $this->validateJobAccess($jobId); - if (!$job) { - return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); - } - - // Only allow stopping of pending or in-progress jobs - if (!$job->canBeStopped()) { - return $this->json(['error' => 'Cannot stop job in current status'], 400); - } - - $job->markAsStopped(); - $this->entityManager->flush(); - - return $this->json(['success' => true]); - } - - - #[Route('/step2/{jobId}', name: 'bulk_info_provider_step2')] - public function step2(int $jobId): Response - { - $this->denyAccessUnlessGranted('@info_providers.create_parts'); - - $job = $this->entityManager->getRepository(BulkInfoProviderImportJob::class)->find($jobId); - - if (!$job) { - $this->addFlash('error', 'Bulk import job not found'); - return $this->redirectToRoute('bulk_info_provider_step1'); - } - - // Check if user owns this job - if ($job->getCreatedBy() !== $this->getUser()) { - $this->addFlash('error', 'Access denied to this bulk import job'); - return $this->redirectToRoute('bulk_info_provider_step1'); - } - - // Get the parts and deserialize search results - $parts = $job->getJobParts()->map(fn($jobPart) => $jobPart->getPart())->toArray(); - $searchResults = $job->getSearchResults($this->entityManager); - - return $this->render('info_providers/bulk_import/step2.html.twig', [ - 'job' => $job, - 'parts' => $parts, - 'search_results' => $searchResults, - ]); - } - - - #[Route('/job/{jobId}/part/{partId}/mark-completed', name: 'bulk_info_provider_mark_completed', methods: ['POST'])] - public function markPartCompleted(int $jobId, int $partId): Response - { - $job = $this->validateJobAccess($jobId); - if (!$job) { - return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); - } - - $job->markPartAsCompleted($partId); - - // Auto-complete job if all parts are done - if ($job->isAllPartsCompleted() && !$job->isCompleted()) { - $job->markAsCompleted(); - } - - $this->entityManager->flush(); - - return $this->json([ - 'success' => true, - 'progress' => $job->getProgressPercentage(), - 'completed_count' => $job->getCompletedPartsCount(), - 'total_count' => $job->getPartCount(), - 'job_completed' => $job->isCompleted() - ]); - } - - #[Route('/job/{jobId}/part/{partId}/mark-skipped', name: 'bulk_info_provider_mark_skipped', methods: ['POST'])] - public function markPartSkipped(int $jobId, int $partId, Request $request): Response - { - $job = $this->validateJobAccess($jobId); - if (!$job) { - return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); - } - - $reason = $request->request->get('reason', ''); - $job->markPartAsSkipped($partId, $reason); - - // Auto-complete job if all parts are done - if ($job->isAllPartsCompleted() && !$job->isCompleted()) { - $job->markAsCompleted(); - } - - $this->entityManager->flush(); - - return $this->json([ - 'success' => true, - 'progress' => $job->getProgressPercentage(), - 'completed_count' => $job->getCompletedPartsCount(), - 'skipped_count' => $job->getSkippedPartsCount(), - 'total_count' => $job->getPartCount(), - 'job_completed' => $job->isCompleted() - ]); - } - - #[Route('/job/{jobId}/part/{partId}/mark-pending', name: 'bulk_info_provider_mark_pending', methods: ['POST'])] - public function markPartPending(int $jobId, int $partId): Response - { - $job = $this->validateJobAccess($jobId); - if (!$job) { - return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); - } - - $job->markPartAsPending($partId); - $this->entityManager->flush(); - - return $this->json([ - 'success' => true, - 'progress' => $job->getProgressPercentage(), - 'completed_count' => $job->getCompletedPartsCount(), - 'skipped_count' => $job->getSkippedPartsCount(), - 'total_count' => $job->getPartCount(), - 'job_completed' => $job->isCompleted() - ]); - } - - #[Route('/job/{jobId}/part/{partId}/research', name: 'bulk_info_provider_research_part', methods: ['POST'])] - public function researchPart(int $jobId, int $partId): JsonResponse - { - $job = $this->validateJobAccess($jobId); - if (!$job) { - return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); - } - - $part = $this->entityManager->getRepository(Part::class)->find($partId); - if (!$part) { - return $this->createErrorResponse('Part not found', 404, ['part_id' => $partId]); - } - - // Only refresh if the entity might be stale (optional optimization) - if ($this->entityManager->getUnitOfWork()->isScheduledForUpdate($part)) { - $this->entityManager->refresh($part); - } - - try { - // Use the job's field mappings to perform the search - $fieldMappingDtos = $job->getFieldMappings(); - $prefetchDetails = $job->isPrefetchDetails(); - - try { - $searchResultsDto = $this->bulkService->performBulkSearch([$part], $fieldMappingDtos, $prefetchDetails); - } catch (\Exception $searchException) { - // Handle "no search results found" as a normal case, not an error - if (str_contains($searchException->getMessage(), 'No search results found')) { - $searchResultsDto = null; - } else { - throw $searchException; - } - } - - // Update the job's search results for this specific part efficiently - $this->updatePartSearchResults($job, $searchResultsDto[0] ?? null); - - // Prefetch details if requested - if ($prefetchDetails && $searchResultsDto !== null) { - $this->bulkService->prefetchDetailsForResults($searchResultsDto); - } - - $this->entityManager->flush(); - - // Return the new results for this part - $newResults = $searchResultsDto[0] ?? null; - - return $this->json([ - 'success' => true, - 'part_id' => $partId, - 'results_count' => $newResults ? $newResults->getResultCount() : 0, - 'errors_count' => $newResults ? $newResults->getErrorCount() : 0, - 'message' => 'Part research completed successfully' - ]); - - } catch (\Exception $e) { - return $this->createErrorResponse( - 'Research failed: ' . $e->getMessage(), - 500, - [ - 'job_id' => $jobId, - 'part_id' => $partId, - 'exception' => $e->getMessage() - ] - ); - } - } - - #[Route('/job/{jobId}/research-all', name: 'bulk_info_provider_research_all', methods: ['POST'])] - public function researchAllParts(int $jobId): JsonResponse - { - $job = $this->validateJobAccess($jobId); - if (!$job) { - return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); - } - - // Get all parts that are not completed or skipped - $parts = []; - foreach ($job->getJobParts() as $jobPart) { - if (!$jobPart->isCompleted() && !$jobPart->isSkipped()) { - $parts[] = $jobPart->getPart(); - } - } - - if (empty($parts)) { - return $this->json([ - 'success' => true, - 'message' => 'No parts to research', - 'researched_count' => 0 - ]); - } - - try { - $fieldMappingDtos = $job->getFieldMappings(); - $prefetchDetails = $job->isPrefetchDetails(); - - // Process in batches to reduce memory usage for large operations - $allResults = new BulkSearchResponseDTO(partResults: []); - $batches = array_chunk($parts, $this->bulkImportBatchSize); - - foreach ($batches as $batch) { - $batchResultsDto = $this->bulkService->performBulkSearch($batch, $fieldMappingDtos, $prefetchDetails); - $allResults = BulkSearchResponseDTO::merge($allResults, $batchResultsDto); - - // Properly manage entity manager memory without losing state - $jobId = $job->getId(); - //$this->entityManager->clear(); //TODO: This seems to cause problems with the user relation, when trying to flush later - $job = $this->entityManager->find(BulkInfoProviderImportJob::class, $jobId); - } - - // Update the job's search results - $job->setSearchResults($allResults); - - // Prefetch details if requested - if ($prefetchDetails) { - $this->bulkService->prefetchDetailsForResults($allResults); - } - - $this->entityManager->flush(); - - return $this->json([ - 'success' => true, - 'researched_count' => count($parts), - 'message' => sprintf('Successfully researched %d parts', count($parts)) - ]); - - } catch (\Exception $e) { - return $this->createErrorResponse( - 'Bulk research failed: ' . $e->getMessage(), - 500, - [ - 'job_id' => $jobId, - 'part_count' => count($parts), - 'exception' => $e->getMessage() - ] - ); - } - } -} diff --git a/src/Controller/InfoProviderController.php b/src/Controller/InfoProviderController.php index b79c307c..dae8213e 100644 --- a/src/Controller/InfoProviderController.php +++ b/src/Controller/InfoProviderController.php @@ -25,7 +25,6 @@ namespace App\Controller; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Part; -use App\Exceptions\OAuthReconnectRequiredException; use App\Form\InfoProviderSystem\PartSearchType; use App\Services\InfoProviderSystem\ExistingPartFinder; use App\Services\InfoProviderSystem\PartInfoRetriever; @@ -176,11 +175,8 @@ class InfoProviderController extends AbstractController $this->addFlash('error',$e->getMessage()); //Log the exception $exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]); - } catch (OAuthReconnectRequiredException $e) { - $this->addFlash('error', t('info_providers.search.error.oauth_reconnect', ['%provider%' => $e->getProviderName()])); } - // modify the array to an array of arrays that has a field for a matching local Part // the advantage to use that format even when we don't look for local parts is that we // always work with the same interface diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 3a121ad2..6708ed4c 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -47,7 +47,6 @@ use App\Services\Parts\PartLotWithdrawAddHelper; use App\Services\Parts\PricedetailHelper; use App\Services\ProjectSystem\ProjectBuildPartHelper; use App\Settings\BehaviorSettings\PartInfoSettings; -use App\Settings\MiscSettings\IpnSuggestSettings; use DateTime; use Doctrine\ORM\EntityManagerInterface; use Exception; @@ -65,18 +64,14 @@ use Symfony\Contracts\Translation\TranslatorInterface; use function Symfony\Component\Translation\t; #[Route(path: '/part')] -final class PartController extends AbstractController +class PartController extends AbstractController { - public function __construct( - private readonly PricedetailHelper $pricedetailHelper, - private readonly PartPreviewGenerator $partPreviewGenerator, + public function __construct(protected PricedetailHelper $pricedetailHelper, + protected PartPreviewGenerator $partPreviewGenerator, private readonly TranslatorInterface $translator, - private readonly AttachmentSubmitHandler $attachmentSubmitHandler, - private readonly EntityManagerInterface $em, - private readonly EventCommentHelper $commentHelper, - private readonly PartInfoSettings $partInfoSettings, - private readonly IpnSuggestSettings $ipnSuggestSettings, - ) { + private readonly AttachmentSubmitHandler $attachmentSubmitHandler, private readonly EntityManagerInterface $em, + protected EventCommentHelper $commentHelper, private readonly PartInfoSettings $partInfoSettings) + { } /** @@ -85,16 +80,9 @@ final class PartController extends AbstractController */ #[Route(path: '/{id}/info/{timestamp}', name: 'part_info')] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] - public function show( - Part $part, - Request $request, - TimeTravel $timeTravel, - HistoryHelper $historyHelper, - DataTableFactory $dataTable, - ParameterExtractor $parameterExtractor, - PartLotWithdrawAddHelper $withdrawAddHelper, - ?string $timestamp = null - ): Response { + public function show(Part $part, Request $request, TimeTravel $timeTravel, HistoryHelper $historyHelper, + DataTableFactory $dataTable, ParameterExtractor $parameterExtractor, PartLotWithdrawAddHelper $withdrawAddHelper, ?string $timestamp = null): Response + { $this->denyAccessUnlessGranted('read', $part); $timeTravel_timestamp = null; @@ -144,43 +132,7 @@ final class PartController extends AbstractController { $this->denyAccessUnlessGranted('edit', $part); - // Check if this is part of a bulk import job - $jobId = $request->query->get('jobId'); - $bulkJob = null; - if ($jobId) { - $bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId); - // Verify user owns this job - if ($bulkJob && $bulkJob->getCreatedBy() !== $this->getUser()) { - $bulkJob = null; - } - } - - return $this->renderPartForm('edit', $request, $part, [], [ - 'bulk_job' => $bulkJob - ]); - } - - #[Route(path: '/{id}/bulk-import-complete/{jobId}', name: 'part_bulk_import_complete', methods: ['POST'])] - public function markBulkImportComplete(Part $part, int $jobId, Request $request): Response - { - $this->denyAccessUnlessGranted('edit', $part); - - if (!$this->isCsrfTokenValid('bulk_complete_' . $part->getId(), $request->request->get('_token'))) { - throw $this->createAccessDeniedException('Invalid CSRF token'); - } - - $bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId); - if (!$bulkJob || $bulkJob->getCreatedBy() !== $this->getUser()) { - throw $this->createNotFoundException('Bulk import job not found'); - } - - $bulkJob->markPartAsCompleted($part->getId()); - $this->em->persist($bulkJob); - $this->em->flush(); - - $this->addFlash('success', 'Part marked as completed in bulk import'); - - return $this->redirectToRoute('bulk_info_provider_step2', ['jobId' => $jobId]); + return $this->renderPartForm('edit', $request, $part); } #[Route(path: '/{id}/delete', name: 'part_delete', methods: ['DELETE'])] @@ -188,7 +140,7 @@ final class PartController extends AbstractController { $this->denyAccessUnlessGranted('delete', $part); - if ($this->isCsrfTokenValid('delete' . $part->getID(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('delete'.$part->getID(), $request->request->get('_token'))) { $this->commentHelper->setMessage($request->request->get('log_comment', null)); @@ -207,15 +159,11 @@ final class PartController extends AbstractController #[Route(path: '/new', name: 'part_new')] #[Route(path: '/{id}/clone', name: 'part_clone')] #[Route(path: '/new_build_part/{project_id}', name: 'part_new_build_part')] - public function new( - Request $request, - EntityManagerInterface $em, - TranslatorInterface $translator, - AttachmentSubmitHandler $attachmentSubmitHandler, - ProjectBuildPartHelper $projectBuildPartHelper, + public function new(Request $request, EntityManagerInterface $em, TranslatorInterface $translator, + AttachmentSubmitHandler $attachmentSubmitHandler, ProjectBuildPartHelper $projectBuildPartHelper, #[MapEntity(mapping: ['id' => 'id'])] ?Part $part = null, - #[MapEntity(mapping: ['project_id' => 'id'])] ?Project $project = null - ): Response { + #[MapEntity(mapping: ['project_id' => 'id'])] ?Project $project = null): Response + { if ($part instanceof Part) { //Clone part @@ -310,14 +258,9 @@ final class PartController extends AbstractController } #[Route(path: '/{id}/from_info_provider/{providerKey}/{providerId}/update', name: 'info_providers_update_part', requirements: ['providerId' => '.+'])] - public function updateFromInfoProvider( - Part $part, - Request $request, - string $providerKey, - string $providerId, - PartInfoRetriever $infoRetriever, - PartMerger $partMerger - ): Response { + public function updateFromInfoProvider(Part $part, Request $request, string $providerKey, string $providerId, + PartInfoRetriever $infoRetriever, PartMerger $partMerger): Response + { $this->denyAccessUnlessGranted('edit', $part); $this->denyAccessUnlessGranted('@info_providers.create_parts'); @@ -331,22 +274,10 @@ final class PartController extends AbstractController $this->addFlash('notice', t('part.merge.flash.please_review')); - // Check if this is part of a bulk import job - $jobId = $request->query->get('jobId'); - $bulkJob = null; - if ($jobId) { - $bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId); - // Verify user owns this job - if ($bulkJob && $bulkJob->getCreatedBy() !== $this->getUser()) { - $bulkJob = null; - } - } - return $this->renderPartForm('update_from_ip', $request, $part, [ 'info_provider_dto' => $dto, ], [ - 'tname_before' => $old_name, - 'bulk_job' => $bulkJob + 'tname_before' => $old_name ]); } @@ -381,7 +312,7 @@ final class PartController extends AbstractController } catch (AttachmentDownloadException $attachmentDownloadException) { $this->addFlash( 'error', - $this->translator->trans('attachment.download_failed') . ' ' . $attachmentDownloadException->getMessage() + $this->translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage() ); } } @@ -422,12 +353,6 @@ final class PartController extends AbstractController return $this->redirectToRoute('part_new'); } - // Check if we're in bulk import mode and preserve jobId - $jobId = $request->query->get('jobId'); - if ($jobId && isset($merge_infos['bulk_job'])) { - return $this->redirectToRoute('part_edit', ['id' => $new_part->getID(), 'jobId' => $jobId]); - } - return $this->redirectToRoute('part_edit', ['id' => $new_part->getID()]); } @@ -446,39 +371,33 @@ final class PartController extends AbstractController $template = 'parts/edit/update_from_ip.html.twig'; } - $partRepository = $this->em->getRepository(Part::class); - - return $this->render( - $template, + return $this->render($template, [ 'part' => $new_part, - 'ipnSuggestions' => $partRepository->autoCompleteIpn($data, $data->getDescription(), $this->ipnSuggestSettings->suggestPartDigits), 'form' => $form, 'merge_old_name' => $merge_infos['tname_before'] ?? null, - 'merge_other' => $merge_infos['other_part'] ?? null, - 'bulk_job' => $merge_infos['bulk_job'] ?? null, - 'jobId' => $request->query->get('jobId') - ] - ); + 'merge_other' => $merge_infos['other_part'] ?? null + ]); } + #[Route(path: '/{id}/add_withdraw', name: 'part_add_withdraw', methods: ['POST'])] public function withdrawAddHandler(Part $part, Request $request, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper): Response { if ($this->isCsrfTokenValid('part_withraw' . $part->getID(), $request->request->get('_csfr'))) { //Retrieve partlot from the request $partLot = $em->find(PartLot::class, $request->request->get('lot_id')); - if (!$partLot instanceof PartLot) { + if(!$partLot instanceof PartLot) { throw new \RuntimeException('Part lot not found!'); } //Ensure that the partlot belongs to the part - if ($partLot->getPart() !== $part) { + if($partLot->getPart() !== $part) { throw new \RuntimeException("The origin partlot does not belong to the part!"); } //Try to determine the target lot (used for move actions), if the parameter is existing $targetId = $request->request->get('target_id', null); - $targetLot = $targetId ? $em->find(PartLot::class, $targetId) : null; + $targetLot = $targetId ? $em->find(PartLot::class, $targetId) : null; if ($targetLot && $targetLot->getPart() !== $part) { throw new \RuntimeException("The target partlot does not belong to the part!"); } @@ -492,12 +411,12 @@ final class PartController extends AbstractController $timestamp = null; $timestamp_str = $request->request->getString('timestamp', ''); //Try to parse the timestamp - if ($timestamp_str !== '') { + if($timestamp_str !== '') { $timestamp = new DateTime($timestamp_str); } //Ensure that the timestamp is not in the future - if ($timestamp !== null && $timestamp > new DateTime("+20min")) { + if($timestamp !== null && $timestamp > new DateTime("+20min")) { throw new \LogicException("The timestamp must not be in the future!"); } @@ -541,7 +460,7 @@ final class PartController extends AbstractController err: //If a redirect was passed, then redirect there - if ($request->request->get('_redirect')) { + if($request->request->get('_redirect')) { return $this->redirect($request->request->get('_redirect')); } //Otherwise just redirect to the part page diff --git a/src/Controller/PartListsController.php b/src/Controller/PartListsController.php index 808b0c5d..b2df18c1 100644 --- a/src/Controller/PartListsController.php +++ b/src/Controller/PartListsController.php @@ -36,7 +36,6 @@ use App\Exceptions\InvalidRegexException; use App\Form\Filters\PartFilterType; use App\Services\Parts\PartsTableActionHandler; use App\Services\Trees\NodesListBuilder; -use App\Settings\BehaviorSettings\SidebarSettings; use App\Settings\BehaviorSettings\TableSettings; use Doctrine\DBAL\Exception\DriverException; use Doctrine\ORM\EntityManagerInterface; @@ -57,21 +56,11 @@ class PartListsController extends AbstractController private readonly NodesListBuilder $nodesListBuilder, private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator, - private readonly TableSettings $tableSettings, - private readonly SidebarSettings $sidebarSettings, + private readonly TableSettings $tableSettings ) { } - /** - * Gets the filter operator to use by default (INCLUDING_CHILDREN or =) - * @return string - */ - private function getFilterOperator(): string - { - return $this->sidebarSettings->dataStructureNodesTableIncludeChildren ? 'INCLUDING_CHILDREN' : '='; - } - #[Route(path: '/table/action', name: 'table_action', methods: ['POST'])] public function tableAction(Request $request, PartsTableActionHandler $actionHandler): Response { @@ -165,18 +154,13 @@ class PartListsController extends AbstractController $filter_changer($filter); } - //If we are in a post request for the tables, we only have to apply the filter form if the submit query param was set - //This saves us some time from creating this complicated term on simple list pages, where no special filter is applied - $filterForm = null; - if ($request->getMethod() !== 'POST' || $request->query->has('part_filter')) { - $filterForm = $this->createForm(PartFilterType::class, $filter, ['method' => 'GET']); - if ($form_changer !== null) { - $form_changer($filterForm); - } - - $filterForm->handleRequest($formRequest); + $filterForm = $this->createForm(PartFilterType::class, $filter, ['method' => 'GET']); + if($form_changer !== null) { + $form_changer($filterForm); } + $filterForm->handleRequest($formRequest); + $table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge( ['filter' => $filter], $additional_table_vars), ['pageLength' => $this->tableSettings->fullDefaultPageSize, 'lengthMenu' => PartsDataTable::LENGTH_MENU]) @@ -202,7 +186,7 @@ class PartListsController extends AbstractController return $this->render($template, array_merge([ 'datatable' => $table, - 'filterForm' => $filterForm?->createView(), + 'filterForm' => $filterForm->createView(), ], $additonal_template_vars)); } @@ -214,7 +198,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/category_list.html.twig', function (PartFilter $filter) use ($category) { - $filter->category->setOperator($this->getFilterOperator())->setValue($category); + $filter->category->setOperator('INCLUDING_CHILDREN')->setValue($category); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('category')->get('value')); }, [ @@ -232,7 +216,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/footprint_list.html.twig', function (PartFilter $filter) use ($footprint) { - $filter->footprint->setOperator($this->getFilterOperator())->setValue($footprint); + $filter->footprint->setOperator('INCLUDING_CHILDREN')->setValue($footprint); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('footprint')->get('value')); }, [ @@ -250,7 +234,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/manufacturer_list.html.twig', function (PartFilter $filter) use ($manufacturer) { - $filter->manufacturer->setOperator($this->getFilterOperator())->setValue($manufacturer); + $filter->manufacturer->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('manufacturer')->get('value')); }, [ @@ -268,7 +252,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/store_location_list.html.twig', function (PartFilter $filter) use ($storelocation) { - $filter->storelocation->setOperator($this->getFilterOperator())->setValue($storelocation); + $filter->storelocation->setOperator('INCLUDING_CHILDREN')->setValue($storelocation); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value')); }, [ @@ -286,7 +270,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/supplier_list.html.twig', function (PartFilter $filter) use ($supplier) { - $filter->supplier->setOperator($this->getFilterOperator())->setValue($supplier); + $filter->supplier->setOperator('INCLUDING_CHILDREN')->setValue($supplier); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('supplier')->get('value')); }, [ diff --git a/src/Controller/SettingsController.php b/src/Controller/SettingsController.php index 15c945f6..3479cf84 100644 --- a/src/Controller/SettingsController.php +++ b/src/Controller/SettingsController.php @@ -64,7 +64,7 @@ class SettingsController extends AbstractController $this->settingsManager->save($settings); //It might be possible, that the tree settings have changed, so clear the cache - $cache->invalidateTags(['tree_tools', 'tree_treeview', 'sidebar_tree_update', 'synonyms']); + $cache->invalidateTags(['tree_treeview', 'sidebar_tree_update']); $this->addFlash('success', t('settings.flash.saved')); } diff --git a/src/Controller/TypeaheadController.php b/src/Controller/TypeaheadController.php index 39821f59..89eac7ff 100644 --- a/src/Controller/TypeaheadController.php +++ b/src/Controller/TypeaheadController.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace App\Controller; use App\Entity\Parameters\AbstractParameter; -use App\Settings\MiscSettings\IpnSuggestSettings; use Symfony\Component\HttpFoundation\Response; use App\Entity\Attachments\Attachment; use App\Entity\Parts\Category; @@ -61,11 +60,8 @@ use Symfony\Component\Serializer\Serializer; #[Route(path: '/typeahead')] class TypeaheadController extends AbstractController { - public function __construct( - protected AttachmentURLGenerator $urlGenerator, - protected Packages $assets, - protected IpnSuggestSettings $ipnSuggestSettings, - ) { + public function __construct(protected AttachmentURLGenerator $urlGenerator, protected Packages $assets) + { } #[Route(path: '/builtInResources/search', name: 'typeahead_builtInRessources')] @@ -187,30 +183,4 @@ class TypeaheadController extends AbstractController return new JsonResponse($data, Response::HTTP_OK, [], true); } - - #[Route(path: '/parts/ipn-suggestions', name: 'ipn_suggestions', methods: ['GET'])] - public function ipnSuggestions( - Request $request, - EntityManagerInterface $entityManager - ): JsonResponse { - $partId = $request->query->get('partId'); - if ($partId === '0' || $partId === 'undefined' || $partId === 'null') { - $partId = null; - } - $categoryId = $request->query->getInt('categoryId'); - $description = base64_decode($request->query->getString('description'), true); - - /** @var Part $part */ - $part = $partId !== null ? $entityManager->getRepository(Part::class)->find($partId) : new Part(); - /** @var Category|null $category */ - $category = $entityManager->getRepository(Category::class)->find($categoryId); - - $clonedPart = clone $part; - $clonedPart->setCategory($category); - - $partRepository = $entityManager->getRepository(Part::class); - $ipnSuggestions = $partRepository->autoCompleteIpn($clonedPart, $description, $this->ipnSuggestSettings->suggestPartDigits); - - return new JsonResponse($ipnSuggestions); - } } diff --git a/src/DataFixtures/DataStructureFixtures.php b/src/DataFixtures/DataStructureFixtures.php index 9c685338..fc713d4d 100644 --- a/src/DataFixtures/DataStructureFixtures.php +++ b/src/DataFixtures/DataStructureFixtures.php @@ -24,7 +24,6 @@ namespace App\DataFixtures; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractStructuralDBElement; -use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; @@ -51,7 +50,7 @@ class DataStructureFixtures extends Fixture implements DependentFixtureInterface { //Reset autoincrement $types = [AttachmentType::class, Project::class, Category::class, Footprint::class, Manufacturer::class, - MeasurementUnit::class, StorageLocation::class, Supplier::class, PartCustomState::class]; + MeasurementUnit::class, StorageLocation::class, Supplier::class,]; foreach ($types as $type) { $this->createNodesForClass($type, $manager); diff --git a/src/DataTables/Filters/AttachmentFilter.php b/src/DataTables/Filters/AttachmentFilter.php index 69d2aeac..d41bbe39 100644 --- a/src/DataTables/Filters/AttachmentFilter.php +++ b/src/DataTables/Filters/AttachmentFilter.php @@ -22,7 +22,6 @@ declare(strict_types=1); */ namespace App\DataTables\Filters; -use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\BooleanConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; @@ -33,7 +32,6 @@ use App\DataTables\Filters\Constraints\TextConstraint; use App\Entity\Attachments\AttachmentType; use App\Services\Trees\NodesListBuilder; use Doctrine\ORM\QueryBuilder; -use Omines\DataTablesBundle\Filter\AbstractFilter; class AttachmentFilter implements FilterInterface { @@ -53,9 +51,6 @@ class AttachmentFilter implements FilterInterface public function __construct(NodesListBuilder $nodesListBuilder) { - //Must be done for every new set of attachment filters, to ensure deterministic parameter names. - AbstractConstraint::resetParameterCounter(); - $this->dbId = new IntConstraint('attachment.id'); $this->name = new TextConstraint('attachment.name'); $this->targetType = new InstanceOfConstraint('attachment'); diff --git a/src/DataTables/Filters/Constraints/AbstractConstraint.php b/src/DataTables/Filters/Constraints/AbstractConstraint.php index c632b2a4..7f16511e 100644 --- a/src/DataTables/Filters/Constraints/AbstractConstraint.php +++ b/src/DataTables/Filters/Constraints/AbstractConstraint.php @@ -28,7 +28,10 @@ abstract class AbstractConstraint implements FilterInterface { use FilterTrait; - protected ?string $identifier; + /** + * @var string + */ + protected string $identifier; /** diff --git a/src/DataTables/Filters/Constraints/FilterTrait.php b/src/DataTables/Filters/Constraints/FilterTrait.php index 2932914a..3260e4e3 100644 --- a/src/DataTables/Filters/Constraints/FilterTrait.php +++ b/src/DataTables/Filters/Constraints/FilterTrait.php @@ -28,7 +28,6 @@ trait FilterTrait { protected bool $useHaving = false; - protected static int $parameterCounter = 0; public function useHaving($value = true): static { @@ -51,18 +50,8 @@ trait FilterTrait { //Replace all special characters with underscores $property = preg_replace('/\W/', '_', $property); - return $property . '_' . (self::$parameterCounter++) . '_'; - } - - /** - * Resets the parameter counter, so the next call to generateParameterIdentifier will start from 0 again. - * This should be done before initializing a new set of filters to a fresh query builder, to ensure that the parameter - * identifiers are deterministic so that they are cacheable. - * @return void - */ - public static function resetParameterCounter(): void - { - self::$parameterCounter = 0; + //Add a random number to the end of the property name for uniqueness + return $property . '_' . uniqid("", false); } /** diff --git a/src/DataTables/Filters/Constraints/Part/BulkImportJobExistsConstraint.php b/src/DataTables/Filters/Constraints/Part/BulkImportJobExistsConstraint.php deleted file mode 100644 index 9d21dd58..00000000 --- a/src/DataTables/Filters/Constraints/Part/BulkImportJobExistsConstraint.php +++ /dev/null @@ -1,59 +0,0 @@ -. - */ - -namespace App\DataTables\Filters\Constraints\Part; - -use App\DataTables\Filters\Constraints\BooleanConstraint; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; -use Doctrine\ORM\QueryBuilder; - -class BulkImportJobExistsConstraint extends BooleanConstraint -{ - - public function __construct() - { - parent::__construct('bulk_import_job_exists'); - } - - public function apply(QueryBuilder $queryBuilder): void - { - // Do not apply a filter if value is null (filter is set to ignore) - if (!$this->isEnabled()) { - return; - } - - // Use EXISTS subquery to avoid join conflicts - $existsSubquery = $queryBuilder->getEntityManager()->createQueryBuilder(); - $existsSubquery->select('1') - ->from(BulkInfoProviderImportJobPart::class, 'bip_exists') - ->where('bip_exists.part = part.id'); - - if ($this->value === true) { - // Filter for parts that ARE in bulk import jobs - $queryBuilder->andWhere('EXISTS (' . $existsSubquery->getDQL() . ')'); - } else { - // Filter for parts that are NOT in bulk import jobs - $queryBuilder->andWhere('NOT EXISTS (' . $existsSubquery->getDQL() . ')'); - } - } -} diff --git a/src/DataTables/Filters/Constraints/Part/BulkImportJobStatusConstraint.php b/src/DataTables/Filters/Constraints/Part/BulkImportJobStatusConstraint.php deleted file mode 100644 index d9451577..00000000 --- a/src/DataTables/Filters/Constraints/Part/BulkImportJobStatusConstraint.php +++ /dev/null @@ -1,64 +0,0 @@ -. - */ - -namespace App\DataTables\Filters\Constraints\Part; - -use App\DataTables\Filters\Constraints\AbstractConstraint; -use App\DataTables\Filters\Constraints\ChoiceConstraint; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; -use Doctrine\ORM\QueryBuilder; - -class BulkImportJobStatusConstraint extends ChoiceConstraint -{ - - public function __construct() - { - parent::__construct('bulk_import_job_status'); - } - - public function apply(QueryBuilder $queryBuilder): void - { - // Do not apply a filter if values are empty or operator is null - if (!$this->isEnabled()) { - return; - } - - // Use EXISTS subquery to check if part has a job with the specified status(es) - $existsSubquery = $queryBuilder->getEntityManager()->createQueryBuilder(); - $existsSubquery->select('1') - ->from(BulkInfoProviderImportJobPart::class, 'bip_status') - ->join('bip_status.job', 'job_status') - ->where('bip_status.part = part.id'); - - // Add status conditions based on operator - if ($this->operator === 'ANY') { - $existsSubquery->andWhere('job_status.status IN (:job_status_values)'); - $queryBuilder->andWhere('EXISTS (' . $existsSubquery->getDQL() . ')'); - $queryBuilder->setParameter('job_status_values', $this->value); - } elseif ($this->operator === 'NONE') { - $existsSubquery->andWhere('job_status.status IN (:job_status_values)'); - $queryBuilder->andWhere('NOT EXISTS (' . $existsSubquery->getDQL() . ')'); - $queryBuilder->setParameter('job_status_values', $this->value); - } - } -} diff --git a/src/DataTables/Filters/Constraints/Part/BulkImportPartStatusConstraint.php b/src/DataTables/Filters/Constraints/Part/BulkImportPartStatusConstraint.php deleted file mode 100644 index 7656a290..00000000 --- a/src/DataTables/Filters/Constraints/Part/BulkImportPartStatusConstraint.php +++ /dev/null @@ -1,61 +0,0 @@ -. - */ - -namespace App\DataTables\Filters\Constraints\Part; - -use App\DataTables\Filters\Constraints\ChoiceConstraint; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; -use Doctrine\ORM\QueryBuilder; - -class BulkImportPartStatusConstraint extends ChoiceConstraint -{ - public function __construct() - { - parent::__construct('bulk_import_part_status'); - } - - public function apply(QueryBuilder $queryBuilder): void - { - // Do not apply a filter if values are empty or operator is null - if (!$this->isEnabled()) { - return; - } - - // Use EXISTS subquery to check if part has the specified status(es) - $existsSubquery = $queryBuilder->getEntityManager()->createQueryBuilder(); - $existsSubquery->select('1') - ->from(BulkInfoProviderImportJobPart::class, 'bip_part_status') - ->where('bip_part_status.part = part.id'); - - // Add status conditions based on operator - if ($this->operator === 'ANY') { - $existsSubquery->andWhere('bip_part_status.status IN (:part_status_values)'); - $queryBuilder->andWhere('EXISTS (' . $existsSubquery->getDQL() . ')'); - $queryBuilder->setParameter('part_status_values', $this->value); - } elseif ($this->operator === 'NONE') { - $existsSubquery->andWhere('bip_part_status.status IN (:part_status_values)'); - $queryBuilder->andWhere('NOT EXISTS (' . $existsSubquery->getDQL() . ')'); - $queryBuilder->setParameter('part_status_values', $this->value); - } - } -} diff --git a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php index 2b28e6b4..02eab7a1 100644 --- a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php @@ -88,7 +88,7 @@ class TagsConstraint extends AbstractConstraint //Escape any %, _ or \ in the tag $tag = addcslashes($tag, '%_\\'); - $tag_identifier_prefix = $this->generateParameterIdentifier('tag'); + $tag_identifier_prefix = uniqid($this->identifier . '_', false); $expr = $queryBuilder->expr(); diff --git a/src/DataTables/Filters/Constraints/TextConstraint.php b/src/DataTables/Filters/Constraints/TextConstraint.php index c6a6fe19..31b12a5e 100644 --- a/src/DataTables/Filters/Constraints/TextConstraint.php +++ b/src/DataTables/Filters/Constraints/TextConstraint.php @@ -96,15 +96,14 @@ class TextConstraint extends AbstractConstraint //The CONTAINS, LIKE, STARTS and ENDS operators use the LIKE operator, but we have to build the value string differently $like_value = null; - $escaped_value = str_replace(['%', '_'], ['\%', '\_'], $this->value); if ($this->operator === 'LIKE') { - $like_value = $this->value; //Here we do not escape anything, as the user may provide % and _ wildcards + $like_value = $this->value; } elseif ($this->operator === 'STARTS') { - $like_value = $escaped_value . '%'; + $like_value = $this->value . '%'; } elseif ($this->operator === 'ENDS') { - $like_value = '%' . $escaped_value; + $like_value = '%' . $this->value; } elseif ($this->operator === 'CONTAINS') { - $like_value = '%' . $escaped_value . '%'; + $like_value = '%' . $this->value . '%'; } if ($like_value !== null) { diff --git a/src/DataTables/Filters/LogFilter.php b/src/DataTables/Filters/LogFilter.php index 38dc2191..35d32e74 100644 --- a/src/DataTables/Filters/LogFilter.php +++ b/src/DataTables/Filters/LogFilter.php @@ -22,7 +22,6 @@ declare(strict_types=1); */ namespace App\DataTables\Filters; -use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\ChoiceConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; @@ -45,9 +44,6 @@ class LogFilter implements FilterInterface public function __construct() { - //Must be done for every new set of attachment filters, to ensure deterministic parameter names. - AbstractConstraint::resetParameterCounter(); - $this->timestamp = new DateTimeConstraint('log.timestamp'); $this->dbId = new IntConstraint('log.id'); $this->level = new ChoiceConstraint('log.level'); diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index cf185dfd..ff98c76f 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -22,16 +22,12 @@ declare(strict_types=1); */ namespace App\DataTables\Filters; -use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\BooleanConstraint; use App\DataTables\Filters\Constraints\ChoiceConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; use App\DataTables\Filters\Constraints\IntConstraint; use App\DataTables\Filters\Constraints\NumberConstraint; -use App\DataTables\Filters\Constraints\Part\BulkImportJobExistsConstraint; -use App\DataTables\Filters\Constraints\Part\BulkImportJobStatusConstraint; -use App\DataTables\Filters\Constraints\Part\BulkImportPartStatusConstraint; use App\DataTables\Filters\Constraints\Part\LessThanDesiredConstraint; use App\DataTables\Filters\Constraints\Part\ParameterConstraint; use App\DataTables\Filters\Constraints\Part\TagsConstraint; @@ -41,7 +37,6 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\PartCustomState; use App\Entity\Parts\PartLot; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; @@ -87,7 +82,6 @@ class PartFilter implements FilterInterface public readonly EntityConstraint $lotOwner; public readonly EntityConstraint $measurementUnit; - public readonly EntityConstraint $partCustomState; public readonly TextConstraint $manufacturer_product_url; public readonly TextConstraint $manufacturer_product_number; public readonly IntConstraint $attachmentsCount; @@ -107,19 +101,8 @@ class PartFilter implements FilterInterface public readonly TextConstraint $bomName; public readonly TextConstraint $bomComment; - /************************************************* - * Bulk Import Job tab - *************************************************/ - - public readonly BulkImportJobExistsConstraint $inBulkImportJob; - public readonly BulkImportJobStatusConstraint $bulkImportJobStatus; - public readonly BulkImportPartStatusConstraint $bulkImportPartStatus; - public function __construct(NodesListBuilder $nodesListBuilder) { - //Must be done for every new set of attachment filters, to ensure deterministic parameter names. - AbstractConstraint::resetParameterCounter(); - $this->name = new TextConstraint('part.name'); $this->description = new TextConstraint('part.description'); $this->comment = new TextConstraint('part.comment'); @@ -130,7 +113,6 @@ class PartFilter implements FilterInterface $this->favorite = new BooleanConstraint('part.favorite'); $this->needsReview = new BooleanConstraint('part.needs_review'); $this->measurementUnit = new EntityConstraint($nodesListBuilder, MeasurementUnit::class, 'part.partUnit'); - $this->partCustomState = new EntityConstraint($nodesListBuilder, PartCustomState::class, 'part.partCustomState'); $this->mass = new NumberConstraint('part.mass'); $this->dbId = new IntConstraint('part.id'); $this->ipn = new TextConstraint('part.ipn'); @@ -144,7 +126,7 @@ class PartFilter implements FilterInterface */ $this->amountSum = (new IntConstraint('( SELECT COALESCE(SUM(__partLot.amount), 0.0) - FROM ' . PartLot::class . ' __partLot + 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()) @@ -180,11 +162,6 @@ class PartFilter implements FilterInterface $this->bomName = new TextConstraint('_projectBomEntries.name'); $this->bomComment = new TextConstraint('_projectBomEntries.comment'); - // Bulk Import Job filters - $this->inBulkImportJob = new BulkImportJobExistsConstraint(); - $this->bulkImportJobStatus = new BulkImportJobStatusConstraint(); - $this->bulkImportPartStatus = new BulkImportPartStatusConstraint(); - } public function apply(QueryBuilder $queryBuilder): void diff --git a/src/DataTables/Filters/PartSearchFilter.php b/src/DataTables/Filters/PartSearchFilter.php index aa8c20f4..6e2e5894 100644 --- a/src/DataTables/Filters/PartSearchFilter.php +++ b/src/DataTables/Filters/PartSearchFilter.php @@ -21,7 +21,6 @@ declare(strict_types=1); * along with this program. If not, see . */ namespace App\DataTables\Filters; -use App\DataTables\Filters\Constraints\AbstractConstraint; use Doctrine\ORM\QueryBuilder; class PartSearchFilter implements FilterInterface @@ -144,8 +143,6 @@ class PartSearchFilter implements FilterInterface if ($this->regex) { $queryBuilder->setParameter('search_query', $this->keyword); } else { - //Escape % and _ characters in the keyword - $this->keyword = str_replace(['%', '_'], ['\%', '\_'], $this->keyword); $queryBuilder->setParameter('search_query', '%' . $this->keyword . '%'); } } diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 0baee630..f0decf27 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -142,25 +142,23 @@ final class PartsDataTable implements DataTableTypeInterface 'label' => $this->translator->trans('part.table.storeLocations'), //We need to use a aggregate function to get the first store location, as we have a one-to-many relation 'orderField' => 'NATSORT(MIN(_storelocations.name))', - 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context), + '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), + 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderAmount($context), 'orderField' => 'amountSum' ]) ->add('minamount', TextColumn::class, [ 'label' => $this->translator->trans('part.table.minamount'), - 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format( - $value, - $context->getPartUnit() - )), + 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format($value, + $context->getPartUnit())), ]) ->add('partUnit', TextColumn::class, [ 'label' => $this->translator->trans('part.table.partUnit'), 'orderField' => 'NATSORT(_partUnit.name)', - 'render' => function ($value, Part $context): string { + 'render' => function($value, Part $context): string { $partUnit = $context->getPartUnit(); if ($partUnit === null) { return ''; @@ -169,24 +167,11 @@ final class PartsDataTable implements DataTableTypeInterface $tmp = htmlspecialchars($partUnit->getName()); if ($partUnit->getUnit()) { - $tmp .= ' (' . htmlspecialchars($partUnit->getUnit()) . ')'; + $tmp .= ' ('.htmlspecialchars($partUnit->getUnit()).')'; } return $tmp; } ]) - ->add('partCustomState', TextColumn::class, [ - 'label' => $this->translator->trans('part.table.partCustomState'), - 'orderField' => 'NATSORT(_partCustomState.name)', - 'render' => function($value, Part $context): string { - $partCustomState = $context->getPartCustomState(); - - if ($partCustomState === null) { - return ''; - } - - return htmlspecialchars($partCustomState->getName()); - } - ]) ->add('addedDate', LocaleDateTimeColumn::class, [ 'label' => $this->translator->trans('part.table.addedDate'), ]) @@ -245,7 +230,7 @@ final class PartsDataTable implements DataTableTypeInterface } if (count($projects) > $max) { - $tmp .= ", + " . (count($projects) - $max); + $tmp .= ", + ".(count($projects) - $max); } return $tmp; @@ -322,7 +307,6 @@ final class PartsDataTable implements DataTableTypeInterface ->addSelect('footprint') ->addSelect('manufacturer') ->addSelect('partUnit') - ->addSelect('partCustomState') ->addSelect('master_picture_attachment') ->addSelect('footprint_attachment') ->addSelect('partLots') @@ -341,7 +325,6 @@ final class PartsDataTable implements DataTableTypeInterface ->leftJoin('orderdetails.supplier', 'suppliers') ->leftJoin('part.attachments', 'attachments') ->leftJoin('part.partUnit', 'partUnit') - ->leftJoin('part.partCustomState', 'partCustomState') ->leftJoin('part.parameters', 'parameters') ->where('part.id IN (:ids)') ->setParameter('ids', $ids) @@ -359,7 +342,6 @@ final class PartsDataTable implements DataTableTypeInterface ->addGroupBy('suppliers') ->addGroupBy('attachments') ->addGroupBy('partUnit') - ->addGroupBy('partCustomState') ->addGroupBy('parameters'); //Get the results in the same order as the IDs were passed @@ -384,7 +366,7 @@ final class PartsDataTable implements DataTableTypeInterface $builder->addSelect( '( SELECT COALESCE(SUM(partLot.amount), 0.0) - FROM ' . PartLot::class . ' partLot + 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()) @@ -431,10 +413,6 @@ final class PartsDataTable implements DataTableTypeInterface $builder->leftJoin('part.partUnit', '_partUnit'); $builder->addGroupBy('_partUnit'); } - if (str_contains($dql, '_partCustomState')) { - $builder->leftJoin('part.partCustomState', '_partCustomState'); - $builder->addGroupBy('_partCustomState'); - } 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 @@ -445,13 +423,6 @@ final class PartsDataTable implements DataTableTypeInterface //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 //$builder->addGroupBy('_projectBomEntries'); } - if (str_contains($dql, '_jobPart')) { - $builder->leftJoin('part.bulkImportJobParts', '_jobPart'); - $builder->leftJoin('_jobPart.job', '_bulkImportJob'); - //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 - //$builder->addGroupBy('_jobPart'); - //$builder->addGroupBy('_bulkImportJob'); - } return $builder; } diff --git a/src/Doctrine/Functions/ILike.php b/src/Doctrine/Functions/ILike.php index ff2d2163..5246220a 100644 --- a/src/Doctrine/Functions/ILike.php +++ b/src/Doctrine/Functions/ILike.php @@ -56,6 +56,7 @@ class ILike extends FunctionNode { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + // if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { $operator = 'LIKE'; } elseif ($platform instanceof PostgreSQLPlatform) { @@ -65,12 +66,6 @@ class ILike extends FunctionNode throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support case insensitive like expressions.'); } - $escape = ""; - if ($platform instanceof SQLitePlatform) { - //SQLite needs ESCAPE explicitly defined backslash as escape character - $escape = " ESCAPE '\\'"; - } - - return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . $escape . ')'; + return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . ')'; } -} +} \ No newline at end of file diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 08aacaa0..00cf581a 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -97,7 +97,7 @@ use function in_array; #[DiscriminatorMap(typeProperty: '_type', mapping: self::API_DISCRIMINATOR_MAP)] abstract class Attachment extends AbstractNamedDBElement { - private const ORM_DISCRIMINATOR_MAP = ['Part' => PartAttachment::class, 'PartCustomState' => PartCustomStateAttachment::class, 'Device' => ProjectAttachment::class, + private const ORM_DISCRIMINATOR_MAP = ['Part' => PartAttachment::class, 'Device' => ProjectAttachment::class, 'AttachmentType' => AttachmentTypeAttachment::class, 'Category' => CategoryAttachment::class, 'Footprint' => FootprintAttachment::class, 'Manufacturer' => ManufacturerAttachment::class, 'Currency' => CurrencyAttachment::class, 'Group' => GroupAttachment::class, 'MeasurementUnit' => MeasurementUnitAttachment::class, @@ -107,8 +107,7 @@ abstract class Attachment extends AbstractNamedDBElement /* * The discriminator map used for API platform. The key should be the same as the api platform short type (the @type JSONLD field). */ - private const API_DISCRIMINATOR_MAP = ["Part" => PartAttachment::class, "PartCustomState" => PartCustomStateAttachment::class, "Project" => ProjectAttachment::class, - "AttachmentType" => AttachmentTypeAttachment::class, + private const API_DISCRIMINATOR_MAP = ["Part" => PartAttachment::class, "Project" => ProjectAttachment::class, "AttachmentType" => AttachmentTypeAttachment::class, "Category" => CategoryAttachment::class, "Footprint" => FootprintAttachment::class, "Manufacturer" => ManufacturerAttachment::class, "Currency" => CurrencyAttachment::class, "Group" => GroupAttachment::class, "MeasurementUnit" => MeasurementUnitAttachment::class, "StorageLocation" => StorageLocationAttachment::class, "Supplier" => SupplierAttachment::class, "User" => UserAttachment::class, "LabelProfile" => LabelAttachment::class]; @@ -166,10 +165,9 @@ abstract class Attachment extends AbstractNamedDBElement * @var string|null The path to the external source if the file is stored externally or was downloaded from an * external source. Null if there is no external source. */ - #[ORM\Column(type: Types::STRING, length: 2048, nullable: true)] + #[ORM\Column(type: Types::STRING, nullable: true)] #[Groups(['attachment:read'])] #[ApiProperty(example: 'http://example.com/image.jpg')] - #[Assert\Length(2048)] protected ?string $external_path = null; /** @@ -552,8 +550,8 @@ abstract class Attachment extends AbstractNamedDBElement */ #[Groups(['attachment:write'])] #[SerializedName('url')] - #[ApiProperty(description: 'Set the path of the attachment here. - Provide either an external URL, a path to a builtin file (like %FOOTPRINTS%/Active/ICs/IC_DFS.png) or an empty + #[ApiProperty(description: 'Set the path of the attachment here. + Provide either an external URL, a path to a builtin file (like %FOOTPRINTS%/Active/ICs/IC_DFS.png) or an empty string if the attachment has an internal file associated and you\'d like to reset the external source. If you set a new (nonempty) file path any associated internal file will be removed!')] public function setURL(?string $url): self diff --git a/src/Entity/Attachments/PartCustomStateAttachment.php b/src/Entity/Attachments/PartCustomStateAttachment.php deleted file mode 100644 index 3a561b13..00000000 --- a/src/Entity/Attachments/PartCustomStateAttachment.php +++ /dev/null @@ -1,45 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Entity\Attachments; - -use App\Entity\Parts\PartCustomState; -use App\Serializer\APIPlatform\OverrideClassDenormalizer; -use Doctrine\ORM\Mapping as ORM; -use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -use Symfony\Component\Serializer\Attribute\Context; - -/** - * An attachment attached to a part custom state element. - * @extends Attachment - */ -#[UniqueEntity(['name', 'attachment_type', 'element'])] -#[ORM\Entity] -class PartCustomStateAttachment extends Attachment -{ - final public const ALLOWED_ELEMENT_CLASS = PartCustomState::class; - - #[ORM\ManyToOne(targetEntity: PartCustomState::class, inversedBy: 'attachments')] - #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] - #[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])] - protected ?AttachmentContainingDBElement $element = null; -} diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index 7d05c93f..947d1339 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -81,10 +81,10 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement /** * @var string The website of the company */ - #[Assert\Url(requireTld: false)] + #[Assert\Url] #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] - #[ORM\Column(type: Types::STRING, length: 2048)] - #[Assert\Length(max: 2048)] + #[ORM\Column(type: Types::STRING)] + #[Assert\Length(max: 255)] protected string $website = ''; #[Groups(['company:read', 'company:write', 'import', 'full', 'extended'])] @@ -93,8 +93,8 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement /** * @var string The link to the website of an article. Use %PARTNUMBER% as placeholder for the part number. */ - #[ORM\Column(type: Types::STRING, length: 2048)] - #[Assert\Length(max: 2048)] + #[ORM\Column(type: Types::STRING)] + #[Assert\Length(max: 255)] #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] protected string $auto_product_url = ''; diff --git a/src/Entity/Base/AbstractDBElement.php b/src/Entity/Base/AbstractDBElement.php index a088b3df..9fb5d648 100644 --- a/src/Entity/Base/AbstractDBElement.php +++ b/src/Entity/Base/AbstractDBElement.php @@ -33,7 +33,6 @@ use App\Entity\Attachments\LabelAttachment; use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; -use App\Entity\Attachments\PartCustomStateAttachment; use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; @@ -41,7 +40,6 @@ use App\Entity\Attachments\UserAttachment; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Category; use App\Entity\PriceInformations\Pricedetail; -use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Entity\Parts\Footprint; @@ -70,41 +68,7 @@ use Symfony\Component\Serializer\Annotation\Groups; * Every database table which are managed with this class (or a subclass of it) * must have the table row "id"!! The ID is the unique key to identify the elements. */ -#[DiscriminatorMap(typeProperty: 'type', mapping: [ - 'attachment_type' => AttachmentType::class, - 'attachment' => Attachment::class, - 'attachment_type_attachment' => AttachmentTypeAttachment::class, - 'category_attachment' => CategoryAttachment::class, - 'currency_attachment' => CurrencyAttachment::class, - 'footprint_attachment' => FootprintAttachment::class, - 'group_attachment' => GroupAttachment::class, - 'label_attachment' => LabelAttachment::class, - 'manufacturer_attachment' => ManufacturerAttachment::class, - 'measurement_unit_attachment' => MeasurementUnitAttachment::class, - 'part_attachment' => PartAttachment::class, - 'part_custom_state_attachment' => PartCustomStateAttachment::class, - 'project_attachment' => ProjectAttachment::class, - 'storelocation_attachment' => StorageLocationAttachment::class, - 'supplier_attachment' => SupplierAttachment::class, - 'user_attachment' => UserAttachment::class, - 'category' => Category::class, - 'project' => Project::class, - 'project_bom_entry' => ProjectBOMEntry::class, - 'footprint' => Footprint::class, - 'group' => Group::class, - 'manufacturer' => Manufacturer::class, - 'orderdetail' => Orderdetail::class, - 'part' => Part::class, - 'part_custom_state' => PartCustomState::class, - 'pricedetail' => Pricedetail::class, - 'storelocation' => StorageLocation::class, - 'part_lot' => PartLot::class, - 'currency' => Currency::class, - 'measurement_unit' => MeasurementUnit::class, - 'parameter' => AbstractParameter::class, - 'supplier' => Supplier::class, - 'user' => User::class] -)] +#[DiscriminatorMap(typeProperty: 'type', mapping: ['attachment_type' => AttachmentType::class, 'attachment' => Attachment::class, 'attachment_type_attachment' => AttachmentTypeAttachment::class, 'category_attachment' => CategoryAttachment::class, 'currency_attachment' => CurrencyAttachment::class, 'footprint_attachment' => FootprintAttachment::class, 'group_attachment' => GroupAttachment::class, 'label_attachment' => LabelAttachment::class, 'manufacturer_attachment' => ManufacturerAttachment::class, 'measurement_unit_attachment' => MeasurementUnitAttachment::class, 'part_attachment' => PartAttachment::class, 'project_attachment' => ProjectAttachment::class, 'storelocation_attachment' => StorageLocationAttachment::class, 'supplier_attachment' => SupplierAttachment::class, 'user_attachment' => UserAttachment::class, 'category' => Category::class, 'project' => Project::class, 'project_bom_entry' => ProjectBOMEntry::class, 'footprint' => Footprint::class, 'group' => Group::class, 'manufacturer' => Manufacturer::class, 'orderdetail' => Orderdetail::class, 'part' => Part::class, 'pricedetail' => Pricedetail::class, 'storelocation' => StorageLocation::class, 'part_lot' => PartLot::class, 'currency' => Currency::class, 'measurement_unit' => MeasurementUnit::class, 'parameter' => AbstractParameter::class, 'supplier' => Supplier::class, 'user' => User::class])] #[ORM\MappedSuperclass(repositoryClass: DBElementRepository::class)] abstract class AbstractDBElement implements JsonSerializable { diff --git a/src/Entity/InfoProviderSystem/BulkImportJobStatus.php b/src/Entity/InfoProviderSystem/BulkImportJobStatus.php deleted file mode 100644 index 7a88802f..00000000 --- a/src/Entity/InfoProviderSystem/BulkImportJobStatus.php +++ /dev/null @@ -1,35 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Entity\InfoProviderSystem; - -use Symfony\Contracts\Translation\TranslatableInterface; -use Symfony\Contracts\Translation\TranslatorInterface; - -enum BulkImportJobStatus: string -{ - case PENDING = 'pending'; - case IN_PROGRESS = 'in_progress'; - case COMPLETED = 'completed'; - case STOPPED = 'stopped'; - case FAILED = 'failed'; -} diff --git a/src/Entity/InfoProviderSystem/BulkImportPartStatus.php b/src/Entity/InfoProviderSystem/BulkImportPartStatus.php deleted file mode 100644 index 0eedc553..00000000 --- a/src/Entity/InfoProviderSystem/BulkImportPartStatus.php +++ /dev/null @@ -1,32 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Entity\InfoProviderSystem; - - -enum BulkImportPartStatus: string -{ - case PENDING = 'pending'; - case COMPLETED = 'completed'; - case SKIPPED = 'skipped'; - case FAILED = 'failed'; -} diff --git a/src/Entity/InfoProviderSystem/BulkInfoProviderImportJob.php b/src/Entity/InfoProviderSystem/BulkInfoProviderImportJob.php deleted file mode 100644 index bc842a26..00000000 --- a/src/Entity/InfoProviderSystem/BulkInfoProviderImportJob.php +++ /dev/null @@ -1,449 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Entity\InfoProviderSystem; - -use App\Entity\Base\AbstractDBElement; -use App\Entity\Parts\Part; -use App\Entity\UserSystem\User; -use App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO; -use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; -use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping as ORM; - -#[ORM\Entity] -#[ORM\Table(name: 'bulk_info_provider_import_jobs')] -class BulkInfoProviderImportJob extends AbstractDBElement -{ - #[ORM\Column(type: Types::TEXT)] - private string $name = ''; - - #[ORM\Column(type: Types::JSON)] - private array $fieldMappings = []; - - /** - * @var BulkSearchFieldMappingDTO[] The deserialized field mappings DTOs, cached for performance - */ - private ?array $fieldMappingsDTO = null; - - #[ORM\Column(type: Types::JSON)] - private array $searchResults = []; - - /** - * @var BulkSearchResponseDTO|null The deserialized search results DTO, cached for performance - */ - private ?BulkSearchResponseDTO $searchResultsDTO = null; - - #[ORM\Column(type: Types::STRING, length: 20, enumType: BulkImportJobStatus::class)] - private BulkImportJobStatus $status = BulkImportJobStatus::PENDING; - - #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] - private \DateTimeImmutable $createdAt; - - #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] - private ?\DateTimeImmutable $completedAt = null; - - #[ORM\Column(type: Types::BOOLEAN)] - private bool $prefetchDetails = false; - - #[ORM\ManyToOne(targetEntity: User::class)] - #[ORM\JoinColumn(nullable: false)] - private ?User $createdBy = null; - - /** @var Collection */ - #[ORM\OneToMany(targetEntity: BulkInfoProviderImportJobPart::class, mappedBy: 'job', cascade: ['persist', 'remove'], orphanRemoval: true)] - private Collection $jobParts; - - public function __construct() - { - $this->createdAt = new \DateTimeImmutable(); - $this->jobParts = new ArrayCollection(); - } - - public function getName(): string - { - return $this->name; - } - - public function getDisplayNameKey(): string - { - return 'info_providers.bulk_import.job_name_template'; - } - - public function getDisplayNameParams(): array - { - return ['%count%' => $this->getPartCount()]; - } - - public function getFormattedTimestamp(): string - { - return $this->createdAt->format('Y-m-d H:i:s'); - } - - public function setName(string $name): self - { - $this->name = $name; - return $this; - } - - public function getJobParts(): Collection - { - return $this->jobParts; - } - - public function addJobPart(BulkInfoProviderImportJobPart $jobPart): self - { - if (!$this->jobParts->contains($jobPart)) { - $this->jobParts->add($jobPart); - $jobPart->setJob($this); - } - return $this; - } - - public function removeJobPart(BulkInfoProviderImportJobPart $jobPart): self - { - if ($this->jobParts->removeElement($jobPart)) { - if ($jobPart->getJob() === $this) { - $jobPart->setJob(null); - } - } - return $this; - } - - public function getPartIds(): array - { - return $this->jobParts->map(fn($jobPart) => $jobPart->getPart()->getId())->toArray(); - } - - public function setPartIds(array $partIds): self - { - // This method is kept for backward compatibility but should be replaced with addJobPart - // Clear existing job parts - $this->jobParts->clear(); - - // Add new job parts (this would need the actual Part entities, not just IDs) - // This is a simplified implementation - in practice, you'd want to pass Part entities - return $this; - } - - public function addPart(Part $part): self - { - $jobPart = new BulkInfoProviderImportJobPart($this, $part); - $this->addJobPart($jobPart); - return $this; - } - - /** - * @return BulkSearchFieldMappingDTO[] The deserialized field mappings - */ - public function getFieldMappings(): array - { - if ($this->fieldMappingsDTO === null) { - // Lazy load the DTOs from the raw JSON data - $this->fieldMappingsDTO = array_map( - static fn($data) => BulkSearchFieldMappingDTO::fromSerializableArray($data), - $this->fieldMappings - ); - } - - return $this->fieldMappingsDTO; - } - - /** - * @param BulkSearchFieldMappingDTO[] $fieldMappings - * @return $this - */ - public function setFieldMappings(array $fieldMappings): self - { - //Ensure that we are dealing with the objects here - if (count($fieldMappings) > 0 && !$fieldMappings[0] instanceof BulkSearchFieldMappingDTO) { - throw new \InvalidArgumentException('Expected an array of FieldMappingDTO objects'); - } - - $this->fieldMappingsDTO = $fieldMappings; - - $this->fieldMappings = array_map( - static fn(BulkSearchFieldMappingDTO $dto) => $dto->toSerializableArray(), - $fieldMappings - ); - return $this; - } - - public function getSearchResultsRaw(): array - { - return $this->searchResults; - } - - public function setSearchResultsRaw(array $searchResults): self - { - $this->searchResults = $searchResults; - return $this; - } - - public function setSearchResults(BulkSearchResponseDTO $searchResponse): self - { - $this->searchResultsDTO = $searchResponse; - $this->searchResults = $searchResponse->toSerializableRepresentation(); - return $this; - } - - public function getSearchResults(EntityManagerInterface $entityManager): BulkSearchResponseDTO - { - if ($this->searchResultsDTO === null) { - // Lazy load the DTO from the raw JSON data - $this->searchResultsDTO = BulkSearchResponseDTO::fromSerializableRepresentation($this->searchResults, $entityManager); - } - return $this->searchResultsDTO; - } - - public function hasSearchResults(): bool - { - return !empty($this->searchResults); - } - - public function getStatus(): BulkImportJobStatus - { - return $this->status; - } - - public function setStatus(BulkImportJobStatus $status): self - { - $this->status = $status; - return $this; - } - - public function getCreatedAt(): \DateTimeImmutable - { - return $this->createdAt; - } - - public function getCompletedAt(): ?\DateTimeImmutable - { - return $this->completedAt; - } - - public function setCompletedAt(?\DateTimeImmutable $completedAt): self - { - $this->completedAt = $completedAt; - return $this; - } - - public function isPrefetchDetails(): bool - { - return $this->prefetchDetails; - } - - public function setPrefetchDetails(bool $prefetchDetails): self - { - $this->prefetchDetails = $prefetchDetails; - return $this; - } - - public function getCreatedBy(): User - { - return $this->createdBy; - } - - public function setCreatedBy(User $createdBy): self - { - $this->createdBy = $createdBy; - return $this; - } - - public function getProgress(): array - { - $progress = []; - foreach ($this->jobParts as $jobPart) { - $progressData = [ - 'status' => $jobPart->getStatus()->value - ]; - - // Only include completed_at if it's not null - if ($jobPart->getCompletedAt() !== null) { - $progressData['completed_at'] = $jobPart->getCompletedAt()->format('c'); - } - - // Only include reason if it's not null - if ($jobPart->getReason() !== null) { - $progressData['reason'] = $jobPart->getReason(); - } - - $progress[$jobPart->getPart()->getId()] = $progressData; - } - return $progress; - } - - public function markAsCompleted(): self - { - $this->status = BulkImportJobStatus::COMPLETED; - $this->completedAt = new \DateTimeImmutable(); - return $this; - } - - public function markAsFailed(): self - { - $this->status = BulkImportJobStatus::FAILED; - $this->completedAt = new \DateTimeImmutable(); - return $this; - } - - public function markAsStopped(): self - { - $this->status = BulkImportJobStatus::STOPPED; - $this->completedAt = new \DateTimeImmutable(); - return $this; - } - - public function markAsInProgress(): self - { - $this->status = BulkImportJobStatus::IN_PROGRESS; - return $this; - } - - public function isPending(): bool - { - return $this->status === BulkImportJobStatus::PENDING; - } - - public function isInProgress(): bool - { - return $this->status === BulkImportJobStatus::IN_PROGRESS; - } - - public function isCompleted(): bool - { - return $this->status === BulkImportJobStatus::COMPLETED; - } - - public function isFailed(): bool - { - return $this->status === BulkImportJobStatus::FAILED; - } - - public function isStopped(): bool - { - return $this->status === BulkImportJobStatus::STOPPED; - } - - public function canBeStopped(): bool - { - return $this->status === BulkImportJobStatus::PENDING || $this->status === BulkImportJobStatus::IN_PROGRESS; - } - - public function getPartCount(): int - { - return $this->jobParts->count(); - } - - public function getResultCount(): int - { - $count = 0; - foreach ($this->searchResults as $partResult) { - $count += count($partResult['search_results'] ?? []); - } - return $count; - } - - public function markPartAsCompleted(int $partId): self - { - $jobPart = $this->findJobPartByPartId($partId); - if ($jobPart) { - $jobPart->markAsCompleted(); - } - return $this; - } - - public function markPartAsSkipped(int $partId, string $reason = ''): self - { - $jobPart = $this->findJobPartByPartId($partId); - if ($jobPart) { - $jobPart->markAsSkipped($reason); - } - return $this; - } - - public function markPartAsPending(int $partId): self - { - $jobPart = $this->findJobPartByPartId($partId); - if ($jobPart) { - $jobPart->markAsPending(); - } - return $this; - } - - public function isPartCompleted(int $partId): bool - { - $jobPart = $this->findJobPartByPartId($partId); - return $jobPart ? $jobPart->isCompleted() : false; - } - - public function isPartSkipped(int $partId): bool - { - $jobPart = $this->findJobPartByPartId($partId); - return $jobPart ? $jobPart->isSkipped() : false; - } - - public function getCompletedPartsCount(): int - { - return $this->jobParts->filter(fn($jobPart) => $jobPart->isCompleted())->count(); - } - - public function getSkippedPartsCount(): int - { - return $this->jobParts->filter(fn($jobPart) => $jobPart->isSkipped())->count(); - } - - private function findJobPartByPartId(int $partId): ?BulkInfoProviderImportJobPart - { - foreach ($this->jobParts as $jobPart) { - if ($jobPart->getPart()->getId() === $partId) { - return $jobPart; - } - } - return null; - } - - public function getProgressPercentage(): float - { - $total = $this->getPartCount(); - if ($total === 0) { - return 100.0; - } - - $completed = $this->getCompletedPartsCount() + $this->getSkippedPartsCount(); - return round(($completed / $total) * 100, 1); - } - - public function isAllPartsCompleted(): bool - { - $total = $this->getPartCount(); - if ($total === 0) { - return true; - } - - $completed = $this->getCompletedPartsCount() + $this->getSkippedPartsCount(); - return $completed >= $total; - } -} diff --git a/src/Entity/InfoProviderSystem/BulkInfoProviderImportJobPart.php b/src/Entity/InfoProviderSystem/BulkInfoProviderImportJobPart.php deleted file mode 100644 index 90519561..00000000 --- a/src/Entity/InfoProviderSystem/BulkInfoProviderImportJobPart.php +++ /dev/null @@ -1,182 +0,0 @@ -. - */ - -declare(strict_types=1); - -/* - * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). - * - * Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -namespace App\Entity\InfoProviderSystem; - -use App\Entity\Base\AbstractDBElement; -use App\Entity\Parts\Part; -use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\Mapping as ORM; - -#[ORM\Entity] -#[ORM\Table(name: 'bulk_info_provider_import_job_parts')] -#[ORM\UniqueConstraint(name: 'unique_job_part', columns: ['job_id', 'part_id'])] -class BulkInfoProviderImportJobPart extends AbstractDBElement -{ - #[ORM\ManyToOne(targetEntity: BulkInfoProviderImportJob::class, inversedBy: 'jobParts')] - #[ORM\JoinColumn(nullable: false)] - private BulkInfoProviderImportJob $job; - - #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'bulkImportJobParts')] - #[ORM\JoinColumn(nullable: false)] - private Part $part; - - #[ORM\Column(type: Types::STRING, length: 20, enumType: BulkImportPartStatus::class)] - private BulkImportPartStatus $status = BulkImportPartStatus::PENDING; - - #[ORM\Column(type: Types::TEXT, nullable: true)] - private ?string $reason = null; - - #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] - private ?\DateTimeImmutable $completedAt = null; - - public function __construct(BulkInfoProviderImportJob $job, Part $part) - { - $this->job = $job; - $this->part = $part; - } - - public function getJob(): BulkInfoProviderImportJob - { - return $this->job; - } - - public function setJob(?BulkInfoProviderImportJob $job): self - { - $this->job = $job; - return $this; - } - - public function getPart(): Part - { - return $this->part; - } - - public function setPart(?Part $part): self - { - $this->part = $part; - return $this; - } - - public function getStatus(): BulkImportPartStatus - { - return $this->status; - } - - public function setStatus(BulkImportPartStatus $status): self - { - $this->status = $status; - return $this; - } - - public function getReason(): ?string - { - return $this->reason; - } - - public function setReason(?string $reason): self - { - $this->reason = $reason; - return $this; - } - - public function getCompletedAt(): ?\DateTimeImmutable - { - return $this->completedAt; - } - - public function setCompletedAt(?\DateTimeImmutable $completedAt): self - { - $this->completedAt = $completedAt; - return $this; - } - - public function markAsCompleted(): self - { - $this->status = BulkImportPartStatus::COMPLETED; - $this->completedAt = new \DateTimeImmutable(); - return $this; - } - - public function markAsSkipped(string $reason = ''): self - { - $this->status = BulkImportPartStatus::SKIPPED; - $this->reason = $reason; - $this->completedAt = new \DateTimeImmutable(); - return $this; - } - - public function markAsFailed(string $reason = ''): self - { - $this->status = BulkImportPartStatus::FAILED; - $this->reason = $reason; - $this->completedAt = new \DateTimeImmutable(); - return $this; - } - - public function markAsPending(): self - { - $this->status = BulkImportPartStatus::PENDING; - $this->reason = null; - $this->completedAt = null; - return $this; - } - - public function isPending(): bool - { - return $this->status === BulkImportPartStatus::PENDING; - } - - public function isCompleted(): bool - { - return $this->status === BulkImportPartStatus::COMPLETED; - } - - public function isSkipped(): bool - { - return $this->status === BulkImportPartStatus::SKIPPED; - } - - public function isFailed(): bool - { - return $this->status === BulkImportPartStatus::FAILED; - } -} diff --git a/src/Entity/LogSystem/CollectionElementDeleted.php b/src/Entity/LogSystem/CollectionElementDeleted.php index 34ab8fba..16bf33f5 100644 --- a/src/Entity/LogSystem/CollectionElementDeleted.php +++ b/src/Entity/LogSystem/CollectionElementDeleted.php @@ -46,7 +46,6 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Entity\Attachments\CategoryAttachment; use App\Entity\Attachments\CurrencyAttachment; -use App\Entity\Attachments\PartCustomStateAttachment; use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\GroupAttachment; @@ -59,8 +58,6 @@ use App\Entity\Attachments\UserAttachment; use App\Entity\Base\AbstractDBElement; use App\Entity\Contracts\LogWithEventUndoInterface; use App\Entity\Contracts\NamedElementInterface; -use App\Entity\Parameters\PartCustomStateParameter; -use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parameters\AttachmentTypeParameter; @@ -161,7 +158,6 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU Part::class => PartParameter::class, StorageLocation::class => StorageLocationParameter::class, Supplier::class => SupplierParameter::class, - PartCustomState::class => PartCustomStateParameter::class, default => throw new \RuntimeException('Unknown target class for parameter: '.$this->getTargetClass()), }; } @@ -177,7 +173,6 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU Manufacturer::class => ManufacturerAttachment::class, MeasurementUnit::class => MeasurementUnitAttachment::class, Part::class => PartAttachment::class, - PartCustomState::class => PartCustomStateAttachment::class, StorageLocation::class => StorageLocationAttachment::class, Supplier::class => SupplierAttachment::class, User::class => UserAttachment::class, diff --git a/src/Entity/LogSystem/LogTargetType.php b/src/Entity/LogSystem/LogTargetType.php index 3b2d8682..1c6e4f8c 100644 --- a/src/Entity/LogSystem/LogTargetType.php +++ b/src/Entity/LogSystem/LogTargetType.php @@ -24,8 +24,6 @@ namespace App\Entity\LogSystem; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Category; @@ -34,7 +32,6 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartAssociation; -use App\Entity\Parts\PartCustomState; use App\Entity\Parts\PartLot; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; @@ -70,9 +67,6 @@ enum LogTargetType: int case LABEL_PROFILE = 19; case PART_ASSOCIATION = 20; - case BULK_INFO_PROVIDER_IMPORT_JOB = 21; - case BULK_INFO_PROVIDER_IMPORT_JOB_PART = 22; - case PART_CUSTOM_STATE = 23; /** * Returns the class name of the target type or null if the target type is NONE. @@ -102,9 +96,6 @@ enum LogTargetType: int self::PARAMETER => AbstractParameter::class, self::LABEL_PROFILE => LabelProfile::class, self::PART_ASSOCIATION => PartAssociation::class, - self::BULK_INFO_PROVIDER_IMPORT_JOB => BulkInfoProviderImportJob::class, - self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => BulkInfoProviderImportJobPart::class, - self::PART_CUSTOM_STATE => PartCustomState::class }; } diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index d84e68ad..39f333da 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -73,8 +73,7 @@ use function sprintf; #[ORM\DiscriminatorMap([0 => CategoryParameter::class, 1 => CurrencyParameter::class, 2 => ProjectParameter::class, 3 => FootprintParameter::class, 4 => GroupParameter::class, 5 => ManufacturerParameter::class, 6 => MeasurementUnitParameter::class, 7 => PartParameter::class, 8 => StorageLocationParameter::class, - 9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class, - 12 => PartCustomStateParameter::class])] + 9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class])] #[ORM\Table('parameters')] #[ORM\Index(columns: ['name'], name: 'parameter_name_idx')] #[ORM\Index(columns: ['param_group'], name: 'parameter_group_idx')] @@ -106,7 +105,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu "AttachmentType" => AttachmentTypeParameter::class, "Category" => CategoryParameter::class, "Currency" => CurrencyParameter::class, "Project" => ProjectParameter::class, "Footprint" => FootprintParameter::class, "Group" => GroupParameter::class, "Manufacturer" => ManufacturerParameter::class, "MeasurementUnit" => MeasurementUnitParameter::class, - "StorageLocation" => StorageLocationParameter::class, "Supplier" => SupplierParameter::class, "PartCustomState" => PartCustomStateParameter::class]; + "StorageLocation" => StorageLocationParameter::class, "Supplier" => SupplierParameter::class]; /** * @var string The class of the element that can be passed to this attachment. Must be overridden in subclasses. @@ -124,7 +123,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu /** * @var float|null the guaranteed minimum value of this property */ - #[Assert\Type(['float', 'null'])] + #[Assert\Type(['float', null])] #[Assert\LessThanOrEqual(propertyPath: 'value_typical', message: 'parameters.validator.min_lesser_typical')] #[Assert\LessThan(propertyPath: 'value_max', message: 'parameters.validator.min_lesser_max')] #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] @@ -134,7 +133,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu /** * @var float|null the typical value of this property */ - #[Assert\Type(['null', 'float'])] + #[Assert\Type([null, 'float'])] #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_typical = null; @@ -142,7 +141,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu /** * @var float|null the maximum value of this property */ - #[Assert\Type(['float', 'null'])] + #[Assert\Type(['float', null])] #[Assert\GreaterThanOrEqual(propertyPath: 'value_typical', message: 'parameters.validator.max_greater_typical')] #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] @@ -461,7 +460,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu return $str; } - + /** * Returns the class of the element that is allowed to be associated with this attachment. * @return string diff --git a/src/Entity/Parameters/PartCustomStateParameter.php b/src/Entity/Parameters/PartCustomStateParameter.php deleted file mode 100644 index ceedf7b4..00000000 --- a/src/Entity/Parameters/PartCustomStateParameter.php +++ /dev/null @@ -1,65 +0,0 @@ -. - */ - -declare(strict_types=1); - -/** - * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). - * - * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -namespace App\Entity\Parameters; - -use App\Entity\Base\AbstractDBElement; -use App\Entity\Parts\PartCustomState; -use App\Repository\ParameterRepository; -use App\Serializer\APIPlatform\OverrideClassDenormalizer; -use Doctrine\ORM\Mapping as ORM; -use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -use Symfony\Component\Serializer\Attribute\Context; - -#[UniqueEntity(fields: ['name', 'group', 'element'])] -#[ORM\Entity(repositoryClass: ParameterRepository::class)] -class PartCustomStateParameter extends AbstractParameter -{ - final public const ALLOWED_ELEMENT_CLASS = PartCustomState::class; - - /** - * @var PartCustomState the element this para is associated with - */ - #[ORM\ManyToOne(targetEntity: PartCustomState::class, inversedBy: 'parameters')] - #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] - #[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])] - protected ?AbstractDBElement $element = null; -} diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index 7fca81bc..99ed3c6d 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -118,13 +118,6 @@ class Category extends AbstractPartsContainingDBElement #[ORM\Column(type: Types::TEXT)] protected string $partname_regex = ''; - /** - * @var string The prefix for ipn generation for created parts in this category. - */ - #[Groups(['full', 'import', 'category:read', 'category:write'])] - #[ORM\Column(type: Types::STRING, length: 255, nullable: false, options: ['default' => ''])] - protected string $part_ipn_prefix = ''; - /** * @var bool Set to true, if the footprints should be disabled for parts this category (not implemented yet). */ @@ -232,16 +225,6 @@ class Category extends AbstractPartsContainingDBElement return $this; } - public function getPartIpnPrefix(): string - { - return $this->part_ipn_prefix; - } - - public function setPartIpnPrefix(string $part_ipn_prefix): void - { - $this->part_ipn_prefix = $part_ipn_prefix; - } - public function isDisableFootprints(): bool { return $this->disable_footprints; diff --git a/src/Entity/Parts/InfoProviderReference.php b/src/Entity/Parts/InfoProviderReference.php index 810aef0c..bfa62f32 100644 --- a/src/Entity/Parts/InfoProviderReference.php +++ b/src/Entity/Parts/InfoProviderReference.php @@ -50,7 +50,7 @@ class InfoProviderReference /** * @var string|null The url of this part inside the provider system or null if this info is not existing */ - #[Column(type: Types::STRING, length: 2048, nullable: true)] + #[Column(type: Types::STRING, nullable: true)] #[Groups(['provider_reference:read', 'full'])] private ?string $provider_url = null; @@ -157,4 +157,4 @@ class InfoProviderReference $ref->last_updated = new \DateTimeImmutable(); return $ref; } -} +} \ No newline at end of file diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index d0a279e3..14a7903f 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Parts; +use App\ApiPlatform\Filter\TagFilter; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; @@ -38,12 +40,10 @@ use ApiPlatform\Serializer\Filter\PropertyFilter; use App\ApiPlatform\Filter\EntityFilter; use App\ApiPlatform\Filter\LikeFilter; use App\ApiPlatform\Filter\PartStoragelocationFilter; -use App\ApiPlatform\Filter\TagFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\PartAttachment; use App\Entity\EDA\EDAPartInfo; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; use App\Entity\Parameters\ParametersTrait; use App\Entity\Parameters\PartParameter; use App\Entity\Parts\PartTraits\AdvancedPropertyTrait; @@ -59,8 +59,8 @@ use App\Repository\PartRepository; use App\Validator\Constraints\UniqueObjectCollection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; -use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -74,6 +74,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; * @extends AttachmentContainingDBElement * @template-use ParametersTrait */ +#[UniqueEntity(fields: ['ipn'], message: 'part.ipn.must_be_unique')] #[ORM\Entity(repositoryClass: PartRepository::class)] #[ORM\EntityListeners([TreeCacheInvalidationListener::class])] #[ORM\Table('`parts`')] @@ -82,18 +83,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Index(columns: ['ipn'], name: 'parts_idx_ipn')] #[ApiResource( operations: [ - new Get(normalizationContext: [ - 'groups' => [ - 'part:read', - 'provider_reference:read', - 'api:basic:read', - 'part_lot:read', - 'orderdetail:read', - 'pricedetail:read', - 'parameter:read', - 'attachment:read', - 'eda_info:read' - ], + new Get(normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read', + 'orderdetail:read', 'pricedetail:read', 'parameter:read', 'attachment:read', 'eda_info:read'], 'openapi_definition_name' => 'Read', ], security: 'is_granted("read", object)'), new GetCollection(security: 'is_granted("@parts.read")'), @@ -101,15 +92,15 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; new Patch(security: 'is_granted("edit", object)'), new Delete(security: 'is_granted("delete", object)'), ], - normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read'], 'openapi_definition_name' => 'Read'], + normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read'], 'openapi_definition_name' => 'Read'], denormalizationContext: ['groups' => ['part:write', 'api:basic:write', 'eda_info:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], )] #[ApiFilter(PropertyFilter::class)] -#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit", "partCustomState"])] +#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit"])] #[ApiFilter(PartStoragelocationFilter::class, properties: ["storage_location"])] #[ApiFilter(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "manufacturer_product_number"])] #[ApiFilter(TagFilter::class, properties: ["tags"])] -#[ApiFilter(BooleanFilter::class, properties: ["favorite", "needs_review"])] +#[ApiFilter(BooleanFilter::class, properties: ["favorite" , "needs_review"])] #[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])] #[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] #[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] @@ -169,12 +160,6 @@ class Part extends AttachmentContainingDBElement #[Groups(['part:read'])] protected ?\DateTimeImmutable $lastModified = null; - /** - * @var Collection - */ - #[ORM\OneToMany(mappedBy: 'part', targetEntity: BulkInfoProviderImportJobPart::class, cascade: ['remove'], orphanRemoval: true)] - protected Collection $bulkImportJobParts; - public function __construct() { @@ -187,7 +172,6 @@ class Part extends AttachmentContainingDBElement $this->associated_parts_as_owner = new ArrayCollection(); $this->associated_parts_as_other = new ArrayCollection(); - $this->bulkImportJobParts = new ArrayCollection(); //By default, the part has no provider $this->providerReference = InfoProviderReference::noProvider(); @@ -246,38 +230,4 @@ class Part extends AttachmentContainingDBElement } } } - - /** - * Get all bulk import job parts for this part - * @return Collection - */ - public function getBulkImportJobParts(): Collection - { - return $this->bulkImportJobParts; - } - - /** - * Add a bulk import job part to this part - */ - public function addBulkImportJobPart(BulkInfoProviderImportJobPart $jobPart): self - { - if (!$this->bulkImportJobParts->contains($jobPart)) { - $this->bulkImportJobParts->add($jobPart); - $jobPart->setPart($this); - } - return $this; - } - - /** - * Remove a bulk import job part from this part - */ - public function removeBulkImportJobPart(BulkInfoProviderImportJobPart $jobPart): self - { - if ($this->bulkImportJobParts->removeElement($jobPart)) { - if ($jobPart->getPart() === $this) { - $jobPart->setPart(null); - } - } - return $this; - } } diff --git a/src/Entity/Parts/PartCustomState.php b/src/Entity/Parts/PartCustomState.php deleted file mode 100644 index 136ff984..00000000 --- a/src/Entity/Parts/PartCustomState.php +++ /dev/null @@ -1,127 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Entity\Parts; - -use ApiPlatform\Metadata\ApiProperty; -use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\PartCustomStateAttachment; -use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; -use ApiPlatform\Doctrine\Orm\Filter\DateFilter; -use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; -use ApiPlatform\Metadata\ApiFilter; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Delete; -use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Metadata\Patch; -use ApiPlatform\Metadata\Post; -use ApiPlatform\Serializer\Filter\PropertyFilter; -use App\ApiPlatform\Filter\LikeFilter; -use App\Entity\Base\AbstractPartsContainingDBElement; -use App\Entity\Base\AbstractStructuralDBElement; -use App\Entity\Parameters\PartCustomStateParameter; -use App\Repository\Parts\PartCustomStateRepository; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; -use Doctrine\Common\Collections\Criteria; -use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Validator\Constraints as Assert; - -/** - * This entity represents a custom part state. - * If an organisation uses Part-DB and has its custom part states, this is useful. - * - * @extends AbstractPartsContainingDBElement - */ -#[ORM\Entity(repositoryClass: PartCustomStateRepository::class)] -#[ORM\Table('`part_custom_states`')] -#[ORM\Index(columns: ['name'], name: 'part_custom_state_name')] -#[ApiResource( - operations: [ - new Get(security: 'is_granted("read", object)'), - new GetCollection(security: 'is_granted("@part_custom_states.read")'), - new Post(securityPostDenormalize: 'is_granted("create", object)'), - new Patch(security: 'is_granted("edit", object)'), - new Delete(security: 'is_granted("delete", object)'), - ], - normalizationContext: ['groups' => ['part_custom_state:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], - denormalizationContext: ['groups' => ['part_custom_state:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], -)] -#[ApiFilter(PropertyFilter::class)] -#[ApiFilter(LikeFilter::class, properties: ["name"])] -#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] -#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] -class PartCustomState extends AbstractPartsContainingDBElement -{ - /** - * @var string The comment info for this element as markdown - */ - #[Groups(['part_custom_state:read', 'part_custom_state:write', 'full', 'import'])] - protected string $comment = ''; - - #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class, cascade: ['persist'])] - #[ORM\OrderBy(['name' => Criteria::ASC])] - protected Collection $children; - - #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] - #[ORM\JoinColumn(name: 'parent_id')] - #[Groups(['part_custom_state:read', 'part_custom_state:write'])] - #[ApiProperty(readableLink: false, writableLink: false)] - protected ?AbstractStructuralDBElement $parent = null; - - /** - * @var Collection - */ - #[Assert\Valid] - #[ORM\OneToMany(targetEntity: PartCustomStateAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => Criteria::ASC])] - #[Groups(['part_custom_state:read', 'part_custom_state:write'])] - protected Collection $attachments; - - #[ORM\ManyToOne(targetEntity: PartCustomStateAttachment::class)] - #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] - #[Groups(['part_custom_state:read', 'part_custom_state:write'])] - protected ?Attachment $master_picture_attachment = null; - - /** @var Collection - */ - #[Assert\Valid] - #[ORM\OneToMany(mappedBy: 'element', targetEntity: PartCustomStateParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] - #[Groups(['part_custom_state:read', 'part_custom_state:write'])] - protected Collection $parameters; - - #[Groups(['part_custom_state:read'])] - protected ?\DateTimeImmutable $addedDate = null; - #[Groups(['part_custom_state:read'])] - protected ?\DateTimeImmutable $lastModified = null; - - public function __construct() - { - parent::__construct(); - $this->children = new ArrayCollection(); - $this->attachments = new ArrayCollection(); - $this->parameters = new ArrayCollection(); - } -} diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php index 2cee7f1a..230ba7b7 100644 --- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php @@ -23,14 +23,12 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; use App\Entity\Parts\InfoProviderReference; -use App\Entity\Parts\PartCustomState; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Part; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints\Length; -use App\Validator\Constraints\UniquePartIpnConstraint; /** * Advanced properties of a part, not related to a more specific group. @@ -66,7 +64,6 @@ trait AdvancedPropertyTrait #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::STRING, length: 100, unique: true, nullable: true)] #[Length(max: 100)] - #[UniquePartIpnConstraint] protected ?string $ipn = null; /** @@ -76,14 +73,6 @@ trait AdvancedPropertyTrait #[Groups(['full', 'part:read'])] protected InfoProviderReference $providerReference; - /** - * @var ?PartCustomState the custom state for the part - */ - #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] - #[ORM\ManyToOne(targetEntity: PartCustomState::class)] - #[ORM\JoinColumn(name: 'id_part_custom_state')] - protected ?PartCustomState $partCustomState = null; - /** * Checks if this part is marked, for that it needs further review. */ @@ -191,24 +180,7 @@ trait AdvancedPropertyTrait return $this; } - /** - * Gets the custom part state for the part - * Returns null if no specific part state is set. - */ - public function getPartCustomState(): ?PartCustomState - { - return $this->partCustomState; - } - /** - * Sets the custom part state. - * - * @return $this - */ - public function setPartCustomState(?PartCustomState $partCustomState): self - { - $this->partCustomState = $partCustomState; - return $this; - } + } diff --git a/src/Entity/Parts/PartTraits/ManufacturerTrait.php b/src/Entity/Parts/PartTraits/ManufacturerTrait.php index 911a0806..5d7f8749 100644 --- a/src/Entity/Parts/PartTraits/ManufacturerTrait.php +++ b/src/Entity/Parts/PartTraits/ManufacturerTrait.php @@ -49,7 +49,7 @@ trait ManufacturerTrait /** * @var string The url to the part on the manufacturer's homepage */ - #[Assert\Url(requireTld: false)] + #[Assert\Url] #[Groups(['full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $manufacturer_product_url = ''; diff --git a/src/Entity/Parts/PartTraits/ProjectTrait.php b/src/Entity/Parts/PartTraits/ProjectTrait.php index 7e1962d3..45719377 100644 --- a/src/Entity/Parts/PartTraits/ProjectTrait.php +++ b/src/Entity/Parts/PartTraits/ProjectTrait.php @@ -15,7 +15,7 @@ trait ProjectTrait /** * @var Collection $project_bom_entries */ - #[ORM\OneToMany(targetEntity: ProjectBOMEntry::class, mappedBy: 'part')] + #[ORM\OneToMany(mappedBy: 'part', targetEntity: ProjectBOMEntry::class, cascade: ['remove'], orphanRemoval: true)] protected Collection $project_bom_entries; /** diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index 8ed76a46..3709b37d 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -124,7 +124,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N /** * @var string The URL to the product on the supplier's website */ - #[Assert\Url(requireTld: false)] + #[Assert\Url] #[Groups(['full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::TEXT)] protected string $supplier_product_url = ''; diff --git a/src/EntityListeners/PartProjectBOMEntryUnlinkListener.php b/src/EntityListeners/PartProjectBOMEntryUnlinkListener.php deleted file mode 100644 index 08a93f76..00000000 --- a/src/EntityListeners/PartProjectBOMEntryUnlinkListener.php +++ /dev/null @@ -1,59 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\EntityListeners; - -use App\Entity\Parts\Part; -use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener; -use Doctrine\ORM\Event\PreRemoveEventArgs; - -/** - * If an part is deleted, this listener makes sure that all ProjectBOMEntries that reference this part, are updated - * to not reference the part anymore, but instead store the part name in the name field. - */ -#[AsEntityListener(event: "preRemove", entity: Part::class)] -class PartProjectBOMEntryUnlinkListener -{ - public function preRemove(Part $part, PreRemoveEventArgs $event): void - { - // Iterate over all ProjectBOMEntries that use this part and put the part name into the name field - foreach ($part->getProjectBomEntries() as $bom_entry) { - $old_name = $bom_entry->getName(); - if ($old_name === null || trim($old_name) === '') { - $bom_entry->setName($part->getName()); - } else { - $bom_entry->setName($old_name . ' (' . $part->getName() . ')'); - } - - $old_comment = $bom_entry->getComment(); - if ($old_comment === null || trim($old_comment) === '') { - $bom_entry->setComment('Part was deleted: ' . $part->getName()); - } else { - $bom_entry->setComment($old_comment . "\n\n Part was deleted: " . $part->getName()); - } - - //Remove the part reference - $bom_entry->setPart(null); - } - } -} diff --git a/src/EnvVarProcessors/AddSlashEnvVarProcessor.php b/src/EnvVarProcessors/AddSlashEnvVarProcessor.php deleted file mode 100644 index aaf0abc9..00000000 --- a/src/EnvVarProcessors/AddSlashEnvVarProcessor.php +++ /dev/null @@ -1,49 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\EnvVarProcessors; - -use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; - -/** - * Env var processor that adds a trailing slash to a string if not already present. - */ -final class AddSlashEnvVarProcessor implements EnvVarProcessorInterface -{ - - public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed - { - $env = $getEnv($name); - if (!is_string($env)) { - throw new \InvalidArgumentException(sprintf('The "addSlash" env var processor only works with strings, got %s.', gettype($env))); - } - return rtrim($env, '/') . '/'; - } - - public static function getProvidedTypes(): array - { - return [ - 'addSlash' => 'string', - ]; - } -} diff --git a/src/EventListener/LogSystem/EventLoggerListener.php b/src/EventListener/LogSystem/EventLoggerListener.php index f5029c28..96c6ef51 100644 --- a/src/EventListener/LogSystem/EventLoggerListener.php +++ b/src/EventListener/LogSystem/EventLoggerListener.php @@ -170,7 +170,6 @@ class EventLoggerListener public function hasFieldRestrictions(AbstractDBElement $element): bool { foreach (array_keys(static::FIELD_BLACKLIST) as $class) { - /** @var string $class */ if ($element instanceof $class) { return true; } @@ -185,7 +184,6 @@ class EventLoggerListener public function shouldFieldBeSaved(AbstractDBElement $element, string $field_name): bool { foreach (static::FIELD_BLACKLIST as $class => $blacklist) { - /** @var string $class */ if ($element instanceof $class && in_array($field_name, $blacklist, true)) { return false; } @@ -217,7 +215,6 @@ class EventLoggerListener $mappings = $metadata->getAssociationMappings(); //Check if class is whitelisted for CollectionElementDeleted entry foreach (static::TRIGGER_ASSOCIATION_LOG_WHITELIST as $class => $whitelist) { - /** @var string $class */ if ($entity instanceof $class) { //Check names foreach ($mappings as $field => $mapping) { diff --git a/src/EventListener/RegisterSynonymsAsTranslationParametersListener.php b/src/EventListener/RegisterSynonymsAsTranslationParametersListener.php deleted file mode 100644 index e7ac7300..00000000 --- a/src/EventListener/RegisterSynonymsAsTranslationParametersListener.php +++ /dev/null @@ -1,93 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\EventListener; - -use App\Services\ElementTypeNameGenerator; -use App\Services\ElementTypes; -use Symfony\Component\DependencyInjection\Attribute\Autowire; -use Symfony\Component\EventDispatcher\Attribute\AsEventListener; -use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\Translation\Translator; -use Symfony\Contracts\Cache\CacheInterface; -use Symfony\Contracts\Cache\ItemInterface; -use Symfony\Contracts\Cache\TagAwareCacheInterface; -use Symfony\Contracts\Translation\TranslatorInterface; - -#[AsEventListener] -readonly class RegisterSynonymsAsTranslationParametersListener -{ - private Translator $translator; - - public function __construct( - #[Autowire(service: 'translator.default')] TranslatorInterface $translator, - private TagAwareCacheInterface $cache, - private ElementTypeNameGenerator $typeNameGenerator) - { - if (!$translator instanceof Translator) { - throw new \RuntimeException('Translator must be an instance of Symfony\Component\Translation\Translator or this listener cannot be used.'); - } - $this->translator = $translator; - } - - public function getSynonymPlaceholders(): array - { - return $this->cache->get('partdb_synonym_placeholders', function (ItemInterface $item) { - $item->tag('synonyms'); - - - $placeholders = []; - - //Generate a placeholder for each element type - foreach (ElementTypes::cases() as $elementType) { - //Versions with capitalized first letter - $capitalized = ucfirst($elementType->value); //We have only ASCII element type values, so this is sufficient - $placeholders['[' . $capitalized . ']'] = $this->typeNameGenerator->typeLabel($elementType); - $placeholders['[[' . $capitalized . ']]'] = $this->typeNameGenerator->typeLabelPlural($elementType); - - //And we have lowercase versions for both - $placeholders['[' . $elementType->value . ']'] = mb_strtolower($this->typeNameGenerator->typeLabel($elementType)); - $placeholders['[[' . $elementType->value . ']]'] = mb_strtolower($this->typeNameGenerator->typeLabelPlural($elementType)); - } - - return $placeholders; - }); - } - - public function __invoke(RequestEvent $event): void - { - //If we already added the parameters, skip adding them again - if (isset($this->translator->getGlobalParameters()['@@partdb_synonyms_registered@@'])) { - return; - } - - //Register all placeholders for synonyms - $placeholders = $this->getSynonymPlaceholders(); - foreach ($placeholders as $key => $value) { - $this->translator->addGlobalParameter($key, $value); - } - - //Register the marker parameter to avoid double registration - $this->translator->addGlobalParameter('@@partdb_synonyms_registered@@', 'registered'); - } -} diff --git a/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php b/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php deleted file mode 100644 index ecc25b4f..00000000 --- a/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php +++ /dev/null @@ -1,97 +0,0 @@ -ipnSuggestSettings->autoAppendSuffix) { - return; - } - - $em = $args->getObjectManager(); - $uow = $em->getUnitOfWork(); - $meta = $em->getClassMetadata(Part::class); - - // Collect all IPNs already reserved in the current flush (so new entities do not collide with each other) - $reservedIpns = []; - - // Helper to assign a collision-free IPN for a Part entity - $ensureUnique = function (Part $part) use ($em, $uow, $meta, &$reservedIpns) { - $ipn = $part->getIpn(); - if ($ipn === null || $ipn === '') { - return; - } - - // Check against IPNs already reserved in the current flush (except itself) - $originalIpn = $ipn; - $candidate = $originalIpn; - $increment = 1; - - $conflicts = function (string $candidate) use ($em, $part, $reservedIpns) { - // Collision within the current flush session? - if (isset($reservedIpns[$candidate]) && $reservedIpns[$candidate] !== $part) { - return true; - } - // Collision with an existing DB row? - $existing = $em->getRepository(Part::class)->findOneBy(['ipn' => $candidate]); - return $existing !== null && $existing->getId() !== $part->getId(); - }; - - while ($conflicts($candidate)) { - $candidate = $originalIpn . '_' . $increment; - $increment++; - } - - if ($candidate !== $ipn) { - $before = $part->getIpn(); - $part->setIpn($candidate); - - // Recompute the change set so Doctrine writes the change - $uow->recomputeSingleEntityChangeSet($meta, $part); - $reservedIpns[$candidate] = $part; - - // If the old IPN was reserved already, clean it up - if ($before !== null && isset($reservedIpns[$before]) && $reservedIpns[$before] === $part) { - unset($reservedIpns[$before]); - } - } else { - // Candidate unchanged, but reserve it so subsequent entities see it - $reservedIpns[$candidate] = $part; - } - }; - - // 1) Iterate over new entities - foreach ($uow->getScheduledEntityInsertions() as $entity) { - if ($entity instanceof Part) { - $ensureUnique($entity); - } - } - - // 2) Iterate over updates (if IPN changed, ensure uniqueness again) - foreach ($uow->getScheduledEntityUpdates() as $entity) { - if ($entity instanceof Part) { - $ensureUnique($entity); - } - } - } -} diff --git a/src/Exceptions/OAuthReconnectRequiredException.php b/src/Exceptions/OAuthReconnectRequiredException.php deleted file mode 100644 index 97abb19f..00000000 --- a/src/Exceptions/OAuthReconnectRequiredException.php +++ /dev/null @@ -1,48 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\Exceptions; - -use Throwable; - -class OAuthReconnectRequiredException extends \RuntimeException -{ - private string $providerName = "unknown"; - - public function __construct(string $message = "You need to reconnect the OAuth connection for this provider!", int $code = 0, ?Throwable $previous = null) - { - parent::__construct($message, $code, $previous); - } - - public static function forProvider(string $providerName): self - { - $exception = new self("You need to reconnect the OAuth connection for the provider '$providerName'!"); - $exception->providerName = $providerName; - return $exception; - } - - public function getProviderName(): string - { - return $this->providerName; - } -} diff --git a/src/Form/AdminPages/CategoryAdminForm.php b/src/Form/AdminPages/CategoryAdminForm.php index 489649ed..44c1dede 100644 --- a/src/Form/AdminPages/CategoryAdminForm.php +++ b/src/Form/AdminPages/CategoryAdminForm.php @@ -84,17 +84,6 @@ class CategoryAdminForm extends BaseEntityAdminForm 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), ]); - $builder->add('part_ipn_prefix', TextType::class, [ - 'required' => false, - 'empty_data' => '', - 'label' => 'category.edit.part_ipn_prefix', - 'help' => 'category.edit.part_ipn_prefix.help', - 'attr' => [ - 'placeholder' => 'category.edit.part_ipn_prefix.placeholder', - ], - 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), - ]); - $builder->add('default_description', RichTextEditorType::class, [ 'required' => false, 'empty_data' => '', diff --git a/src/Form/AdminPages/ImportType.php b/src/Form/AdminPages/ImportType.php index 0bd3cea1..3e87812c 100644 --- a/src/Form/AdminPages/ImportType.php +++ b/src/Form/AdminPages/ImportType.php @@ -59,8 +59,6 @@ class ImportType extends AbstractType 'XML' => 'xml', 'CSV' => 'csv', 'YAML' => 'yaml', - 'XLSX' => 'xlsx', - 'XLS' => 'xls', ], 'label' => 'export.format', 'disabled' => $disabled, diff --git a/src/Form/AdminPages/PartCustomStateAdminForm.php b/src/Form/AdminPages/PartCustomStateAdminForm.php deleted file mode 100644 index b8bb2815..00000000 --- a/src/Form/AdminPages/PartCustomStateAdminForm.php +++ /dev/null @@ -1,27 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Form\AdminPages; - -class PartCustomStateAdminForm extends BaseEntityAdminForm -{ -} diff --git a/src/Form/Extension/TogglePasswordTypeExtension.php b/src/Form/Extension/TogglePasswordTypeExtension.php index fec4c0b3..8f7320df 100644 --- a/src/Form/Extension/TogglePasswordTypeExtension.php +++ b/src/Form/Extension/TogglePasswordTypeExtension.php @@ -46,8 +46,8 @@ final class TogglePasswordTypeExtension extends AbstractTypeExtension { $resolver->setDefaults([ 'toggle' => false, - 'hidden_label' => new TranslatableMessage('password_toggle.hide'), - 'visible_label' => new TranslatableMessage('password_toggle.show'), + 'hidden_label' => 'Hide', + 'visible_label' => 'Show', 'hidden_icon' => 'Default', 'visible_icon' => 'Default', 'button_classes' => ['toggle-password-button'], diff --git a/src/Form/Filters/LogFilterType.php b/src/Form/Filters/LogFilterType.php index 30abf723..42b367b7 100644 --- a/src/Form/Filters/LogFilterType.php +++ b/src/Form/Filters/LogFilterType.php @@ -100,7 +100,7 @@ class LogFilterType extends AbstractType ]); $builder->add('user', UserEntityConstraintType::class, [ - 'label' => 'log.user', + 'label' => 'log.user', ]); $builder->add('targetType', EnumConstraintType::class, [ @@ -128,14 +128,11 @@ class LogFilterType extends AbstractType LogTargetType::PARAMETER => 'parameter.label', LogTargetType::LABEL_PROFILE => 'label_profile.label', LogTargetType::PART_ASSOCIATION => 'part_association.label', - LogTargetType::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.label', - LogTargetType::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.label', - LogTargetType::PART_CUSTOM_STATE => 'part_custom_state.label', }, ]); $builder->add('targetId', NumberConstraintType::class, [ - 'label' => 'log.target_id', + 'label' => 'log.target_id', 'min' => 1, 'step' => 1, ]); diff --git a/src/Form/Filters/PartFilterType.php b/src/Form/Filters/PartFilterType.php index e101c635..dfe449d1 100644 --- a/src/Form/Filters/PartFilterType.php +++ b/src/Form/Filters/PartFilterType.php @@ -22,27 +22,19 @@ declare(strict_types=1); */ namespace App\Form\Filters; -use App\DataTables\Filters\Constraints\Part\BulkImportPartStatusConstraint; use App\DataTables\Filters\Constraints\Part\ParameterConstraint; use App\DataTables\Filters\PartFilter; use App\Entity\Attachments\AttachmentType; -use App\Entity\InfoProviderSystem\BulkImportJobStatus; -use App\Entity\InfoProviderSystem\BulkImportPartStatus; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\PartCustomState; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\ProjectSystem\Project; use App\Form\Filters\Constraints\BooleanConstraintType; -use App\Form\Filters\Constraints\BulkImportJobExistsConstraintType; -use App\Form\Filters\Constraints\BulkImportJobStatusConstraintType; -use App\Form\Filters\Constraints\BulkImportPartStatusConstraintType; use App\Form\Filters\Constraints\ChoiceConstraintType; use App\Form\Filters\Constraints\DateTimeConstraintType; -use App\Form\Filters\Constraints\EnumConstraintType; use App\Form\Filters\Constraints\NumberConstraintType; use App\Form\Filters\Constraints\ParameterConstraintType; use App\Form\Filters\Constraints\StructuralEntityConstraintType; @@ -58,8 +50,6 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use function Symfony\Component\Translation\t; - class PartFilterType extends AbstractType { public function __construct(private readonly Security $security) @@ -140,11 +130,6 @@ class PartFilterType extends AbstractType 'entity_class' => MeasurementUnit::class ]); - $builder->add('partCustomState', StructuralEntityConstraintType::class, [ - 'label' => 'part.edit.partCustomState', - 'entity_class' => PartCustomState::class - ]); - $builder->add('lastModified', DateTimeConstraintType::class, [ 'label' => 'lastModified' ]); @@ -313,31 +298,6 @@ class PartFilterType extends AbstractType } - /************************************************************************** - * Bulk Import Job tab - **************************************************************************/ - if ($this->security->isGranted('@info_providers.create_parts')) { - $builder - ->add('inBulkImportJob', BooleanConstraintType::class, [ - 'label' => 'part.filter.in_bulk_import_job', - ]) - ->add('bulkImportJobStatus', EnumConstraintType::class, [ - 'enum_class' => BulkImportJobStatus::class, - 'label' => 'part.filter.bulk_import_job_status', - 'choice_label' => function (BulkImportJobStatus $value) { - return t('bulk_import.status.' . $value->value); - }, - ]) - ->add('bulkImportPartStatus', EnumConstraintType::class, [ - 'enum_class' => BulkImportPartStatus::class, - 'label' => 'part.filter.bulk_import_part_status', - 'choice_label' => function (BulkImportPartStatus $value) { - return t('bulk_import.part_status.' . $value->value); - }, - ]) - ; - } - $builder->add('submit', SubmitType::class, [ 'label' => 'filter.submit', diff --git a/src/Form/History/EnforceEventCommentTypesType.php b/src/Form/History/EnforceEventCommentTypesType.php index 85e43e6e..8bb095b9 100644 --- a/src/Form/History/EnforceEventCommentTypesType.php +++ b/src/Form/History/EnforceEventCommentTypesType.php @@ -38,7 +38,7 @@ class EnforceEventCommentTypesType extends AbstractType return EnumType::class; } - public function configureOptions(OptionsResolver $resolver): void + public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'multiple' => true, @@ -46,4 +46,4 @@ class EnforceEventCommentTypesType extends AbstractType 'empty_data' => [], ]); } -} +} \ No newline at end of file diff --git a/src/Form/InfoProviderSystem/BulkProviderSearchType.php b/src/Form/InfoProviderSystem/BulkProviderSearchType.php deleted file mode 100644 index 24a3cfb4..00000000 --- a/src/Form/InfoProviderSystem/BulkProviderSearchType.php +++ /dev/null @@ -1,62 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Form\InfoProviderSystem; - -use App\Entity\Parts\Part; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\CollectionType; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; - -class BulkProviderSearchType extends AbstractType -{ - public function buildForm(FormBuilderInterface $builder, array $options): void - { - $parts = $options['parts']; - - $builder->add('part_configurations', CollectionType::class, [ - 'entry_type' => PartProviderConfigurationType::class, - 'entry_options' => [ - 'label' => false, - ], - 'allow_add' => false, - 'allow_delete' => false, - 'label' => false, - ]); - - $builder->add('submit', SubmitType::class, [ - 'label' => 'info_providers.bulk_search.submit' - ]); - } - - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'parts' => [], - ]); - $resolver->setRequired('parts'); - } -} \ No newline at end of file diff --git a/src/Form/InfoProviderSystem/FieldToProviderMappingType.php b/src/Form/InfoProviderSystem/FieldToProviderMappingType.php deleted file mode 100644 index 13e9581e..00000000 --- a/src/Form/InfoProviderSystem/FieldToProviderMappingType.php +++ /dev/null @@ -1,75 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Form\InfoProviderSystem; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\IntegerType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; - -class FieldToProviderMappingType extends AbstractType -{ - public function buildForm(FormBuilderInterface $builder, array $options): void - { - $fieldChoices = $options['field_choices'] ?? []; - - $builder->add('field', ChoiceType::class, [ - 'label' => 'info_providers.bulk_search.search_field', - 'choices' => $fieldChoices, - 'expanded' => false, - 'multiple' => false, - 'required' => false, - 'placeholder' => 'info_providers.bulk_search.field.select', - ]); - - $builder->add('providers', ProviderSelectType::class, [ - 'label' => 'info_providers.bulk_search.providers', - 'help' => 'info_providers.bulk_search.providers.help', - 'required' => false, - ]); - - $builder->add('priority', IntegerType::class, [ - 'label' => 'info_providers.bulk_search.priority', - 'help' => 'info_providers.bulk_search.priority.help', - 'required' => false, - 'data' => 1, // Default priority - 'attr' => [ - 'min' => 1, - 'max' => 10, - 'class' => 'form-control-sm', - 'style' => 'width: 80px;' - ], - 'constraints' => [ - new \Symfony\Component\Validator\Constraints\Range(['min' => 1, 'max' => 10]), - ], - ]); - } - - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'field_choices' => [], - ]); - } -} diff --git a/src/Form/InfoProviderSystem/GlobalFieldMappingType.php b/src/Form/InfoProviderSystem/GlobalFieldMappingType.php deleted file mode 100644 index ea70284f..00000000 --- a/src/Form/InfoProviderSystem/GlobalFieldMappingType.php +++ /dev/null @@ -1,67 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Form\InfoProviderSystem; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\CheckboxType; -use Symfony\Component\Form\Extension\Core\Type\CollectionType; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; - -class GlobalFieldMappingType extends AbstractType -{ - public function buildForm(FormBuilderInterface $builder, array $options): void - { - $fieldChoices = $options['field_choices'] ?? []; - - $builder->add('field_mappings', CollectionType::class, [ - 'entry_type' => FieldToProviderMappingType::class, - 'entry_options' => [ - 'label' => false, - 'field_choices' => $fieldChoices, - ], - 'allow_add' => true, - 'allow_delete' => true, - 'prototype' => true, - 'label' => false, - ]); - - $builder->add('prefetch_details', CheckboxType::class, [ - 'label' => 'info_providers.bulk_import.prefetch_details', - 'required' => false, - 'help' => 'info_providers.bulk_import.prefetch_details_help', - ]); - - $builder->add('submit', SubmitType::class, [ - 'label' => 'info_providers.bulk_import.search.submit' - ]); - } - - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'field_choices' => [], - ]); - } -} \ No newline at end of file diff --git a/src/Form/InfoProviderSystem/PartProviderConfigurationType.php b/src/Form/InfoProviderSystem/PartProviderConfigurationType.php deleted file mode 100644 index cecf62a3..00000000 --- a/src/Form/InfoProviderSystem/PartProviderConfigurationType.php +++ /dev/null @@ -1,55 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Form\InfoProviderSystem; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; -use Symfony\Component\Form\FormBuilderInterface; - -class PartProviderConfigurationType extends AbstractType -{ - public function buildForm(FormBuilderInterface $builder, array $options): void - { - $builder->add('part_id', HiddenType::class); - - $builder->add('search_field', ChoiceType::class, [ - 'label' => 'info_providers.bulk_search.search_field', - 'choices' => [ - 'info_providers.bulk_search.field.mpn' => 'mpn', - 'info_providers.bulk_search.field.name' => 'name', - 'info_providers.bulk_search.field.digikey_spn' => 'digikey_spn', - 'info_providers.bulk_search.field.mouser_spn' => 'mouser_spn', - 'info_providers.bulk_search.field.lcsc_spn' => 'lcsc_spn', - 'info_providers.bulk_search.field.farnell_spn' => 'farnell_spn', - ], - 'expanded' => false, - 'multiple' => false, - ]); - - $builder->add('providers', ProviderSelectType::class, [ - 'label' => 'info_providers.bulk_search.providers', - 'help' => 'info_providers.bulk_search.providers.help', - ]); - } -} \ No newline at end of file diff --git a/src/Form/InfoProviderSystem/ProviderSelectType.php b/src/Form/InfoProviderSystem/ProviderSelectType.php index bad3edaa..95e10791 100644 --- a/src/Form/InfoProviderSystem/ProviderSelectType.php +++ b/src/Form/InfoProviderSystem/ProviderSelectType.php @@ -30,7 +30,6 @@ use Symfony\Component\Form\ChoiceList\ChoiceList; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Translation\StaticMessage; class ProviderSelectType extends AbstractType { @@ -71,10 +70,10 @@ class ProviderSelectType extends AbstractType //The choice_label and choice_value only needs to be set if we want the objects $resolver->setDefault('choice_label', function (Options $options){ if ('object' === $options['input']) { - return ChoiceList::label($this, static fn (?InfoProviderInterface $choice) => new StaticMessage($choice?->getProviderInfo()['name'])); + return ChoiceList::label($this, static fn (?InfoProviderInterface $choice) => $choice?->getProviderInfo()['name']); } - return static fn ($choice, $key, $value) => new StaticMessage($key); + return null; }); $resolver->setDefault('choice_value', function (Options $options) { if ('object' === $options['input']) { diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index b8276589..0bd3d0e3 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -30,7 +30,6 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\ManufacturingStatus; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\PartCustomState; use App\Entity\PriceInformations\Orderdetail; use App\Form\AttachmentFormType; use App\Form\ParameterType; @@ -42,7 +41,6 @@ use App\Form\Type\StructuralEntityType; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Services\LogSystem\EventCommentNeededHelper; use App\Services\LogSystem\EventCommentType; -use App\Settings\MiscSettings\IpnSuggestSettings; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -58,12 +56,8 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class PartBaseType extends AbstractType { - public function __construct( - protected Security $security, - protected UrlGeneratorInterface $urlGenerator, - protected EventCommentNeededHelper $event_comment_needed_helper, - protected IpnSuggestSettings $ipnSuggestSettings, - ) { + public function __construct(protected Security $security, protected UrlGeneratorInterface $urlGenerator, protected EventCommentNeededHelper $event_comment_needed_helper) + { } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -75,39 +69,6 @@ class PartBaseType extends AbstractType /** @var PartDetailDTO|null $dto */ $dto = $options['info_provider_dto']; - $descriptionAttr = [ - 'placeholder' => 'part.edit.description.placeholder', - 'rows' => 2, - ]; - - if ($this->ipnSuggestSettings->useDuplicateDescription) { - // Only add attribute when duplicate description feature is enabled - $descriptionAttr['data-ipn-suggestion'] = 'descriptionField'; - } - - $ipnAttr = [ - 'class' => 'ipn-suggestion-field', - 'data-elements--ipn-suggestion-target' => 'input', - 'autocomplete' => 'off', - ]; - - if ($this->ipnSuggestSettings->regex !== null && $this->ipnSuggestSettings->regex !== '') { - $ipnAttr['pattern'] = $this->ipnSuggestSettings->regex; - $ipnAttr['placeholder'] = $this->ipnSuggestSettings->regex; - $ipnAttr['title'] = $this->ipnSuggestSettings->regexHelp; - } - - $ipnOptions = [ - 'required' => false, - 'empty_data' => null, - 'label' => 'part.edit.ipn', - 'attr' => $ipnAttr, - ]; - - if (isset($ipnAttr['pattern']) && $this->ipnSuggestSettings->regexHelp !== null && $this->ipnSuggestSettings->regexHelp !== '') { - $ipnOptions['help'] = $this->ipnSuggestSettings->regexHelp; - } - //Common section $builder ->add('name', TextType::class, [ @@ -122,7 +83,10 @@ class PartBaseType extends AbstractType 'empty_data' => '', 'label' => 'part.edit.description', 'mode' => 'markdown-single_line', - 'attr' => $descriptionAttr, + 'attr' => [ + 'placeholder' => 'part.edit.description.placeholder', + 'rows' => 2, + ], ]) ->add('minAmount', SIUnitType::class, [ 'attr' => [ @@ -140,9 +104,6 @@ class PartBaseType extends AbstractType 'disable_not_selectable' => true, //Do not require category for new parts, so that the user must select the category by hand and cannot forget it (the requirement is handled by the constraint in the entity) 'required' => !$new_part, - 'attr' => [ - 'data-ipn-suggestion' => 'categoryField', - ] ]) ->add('footprint', StructuralEntityType::class, [ 'class' => Footprint::class, @@ -210,13 +171,11 @@ class PartBaseType extends AbstractType 'disable_not_selectable' => true, 'label' => 'part.edit.partUnit', ]) - ->add('partCustomState', StructuralEntityType::class, [ - 'class' => PartCustomState::class, + ->add('ipn', TextType::class, [ 'required' => false, - 'disable_not_selectable' => true, - 'label' => 'part.edit.partCustomState', - ]) - ->add('ipn', TextType::class, $ipnOptions); + 'empty_data' => null, + 'label' => 'part.edit.ipn', + ]); //Comment section $builder->add('comment', RichTextEditorType::class, [ diff --git a/src/Form/Settings/LanguageMenuEntriesType.php b/src/Form/Settings/LanguageMenuEntriesType.php deleted file mode 100644 index 9bc2e850..00000000 --- a/src/Form/Settings/LanguageMenuEntriesType.php +++ /dev/null @@ -1,56 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\Form\Settings; - -use Symfony\Component\DependencyInjection\Attribute\Autowire; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\LanguageType; -use Symfony\Component\Intl\Languages; -use Symfony\Component\OptionsResolver\OptionsResolver; - -class LanguageMenuEntriesType extends AbstractType -{ - public function __construct(#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages) - { - - } - - public function getParent(): string - { - return LanguageType::class; - } - - public function configureOptions(OptionsResolver $resolver): void - { - $choices = []; - foreach ($this->preferred_languages as $lang_code) { - $choices[Languages::getName($lang_code)] = $lang_code; - } - - $resolver->setDefaults([ - 'choice_loader' => null, - 'choices' => $choices, - ]); - } -} diff --git a/src/Form/Settings/TypeSynonymRowType.php b/src/Form/Settings/TypeSynonymRowType.php deleted file mode 100644 index f3b8f0b6..00000000 --- a/src/Form/Settings/TypeSynonymRowType.php +++ /dev/null @@ -1,150 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Form\Settings; - -use App\Services\ElementTypes; -use App\Settings\SystemSettings\LocalizationSettings; -use Symfony\Component\DependencyInjection\Attribute\Autowire; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\EnumType; -use Symfony\Component\Form\Extension\Core\Type\LocaleType; -use Symfony\Component\Form\Extension\Core\Type\TextType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Intl\Locales; -use Symfony\Component\Translation\StaticMessage; -use Symfony\Component\Validator\Constraints as Assert; -use Symfony\Contracts\Translation\TranslatorInterface; - -/** - * A single translation row: data source + language + translations (singular/plural). - */ -class TypeSynonymRowType extends AbstractType -{ - - private const PREFERRED_TYPES = [ - ElementTypes::CATEGORY, - ElementTypes::STORAGE_LOCATION, - ElementTypes::FOOTPRINT, - ElementTypes::MANUFACTURER, - ElementTypes::SUPPLIER, - ElementTypes::PROJECT, - ]; - - public function __construct( - private readonly LocalizationSettings $localizationSettings, - private readonly TranslatorInterface $translator, - #[Autowire(param: 'partdb.locale_menu')] private readonly array $preferredLanguagesParam, - ) { - } - - public function buildForm(FormBuilderInterface $builder, array $options): void - { - $builder - ->add('dataSource', EnumType::class, [ - 'class' => ElementTypes::class, - 'label' => false, - 'required' => true, - 'constraints' => [ - new Assert\NotBlank(), - ], - 'choice_label' => function (ElementTypes $choice) { - return new StaticMessage( - $this->translator->trans($choice->getDefaultLabelKey()) . ' (' . $this->translator->trans($choice->getDefaultPluralLabelKey()) . ')' - ); - }, - 'row_attr' => ['class' => 'mb-0'], - 'attr' => ['class' => 'form-select-sm'], - 'preferred_choices' => self::PREFERRED_TYPES - ]) - ->add('locale', LocaleType::class, [ - 'label' => false, - 'required' => true, - // Restrict to languages configured in the language menu: disable ChoiceLoader and provide explicit choices - 'choice_loader' => null, - 'choices' => $this->buildLocaleChoices(true), - 'preferred_choices' => $this->getPreferredLocales(), - 'constraints' => [ - new Assert\NotBlank(), - ], - 'row_attr' => ['class' => 'mb-0'], - 'attr' => ['class' => 'form-select-sm'] - ]) - ->add('translation_singular', TextType::class, [ - 'label' => false, - 'required' => true, - 'empty_data' => '', - 'constraints' => [ - new Assert\NotBlank(), - ], - 'row_attr' => ['class' => 'mb-0'], - 'attr' => ['class' => 'form-select-sm'] - ]) - ->add('translation_plural', TextType::class, [ - 'label' => false, - 'required' => true, - 'empty_data' => '', - 'constraints' => [ - new Assert\NotBlank(), - ], - 'row_attr' => ['class' => 'mb-0'], - 'attr' => ['class' => 'form-select-sm'] - ]); - } - - - /** - * Returns only locales configured in the language menu (settings) or falls back to the parameter. - * Format: ['German (DE)' => 'de', ...] - */ - private function buildLocaleChoices(bool $returnPossible = false): array - { - $locales = $this->getPreferredLocales(); - - if ($returnPossible) { - $locales = $this->getPossibleLocales(); - } - - $choices = []; - foreach ($locales as $code) { - $label = Locales::getName($code); - $choices[$label . ' (' . strtoupper($code) . ')'] = $code; - } - return $choices; - } - - /** - * Source of allowed locales: - * 1) LocalizationSettings->languageMenuEntries (if set) - * 2) Fallback: parameter partdb.locale_menu - */ - private function getPreferredLocales(): array - { - $fromSettings = $this->localizationSettings->languageMenuEntries ?? []; - return !empty($fromSettings) ? array_values($fromSettings) : array_values($this->preferredLanguagesParam); - } - - private function getPossibleLocales(): array - { - return array_values($this->preferredLanguagesParam); - } -} diff --git a/src/Form/Settings/TypeSynonymsCollectionType.php b/src/Form/Settings/TypeSynonymsCollectionType.php deleted file mode 100644 index 4756930a..00000000 --- a/src/Form/Settings/TypeSynonymsCollectionType.php +++ /dev/null @@ -1,223 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Form\Settings; - -use App\Services\ElementTypes; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\CallbackTransformer; -use Symfony\Component\Form\Extension\Core\Type\CollectionType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormError; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; -use Symfony\Component\Intl\Locales; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Contracts\Translation\TranslatorInterface; - -/** - * Flat collection of translation rows. - * View data: list [{dataSource, locale, translation_singular, translation_plural}, ...] - * Model data: same structure (list). Optionally expands a nested map to a list. - */ -class TypeSynonymsCollectionType extends AbstractType -{ - public function __construct(private readonly TranslatorInterface $translator) - { - } - - private function flattenStructure(array $modelValue): array - { - //If the model is already flattened, return as is - if (array_is_list($modelValue)) { - return $modelValue; - } - - $out = []; - foreach ($modelValue as $dataSource => $locales) { - if (!is_array($locales)) { - continue; - } - foreach ($locales as $locale => $translations) { - if (!is_array($translations)) { - continue; - } - $out[] = [ - //Convert string to enum value - 'dataSource' => ElementTypes::from($dataSource), - 'locale' => $locale, - 'translation_singular' => $translations['singular'] ?? '', - 'translation_plural' => $translations['plural'] ?? '', - ]; - } - } - return $out; - } - - public function buildForm(FormBuilderInterface $builder, array $options): void - { - $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void { - //Flatten the structure - $data = $event->getData(); - $event->setData($this->flattenStructure($data)); - }); - - $builder->addModelTransformer(new CallbackTransformer( - // Model -> View - $this->flattenStructure(...), - // View -> Model (keep list; let existing behavior unchanged) - function (array $viewValue) { - //Turn our flat list back into the structured array - - $out = []; - - foreach ($viewValue as $row) { - if (!is_array($row)) { - continue; - } - $dataSource = $row['dataSource'] ?? null; - $locale = $row['locale'] ?? null; - $translation_singular = $row['translation_singular'] ?? null; - $translation_plural = $row['translation_plural'] ?? null; - - if ($dataSource === null || - !is_string($locale) || $locale === '' - ) { - continue; - } - - $out[$dataSource->value][$locale] = [ - 'singular' => is_string($translation_singular) ? $translation_singular : '', - 'plural' => is_string($translation_plural) ? $translation_plural : '', - ]; - } - - return $out; - } - )); - - // Validation and normalization (duplicates + sorting) during SUBMIT - $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event): void { - $form = $event->getForm(); - $rows = $event->getData(); - - if (!is_array($rows)) { - return; - } - - // Duplicate check: (dataSource, locale) must be unique - $seen = []; - $hasDuplicate = false; - - foreach ($rows as $idx => $row) { - if (!is_array($row)) { - continue; - } - $ds = $row['dataSource'] ?? null; - $loc = $row['locale'] ?? null; - - if ($ds !== null && is_string($loc) && $loc !== '') { - $key = $ds->value . '|' . $loc; - if (isset($seen[$key])) { - $hasDuplicate = true; - - if ($form->has((string)$idx)) { - $child = $form->get((string)$idx); - - if ($child->has('dataSource')) { - $child->get('dataSource')->addError( - new FormError($this->translator->trans( - 'settings.synonyms.type_synonyms.collection_type.duplicate', - [], 'validators' - )) - ); - } - if ($child->has('locale')) { - $child->get('locale')->addError( - new FormError($this->translator->trans( - 'settings.synonyms.type_synonyms.collection_type.duplicate', - [], 'validators' - )) - ); - } - } - } else { - $seen[$key] = true; - } - } - } - - if ($hasDuplicate) { - return; - } - - // Overall sort: first by dataSource key, then by localized language name - $sortable = $rows; - - usort($sortable, static function ($a, $b) { - $aDs = $a['dataSource']->value ?? ''; - $bDs = $b['dataSource']->value ?? ''; - - $cmpDs = strcasecmp($aDs, $bDs); - if ($cmpDs !== 0) { - return $cmpDs; - } - - $aLoc = (string)($a['locale'] ?? ''); - $bLoc = (string)($b['locale'] ?? ''); - - $aName = Locales::getName($aLoc); - $bName = Locales::getName($bLoc); - - return strcasecmp($aName, $bName); - }); - - $event->setData($sortable); - }); - } - - public function configureOptions(OptionsResolver $resolver): void - { - - // Defaults for the collection and entry type - $resolver->setDefaults([ - 'entry_type' => TypeSynonymRowType::class, - 'allow_add' => true, - 'allow_delete' => true, - 'by_reference' => false, - 'required' => false, - 'prototype' => true, - 'empty_data' => [], - 'entry_options' => ['label' => false], - ]); - } - - public function getParent(): ?string - { - return CollectionType::class; - } - - public function getBlockPrefix(): string - { - return 'type_synonyms_collection'; - } -} diff --git a/src/Form/Type/LocaleSelectType.php b/src/Form/Type/LocaleSelectType.php index 6dc6f9fc..aa7af5c2 100644 --- a/src/Form/Type/LocaleSelectType.php +++ b/src/Form/Type/LocaleSelectType.php @@ -30,6 +30,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; /** * A locale select field that uses the preferred languages from the configuration. + */ class LocaleSelectType extends AbstractType { @@ -43,10 +44,10 @@ class LocaleSelectType extends AbstractType return LocaleType::class; } - public function configureOptions(OptionsResolver $resolver): void + public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'preferred_choices' => $this->preferred_languages, ]); } -} +} \ No newline at end of file diff --git a/src/Form/Type/StructuralEntityType.php b/src/Form/Type/StructuralEntityType.php index 51eb21a1..1018eeeb 100644 --- a/src/Form/Type/StructuralEntityType.php +++ b/src/Form/Type/StructuralEntityType.php @@ -110,10 +110,8 @@ class StructuralEntityType extends AbstractType //If no help text is explicitly set, we use the dto value as help text and show it as html $resolver->setDefault('help', fn(Options $options) => $this->dtoText($options['dto_value'])); $resolver->setDefault('help_html', fn(Options $options) => $options['dto_value'] !== null); - - //Normalize the attr to merge custom attributes - $resolver->setNormalizer('attr', function (Options $options, $value) { + $resolver->setDefault('attr', function (Options $options) { $tmp = [ 'data-controller' => $options['controller'], 'data-allow-add' => $options['allow_add'] ? 'true' : 'false', @@ -123,7 +121,7 @@ class StructuralEntityType extends AbstractType $tmp['data-empty-message'] = $options['empty_message']; } - return array_merge($tmp, $value); + return $tmp; }); } diff --git a/src/Repository/PartRepository.php b/src/Repository/PartRepository.php index 9d5fee5e..edccd74b 100644 --- a/src/Repository/PartRepository.php +++ b/src/Repository/PartRepository.php @@ -22,35 +22,17 @@ declare(strict_types=1); namespace App\Repository; -use App\Entity\Parts\Category; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Settings\MiscSettings\IpnSuggestSettings; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; use Doctrine\ORM\QueryBuilder; -use Symfony\Contracts\Translation\TranslatorInterface; -use Doctrine\ORM\EntityManagerInterface; /** * @extends NamedDBElementRepository */ class PartRepository extends NamedDBElementRepository { - private TranslatorInterface $translator; - private IpnSuggestSettings $ipnSuggestSettings; - - public function __construct( - EntityManagerInterface $em, - TranslatorInterface $translator, - IpnSuggestSettings $ipnSuggestSettings, - ) { - parent::__construct($em, $em->getClassMetadata(Part::class)); - - $this->translator = $translator; - $this->ipnSuggestSettings = $ipnSuggestSettings; - } - /** * Gets the summed up instock of all parts (only parts without a measurement unit). * @@ -102,7 +84,8 @@ class PartRepository extends NamedDBElementRepository ->where('ILIKE(part.name, :query) = TRUE') ->orWhere('ILIKE(part.description, :query) = TRUE') ->orWhere('ILIKE(category.name, :query) = TRUE') - ->orWhere('ILIKE(footprint.name, :query) = TRUE'); + ->orWhere('ILIKE(footprint.name, :query) = TRUE') + ; $qb->setParameter('query', '%'.$query.'%'); @@ -111,282 +94,4 @@ class PartRepository extends NamedDBElementRepository return $qb->getQuery()->getResult(); } - - /** - * Provides IPN (Internal Part Number) suggestions for a given part based on its category, description, - * and configured autocomplete digit length. - * - * This function generates suggestions for common prefixes and incremented prefixes based on - * the part's current category and its hierarchy. If the part is unsaved, a default "n.a." prefix is returned. - * - * @param Part $part The part for which autocomplete suggestions are generated. - * @param string $description description to assist in generating suggestions. - * @param int $suggestPartDigits The number of digits used in autocomplete increments. - * - * @return array An associative array containing the following keys: - * - 'commonPrefixes': List of common prefixes found for the part. - * - 'prefixesPartIncrement': Increments for the generated prefixes, including hierarchical prefixes. - */ - public function autoCompleteIpn(Part $part, string $description, int $suggestPartDigits): array - { - $category = $part->getCategory(); - $ipnSuggestions = ['commonPrefixes' => [], 'prefixesPartIncrement' => []]; - - //Show global prefix first if configured - if ($this->ipnSuggestSettings->globalPrefix !== null && $this->ipnSuggestSettings->globalPrefix !== '') { - $ipnSuggestions['commonPrefixes'][] = [ - 'title' => $this->ipnSuggestSettings->globalPrefix, - 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.global_prefix') - ]; - - $increment = $this->generateNextPossibleGlobalIncrement(); - $ipnSuggestions['prefixesPartIncrement'][] = [ - 'title' => $this->ipnSuggestSettings->globalPrefix . $increment, - 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.global_prefix') - ]; - } - - if (strlen($description) > 150) { - $description = substr($description, 0, 150); - } - - if ($description !== '' && $this->ipnSuggestSettings->useDuplicateDescription) { - // Check if the description is already used in another part, - - $suggestionByDescription = $this->getIpnSuggestByDescription($description); - - if ($suggestionByDescription !== null && $suggestionByDescription !== $part->getIpn() && $part->getIpn() !== null && $part->getIpn() !== '') { - $ipnSuggestions['prefixesPartIncrement'][] = [ - 'title' => $part->getIpn(), - 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.description.current-increment') - ]; - } - - if ($suggestionByDescription !== null) { - $ipnSuggestions['prefixesPartIncrement'][] = [ - 'title' => $suggestionByDescription, - 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.description.increment') - ]; - } - } - - // Validate the category and ensure it's an instance of Category - if ($category instanceof Category) { - $currentPath = $category->getPartIpnPrefix(); - $directIpnPrefixEmpty = $category->getPartIpnPrefix() === ''; - $currentPath = $currentPath === '' ? $this->ipnSuggestSettings->fallbackPrefix : $currentPath; - - $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits); - - $ipnSuggestions['commonPrefixes'][] = [ - 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator, - 'description' => $directIpnPrefixEmpty ? $this->translator->trans('part.edit.tab.advanced.ipn.prefix_empty.direct_category', ['%name%' => $category->getName()]) : $this->translator->trans('part.edit.tab.advanced.ipn.prefix.direct_category') - ]; - - $ipnSuggestions['prefixesPartIncrement'][] = [ - 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator . $increment, - 'description' => $directIpnPrefixEmpty ? $this->translator->trans('part.edit.tab.advanced.ipn.prefix_empty.direct_category', ['%name%' => $category->getName()]) : $this->translator->trans('part.edit.tab.advanced.ipn.prefix.direct_category.increment') - ]; - - // Process parent categories - $parentCategory = $category->getParent(); - - while ($parentCategory instanceof Category) { - // Prepend the parent category's prefix to the current path - $effectiveIPNPrefix = $parentCategory->getPartIpnPrefix() === '' ? $this->ipnSuggestSettings->fallbackPrefix : $parentCategory->getPartIpnPrefix(); - - $currentPath = $effectiveIPNPrefix . $this->ipnSuggestSettings->categorySeparator . $currentPath; - - $ipnSuggestions['commonPrefixes'][] = [ - 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator, - 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment') - ]; - - $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits); - - $ipnSuggestions['prefixesPartIncrement'][] = [ - 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator . $increment, - 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.hierarchical.increment') - ]; - - // Move to the next parent category - $parentCategory = $parentCategory->getParent(); - } - } elseif ($part->getID() === null) { - $ipnSuggestions['commonPrefixes'][] = [ - 'title' => $this->ipnSuggestSettings->fallbackPrefix, - 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.not_saved') - ]; - } - - return $ipnSuggestions; - } - - /** - * Suggests the next IPN (Internal Part Number) based on the provided part description. - * - * Searches for parts with similar descriptions and retrieves their existing IPNs to calculate the next suggestion. - * Returns null if the description is empty or no suggestion can be generated. - * - * @param string $description The part description to search for. - * - * @return string|null The suggested IPN, or null if no suggestion is possible. - * - * @throws NonUniqueResultException - */ - public function getIpnSuggestByDescription(string $description): ?string - { - if ($description === '') { - return null; - } - - $qb = $this->createQueryBuilder('part'); - - $qb->select('part') - ->where('part.description LIKE :descriptionPattern') - ->setParameter('descriptionPattern', $description.'%') - ->orderBy('part.id', 'ASC'); - - $partsBySameDescription = $qb->getQuery()->getResult(); - $givenIpnsWithSameDescription = []; - - foreach ($partsBySameDescription as $part) { - if ($part->getIpn() === null || $part->getIpn() === '') { - continue; - } - - $givenIpnsWithSameDescription[] = $part->getIpn(); - } - - return $this->getNextIpnSuggestion($givenIpnsWithSameDescription); - } - - private function generateNextPossibleGlobalIncrement(): string - { - $qb = $this->createQueryBuilder('part'); - - - $qb->select('part.ipn') - ->where('REGEXP(part.ipn, :ipnPattern) = TRUE') - ->setParameter('ipnPattern', '^' . preg_quote($this->ipnSuggestSettings->globalPrefix, '/') . '\d+$') - ->orderBy('NATSORT(part.ipn)', 'DESC') - ->setMaxResults(1) - ; - - $highestIPN = $qb->getQuery()->getOneOrNullResult(); - if ($highestIPN !== null) { - //Remove the prefix and extract the increment part - $incrementPart = substr($highestIPN['ipn'], strlen($this->ipnSuggestSettings->globalPrefix)); - //Extract a number using regex - preg_match('/(\d+)$/', $incrementPart, $matches); - $incrementInt = isset($matches[1]) ? (int) $matches[1] + 1 : 0; - } else { - $incrementInt = 1; - } - - - return str_pad((string) $incrementInt, $this->ipnSuggestSettings->suggestPartDigits, '0', STR_PAD_LEFT); - } - - /** - * Generates the next possible increment for a part within a given category, while ensuring uniqueness. - * - * This method calculates the next available increment for a part's identifier (`ipn`) based on the current path - * and the number of digits specified for the autocomplete feature. It ensures that the generated identifier - * aligns with the expected length and does not conflict with already existing identifiers in the same category. - * - * @param string $currentPath The base path or prefix for the part's identifier. - * @param Part $currentPart The part entity for which the increment is being generated. - * @param int $suggestPartDigits The number of digits reserved for the increment. - * - * @return string The next possible increment as a zero-padded string. - * - * @throws NonUniqueResultException If the query returns non-unique results. - * @throws NoResultException If the query fails to return a result. - */ - private function generateNextPossiblePartIncrement(string $currentPath, Part $currentPart, int $suggestPartDigits): string - { - $qb = $this->createQueryBuilder('part'); - - $expectedLength = strlen($currentPath) + strlen($this->ipnSuggestSettings->categorySeparator) + $suggestPartDigits; // Path + '-' + $suggestPartDigits digits - - // Fetch all parts in the given category, sorted by their ID in ascending order - $qb->select('part') - ->where('part.ipn LIKE :ipnPattern') - ->andWhere('LENGTH(part.ipn) = :expectedLength') - ->setParameter('ipnPattern', $currentPath . '%') - ->setParameter('expectedLength', $expectedLength) - ->orderBy('part.id', 'ASC'); - - $parts = $qb->getQuery()->getResult(); - - // Collect all used increments in the category - $usedIncrements = []; - foreach ($parts as $part) { - if ($part->getIpn() === null || $part->getIpn() === '') { - continue; - } - - if ($part->getId() === $currentPart->getId() && $currentPart->getID() !== null) { - // Extract and return the current part's increment directly - $incrementPart = substr($part->getIpn(), -$suggestPartDigits); - if (is_numeric($incrementPart)) { - return str_pad((string) $incrementPart, $suggestPartDigits, '0', STR_PAD_LEFT); - } - } - - // Extract last $autocompletePartDigits digits for possible available part increment - $incrementPart = substr($part->getIpn(), -$suggestPartDigits); - if (is_numeric($incrementPart)) { - $usedIncrements[] = (int) $incrementPart; - } - - } - - // Generate the next free $autocompletePartDigits-digit increment - $nextIncrement = 1; // Start at the beginning - - while (in_array($nextIncrement, $usedIncrements, true)) { - $nextIncrement++; - } - - return str_pad((string) $nextIncrement, $suggestPartDigits, '0', STR_PAD_LEFT); - } - - /** - * Generates the next IPN suggestion based on the maximum numeric suffix found in the given IPNs. - * - * The new IPN is constructed using the base format of the first provided IPN, - * incremented by the next free numeric suffix. If no base IPNs are found, - * returns null. - * - * @param array $givenIpns List of IPNs to analyze. - * - * @return string|null The next suggested IPN, or null if no base IPNs can be derived. - */ - private function getNextIpnSuggestion(array $givenIpns): ?string { - $maxSuffix = 0; - - foreach ($givenIpns as $ipn) { - // Check whether the IPN contains a suffix "_ " - if (preg_match('/_(\d+)$/', $ipn, $matches)) { - $suffix = (int)$matches[1]; - if ($suffix > $maxSuffix) { - $maxSuffix = $suffix; // Höchste Nummer speichern - } - } - } - - // Find the basic format (the IPN without suffix) from the first IPN - $baseIpn = $givenIpns[0] ?? ''; - $baseIpn = preg_replace('/_\d+$/', '', $baseIpn); // Remove existing "_ " - - if ($baseIpn === '') { - return null; - } - - // Generate next free possible IPN - return $baseIpn . '_' . ($maxSuffix + 1); - } - } diff --git a/src/Repository/Parts/PartCustomStateRepository.php b/src/Repository/Parts/PartCustomStateRepository.php deleted file mode 100644 index d66221a2..00000000 --- a/src/Repository/Parts/PartCustomStateRepository.php +++ /dev/null @@ -1,48 +0,0 @@ -. - */ -namespace App\Repository\Parts; - -use App\Entity\Parts\PartCustomState; -use App\Repository\AbstractPartsContainingRepository; -use InvalidArgumentException; - -class PartCustomStateRepository extends AbstractPartsContainingRepository -{ - public function getParts(object $element, string $nameOrderDirection = "ASC"): array - { - if (!$element instanceof PartCustomState) { - throw new InvalidArgumentException('$element must be an PartCustomState!'); - } - - return $this->getPartsByField($element, $nameOrderDirection, 'partUnit'); - } - - public function getPartsCount(object $element): int - { - if (!$element instanceof PartCustomState) { - throw new InvalidArgumentException('$element must be an PartCustomState!'); - } - - return $this->getPartsCountByField($element, 'partUnit'); - } -} diff --git a/src/Security/UserChecker.php b/src/Security/UserChecker.php index 239a6096..16afb37e 100644 --- a/src/Security/UserChecker.php +++ b/src/Security/UserChecker.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace App\Security; use App\Entity\UserSystem\User; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AccountStatusException; use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use Symfony\Component\Security\Core\User\UserCheckerInterface; @@ -52,7 +51,7 @@ final class UserChecker implements UserCheckerInterface * * @throws AccountStatusException */ - public function checkPostAuth(UserInterface $user, ?TokenInterface $token = null): void + public function checkPostAuth(UserInterface $user): void { if (!$user instanceof User) { return; diff --git a/src/Security/Voter/AttachmentVoter.php b/src/Security/Voter/AttachmentVoter.php index df3d73a7..bd7ae4df 100644 --- a/src/Security/Voter/AttachmentVoter.php +++ b/src/Security/Voter/AttachmentVoter.php @@ -22,7 +22,6 @@ declare(strict_types=1); namespace App\Security\Voter; -use App\Entity\Attachments\PartCustomStateAttachment; use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\AttachmentContainingDBElement; @@ -100,8 +99,6 @@ final class AttachmentVoter extends Voter $param = 'measurement_units'; } elseif (is_a($subject, PartAttachment::class, true)) { $param = 'parts'; - } elseif (is_a($subject, PartCustomStateAttachment::class, true)) { - $param = 'part_custom_states'; } elseif (is_a($subject, StorageLocationAttachment::class, true)) { $param = 'storelocations'; } elseif (is_a($subject, SupplierAttachment::class, true)) { diff --git a/src/Security/Voter/BOMEntryVoter.php b/src/Security/Voter/BOMEntryVoter.php index 4ce40d47..121c8172 100644 --- a/src/Security/Voter/BOMEntryVoter.php +++ b/src/Security/Voter/BOMEntryVoter.php @@ -27,7 +27,6 @@ use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -47,7 +46,7 @@ class BOMEntryVoter extends Voter return $this->supportsAttribute($attribute) && is_a($subject, ProjectBOMEntry::class, true); } - protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { if (!is_a($subject, ProjectBOMEntry::class, true)) { return false; @@ -88,4 +87,4 @@ class BOMEntryVoter extends Voter { return $subjectType === 'string' || is_a($subjectType, ProjectBOMEntry::class, true); } -} +} \ No newline at end of file diff --git a/src/Security/Voter/HasAccessPermissionsVoter.php b/src/Security/Voter/HasAccessPermissionsVoter.php index 9adef977..bd466d07 100644 --- a/src/Security/Voter/HasAccessPermissionsVoter.php +++ b/src/Security/Voter/HasAccessPermissionsVoter.php @@ -26,7 +26,6 @@ namespace App\Security\Voter; use App\Services\UserSystem\PermissionManager; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -42,7 +41,7 @@ final class HasAccessPermissionsVoter extends Voter { } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { $user = $this->helper->resolveUser($token); return $this->permissionManager->hasAnyPermissionSetToAllowInherited($user); @@ -57,4 +56,4 @@ final class HasAccessPermissionsVoter extends Voter { return $attribute === self::ROLE; } -} +} \ No newline at end of file diff --git a/src/Security/Voter/ParameterVoter.php b/src/Security/Voter/ParameterVoter.php index 5dc30ea2..f59bdeaf 100644 --- a/src/Security/Voter/ParameterVoter.php +++ b/src/Security/Voter/ParameterVoter.php @@ -22,7 +22,6 @@ declare(strict_types=1); */ namespace App\Security\Voter; -use App\Entity\Parameters\PartCustomStateParameter; use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractDBElement; @@ -98,8 +97,6 @@ final class ParameterVoter extends Voter $param = 'measurement_units'; } elseif (is_a($subject, PartParameter::class, true)) { $param = 'parts'; - } elseif (is_a($subject, PartCustomStateParameter::class, true)) { - $param = 'part_custom_states'; } elseif (is_a($subject, StorageLocationParameter::class, true)) { $param = 'storelocations'; } elseif (is_a($subject, SupplierParameter::class, true)) { diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php index 16d38e05..ad0299a7 100644 --- a/src/Security/Voter/StructureVoter.php +++ b/src/Security/Voter/StructureVoter.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\Attachments\AttachmentType; -use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; @@ -54,7 +53,6 @@ final class StructureVoter extends Voter Supplier::class => 'suppliers', Currency::class => 'currencies', MeasurementUnit::class => 'measurement_units', - PartCustomState::class => 'part_custom_states', ]; public function __construct(private readonly VoterHelper $helper) diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php index c7e69257..a30163ae 100644 --- a/src/Services/Attachments/AttachmentSubmitHandler.php +++ b/src/Services/Attachments/AttachmentSubmitHandler.php @@ -30,7 +30,6 @@ use App\Entity\Attachments\AttachmentUpload; use App\Entity\Attachments\CategoryAttachment; use App\Entity\Attachments\CurrencyAttachment; use App\Entity\Attachments\LabelAttachment; -use App\Entity\Attachments\PartCustomStateAttachment; use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\GroupAttachment; @@ -58,9 +57,6 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; */ class AttachmentSubmitHandler { - /** - * @var array The mapping used to determine which folder will be used for an attachment type - */ protected array $folder_mapping; private ?int $max_upload_size_bytes = null; @@ -81,7 +77,6 @@ class AttachmentSubmitHandler //The mapping used to determine which folder will be used for an attachment type $this->folder_mapping = [ PartAttachment::class => 'part', - PartCustomStateAttachment::class => 'part_custom_state', AttachmentTypeAttachment::class => 'attachment_type', CategoryAttachment::class => 'category', CurrencyAttachment::class => 'currency', @@ -165,7 +160,6 @@ class AttachmentSubmitHandler } else { //If not, check for instance of: foreach ($this->folder_mapping as $class => $folder) { - /** @var string $class */ if ($attachment instanceof $class) { $prefix = $folder; break; diff --git a/src/Services/Attachments/PartPreviewGenerator.php b/src/Services/Attachments/PartPreviewGenerator.php index 9aedba74..ba6e5db0 100644 --- a/src/Services/Attachments/PartPreviewGenerator.php +++ b/src/Services/Attachments/PartPreviewGenerator.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace App\Services\Attachments; use App\Entity\Parts\Footprint; -use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\StorageLocation; diff --git a/src/EnvVarProcessors/CustomEnvVarProcessor.php b/src/Services/CustomEnvVarProcessor.php similarity index 98% rename from src/EnvVarProcessors/CustomEnvVarProcessor.php rename to src/Services/CustomEnvVarProcessor.php index 55a6b94d..f269cc7d 100644 --- a/src/EnvVarProcessors/CustomEnvVarProcessor.php +++ b/src/Services/CustomEnvVarProcessor.php @@ -20,7 +20,7 @@ declare(strict_types=1); -namespace App\EnvVarProcessors; +namespace App\Services; use Closure; use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; diff --git a/src/Services/EDA/KiCadHelper.php b/src/Services/EDA/KiCadHelper.php index 4b7c5e5a..75c2cc34 100644 --- a/src/Services/EDA/KiCadHelper.php +++ b/src/Services/EDA/KiCadHelper.php @@ -233,10 +233,6 @@ class KiCadHelper } $result["fields"]["Part-DB Unit"] = $this->createField($unit); } - if ($part->getPartCustomState() !== null) { - $customState = $part->getPartCustomState()->getName(); - $result["fields"]["Part-DB Custom state"] = $this->createField($customState); - } if ($part->getMass()) { $result["fields"]["Mass"] = $this->createField($part->getMass() . ' g'); } diff --git a/src/Services/ElementTypeNameGenerator.php b/src/Services/ElementTypeNameGenerator.php index 19bb19f5..14247145 100644 --- a/src/Services/ElementTypeNameGenerator.php +++ b/src/Services/ElementTypeNameGenerator.php @@ -22,33 +22,64 @@ declare(strict_types=1); namespace App\Services; -use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentContainingDBElement; +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractDBElement; use App\Entity\Contracts\NamedElementInterface; +use App\Entity\Parts\PartAssociation; +use App\Entity\ProjectSystem\Project; +use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; +use App\Entity\Parts\Category; +use App\Entity\Parts\Footprint; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; +use App\Entity\Parts\StorageLocation; +use App\Entity\Parts\Supplier; +use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; -use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; +use App\Entity\UserSystem\Group; +use App\Entity\UserSystem\User; use App\Exceptions\EntityNotSupportedException; -use App\Settings\SynonymSettings; use Symfony\Contracts\Translation\TranslatorInterface; /** * @see \App\Tests\Services\ElementTypeNameGeneratorTest */ -final readonly class ElementTypeNameGenerator +class ElementTypeNameGenerator { + protected array $mapping; - public function __construct( - private TranslatorInterface $translator, - private EntityURLGenerator $entityURLGenerator, - private SynonymSettings $synonymsSettings, - ) + public function __construct(protected TranslatorInterface $translator, private readonly EntityURLGenerator $entityURLGenerator) { + //Child classes has to become before parent classes + $this->mapping = [ + Attachment::class => $this->translator->trans('attachment.label'), + Category::class => $this->translator->trans('category.label'), + AttachmentType::class => $this->translator->trans('attachment_type.label'), + Project::class => $this->translator->trans('project.label'), + ProjectBOMEntry::class => $this->translator->trans('project_bom_entry.label'), + Footprint::class => $this->translator->trans('footprint.label'), + Manufacturer::class => $this->translator->trans('manufacturer.label'), + MeasurementUnit::class => $this->translator->trans('measurement_unit.label'), + Part::class => $this->translator->trans('part.label'), + PartLot::class => $this->translator->trans('part_lot.label'), + StorageLocation::class => $this->translator->trans('storelocation.label'), + Supplier::class => $this->translator->trans('supplier.label'), + Currency::class => $this->translator->trans('currency.label'), + Orderdetail::class => $this->translator->trans('orderdetail.label'), + Pricedetail::class => $this->translator->trans('pricedetail.label'), + Group::class => $this->translator->trans('group.label'), + User::class => $this->translator->trans('user.label'), + AbstractParameter::class => $this->translator->trans('parameter.label'), + LabelProfile::class => $this->translator->trans('label_profile.label'), + PartAssociation::class => $this->translator->trans('part_association.label'), + ]; } /** @@ -62,69 +93,27 @@ final readonly class ElementTypeNameGenerator * @return string the localized label for the entity type * * @throws EntityNotSupportedException when the passed entity is not supported - * @deprecated Use label() instead */ public function getLocalizedTypeLabel(object|string $entity): string { - return $this->typeLabel($entity); - } + $class = is_string($entity) ? $entity : $entity::class; - private function resolveSynonymLabel(ElementTypes $type, ?string $locale, bool $plural): ?string - { - $locale ??= $this->translator->getLocale(); - - if ($this->synonymsSettings->isSynonymDefinedForType($type)) { - if ($plural) { - $syn = $this->synonymsSettings->getPluralSynonymForType($type, $locale); - } else { - $syn = $this->synonymsSettings->getSingularSynonymForType($type, $locale); - } - - if ($syn === null) { - //Try to fall back to english - if ($plural) { - $syn = $this->synonymsSettings->getPluralSynonymForType($type, 'en'); - } else { - $syn = $this->synonymsSettings->getSingularSynonymForType($type, 'en'); - } - } - - return $syn; + //Check if we have a direct array entry for our entity class, then we can use it + if (isset($this->mapping[$class])) { + return $this->mapping[$class]; } - return null; + //Otherwise iterate over array and check for inheritance (needed when the proxy element from doctrine are passed) + foreach ($this->mapping as $class_to_check => $translation) { + if (is_a($entity, $class_to_check, true)) { + return $translation; + } + } + + //When nothing was found throw an exception + throw new EntityNotSupportedException(sprintf('No localized label for the element with type %s was found!', is_object($entity) ? $entity::class : (string) $entity)); } - /** - * Gets a localized label for the type of the entity. If user defined synonyms are defined, - * these are used instead of the default labels. - * @param object|string $entity - * @param string|null $locale - * @return string - */ - public function typeLabel(object|string $entity, ?string $locale = null): string - { - $type = ElementTypes::fromValue($entity); - - return $this->resolveSynonymLabel($type, $locale, false) - ?? $this->translator->trans($type->getDefaultLabelKey(), locale: $locale); - } - - /** - * Similar to label(), but returns the plural version of the label. - * @param object|string $entity - * @param string|null $locale - * @return string - */ - public function typeLabelPlural(object|string $entity, ?string $locale = null): string - { - $type = ElementTypes::fromValue($entity); - - return $this->resolveSynonymLabel($type, $locale, true) - ?? $this->translator->trans($type->getDefaultPluralLabelKey(), locale: $locale); - } - - /** * Returns a string like in the format ElementType: ElementName. * For example this could be something like: "Part: BC547". @@ -139,17 +128,17 @@ final readonly class ElementTypeNameGenerator */ public function getTypeNameCombination(NamedElementInterface $entity, bool $use_html = false): string { - $type = $this->typeLabel($entity); + $type = $this->getLocalizedTypeLabel($entity); if ($use_html) { - return '' . $type . ': ' . htmlspecialchars($entity->getName()); + return ''.$type.': '.htmlspecialchars($entity->getName()); } - return $type . ': ' . $entity->getName(); + return $type.': '.$entity->getName(); } /** - * Returns a HTML formatted label for the given entity in the format "Type: Name" (on elements with a name) and + * Returns a HTML formatted label for the given enitity in the format "Type: Name" (on elements with a name) and * "Type: ID" (on elements without a name). If possible the value is given as a link to the element. * @param AbstractDBElement $entity The entity for which the label should be generated * @param bool $include_associated If set to true, the associated entity (like the part belonging to a part lot) is included in the label to give further information @@ -170,7 +159,7 @@ final readonly class ElementTypeNameGenerator } else { //Target does not have a name $tmp = sprintf( '%s: %s', - $this->typeLabel($entity), + $this->getLocalizedTypeLabel($entity), $entity->getID() ); } @@ -214,7 +203,7 @@ final readonly class ElementTypeNameGenerator { return sprintf( '%s: %s [%s]', - $this->typeLabel($class), + $this->getLocalizedTypeLabel($class), $id, $this->translator->trans('log.target_deleted') ); diff --git a/src/Services/ElementTypes.php b/src/Services/ElementTypes.php deleted file mode 100644 index 6ce8f851..00000000 --- a/src/Services/ElementTypes.php +++ /dev/null @@ -1,229 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\Services; - -use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentType; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob; -use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; -use App\Entity\LabelSystem\LabelProfile; -use App\Entity\Parameters\AbstractParameter; -use App\Entity\Parts\Category; -use App\Entity\Parts\Footprint; -use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Part; -use App\Entity\Parts\PartAssociation; -use App\Entity\Parts\PartCustomState; -use App\Entity\Parts\PartLot; -use App\Entity\Parts\StorageLocation; -use App\Entity\Parts\Supplier; -use App\Entity\PriceInformations\Currency; -use App\Entity\PriceInformations\Orderdetail; -use App\Entity\PriceInformations\Pricedetail; -use App\Entity\ProjectSystem\Project; -use App\Entity\ProjectSystem\ProjectBOMEntry; -use App\Entity\UserSystem\Group; -use App\Entity\UserSystem\User; -use App\Exceptions\EntityNotSupportedException; -use Symfony\Contracts\Translation\TranslatableInterface; -use Symfony\Contracts\Translation\TranslatorInterface; - -enum ElementTypes: string implements TranslatableInterface -{ - case ATTACHMENT = "attachment"; - case CATEGORY = "category"; - case ATTACHMENT_TYPE = "attachment_type"; - case PROJECT = "project"; - case PROJECT_BOM_ENTRY = "project_bom_entry"; - case FOOTPRINT = "footprint"; - case MANUFACTURER = "manufacturer"; - case MEASUREMENT_UNIT = "measurement_unit"; - case PART = "part"; - case PART_LOT = "part_lot"; - case STORAGE_LOCATION = "storage_location"; - case SUPPLIER = "supplier"; - case CURRENCY = "currency"; - case ORDERDETAIL = "orderdetail"; - case PRICEDETAIL = "pricedetail"; - case GROUP = "group"; - case USER = "user"; - case PARAMETER = "parameter"; - case LABEL_PROFILE = "label_profile"; - case PART_ASSOCIATION = "part_association"; - case BULK_INFO_PROVIDER_IMPORT_JOB = "bulk_info_provider_import_job"; - case BULK_INFO_PROVIDER_IMPORT_JOB_PART = "bulk_info_provider_import_job_part"; - case PART_CUSTOM_STATE = "part_custom_state"; - - //Child classes has to become before parent classes - private const CLASS_MAPPING = [ - Attachment::class => self::ATTACHMENT, - Category::class => self::CATEGORY, - AttachmentType::class => self::ATTACHMENT_TYPE, - Project::class => self::PROJECT, - ProjectBOMEntry::class => self::PROJECT_BOM_ENTRY, - Footprint::class => self::FOOTPRINT, - Manufacturer::class => self::MANUFACTURER, - MeasurementUnit::class => self::MEASUREMENT_UNIT, - Part::class => self::PART, - PartLot::class => self::PART_LOT, - StorageLocation::class => self::STORAGE_LOCATION, - Supplier::class => self::SUPPLIER, - Currency::class => self::CURRENCY, - Orderdetail::class => self::ORDERDETAIL, - Pricedetail::class => self::PRICEDETAIL, - Group::class => self::GROUP, - User::class => self::USER, - AbstractParameter::class => self::PARAMETER, - LabelProfile::class => self::LABEL_PROFILE, - PartAssociation::class => self::PART_ASSOCIATION, - BulkInfoProviderImportJob::class => self::BULK_INFO_PROVIDER_IMPORT_JOB, - BulkInfoProviderImportJobPart::class => self::BULK_INFO_PROVIDER_IMPORT_JOB_PART, - PartCustomState::class => self::PART_CUSTOM_STATE, - ]; - - /** - * Gets the default translation key for the label of the element type (singular form). - */ - public function getDefaultLabelKey(): string - { - return match ($this) { - self::ATTACHMENT => 'attachment.label', - self::CATEGORY => 'category.label', - self::ATTACHMENT_TYPE => 'attachment_type.label', - self::PROJECT => 'project.label', - self::PROJECT_BOM_ENTRY => 'project_bom_entry.label', - self::FOOTPRINT => 'footprint.label', - self::MANUFACTURER => 'manufacturer.label', - self::MEASUREMENT_UNIT => 'measurement_unit.label', - self::PART => 'part.label', - self::PART_LOT => 'part_lot.label', - self::STORAGE_LOCATION => 'storelocation.label', - self::SUPPLIER => 'supplier.label', - self::CURRENCY => 'currency.label', - self::ORDERDETAIL => 'orderdetail.label', - self::PRICEDETAIL => 'pricedetail.label', - self::GROUP => 'group.label', - self::USER => 'user.label', - self::PARAMETER => 'parameter.label', - self::LABEL_PROFILE => 'label_profile.label', - self::PART_ASSOCIATION => 'part_association.label', - self::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.label', - self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.label', - self::PART_CUSTOM_STATE => 'part_custom_state.label', - }; - } - - public function getDefaultPluralLabelKey(): string - { - return match ($this) { - self::ATTACHMENT => 'attachment.labelp', - self::CATEGORY => 'category.labelp', - self::ATTACHMENT_TYPE => 'attachment_type.labelp', - self::PROJECT => 'project.labelp', - self::PROJECT_BOM_ENTRY => 'project_bom_entry.labelp', - self::FOOTPRINT => 'footprint.labelp', - self::MANUFACTURER => 'manufacturer.labelp', - self::MEASUREMENT_UNIT => 'measurement_unit.labelp', - self::PART => 'part.labelp', - self::PART_LOT => 'part_lot.labelp', - self::STORAGE_LOCATION => 'storelocation.labelp', - self::SUPPLIER => 'supplier.labelp', - self::CURRENCY => 'currency.labelp', - self::ORDERDETAIL => 'orderdetail.labelp', - self::PRICEDETAIL => 'pricedetail.labelp', - self::GROUP => 'group.labelp', - self::USER => 'user.labelp', - self::PARAMETER => 'parameter.labelp', - self::LABEL_PROFILE => 'label_profile.labelp', - self::PART_ASSOCIATION => 'part_association.labelp', - self::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.labelp', - self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.labelp', - self::PART_CUSTOM_STATE => 'part_custom_state.labelp', - }; - } - - /** - * Used to get a user-friendly representation of the object that can be translated. - * For this the singular default label key is used. - * @param TranslatorInterface $translator - * @param string|null $locale - * @return string - */ - public function trans(TranslatorInterface $translator, ?string $locale = null): string - { - return $translator->trans($this->getDefaultLabelKey(), locale: $locale); - } - - /** - * Determines the ElementType from a value, which can either be an enum value, an ElementTypes instance, a class name or an object instance. - * @param string|object $value - * @return self - */ - public static function fromValue(string|object $value): self - { - if ($value instanceof self) { - return $value; - } - if (is_object($value)) { - return self::fromClass($value); - } - - - //Otherwise try to parse it as enum value first - $enumValue = self::tryFrom($value); - - //Otherwise try to get it from class name - return $enumValue ?? self::fromClass($value); - } - - /** - * Determines the ElementType from a class name or object instance. - * @param string|object $class - * @throws EntityNotSupportedException if the class is not supported - * @return self - */ - public static function fromClass(string|object $class): self - { - if (is_object($class)) { - $className = get_class($class); - } else { - $className = $class; - } - - if (array_key_exists($className, self::CLASS_MAPPING)) { - return self::CLASS_MAPPING[$className]; - } - - //Otherwise we need to check for inheritance - foreach (self::CLASS_MAPPING as $entityClass => $elementType) { - if (is_a($className, $entityClass, true)) { - return $elementType; - } - } - - throw new EntityNotSupportedException(sprintf('No localized label for the element with type %s was found!', $className)); - } - -} diff --git a/src/Services/EntityMergers/EntityMerger.php b/src/Services/EntityMergers/EntityMerger.php index f8cf8a11..c0be84ee 100644 --- a/src/Services/EntityMergers/EntityMerger.php +++ b/src/Services/EntityMergers/EntityMerger.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace App\Services\EntityMergers; use App\Services\EntityMergers\Mergers\EntityMergerInterface; -use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; +use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; /** * This service is used to merge two entities together. @@ -32,7 +32,7 @@ use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; */ class EntityMerger { - public function __construct(#[AutowireIterator('app.entity_merger')] protected iterable $mergers) + public function __construct(#[TaggedIterator('app.entity_merger')] protected iterable $mergers) { } @@ -73,4 +73,4 @@ class EntityMerger } return $merger->merge($target, $other, $context); } -} +} \ No newline at end of file diff --git a/src/Services/EntityMergers/Mergers/PartMerger.php b/src/Services/EntityMergers/Mergers/PartMerger.php index d1f5c137..4ce779e8 100644 --- a/src/Services/EntityMergers/Mergers/PartMerger.php +++ b/src/Services/EntityMergers/Mergers/PartMerger.php @@ -65,7 +65,6 @@ class PartMerger implements EntityMergerInterface $this->useOtherValueIfNotNull($target, $other, 'footprint'); $this->useOtherValueIfNotNull($target, $other, 'category'); $this->useOtherValueIfNotNull($target, $other, 'partUnit'); - $this->useOtherValueIfNotNull($target, $other, 'partCustomState'); //We assume that the higher value is the correct one for minimum instock $this->useLargerValue($target, $other, 'minamount'); @@ -101,8 +100,7 @@ class PartMerger implements EntityMergerInterface return $target; } - private 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(); @@ -143,39 +141,40 @@ class PartMerger implements EntityMergerInterface $owner->addAssociatedPartsAsOwner($clone); } - // Merge orderdetails, considering same supplier+part number as duplicates $this->mergeCollections($target, $other, 'orderdetails', function (Orderdetail $t, Orderdetail $o) { - // If supplier and part number match, merge the orderdetails - if ($t->getSupplier() === $o->getSupplier() && $t->getSupplierPartNr() === $o->getSupplierPartNr()) { - // Update URL if target doesn't have one - if (empty($t->getSupplierProductUrl(false)) && !empty($o->getSupplierProductUrl(false))) { - $t->setSupplierProductUrl($o->getSupplierProductUrl(false)); - } - // Merge price details: add new ones, update empty ones, keep existing non-empty ones - foreach ($o->getPricedetails() as $otherPrice) { - $found = false; - foreach ($t->getPricedetails() as $targetPrice) { - if ($targetPrice->getMinDiscountQuantity() === $otherPrice->getMinDiscountQuantity() - && $targetPrice->getCurrency() === $otherPrice->getCurrency()) { - // Only update price if the existing one is zero/empty (most logical) - if ($targetPrice->getPrice()->isZero()) { - $targetPrice->setPrice($otherPrice->getPrice()); - $targetPrice->setPriceRelatedQuantity($otherPrice->getPriceRelatedQuantity()); - } - $found = true; - break; - } - } - // Add completely new price tiers - if (!$found) { - $clonedPrice = clone $otherPrice; - $clonedPrice->setOrderdetail($t); - $t->addPricedetail($clonedPrice); - } - } - return true; // Consider them equal so the other one gets skipped + //First check that the orderdetails infos are equal + $tmp = $t->getSupplier() === $o->getSupplier() + && $t->getSupplierPartNr() === $o->getSupplierPartNr() + && $t->getSupplierProductUrl(false) === $o->getSupplierProductUrl(false); + + if (!$tmp) { + return false; } - return false; // Different supplier/part number, add as new + + //Check if the pricedetails are equal + $t_pricedetails = $t->getPricedetails(); + $o_pricedetails = $o->getPricedetails(); + //Ensure that both pricedetails have the same length + if (count($t_pricedetails) !== count($o_pricedetails)) { + return false; + } + + //Check if all pricedetails are equal + for ($n=0, $nMax = count($t_pricedetails); $n< $nMax; $n++) { + $t_price = $t_pricedetails->get($n); + $o_price = $o_pricedetails->get($n); + + if (!$t_price->getPrice()->isEqualTo($o_price->getPrice()) + || $t_price->getCurrency() !== $o_price->getCurrency() + || $t_price->getPriceRelatedQuantity() !== $o_price->getPriceRelatedQuantity() + || $t_price->getMinDiscountQuantity() !== $o_price->getMinDiscountQuantity() + ) { + return false; + } + } + + //If all pricedetails are equal, the orderdetails are equal + return true; }); //The pricedetails are not correctly assigned to the new orderdetails, so fix that foreach ($target->getOrderdetails() as $orderdetail) { diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 91e271cc..78db06f0 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -27,7 +27,6 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\PartAttachment; use App\Entity\Base\AbstractDBElement; use App\Entity\Parameters\PartParameter; -use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; @@ -108,7 +107,6 @@ class EntityURLGenerator MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', LabelProfile::class => 'label_profile_edit', - PartCustomState::class => 'part_custom_state_edit', ]; try { @@ -215,7 +213,6 @@ class EntityURLGenerator MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', LabelProfile::class => 'label_profile_edit', - PartCustomState::class => 'part_custom_state_edit', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -246,7 +243,6 @@ class EntityURLGenerator MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', LabelProfile::class => 'label_profile_edit', - PartCustomState::class => 'part_custom_state_edit', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -278,7 +274,6 @@ class EntityURLGenerator MeasurementUnit::class => 'measurement_unit_new', Group::class => 'group_new', LabelProfile::class => 'label_profile_new', - PartCustomState::class => 'part_custom_state_new', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity)); @@ -310,7 +305,6 @@ class EntityURLGenerator MeasurementUnit::class => 'measurement_unit_clone', Group::class => 'group_clone', LabelProfile::class => 'label_profile_clone', - PartCustomState::class => 'part_custom_state_clone', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -356,7 +350,6 @@ class EntityURLGenerator MeasurementUnit::class => 'measurement_unit_delete', Group::class => 'group_delete', LabelProfile::class => 'label_profile_delete', - PartCustomState::class => 'part_custom_state_delete', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); diff --git a/src/Services/Formatters/SIFormatter.php b/src/Services/Formatters/SIFormatter.php index b83501fa..a6325987 100644 --- a/src/Services/Formatters/SIFormatter.php +++ b/src/Services/Formatters/SIFormatter.php @@ -38,11 +38,6 @@ class SIFormatter */ public function getMagnitude(float $value): int { - //Check for zero, as log10(0) is undefined/gives -infinity, which leads to casting issues in PHP8.5+ - if (0.0 === $value) { - return 0; - } - return (int) floor(log10(abs($value))); } diff --git a/src/Services/ImportExportSystem/BOMImporter.php b/src/Services/ImportExportSystem/BOMImporter.php index e511c04d..862fa463 100644 --- a/src/Services/ImportExportSystem/BOMImporter.php +++ b/src/Services/ImportExportSystem/BOMImporter.php @@ -134,7 +134,7 @@ class BOMImporter private function parseKiCADPCB(string $data): array { - $csv = Reader::fromString($data); + $csv = Reader::createFromString($data); $csv->setDelimiter(';'); $csv->setHeaderOffset(0); @@ -175,7 +175,7 @@ class BOMImporter */ private function validateKiCADPCB(string $data): array { - $csv = Reader::fromString($data); + $csv = Reader::createFromString($data); $csv->setDelimiter(';'); $csv->setHeaderOffset(0); @@ -202,7 +202,7 @@ class BOMImporter // Handle potential BOM (Byte Order Mark) at the beginning $data = preg_replace('/^\xEF\xBB\xBF/', '', $data); - $csv = Reader::fromString($data); + $csv = Reader::createFromString($data); $csv->setDelimiter($delimiter); $csv->setHeaderOffset(0); @@ -262,7 +262,7 @@ class BOMImporter // Handle potential BOM (Byte Order Mark) at the beginning $data = preg_replace('/^\xEF\xBB\xBF/', '', $data); - $csv = Reader::fromString($data); + $csv = Reader::createFromString($data); $csv->setDelimiter($delimiter); $csv->setHeaderOffset(0); diff --git a/src/Services/ImportExportSystem/EntityExporter.php b/src/Services/ImportExportSystem/EntityExporter.php index 70feb8e6..271642da 100644 --- a/src/Services/ImportExportSystem/EntityExporter.php +++ b/src/Services/ImportExportSystem/EntityExporter.php @@ -38,9 +38,6 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\Serializer\SerializerInterface; use function Symfony\Component\String\u; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Writer\Xlsx; -use PhpOffice\PhpSpreadsheet\Writer\Xls; /** * Use this class to export an entity to multiple file formats. @@ -55,7 +52,7 @@ class EntityExporter protected function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('format', 'csv'); - $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml', 'xlsx', 'xls']); + $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml']); $resolver->setDefault('csv_delimiter', ';'); $resolver->setAllowedTypes('csv_delimiter', 'string'); @@ -91,35 +88,28 @@ class EntityExporter $options = $resolver->resolve($options); - //Handle Excel formats by converting from CSV - if (in_array($options['format'], ['xlsx', 'xls'], true)) { - return $this->exportToExcel($entities, $options); - } - //If include children is set, then we need to add the include_children group $groups = [$options['level']]; if ($options['include_children']) { $groups[] = 'include_children'; } - return $this->serializer->serialize( - $entities, - $options['format'], + return $this->serializer->serialize($entities, $options['format'], [ 'groups' => $groups, 'as_collection' => true, 'csv_delimiter' => $options['csv_delimiter'], 'xml_root_node_name' => 'PartDBExport', 'partdb_export' => true, - //Skip the item normalizer, so that we dont get IRIs in the output + //Skip the item normalizer, so that we dont get IRIs in the output SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER => true, - //Handle circular references + //Handle circular references AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => $this->handleCircularReference(...), ] ); } - private function handleCircularReference(object $object): string + private function handleCircularReference(object $object, string $format, array $context): string { if ($object instanceof AbstractStructuralDBElement) { return $object->getFullPath("->"); @@ -129,75 +119,7 @@ class EntityExporter return $object->__toString(); } - throw new CircularReferenceException('Circular reference detected for object of type ' . get_class($object)); - } - - /** - * Exports entities to Excel format (xlsx or xls). - * - * @param AbstractNamedDBElement[] $entities The entities to export - * @param array $options The export options - * - * @return string The Excel file content as binary string - */ - protected function exportToExcel(array $entities, array $options): string - { - //First get CSV data using existing serializer - $groups = [$options['level']]; - if ($options['include_children']) { - $groups[] = 'include_children'; - } - - $csvData = $this->serializer->serialize( - $entities, - 'csv', - [ - 'groups' => $groups, - 'as_collection' => true, - 'csv_delimiter' => $options['csv_delimiter'], - 'partdb_export' => true, - SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER => true, - AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => $this->handleCircularReference(...), - ] - ); - - //Convert CSV to Excel - $spreadsheet = new Spreadsheet(); - $worksheet = $spreadsheet->getActiveSheet(); - - $rows = explode("\n", $csvData); - $rowIndex = 1; - - foreach ($rows as $row) { - if (trim($row) === '') { - continue; - } - - $columns = str_getcsv($row, $options['csv_delimiter'], '"', '\\'); - $colIndex = 1; - - foreach ($columns as $column) { - $cellCoordinate = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex) . $rowIndex; - $worksheet->setCellValue($cellCoordinate, $column); - $colIndex++; - } - $rowIndex++; - } - - //Save to memory stream - $writer = $options['format'] === 'xlsx' ? new Xlsx($spreadsheet) : new Xls($spreadsheet); - - $memFile = fopen("php://temp", 'r+b'); - $writer->save($memFile); - rewind($memFile); - $content = stream_get_contents($memFile); - fclose($memFile); - - if ($content === false) { - throw new \RuntimeException('Failed to read Excel content from memory stream.'); - } - - return $content; + throw new CircularReferenceException('Circular reference detected for object of type '.get_class($object)); } /** @@ -234,15 +156,19 @@ class EntityExporter //Determine the content type for the response + //Plain text should work for all types + $content_type = 'text/plain'; + //Try to use better content types based on the format $format = $options['format']; - $content_type = match ($format) { - 'xml' => 'application/xml', - 'json' => 'application/json', - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'xls' => 'application/vnd.ms-excel', - default => 'text/plain', - }; + switch ($format) { + case 'xml': + $content_type = 'application/xml'; + break; + case 'json': + $content_type = 'application/json'; + break; + } $response->headers->set('Content-Type', $content_type); //If view option is not specified, then download the file. @@ -260,7 +186,7 @@ class EntityExporter $level = $options['level']; - $filename = "export_{$entity_name}_{$level}.{$format}"; + $filename = 'export_'.$entity_name.'_'.$level.'.'.$format; //Sanitize the filename $filename = FilenameSanatizer::sanitizeFilename($filename); diff --git a/src/Services/ImportExportSystem/EntityImporter.php b/src/Services/ImportExportSystem/EntityImporter.php index 459866ba..11915cfb 100644 --- a/src/Services/ImportExportSystem/EntityImporter.php +++ b/src/Services/ImportExportSystem/EntityImporter.php @@ -38,9 +38,6 @@ use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; -use PhpOffice\PhpSpreadsheet\IOFactory; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -use Psr\Log\LoggerInterface; /** * @see \App\Tests\Services\ImportExportSystem\EntityImporterTest @@ -53,7 +50,7 @@ class EntityImporter */ private const ENCODINGS = ["ASCII", "UTF-8", "ISO-8859-1", "ISO-8859-15", "Windows-1252", "UTF-16", "UTF-32"]; - public function __construct(protected SerializerInterface $serializer, protected EntityManagerInterface $em, protected ValidatorInterface $validator, protected LoggerInterface $logger) + public function __construct(protected SerializerInterface $serializer, protected EntityManagerInterface $em, protected ValidatorInterface $validator) { } @@ -105,7 +102,7 @@ class EntityImporter foreach ($names as $name) { //Count indentation level (whitespace characters at the beginning of the line) - $identSize = strlen($name) - strlen(ltrim($name)); + $identSize = strlen($name)-strlen(ltrim($name)); //If the line is intended more than the last line, we have a new parent element if ($identSize > end($indentations)) { @@ -198,20 +195,16 @@ class EntityImporter } //The [] behind class_name denotes that we expect an array. - $entities = $this->serializer->deserialize( - $data, - $options['class'] . '[]', - $options['format'], + $entities = $this->serializer->deserialize($data, $options['class'].'[]', $options['format'], [ 'groups' => $groups, 'csv_delimiter' => $options['csv_delimiter'], 'create_unknown_datastructures' => $options['create_unknown_datastructures'], 'path_delimiter' => $options['path_delimiter'], 'partdb_import' => true, - //Disable API Platform normalizer, as we don't want to use it here + //Disable API Platform normalizer, as we don't want to use it here SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER => true, - ] - ); + ]); //Ensure we have an array of entity elements. if (!is_array($entities)) { @@ -286,7 +279,7 @@ class EntityImporter 'path_delimiter' => '->', //The delimiter used to separate the path elements in the name of a structural element ]); - $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml', 'xlsx', 'xls']); + $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml']); $resolver->setAllowedTypes('csv_delimiter', 'string'); $resolver->setAllowedTypes('preserve_children', 'bool'); $resolver->setAllowedTypes('class', 'string'); @@ -342,33 +335,6 @@ class EntityImporter */ public function importFile(File $file, array $options = [], array &$errors = []): array { - $resolver = new OptionsResolver(); - $this->configureOptions($resolver); - $options = $resolver->resolve($options); - - if (in_array($options['format'], ['xlsx', 'xls'], true)) { - $this->logger->info('Converting Excel file to CSV', [ - 'filename' => $file->getFilename(), - 'format' => $options['format'], - 'delimiter' => $options['csv_delimiter'] - ]); - - $csvData = $this->convertExcelToCsv($file, $options['csv_delimiter']); - $options['format'] = 'csv'; - - $this->logger->debug('Excel to CSV conversion completed', [ - 'csv_length' => strlen($csvData), - 'csv_lines' => substr_count($csvData, "\n") + 1 - ]); - - // Log the converted CSV for debugging (first 1000 characters) - $this->logger->debug('Converted CSV preview', [ - 'csv_preview' => substr($csvData, 0, 1000) . (strlen($csvData) > 1000 ? '...' : '') - ]); - - return $this->importString($csvData, $options, $errors); - } - return $this->importString($file->getContent(), $options, $errors); } @@ -388,103 +354,10 @@ class EntityImporter 'xml' => 'xml', 'csv', 'tsv' => 'csv', 'yaml', 'yml' => 'yaml', - 'xlsx' => 'xlsx', - 'xls' => 'xls', default => null, }; } - /** - * Converts Excel file to CSV format using PhpSpreadsheet. - * - * @param File $file The Excel file to convert - * @param string $delimiter The CSV delimiter to use - * - * @return string The CSV data as string - */ - protected function convertExcelToCsv(File $file, string $delimiter = ';'): string - { - try { - $this->logger->debug('Loading Excel file', ['path' => $file->getPathname()]); - $spreadsheet = IOFactory::load($file->getPathname()); - $worksheet = $spreadsheet->getActiveSheet(); - - $csvData = []; - $highestRow = $worksheet->getHighestRow(); - $highestColumn = $worksheet->getHighestColumn(); - - $this->logger->debug('Excel file dimensions', [ - 'rows' => $highestRow, - 'columns_detected' => $highestColumn, - 'worksheet_title' => $worksheet->getTitle() - ]); - - $highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn); - - for ($row = 1; $row <= $highestRow; $row++) { - $rowData = []; - - // Read all columns using numeric index - for ($colIndex = 1; $colIndex <= $highestColumnIndex; $colIndex++) { - $col = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex); - try { - $cellValue = $worksheet->getCell("{$col}{$row}")->getCalculatedValue(); - $rowData[] = $cellValue ?? ''; - - } catch (\Exception $e) { - $this->logger->warning('Error reading cell value', [ - 'cell' => "{$col}{$row}", - 'error' => $e->getMessage() - ]); - $rowData[] = ''; - } - } - - $csvRow = implode($delimiter, array_map(function ($value) use ($delimiter) { - $value = (string) $value; - if (strpos($value, $delimiter) !== false || strpos($value, '"') !== false || strpos($value, "\n") !== false) { - return '"' . str_replace('"', '""', $value) . '"'; - } - return $value; - }, $rowData)); - - $csvData[] = $csvRow; - - // Log first few rows for debugging - if ($row <= 3) { - $this->logger->debug("Row {$row} converted", [ - 'original_data' => $rowData, - 'csv_row' => $csvRow, - 'first_cell_raw' => $worksheet->getCell("A{$row}")->getValue(), - 'first_cell_calculated' => $worksheet->getCell("A{$row}")->getCalculatedValue() - ]); - } - } - - $result = implode("\n", $csvData); - - $this->logger->info('Excel to CSV conversion successful', [ - 'total_rows' => count($csvData), - 'total_characters' => strlen($result) - ]); - - $this->logger->debug('Full CSV data', [ - 'csv_data' => $result - ]); - - return $result; - - } catch (\Exception $e) { - $this->logger->error('Failed to convert Excel to CSV', [ - 'file' => $file->getFilename(), - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString() - ]); - throw $e; - } - } - - /** * This functions corrects the parent setting based on the children value of the parent. * diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php index 9e674f05..1f842c23 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php @@ -29,7 +29,6 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\PartCustomState; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use Doctrine\ORM\EntityManagerInterface; @@ -149,26 +148,6 @@ class PKDatastructureImporter return is_countable($partunit_data) ? count($partunit_data) : 0; } - public function importPartCustomStates(array $data): int - { - if (!isset($data['partcustomstate'])) { - throw new \RuntimeException('$data must contain a "partcustomstate" key!'); - } - - $partCustomStateData = $data['partcustomstate']; - foreach ($partCustomStateData as $partCustomState) { - $customState = new PartCustomState(); - $customState->setName($partCustomState['name']); - - $this->setIDOfEntity($customState, $partCustomState['id']); - $this->em->persist($customState); - } - - $this->em->flush(); - - return is_countable($partCustomStateData) ? count($partCustomStateData) : 0; - } - public function importCategories(array $data): int { if (!isset($data['partcategory'])) { diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php index ab06a134..80c2dbf7 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php @@ -91,8 +91,6 @@ class PKPartImporter $this->setAssociationField($entity, 'partUnit', MeasurementUnit::class, $part['partUnit_id']); } - $this->setAssociationField($entity, 'partCustomState', MeasurementUnit::class, $part['partCustomState_id']); - //Create a part lot to store the stock level and location $lot = new PartLot(); $lot->setAmount((float) ($part['stockLevel'] ?? 0)); diff --git a/src/Services/InfoProviderSystem/BulkInfoProviderService.php b/src/Services/InfoProviderSystem/BulkInfoProviderService.php deleted file mode 100644 index 586fb873..00000000 --- a/src/Services/InfoProviderSystem/BulkInfoProviderService.php +++ /dev/null @@ -1,380 +0,0 @@ - Cache for normalized supplier names */ - private array $supplierCache = []; - - public function __construct( - private readonly PartInfoRetriever $infoRetriever, - private readonly ExistingPartFinder $existingPartFinder, - private readonly ProviderRegistry $providerRegistry, - private readonly EntityManagerInterface $entityManager, - private readonly LoggerInterface $logger - ) {} - - /** - * Perform bulk search across multiple parts and providers. - * - * @param Part[] $parts Array of parts to search for - * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mappings defining search strategy - * @param bool $prefetchDetails Whether to prefetch detailed information for results - * @return BulkSearchResponseDTO Structured response containing all search results - * @throws \InvalidArgumentException If no valid parts provided - * @throws \RuntimeException If no search results found for any parts - */ - public function performBulkSearch(array $parts, array $fieldMappings, bool $prefetchDetails = false): BulkSearchResponseDTO - { - if (empty($parts)) { - throw new \InvalidArgumentException('No valid parts found for bulk import'); - } - - $partResults = []; - $hasAnyResults = false; - - // Group providers by batch capability - $batchProviders = []; - $regularProviders = []; - - foreach ($fieldMappings as $mapping) { - foreach ($mapping->providers as $providerKey) { - if (!is_string($providerKey)) { - $this->logger->error('Invalid provider key type', [ - 'providerKey' => $providerKey, - 'type' => gettype($providerKey) - ]); - continue; - } - - $provider = $this->providerRegistry->getProviderByKey($providerKey); - if ($provider instanceof BatchInfoProviderInterface) { - $batchProviders[$providerKey] = $provider; - } else { - $regularProviders[$providerKey] = $provider; - } - } - } - - // Process batch providers first (more efficient) - $batchResults = $this->processBatchProviders($parts, $fieldMappings, $batchProviders); - - // Process regular providers - $regularResults = $this->processRegularProviders($parts, $fieldMappings, $regularProviders, $batchResults); - - // Combine and format results for each part - foreach ($parts as $part) { - $searchResults = []; - - // Get results from batch and regular processing - $allResults = array_merge( - $batchResults[$part->getId()] ?? [], - $regularResults[$part->getId()] ?? [] - ); - - if (!empty($allResults)) { - $hasAnyResults = true; - $searchResults = $this->formatSearchResults($allResults); - } - - $partResults[] = new BulkSearchPartResultsDTO( - part: $part, - searchResults: $searchResults, - errors: [] - ); - } - - if (!$hasAnyResults) { - throw new \RuntimeException('No search results found for any of the selected parts'); - } - - $response = new BulkSearchResponseDTO($partResults); - - // Prefetch details if requested - if ($prefetchDetails) { - $this->prefetchDetailsForResults($response); - } - - return $response; - } - - /** - * Process parts using batch-capable info providers. - * - * @param Part[] $parts Array of parts to search for - * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mapping configurations - * @param array $batchProviders Batch providers indexed by key - * @return array Results indexed by part ID - */ - private function processBatchProviders(array $parts, array $fieldMappings, array $batchProviders): array - { - $batchResults = []; - - foreach ($batchProviders as $providerKey => $provider) { - $keywords = $this->collectKeywordsForProvider($parts, $fieldMappings, $providerKey); - - if (empty($keywords)) { - continue; - } - - try { - $providerResults = $provider->searchByKeywordsBatch($keywords); - - // Map results back to parts - foreach ($parts as $part) { - foreach ($fieldMappings as $mapping) { - if (!in_array($providerKey, $mapping->providers, true)) { - continue; - } - - $keyword = $this->getKeywordFromField($part, $mapping->field); - if ($keyword && isset($providerResults[$keyword])) { - foreach ($providerResults[$keyword] as $dto) { - $batchResults[$part->getId()][] = new BulkSearchPartResultDTO( - searchResult: $dto, - sourceField: $mapping->field, - sourceKeyword: $keyword, - localPart: $this->existingPartFinder->findFirstExisting($dto), - priority: $mapping->priority - ); - } - } - } - } - } catch (\Exception $e) { - $this->logger->error('Batch search failed for provider ' . $providerKey, [ - 'error' => $e->getMessage(), - 'provider' => $providerKey - ]); - } - } - - return $batchResults; - } - - /** - * Process parts using regular (non-batch) info providers. - * - * @param Part[] $parts Array of parts to search for - * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mapping configurations - * @param array $regularProviders Regular providers indexed by key - * @param array $excludeResults Results to exclude (from batch processing) - * @return array Results indexed by part ID - */ - private function processRegularProviders(array $parts, array $fieldMappings, array $regularProviders, array $excludeResults): array - { - $regularResults = []; - - foreach ($parts as $part) { - $regularResults[$part->getId()] = []; - - // Skip if we already have batch results for this part - if (!empty($excludeResults[$part->getId()] ?? [])) { - continue; - } - - foreach ($fieldMappings as $mapping) { - $providers = array_intersect($mapping->providers, array_keys($regularProviders)); - - if (empty($providers)) { - continue; - } - - $keyword = $this->getKeywordFromField($part, $mapping->field); - if (!$keyword) { - continue; - } - - try { - $dtos = $this->infoRetriever->searchByKeyword($keyword, $providers); - - foreach ($dtos as $dto) { - $regularResults[$part->getId()][] = new BulkSearchPartResultDTO( - searchResult: $dto, - sourceField: $mapping->field, - sourceKeyword: $keyword, - localPart: $this->existingPartFinder->findFirstExisting($dto), - priority: $mapping->priority - ); - } - } catch (ClientException $e) { - $this->logger->error('Regular search failed', [ - 'part_id' => $part->getId(), - 'field' => $mapping->field, - 'error' => $e->getMessage() - ]); - } - } - } - - return $regularResults; - } - - /** - * Collect unique keywords for a specific provider from all parts and field mappings. - * - * @param Part[] $parts Array of parts to collect keywords from - * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mapping configurations - * @param string $providerKey The provider key to collect keywords for - * @return string[] Array of unique keywords - */ - private function collectKeywordsForProvider(array $parts, array $fieldMappings, string $providerKey): array - { - $keywords = []; - - foreach ($parts as $part) { - foreach ($fieldMappings as $mapping) { - if (!in_array($providerKey, $mapping->providers, true)) { - continue; - } - - $keyword = $this->getKeywordFromField($part, $mapping->field); - if ($keyword && !in_array($keyword, $keywords, true)) { - $keywords[] = $keyword; - } - } - } - - return $keywords; - } - - private function getKeywordFromField(Part $part, string $field): ?string - { - return match ($field) { - 'mpn' => $part->getManufacturerProductNumber(), - 'name' => $part->getName(), - default => $this->getSupplierPartNumber($part, $field) - }; - } - - private function getSupplierPartNumber(Part $part, string $field): ?string - { - if (!str_ends_with($field, '_spn')) { - return null; - } - - $supplierKey = substr($field, 0, -4); - $supplier = $this->getSupplierByNormalizedName($supplierKey); - - if (!$supplier) { - return null; - } - - $orderDetail = $part->getOrderdetails()->filter( - fn($od) => $od->getSupplier()?->getId() === $supplier->getId() - )->first(); - - return $orderDetail !== false ? $orderDetail->getSupplierpartnr() : null; - } - - /** - * Get supplier by normalized name with caching to prevent N+1 queries. - * - * @param string $normalizedKey The normalized supplier key to search for - * @return Supplier|null The matching supplier or null if not found - */ - private function getSupplierByNormalizedName(string $normalizedKey): ?Supplier - { - // Check cache first - if (isset($this->supplierCache[$normalizedKey])) { - return $this->supplierCache[$normalizedKey]; - } - - // Use efficient database query with PHP normalization - // Since DQL doesn't support REPLACE, we'll load all suppliers once and cache the normalization - if (empty($this->supplierCache)) { - $this->loadSuppliersIntoCache(); - } - - $supplier = $this->supplierCache[$normalizedKey] ?? null; - - // Cache the result (including null results to prevent repeated queries) - $this->supplierCache[$normalizedKey] = $supplier; - - return $supplier; - } - - /** - * Load all suppliers into cache with normalized names to avoid N+1 queries. - */ - private function loadSuppliersIntoCache(): void - { - /** @var Supplier[] $suppliers */ - $suppliers = $this->entityManager->getRepository(Supplier::class)->findAll(); - - foreach ($suppliers as $supplier) { - $normalizedName = strtolower(str_replace([' ', '-', '_'], '_', $supplier->getName())); - $this->supplierCache[$normalizedName] = $supplier; - } - } - - /** - * Format and deduplicate search results. - * - * @param BulkSearchPartResultDTO[] $bulkResults Array of bulk search results - * @return BulkSearchPartResultDTO[] Array of formatted search results with metadata - */ - private function formatSearchResults(array $bulkResults): array - { - // Sort by priority and remove duplicates - usort($bulkResults, fn($a, $b) => $a->priority <=> $b->priority); - - $uniqueResults = []; - $seenKeys = []; - - foreach ($bulkResults as $result) { - $key = "{$result->searchResult->provider_key}|{$result->searchResult->provider_id}"; - if (!in_array($key, $seenKeys, true)) { - $seenKeys[] = $key; - $uniqueResults[] = $result; - } - } - - return $uniqueResults; - } - - /** - * Prefetch detailed information for search results. - * - * @param BulkSearchResponseDTO $searchResults Search results (supports both new DTO and legacy array format) - */ - public function prefetchDetailsForResults(BulkSearchResponseDTO $searchResults): void - { - $prefetchCount = 0; - - // Handle both new DTO format and legacy array format for backwards compatibility - foreach ($searchResults->partResults as $partResult) { - foreach ($partResult->searchResults as $result) { - $dto = $result->searchResult; - - try { - $this->infoRetriever->getDetails($dto->provider_key, $dto->provider_id); - $prefetchCount++; - } catch (\Exception $e) { - $this->logger->warning('Failed to prefetch details for provider part', [ - 'provider_key' => $dto->provider_key, - 'provider_id' => $dto->provider_id, - 'error' => $e->getMessage() - ]); - } - } - } - - $this->logger->info("Prefetched details for {$prefetchCount} search results"); - } -} diff --git a/src/Services/InfoProviderSystem/DTOs/BulkSearchFieldMappingDTO.php b/src/Services/InfoProviderSystem/DTOs/BulkSearchFieldMappingDTO.php deleted file mode 100644 index 47d8ac69..00000000 --- a/src/Services/InfoProviderSystem/DTOs/BulkSearchFieldMappingDTO.php +++ /dev/null @@ -1,108 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Services\InfoProviderSystem\DTOs; - -use App\Services\InfoProviderSystem\Providers\InfoProviderInterface; - -/** - * Represents a mapping between a part field and the info providers that should search in that field. - */ -readonly class BulkSearchFieldMappingDTO -{ - /** @var string[] $providers Array of provider keys to search with (e.g., ['digikey', 'farnell']) */ - public array $providers; - - /** - * @param string $field The field to search in (e.g., 'mpn', 'name', or supplier-specific fields like 'digikey_spn') - * @param string[]|InfoProviderInterface[] $providers Array of provider keys to search with (e.g., ['digikey', 'farnell']) - * @param int $priority Priority for this field mapping (1-10, lower numbers = higher priority) - */ - public function __construct( - public string $field, - array $providers = [], - public int $priority = 1 - ) { - if ($priority < 1 || $priority > 10) { - throw new \InvalidArgumentException('Priority must be between 1 and 10'); - } - - //Ensure that providers are provided as keys - foreach ($providers as &$provider) { - if ($provider instanceof InfoProviderInterface) { - $provider = $provider->getProviderKey(); - } - if (!is_string($provider)) { - throw new \InvalidArgumentException('Providers must be provided as strings or InfoProviderInterface instances'); - } - } - unset($provider); - $this->providers = $providers; - } - - /** - * Create a FieldMappingDTO from legacy array format. - * @param array{field: string, providers: string[], priority?: int} $data - */ - public static function fromSerializableArray(array $data): self - { - return new self( - field: $data['field'], - providers: $data['providers'] ?? [], - priority: $data['priority'] ?? 1 - ); - } - - /** - * Convert this DTO to the legacy array format for backwards compatibility. - * @return array{field: string, providers: string[], priority: int} - */ - public function toSerializableArray(): array - { - return [ - 'field' => $this->field, - 'providers' => $this->providers, - 'priority' => $this->priority, - ]; - } - - /** - * Check if this field mapping is for a supplier part number field. - */ - public function isSupplierPartNumberField(): bool - { - return str_ends_with($this->field, '_spn'); - } - - /** - * Get the supplier key from a supplier part number field. - * Returns null if this is not a supplier part number field. - */ - public function getSupplierKey(): ?string - { - if (!$this->isSupplierPartNumberField()) { - return null; - } - - return substr($this->field, 0, -4); - } -} diff --git a/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultDTO.php b/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultDTO.php deleted file mode 100644 index d46624d4..00000000 --- a/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultDTO.php +++ /dev/null @@ -1,44 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Services\InfoProviderSystem\DTOs; - -use App\Entity\Parts\Part; - -/** - * Represents a single search result from bulk search with additional context information, like how the part was found. - */ -readonly class BulkSearchPartResultDTO -{ - public function __construct( - /** The base search result DTO containing provider data */ - public SearchResultDTO $searchResult, - /** The field that was used to find this result */ - public ?string $sourceField = null, - /** The actual keyword that was searched for */ - public ?string $sourceKeyword = null, - /** Local part that matches this search result, if any */ - public ?Part $localPart = null, - /** Priority for this search result */ - public int $priority = 1 - ) {} -} diff --git a/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultsDTO.php b/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultsDTO.php deleted file mode 100644 index 8614f4ec..00000000 --- a/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultsDTO.php +++ /dev/null @@ -1,83 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Services\InfoProviderSystem\DTOs; - -use App\Entity\Parts\Part; - -/** - * Represents the search results for a single part from bulk info provider search. - * It contains multiple search results, that match the part. - */ -readonly class BulkSearchPartResultsDTO -{ - /** - * @param Part $part The part that was searched for - * @param BulkSearchPartResultDTO[] $searchResults Array of search results found for this part - * @param string[] $errors Array of error messages encountered during search - */ - public function __construct( - public Part $part, - public array $searchResults = [], - public array $errors = [] - ) {} - - /** - * Check if this part has any search results. - */ - public function hasResults(): bool - { - return !empty($this->searchResults); - } - - /** - * Check if this part has any errors. - */ - public function hasErrors(): bool - { - return !empty($this->errors); - } - - /** - * Get the number of search results for this part. - */ - public function getResultCount(): int - { - return count($this->searchResults); - } - - public function getErrorCount(): int - { - return count($this->errors); - } - - /** - * Get search results sorted by priority (ascending). - * @return BulkSearchPartResultDTO[] - */ - public function getResultsSortedByPriority(): array - { - $results = $this->searchResults; - usort($results, static fn(BulkSearchPartResultDTO $a, BulkSearchPartResultDTO $b) => $a->priority <=> $b->priority); - return $results; - } -} diff --git a/src/Services/InfoProviderSystem/DTOs/BulkSearchResponseDTO.php b/src/Services/InfoProviderSystem/DTOs/BulkSearchResponseDTO.php deleted file mode 100644 index 58e9e240..00000000 --- a/src/Services/InfoProviderSystem/DTOs/BulkSearchResponseDTO.php +++ /dev/null @@ -1,231 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Services\InfoProviderSystem\DTOs; - -use App\Entity\Parts\Part; -use Doctrine\ORM\EntityManagerInterface; -use Traversable; - -/** - * Represents the complete response from a bulk info provider search operation. - * It contains a list of PartSearchResultDTOs, one for each part searched. - */ -readonly class BulkSearchResponseDTO implements \ArrayAccess, \IteratorAggregate -{ - /** - * @param BulkSearchPartResultsDTO[] $partResults Array of search results for each part - */ - public function __construct( - public array $partResults - ) {} - - /** - * Replaces the search results for a specific part, and returns a new instance. - * The part to replaced, is identified by the part property of the new_results parameter. - * The original instance remains unchanged. - * @param BulkSearchPartResultsDTO $new_results - * @return BulkSearchResponseDTO - */ - public function replaceResultsForPart(BulkSearchPartResultsDTO $new_results): self - { - $array = $this->partResults; - $replaced = false; - foreach ($array as $index => $partResult) { - if ($partResult->part === $new_results->part) { - $array[$index] = $new_results; - $replaced = true; - break; - } - } - - if (!$replaced) { - throw new \InvalidArgumentException("Part not found in existing results."); - } - - return new self($array); - } - - /** - * Check if any parts have search results. - */ - public function hasAnyResults(): bool - { - foreach ($this->partResults as $partResult) { - if ($partResult->hasResults()) { - return true; - } - } - return false; - } - - /** - * Get the total number of search results across all parts. - */ - public function getTotalResultCount(): int - { - $count = 0; - foreach ($this->partResults as $partResult) { - $count += $partResult->getResultCount(); - } - return $count; - } - - /** - * Get all parts that have search results. - * @return BulkSearchPartResultsDTO[] - */ - public function getPartsWithResults(): array - { - return array_filter($this->partResults, fn($result) => $result->hasResults()); - } - - /** - * Get all parts that have errors. - * @return BulkSearchPartResultsDTO[] - */ - public function getPartsWithErrors(): array - { - return array_filter($this->partResults, fn($result) => $result->hasErrors()); - } - - /** - * Get the number of parts processed. - */ - public function getPartCount(): int - { - return count($this->partResults); - } - - /** - * Get the number of parts with successful results. - */ - public function getSuccessfulPartCount(): int - { - return count($this->getPartsWithResults()); - } - - /** - * Merge multiple BulkSearchResponseDTO instances into one. - * @param BulkSearchResponseDTO ...$responses - * @return BulkSearchResponseDTO - */ - public static function merge(BulkSearchResponseDTO ...$responses): BulkSearchResponseDTO - { - $mergedResults = []; - foreach ($responses as $response) { - foreach ($response->partResults as $partResult) { - $mergedResults[] = $partResult; - } - } - return new BulkSearchResponseDTO($mergedResults); - } - - /** - * Convert this DTO to a serializable representation suitable for storage in the database - * @return array - */ - public function toSerializableRepresentation(): array - { - $serialized = []; - - foreach ($this->partResults as $partResult) { - $partData = [ - 'part_id' => $partResult->part->getId(), - 'search_results' => [], - 'errors' => $partResult->errors ?? [] - ]; - - foreach ($partResult->searchResults as $result) { - $partData['search_results'][] = [ - 'dto' => $result->searchResult->toNormalizedSearchResultArray(), - 'source_field' => $result->sourceField ?? null, - 'source_keyword' => $result->sourceKeyword ?? null, - 'localPart' => $result->localPart?->getId(), - 'priority' => $result->priority - ]; - } - - $serialized[] = $partData; - } - - return $serialized; - } - - /** - * Creates a BulkSearchResponseDTO from a serializable representation. - * @param array $data - * @param EntityManagerInterface $entityManager - * @return BulkSearchResponseDTO - * @throws \Doctrine\ORM\Exception\ORMException - */ - public static function fromSerializableRepresentation(array $data, EntityManagerInterface $entityManager): BulkSearchResponseDTO - { - $partResults = []; - foreach ($data as $partData) { - $partResults[] = new BulkSearchPartResultsDTO( - part: $entityManager->getReference(Part::class, $partData['part_id']), - searchResults: array_map(fn($result) => new BulkSearchPartResultDTO( - searchResult: SearchResultDTO::fromNormalizedSearchResultArray($result['dto']), - sourceField: $result['source_field'] ?? null, - sourceKeyword: $result['source_keyword'] ?? null, - localPart: isset($result['localPart']) ? $entityManager->getReference(Part::class, $result['localPart']) : null, - priority: $result['priority'] ?? null - ), $partData['search_results'] ?? []), - errors: $partData['errors'] ?? [] - ); - } - - return new BulkSearchResponseDTO($partResults); - } - - public function offsetExists(mixed $offset): bool - { - if (!is_int($offset)) { - throw new \InvalidArgumentException("Offset must be an integer."); - } - return isset($this->partResults[$offset]); - } - - public function offsetGet(mixed $offset): ?BulkSearchPartResultsDTO - { - if (!is_int($offset)) { - throw new \InvalidArgumentException("Offset must be an integer."); - } - return $this->partResults[$offset] ?? null; - } - - public function offsetSet(mixed $offset, mixed $value): void - { - throw new \LogicException("BulkSearchResponseDTO is immutable."); - } - - public function offsetUnset(mixed $offset): void - { - throw new \LogicException('BulkSearchResponseDTO is immutable.'); - } - - public function getIterator(): Traversable - { - return new \ArrayIterator($this->partResults); - } -} diff --git a/src/Services/InfoProviderSystem/DTOs/FileDTO.php b/src/Services/InfoProviderSystem/DTOs/FileDTO.php index 84eed0c9..0d1db76a 100644 --- a/src/Services/InfoProviderSystem/DTOs/FileDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/FileDTO.php @@ -28,12 +28,12 @@ namespace App\Services\InfoProviderSystem\DTOs; * This could be a datasheet, a 3D model, a picture or similar. * @see \App\Tests\Services\InfoProviderSystem\DTOs\FileDTOTest */ -readonly class FileDTO +class FileDTO { /** * @var string The URL where to get this file */ - public string $url; + public readonly string $url; /** * @param string $url The URL where to get this file @@ -41,7 +41,7 @@ readonly class FileDTO */ public function __construct( string $url, - public ?string $name = null, + public readonly ?string $name = null, ) { //Find all occurrences of non URL safe characters and replace them with their URL encoded version. //We only want to replace characters which can not have a valid meaning in a URL (what would break the URL). @@ -50,4 +50,4 @@ readonly class FileDTO } -} +} \ No newline at end of file diff --git a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php index f5868039..0b54d1a9 100644 --- a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php @@ -28,17 +28,17 @@ namespace App\Services\InfoProviderSystem\DTOs; * This could be a voltage, a current, a temperature or similar. * @see \App\Tests\Services\InfoProviderSystem\DTOs\ParameterDTOTest */ -readonly class ParameterDTO +class ParameterDTO { public function __construct( - public string $name, - public ?string $value_text = null, - public ?float $value_typ = null, - public ?float $value_min = null, - public ?float $value_max = null, - public ?string $unit = null, - public ?string $symbol = null, - public ?string $group = null, + public readonly string $name, + public readonly ?string $value_text = null, + public readonly ?float $value_typ = null, + public readonly ?float $value_min = null, + public readonly ?float $value_max = null, + public readonly ?string $unit = null, + public readonly ?string $symbol = null, + public readonly ?string $group = null, ) { } diff --git a/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php b/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php index 41d50510..9f365f1e 100644 --- a/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php @@ -70,4 +70,4 @@ class PartDetailDTO extends SearchResultDTO footprint: $footprint, ); } -} +} \ No newline at end of file diff --git a/src/Services/InfoProviderSystem/DTOs/PriceDTO.php b/src/Services/InfoProviderSystem/DTOs/PriceDTO.php index 2acf3e57..f1eb28f7 100644 --- a/src/Services/InfoProviderSystem/DTOs/PriceDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PriceDTO.php @@ -28,21 +28,21 @@ use Brick\Math\BigDecimal; /** * This DTO represents a price for a single unit in a certain discount range */ -readonly class PriceDTO +class PriceDTO { - private BigDecimal $price_as_big_decimal; + private readonly BigDecimal $price_as_big_decimal; public function __construct( /** @var float The minimum amount that needs to get ordered for this price to be valid */ - public float $minimum_discount_amount, + public readonly float $minimum_discount_amount, /** @var string The price as string (with .) */ - public string $price, + public readonly string $price, /** @var string The currency of the used ISO code of this price detail */ - public ?string $currency_iso_code, + public readonly ?string $currency_iso_code, /** @var bool If the price includes tax */ - public ?bool $includes_tax = true, + public readonly ?bool $includes_tax = true, /** @var float the price related quantity */ - public ?float $price_related_quantity = 1.0, + public readonly ?float $price_related_quantity = 1.0, ) { $this->price_as_big_decimal = BigDecimal::of($this->price); diff --git a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php index 9ac142ff..bcd8be43 100644 --- a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php @@ -27,15 +27,15 @@ 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 */ -readonly class PurchaseInfoDTO +class PurchaseInfoDTO { public function __construct( - public string $distributor_name, - public string $order_number, + public readonly string $distributor_name, + public readonly string $order_number, /** @var PriceDTO[] */ - public array $prices, + public readonly array $prices, /** @var string|null An url to the product page of the vendor */ - public ?string $product_url = null, + public readonly ?string $product_url = null, ) { //Ensure that the prices are PriceDTO instances @@ -45,4 +45,4 @@ readonly class PurchaseInfoDTO } } } -} +} \ No newline at end of file diff --git a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php index a70b2486..28943702 100644 --- a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php @@ -59,8 +59,8 @@ class SearchResultDTO public readonly ?string $provider_url = null, /** @var string|null A footprint representation of the providers page */ public readonly ?string $footprint = null, - ) - { + ) { + if ($preview_image_url !== null) { //Utilize the escaping mechanism of FileDTO to ensure that the preview image URL is correctly encoded //See issue #521: https://github.com/Part-DB/Part-DB-server/issues/521 @@ -71,47 +71,4 @@ class SearchResultDTO $this->preview_image_url = null; } } - - /** - * This method creates a normalized array representation of the DTO. - * @return array - */ - public function toNormalizedSearchResultArray(): array - { - return [ - 'provider_key' => $this->provider_key, - 'provider_id' => $this->provider_id, - 'name' => $this->name, - 'description' => $this->description, - 'category' => $this->category, - 'manufacturer' => $this->manufacturer, - 'mpn' => $this->mpn, - 'preview_image_url' => $this->preview_image_url, - 'manufacturing_status' => $this->manufacturing_status?->value, - 'provider_url' => $this->provider_url, - 'footprint' => $this->footprint, - ]; - } - - /** - * Creates a SearchResultDTO from a normalized array representation. - * @param array $data - * @return self - */ - public static function fromNormalizedSearchResultArray(array $data): self - { - return new self( - provider_key: $data['provider_key'], - provider_id: $data['provider_id'], - name: $data['name'], - description: $data['description'], - category: $data['category'] ?? null, - manufacturer: $data['manufacturer'] ?? null, - mpn: $data['mpn'] ?? null, - preview_image_url: $data['preview_image_url'] ?? null, - manufacturing_status: isset($data['manufacturing_status']) ? ManufacturingStatus::tryFrom($data['manufacturing_status']) : null, - provider_url: $data['provider_url'] ?? null, - footprint: $data['footprint'] ?? null, - ); - } -} +} \ No newline at end of file diff --git a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php index a655a0df..d09f1d05 100644 --- a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php +++ b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php @@ -221,7 +221,7 @@ final class DTOtoEntityConverter $attachment = $this->convertFile($image, $image_type); $attachments_grouped[$attachment->getName()][] = $attachment; - if (count($attachments_grouped[$attachment->getName()]) > 1) { + if (count($attachments_grouped[$attachment->getName()] ?? []) > 1) { $attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()]) + 1) . ')'); } @@ -236,7 +236,7 @@ final class DTOtoEntityConverter $attachment = $this->convertFile($datasheet, $datasheet_type); $attachments_grouped[$attachment->getName()][] = $attachment; - if (count($attachments_grouped[$attachment->getName()]) > 1) { + if (count($attachments_grouped[$attachment->getName()] ?? []) > 1) { $attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()])) . ')'); } @@ -357,4 +357,4 @@ final class DTOtoEntityConverter return $tmp; } -} +} \ No newline at end of file diff --git a/src/Services/InfoProviderSystem/Providers/BatchInfoProviderInterface.php b/src/Services/InfoProviderSystem/Providers/BatchInfoProviderInterface.php deleted file mode 100644 index 549f117a..00000000 --- a/src/Services/InfoProviderSystem/Providers/BatchInfoProviderInterface.php +++ /dev/null @@ -1,40 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\Services\InfoProviderSystem\Providers; - -use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; - -/** - * This interface marks a provider as a info provider which can provide information directly in batch operations - */ -interface BatchInfoProviderInterface extends InfoProviderInterface -{ - /** - * Search for multiple keywords in a single batch operation and return the results, ordered by the keywords. - * This allows for a more efficient search compared to running multiple single searches. - * @param string[] $keywords - * @return array An associative array where the key is the keyword and the value is the search results for that keyword - */ - public function searchByKeywordsBatch(array $keywords): array; -} diff --git a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php index 423b5244..51f460e4 100644 --- a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php +++ b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace App\Services\InfoProviderSystem\Providers; use App\Entity\Parts\ManufacturingStatus; -use App\Exceptions\OAuthReconnectRequiredException; use App\Services\InfoProviderSystem\DTOs\FileDTO; use App\Services\InfoProviderSystem\DTOs\ParameterDTO; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; @@ -118,22 +117,12 @@ class DigikeyProvider implements InfoProviderInterface ]; //$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [ - try { - $response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [ - 'json' => $request, - 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) - ]); - - $response_array = $response->toArray(); - } catch (\InvalidArgumentException $exception) { - //Check if the exception was caused by an invalid or expired token - if (str_contains($exception->getMessage(), 'access_token')) { - throw OAuthReconnectRequiredException::forProvider($this->getProviderKey()); - } - - throw $exception; - } + $response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [ + 'json' => $request, + 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) + ]); + $response_array = $response->toArray(); $result = []; @@ -161,18 +150,9 @@ class DigikeyProvider implements InfoProviderInterface public function getDetails(string $id): PartDetailDTO { - try { - $response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [ - 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) - ]); - } catch (\InvalidArgumentException $exception) { - //Check if the exception was caused by an invalid or expired token - if (str_contains($exception->getMessage(), 'access_token')) { - throw OAuthReconnectRequiredException::forProvider($this->getProviderKey()); - } - - throw $exception; - } + $response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [ + 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) + ]); $response_array = $response->toArray(); $product = $response_array['Product']; diff --git a/src/Services/InfoProviderSystem/Providers/EmptyProvider.php b/src/Services/InfoProviderSystem/Providers/EmptyProvider.php deleted file mode 100644 index e0de9772..00000000 --- a/src/Services/InfoProviderSystem/Providers/EmptyProvider.php +++ /dev/null @@ -1,76 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\Services\InfoProviderSystem\Providers; - -use App\Services\InfoProviderSystem\DTOs\FileDTO; -use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; -use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; -use Symfony\Component\DependencyInjection\Attribute\When; - -/** - * This is a provider, which is used during tests. It always returns no results. - */ -#[When(env: 'test')] -class EmptyProvider implements InfoProviderInterface -{ - public function getProviderInfo(): array - { - return [ - 'name' => 'Empty Provider', - 'description' => 'This is a test provider', - //'url' => 'https://example.com', - 'disabled_help' => 'This provider is disabled for testing purposes' - ]; - } - - public function getProviderKey(): string - { - return 'empty'; - } - - public function isActive(): bool - { - return true; - } - - public function searchByKeyword(string $keyword): array - { - return [ - - ]; - } - - public function getCapabilities(): array - { - return [ - ProviderCapabilities::BASIC, - ProviderCapabilities::FOOTPRINT, - ]; - } - - public function getDetails(string $id): PartDetailDTO - { - throw new \RuntimeException('No part details available'); - } -} diff --git a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php index ede34eb8..2d83fc7c 100755 --- a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php +++ b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php @@ -33,7 +33,7 @@ use App\Settings\InfoProviderSystem\LCSCSettings; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Contracts\HttpClient\HttpClientInterface; -class LCSCProvider implements BatchInfoProviderInterface +class LCSCProvider implements InfoProviderInterface { private const ENDPOINT_URL = 'https://wmsc.lcsc.com/ftps/wm'; @@ -69,10 +69,9 @@ class LCSCProvider implements BatchInfoProviderInterface /** * @param string $id - * @param bool $lightweight If true, skip expensive operations like datasheet resolution * @return PartDetailDTO */ - private function queryDetail(string $id, bool $lightweight = false): PartDetailDTO + private function queryDetail(string $id): PartDetailDTO { $response = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/product/detail", [ 'headers' => [ @@ -90,7 +89,7 @@ class LCSCProvider implements BatchInfoProviderInterface throw new \RuntimeException('Could not find product code: ' . $id); } - return $this->getPartDetail($product, $lightweight); + return $this->getPartDetail($product); } /** @@ -100,42 +99,30 @@ class LCSCProvider implements BatchInfoProviderInterface private function getRealDatasheetUrl(?string $url): string { if ($url !== null && trim($url) !== '' && preg_match("/^https:\/\/(datasheet\.lcsc\.com|www\.lcsc\.com\/datasheet)\/.*(C\d+)\.pdf$/", $url, $matches) > 0) { - if (preg_match("/^https:\/\/datasheet\.lcsc\.com\/lcsc\/(.*\.pdf)$/", $url, $rewriteMatches) > 0) { - $url = 'https://www.lcsc.com/datasheet/lcsc_datasheet_' . $rewriteMatches[1]; - } - $response = $this->lcscClient->request('GET', $url, [ - 'headers' => [ - 'Referer' => 'https://www.lcsc.com/product-detail/_' . $matches[2] . '.html' - ], - ]); - if (preg_match('/(previewPdfUrl): ?("[^"]+wmsc\.lcsc\.com[^"]+\.pdf")/', $response->getContent(), $matches) > 0) { - //HACKY: The URL string contains escaped characters like \u002F, etc. To decode it, the JSON decoding is reused - //See https://github.com/Part-DB/Part-DB-server/pull/582#issuecomment-2033125934 - $jsonObj = json_decode('{"' . $matches[1] . '": ' . $matches[2] . '}'); - $url = $jsonObj->previewPdfUrl; - } + if (preg_match("/^https:\/\/datasheet\.lcsc\.com\/lcsc\/(.*\.pdf)$/", $url, $rewriteMatches) > 0) { + $url = 'https://www.lcsc.com/datasheet/lcsc_datasheet_' . $rewriteMatches[1]; + } + $response = $this->lcscClient->request('GET', $url, [ + 'headers' => [ + 'Referer' => 'https://www.lcsc.com/product-detail/_' . $matches[2] . '.html' + ], + ]); + if (preg_match('/(previewPdfUrl): ?("[^"]+wmsc\.lcsc\.com[^"]+\.pdf")/', $response->getContent(), $matches) > 0) { + //HACKY: The URL string contains escaped characters like \u002F, etc. To decode it, the JSON decoding is reused + //See https://github.com/Part-DB/Part-DB-server/pull/582#issuecomment-2033125934 + $jsonObj = json_decode('{"' . $matches[1] . '": ' . $matches[2] . '}'); + $url = $jsonObj->previewPdfUrl; + } } return $url; } /** * @param string $term - * @param bool $lightweight If true, skip expensive operations like datasheet resolution * @return PartDetailDTO[] */ - private function queryByTerm(string $term, bool $lightweight = false): array + private function queryByTerm(string $term): array { - // Optimize: If term looks like an LCSC part number (starts with C followed by digits), - // use direct detail query instead of slower search - if (preg_match('/^C\d+$/i', trim($term))) { - try { - return [$this->queryDetail(trim($term), $lightweight)]; - } catch (\Exception $e) { - // If direct lookup fails, fall back to search - // This handles cases where the C-code might not exist - } - } - $response = $this->lcscClient->request('POST', self::ENDPOINT_URL . "/search/v2/global", [ 'headers' => [ 'Cookie' => new Cookie('currencyCode', $this->settings->currency) @@ -158,11 +145,11 @@ class LCSCProvider implements BatchInfoProviderInterface // 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) { - $result[] = $this->queryDetail($tipProductCode, $lightweight); + $result[] = $this->queryDetail($tipProductCode); } foreach ($products as $product) { - $result[] = $this->getPartDetail($product, $lightweight); + $result[] = $this->getPartDetail($product); } return $result; @@ -191,7 +178,7 @@ class LCSCProvider implements BatchInfoProviderInterface * @param array $product * @return PartDetailDTO */ - private function getPartDetail(array $product, bool $lightweight = false): PartDetailDTO + private function getPartDetail(array $product): PartDetailDTO { // Get product images in advance $product_images = $this->getProductImages($product['productImages'] ?? null); @@ -227,10 +214,10 @@ class LCSCProvider implements BatchInfoProviderInterface manufacturing_status: null, provider_url: $this->getProductShortURL($product['productCode']), footprint: $this->sanitizeField($footprint), - datasheets: $lightweight ? [] : $this->getProductDatasheets($product['pdfUrl'] ?? null), - images: $product_images, // Always include images - users need to see them - parameters: $lightweight ? [] : $this->attributesToParameters($product['paramVOList'] ?? []), - vendor_infos: $lightweight ? [] : $this->pricesToVendorInfo($product['productCode'], $this->getProductShortURL($product['productCode']), $product['productPriceList'] ?? []), + datasheets: $this->getProductDatasheets($product['pdfUrl'] ?? null), + images: $product_images, + parameters: $this->attributesToParameters($product['paramVOList'] ?? []), + vendor_infos: $this->pricesToVendorInfo($product['productCode'], $this->getProductShortURL($product['productCode']), $product['productPriceList'] ?? []), mass: $product['weight'] ?? null, ); } @@ -299,7 +286,7 @@ class LCSCProvider implements BatchInfoProviderInterface */ private function getProductShortURL(string $product_code): string { - return 'https://www.lcsc.com/product-detail/' . $product_code . '.html'; + return 'https://www.lcsc.com/product-detail/' . $product_code .'.html'; } /** @@ -340,7 +327,7 @@ class LCSCProvider implements BatchInfoProviderInterface //Skip this attribute if it's empty if (in_array(trim((string) $attribute['paramValueEn']), ['', '-'], true)) { - continue; + continue; } $result[] = ParameterDTO::parseValueIncludingUnit(name: $attribute['paramNameEn'], value: $attribute['paramValueEn'], group: null); @@ -351,86 +338,12 @@ class LCSCProvider implements BatchInfoProviderInterface public function searchByKeyword(string $keyword): array { - return $this->queryByTerm($keyword, true); // Use lightweight mode for search - } - - /** - * Batch search multiple keywords asynchronously (like JavaScript Promise.all) - * @param array $keywords Array of keywords to search - * @return array Results indexed by keyword - */ - public function searchByKeywordsBatch(array $keywords): array - { - if (empty($keywords)) { - return []; - } - - $responses = []; - $results = []; - - // Start all requests immediately (like JavaScript promises without await) - foreach ($keywords as $keyword) { - if (preg_match('/^C\d+$/i', trim($keyword))) { - // Direct detail API call for C-codes - $responses[$keyword] = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/product/detail", [ - 'headers' => [ - 'Cookie' => new Cookie('currencyCode', $this->settings->currency) - ], - 'query' => [ - 'productCode' => trim($keyword), - ], - ]); - } else { - // Search API call for other terms - $responses[$keyword] = $this->lcscClient->request('POST', self::ENDPOINT_URL . "/search/v2/global", [ - 'headers' => [ - 'Cookie' => new Cookie('currencyCode', $this->settings->currency) - ], - 'json' => [ - 'keyword' => $keyword, - ], - ]); - } - } - - // Now collect all results (like .then() in JavaScript) - foreach ($responses as $keyword => $response) { - try { - $arr = $response->toArray(); // This waits for the response - $results[$keyword] = $this->processSearchResponse($arr, $keyword); - } catch (\Exception $e) { - $results[$keyword] = []; // Empty results on error - } - } - - return $results; - } - - private function processSearchResponse(array $arr, string $keyword): array - { - $result = []; - - // Check if this looks like a detail response (direct C-code lookup) - if (isset($arr['result']['productCode'])) { - $product = $arr['result']; - $result[] = $this->getPartDetail($product, true); // lightweight mode - } else { - // This is a search response - $products = $arr['result']['productSearchResultVO']['productList'] ?? []; - $tipProductCode = $arr['result']['tipProductDetailUrlVO']['productCode'] ?? null; - - // If no products but has tip, we'd need another API call - skip for batch mode - foreach ($products as $product) { - $result[] = $this->getPartDetail($product, true); // lightweight mode - } - } - - return $result; + return $this->queryByTerm($keyword); } public function getDetails(string $id): PartDetailDTO { - $tmp = $this->queryByTerm($id, false); + $tmp = $this->queryByTerm($id); if (count($tmp) === 0) { throw new \RuntimeException('No part found with ID ' . $id); } diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index 3171c994..6639e5c1 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -132,15 +132,6 @@ class MouserProvider implements InfoProviderInterface ], ]); - // Check for API errors before processing response - if ($response->getStatusCode() !== 200) { - throw new \RuntimeException(sprintf( - 'Mouser API returned HTTP %d: %s', - $response->getStatusCode(), - $response->getContent(false) - )); - } - return $this->responseToDTOArray($response); } @@ -178,16 +169,6 @@ class MouserProvider implements InfoProviderInterface ] ], ]); - - // Check for API errors before processing response - if ($response->getStatusCode() !== 200) { - throw new \RuntimeException(sprintf( - 'Mouser API returned HTTP %d: %s', - $response->getStatusCode(), - $response->getContent(false) - )); - } - $tmp = $this->responseToDTOArray($response); //Ensure that we have exactly one result @@ -305,17 +286,6 @@ class MouserProvider implements InfoProviderInterface return (float)$val; } - private function mapCurrencyCode(string $currency): string - { - //Mouser uses "RMB" for Chinese Yuan, but the correct ISO code is "CNY" - if ($currency === "RMB") { - return "CNY"; - } - - //For all other currencies, we assume that the ISO code is correct - return $currency; - } - /** * Converts the pricing (StandardPricing field) from the Mouser API to an array of PurchaseInfoDTOs * @param array $price_breaks @@ -332,7 +302,7 @@ class MouserProvider implements InfoProviderInterface $prices[] = new PriceDTO( minimum_discount_amount: $price_break['Quantity'], price: (string)$number, - currency_iso_code: $this->mapCurrencyCode($price_break['Currency']) + currency_iso_code: $price_break['Currency'] ); } diff --git a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php index 3df7d227..7ceb30dd 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php @@ -95,11 +95,6 @@ final class BarcodeContentGenerator return $prefix.$id; } - /** - * @param array $map - * @param object $target - * @return string - */ private function classToString(array $map, object $target): string { $class = $target::class; diff --git a/src/Services/LabelSystem/SandboxedTwigFactory.php b/src/Services/LabelSystem/SandboxedTwigFactory.php index d5e09fa5..d6ea6968 100644 --- a/src/Services/LabelSystem/SandboxedTwigFactory.php +++ b/src/Services/LabelSystem/SandboxedTwigFactory.php @@ -133,7 +133,7 @@ final class SandboxedTwigFactory Supplier::class => ['getShippingCosts', 'getDefaultCurrency'], Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getIpn', 'getProviderReference', 'getDescription', 'getComment', 'isFavorite', 'getCategory', 'getFootprint', - 'getPartLots', 'getPartUnit', 'getPartCustomState', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum', + 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum', 'getManufacturerProductUrl', 'getCustomProductURL', 'getManufacturingStatus', 'getManufacturer', 'getManufacturerProductNumber', 'getOrderdetails', 'isObsolete', 'getParameters', 'getGroupedParameters', diff --git a/src/Services/Parts/PartsTableActionHandler.php b/src/Services/Parts/PartsTableActionHandler.php index 945cff7b..616df229 100644 --- a/src/Services/Parts/PartsTableActionHandler.php +++ b/src/Services/Parts/PartsTableActionHandler.php @@ -30,11 +30,13 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; +use App\Repository\PartRepository; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Contracts\Translation\TranslatableInterface; use function Symfony\Component\Translation\t; @@ -98,7 +100,7 @@ implode(',', array_map(static fn (PartLot $lot) => $lot->getID(), $part->getPart //When action starts with "export_" we have to redirect to the export controller $matches = []; - if (preg_match('/^export_(json|yaml|xml|csv|xlsx)$/', $action, $matches)) { + if (preg_match('/^export_(json|yaml|xml|csv)$/', $action, $matches)) { $ids = implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts)); $level = match ($target_id) { 2 => 'extended', @@ -117,16 +119,6 @@ implode(',', array_map(static fn (PartLot $lot) => $lot->getID(), $part->getPart ); } - if ($action === 'bulk_info_provider_import') { - $ids = implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts)); - return new RedirectResponse( - $this->urlGenerator->generate('bulk_info_provider_step1', [ - 'ids' => $ids, - '_redirect' => $redirect_url - ]) - ); - } - //Iterate over the parts and apply the action to it: foreach ($selected_parts as $part) { diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php index a541c29d..269c7e4c 100644 --- a/src/Services/ProjectSystem/ProjectBuildHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -31,9 +31,9 @@ use App\Services\Parts\PartLotWithdrawAddHelper; /** * @see \App\Tests\Services\ProjectSystem\ProjectBuildHelperTest */ -final readonly class ProjectBuildHelper +class ProjectBuildHelper { - public function __construct(private PartLotWithdrawAddHelper $withdraw_add_helper) + public function __construct(private readonly PartLotWithdrawAddHelper $withdraw_add_helper) { } @@ -63,35 +63,18 @@ final readonly class ProjectBuildHelper */ public function getMaximumBuildableCount(Project $project): int { - $bom_entries = $project->getBomEntries(); - if ($bom_entries->isEmpty()) { - return 0; - } $maximum_buildable_count = PHP_INT_MAX; - foreach ($bom_entries as $bom_entry) { + foreach ($project->getBomEntries() as $bom_entry) { //Skip BOM entries without a part (as we can not determine that) if (!$bom_entry->isPartBomEntry()) { continue; } + //The maximum buildable count for the whole project is the minimum of all BOM entries $maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry)); } - return $maximum_buildable_count; - } - /** - * Returns the maximum buildable amount of the given project as string, based on the stock of the used parts in the BOM. - * If the maximum buildable count is infinite, the string '∞' is returned. - * @param Project $project - * @return string - */ - public function getMaximumBuildableCountAsString(Project $project): string - { - $max_count = $this->getMaximumBuildableCount($project); - if ($max_count === PHP_INT_MAX) { - return '∞'; - } - return (string) $max_count; + return $maximum_buildable_count; } /** diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index 37a09b09..f7a9d1c4 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -29,7 +29,6 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\PartCustomState; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; @@ -38,7 +37,6 @@ use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use App\Helpers\Trees\TreeViewNode; use App\Services\Cache\UserCacheKeyGenerator; -use App\Services\ElementTypeNameGenerator; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Contracts\Cache\ItemInterface; @@ -51,14 +49,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class ToolsTreeBuilder { - public function __construct( - protected TranslatorInterface $translator, - protected UrlGeneratorInterface $urlGenerator, - protected TagAwareCacheInterface $cache, - protected UserCacheKeyGenerator $keyGenerator, - protected Security $security, - private readonly ElementTypeNameGenerator $elementTypeNameGenerator, - ) { + public function __construct(protected TranslatorInterface $translator, protected UrlGeneratorInterface $urlGenerator, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator, protected Security $security) + { } /** @@ -146,11 +138,6 @@ class ToolsTreeBuilder $this->translator->trans('info_providers.search.title'), $this->urlGenerator->generate('info_providers_search') ))->setIcon('fa-treeview fa-fw fa-solid fa-cloud-arrow-down'); - - $nodes[] = (new TreeViewNode( - $this->translator->trans('info_providers.bulk_import.manage_jobs'), - $this->urlGenerator->generate('bulk_info_provider_manage') - ))->setIcon('fa-treeview fa-fw fa-solid fa-tasks'); } return $nodes; @@ -167,70 +154,64 @@ class ToolsTreeBuilder if ($this->security->isGranted('read', new AttachmentType())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(AttachmentType::class), + $this->translator->trans('tree.tools.edit.attachment_types'), $this->urlGenerator->generate('attachment_type_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-file-alt'); } if ($this->security->isGranted('read', new Category())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(Category::class), + $this->translator->trans('tree.tools.edit.categories'), $this->urlGenerator->generate('category_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-tags'); } if ($this->security->isGranted('read', new Project())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(Project::class), + $this->translator->trans('tree.tools.edit.projects'), $this->urlGenerator->generate('project_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-archive'); } if ($this->security->isGranted('read', new Supplier())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(Supplier::class), + $this->translator->trans('tree.tools.edit.suppliers'), $this->urlGenerator->generate('supplier_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-truck'); } if ($this->security->isGranted('read', new Manufacturer())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(Manufacturer::class), + $this->translator->trans('tree.tools.edit.manufacturer'), $this->urlGenerator->generate('manufacturer_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-industry'); } if ($this->security->isGranted('read', new StorageLocation())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(StorageLocation::class), + $this->translator->trans('tree.tools.edit.storelocation'), $this->urlGenerator->generate('store_location_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-cube'); } if ($this->security->isGranted('read', new Footprint())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(Footprint::class), + $this->translator->trans('tree.tools.edit.footprint'), $this->urlGenerator->generate('footprint_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-microchip'); } if ($this->security->isGranted('read', new Currency())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(Currency::class), + $this->translator->trans('tree.tools.edit.currency'), $this->urlGenerator->generate('currency_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-coins'); } if ($this->security->isGranted('read', new MeasurementUnit())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(MeasurementUnit::class), + $this->translator->trans('tree.tools.edit.measurement_unit'), $this->urlGenerator->generate('measurement_unit_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-balance-scale'); } if ($this->security->isGranted('read', new LabelProfile())) { $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(LabelProfile::class), + $this->translator->trans('tree.tools.edit.label_profile'), $this->urlGenerator->generate('label_profile_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-qrcode'); } - if ($this->security->isGranted('read', new PartCustomState())) { - $nodes[] = (new TreeViewNode( - $this->elementTypeNameGenerator->typeLabelPlural(PartCustomState::class), - $this->urlGenerator->generate('part_custom_state_new') - ))->setIcon('fa-fw fa-treeview fa-solid fa-tools'); - } if ($this->security->isGranted('create', new Part())) { $nodes[] = (new TreeViewNode( $this->translator->trans('tree.tools.edit.part'), diff --git a/src/Services/Trees/TreeViewGenerator.php b/src/Services/Trees/TreeViewGenerator.php index d55c87b7..73ffa5ba 100644 --- a/src/Services/Trees/TreeViewGenerator.php +++ b/src/Services/Trees/TreeViewGenerator.php @@ -34,9 +34,9 @@ use App\Entity\ProjectSystem\Project; use App\Helpers\Trees\TreeViewNode; use App\Helpers\Trees\TreeViewNodeIterator; use App\Repository\NamedDBElementRepository; +use App\Repository\StructuralDBElementRepository; use App\Services\Cache\ElementCacheTagGenerator; use App\Services\Cache\UserCacheKeyGenerator; -use App\Services\ElementTypeNameGenerator; use App\Services\EntityURLGenerator; use App\Settings\BehaviorSettings\SidebarSettings; use Doctrine\ORM\EntityManagerInterface; @@ -67,7 +67,6 @@ class TreeViewGenerator protected TranslatorInterface $translator, private readonly UrlGeneratorInterface $router, private readonly SidebarSettings $sidebarSettings, - private readonly ElementTypeNameGenerator $elementTypeNameGenerator ) { $this->rootNodeEnabled = $this->sidebarSettings->rootNodeEnabled; $this->rootNodeExpandedByDefault = $this->sidebarSettings->rootNodeExpanded; @@ -213,7 +212,15 @@ class TreeViewGenerator protected function entityClassToRootNodeString(string $class): string { - return $this->elementTypeNameGenerator->typeLabelPlural($class); + return match ($class) { + Category::class => $this->translator->trans('category.labelp'), + StorageLocation::class => $this->translator->trans('storelocation.labelp'), + Footprint::class => $this->translator->trans('footprint.labelp'), + Manufacturer::class => $this->translator->trans('manufacturer.labelp'), + Supplier::class => $this->translator->trans('supplier.labelp'), + Project::class => $this->translator->trans('project.labelp'), + default => $this->translator->trans('tree.root_node.text'), + }; } protected function entityClassToRootNodeIcon(string $class): ?string diff --git a/src/Services/UserSystem/PermissionPresetsHelper.php b/src/Services/UserSystem/PermissionPresetsHelper.php index a3ed01b8..554da8b3 100644 --- a/src/Services/UserSystem/PermissionPresetsHelper.php +++ b/src/Services/UserSystem/PermissionPresetsHelper.php @@ -102,7 +102,6 @@ class PermissionPresetsHelper $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'attachment_types', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'currencies', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'measurement_units', PermissionData::ALLOW); - $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'part_custom_states', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'suppliers', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'projects', PermissionData::ALLOW); @@ -132,7 +131,6 @@ class PermissionPresetsHelper $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'attachment_types', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'currencies', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'measurement_units', PermissionData::ALLOW, ['import']); - $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'part_custom_states', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'suppliers', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'projects', PermissionData::ALLOW, ['import']); diff --git a/src/Settings/AppSettings.php b/src/Settings/AppSettings.php index 14d9395e..1695638a 100644 --- a/src/Settings/AppSettings.php +++ b/src/Settings/AppSettings.php @@ -26,7 +26,7 @@ namespace App\Settings; use App\Settings\BehaviorSettings\BehaviorSettings; use App\Settings\InfoProviderSystem\InfoProviderSettings; use App\Settings\MiscSettings\MiscSettings; -use App\Settings\SystemSettings\SystemSettings; +use App\Settings\SystemSettings\AttachmentsSettings; use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsTrait; @@ -47,12 +47,6 @@ class AppSettings #[EmbeddedSettings()] public ?InfoProviderSettings $infoProviders = null; - #[EmbeddedSettings] - public ?SynonymSettings $synonyms = null; - #[EmbeddedSettings()] public ?MiscSettings $miscSettings = null; - - - -} +} \ No newline at end of file diff --git a/src/Settings/BehaviorSettings/BehaviorSettings.php b/src/Settings/BehaviorSettings/BehaviorSettings.php index 3053073f..1251a097 100644 --- a/src/Settings/BehaviorSettings/BehaviorSettings.php +++ b/src/Settings/BehaviorSettings/BehaviorSettings.php @@ -26,9 +26,8 @@ namespace App\Settings\BehaviorSettings; use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsTrait; -use Symfony\Component\Translation\TranslatableMessage as TM; -#[Settings(label: new TM("settings.behavior"))] +#[Settings] class BehaviorSettings { use SettingsTrait; @@ -41,4 +40,4 @@ class BehaviorSettings #[EmbeddedSettings] public ?PartInfoSettings $partInfo = null; -} +} \ No newline at end of file diff --git a/src/Settings/BehaviorSettings/PartTableColumns.php b/src/Settings/BehaviorSettings/PartTableColumns.php index c025c952..eea6ad86 100644 --- a/src/Settings/BehaviorSettings/PartTableColumns.php +++ b/src/Settings/BehaviorSettings/PartTableColumns.php @@ -46,7 +46,6 @@ enum PartTableColumns : string implements TranslatableInterface case FAVORITE = "favorite"; case MANUFACTURING_STATUS = "manufacturing_status"; case MPN = "manufacturer_product_number"; - case CUSTOM_PART_STATE = 'partCustomState'; case MASS = "mass"; case TAGS = "tags"; case ATTACHMENTS = "attachments"; @@ -64,4 +63,4 @@ enum PartTableColumns : string implements TranslatableInterface return $translator->trans($key, locale: $locale); } -} +} \ No newline at end of file diff --git a/src/Settings/BehaviorSettings/SidebarSettings.php b/src/Settings/BehaviorSettings/SidebarSettings.php index a1ff6985..1266fa47 100644 --- a/src/Settings/BehaviorSettings/SidebarSettings.php +++ b/src/Settings/BehaviorSettings/SidebarSettings.php @@ -73,11 +73,4 @@ class SidebarSettings */ #[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeRedirectsToNewEntity"))] public bool $rootNodeRedirectsToNewEntity = false; - - /** - * @var bool Whether to include child nodes in the data structure nodes table, or only show the selected node's parts. - */ - #[SettingsParameter(label: new TM("settings.behavior.sidebar.data_structure_nodes_table_include_children"), - description: new TM("settings.behavior.sidebar.data_structure_nodes_table_include_children.help"))] - public bool $dataStructureNodesTableIncludeChildren = true; -} +} \ No newline at end of file diff --git a/src/Settings/BehaviorSettings/TableSettings.php b/src/Settings/BehaviorSettings/TableSettings.php index b3421e41..b6964876 100644 --- a/src/Settings/BehaviorSettings/TableSettings.php +++ b/src/Settings/BehaviorSettings/TableSettings.php @@ -68,7 +68,7 @@ class TableSettings #[Assert\All([new Assert\Type(PartTableColumns::class)])] public array $partsDefaultColumns = [PartTableColumns::NAME, PartTableColumns::DESCRIPTION, PartTableColumns::CATEGORY, PartTableColumns::FOOTPRINT, PartTableColumns::MANUFACTURER, - PartTableColumns::LOCATION, PartTableColumns::AMOUNT, PartTableColumns::CUSTOM_PART_STATE]; + PartTableColumns::LOCATION, PartTableColumns::AMOUNT]; #[SettingsParameter(label: new TM("settings.behavior.table.preview_image_min_width"), formOptions: ['attr' => ['min' => 1, 'max' => 100]], diff --git a/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php b/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php index fac6aae9..03fff0bf 100644 --- a/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php +++ b/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php @@ -40,6 +40,6 @@ class InfoProviderGeneralSettings */ #[SettingsParameter(type: ArrayType::class, label: new TM("settings.ips.default_providers"), description: new TM("settings.ips.default_providers.help"), options: ['type' => StringType::class], - formType: ProviderSelectType::class, formOptions: ['input' => 'string', 'required' => false, 'empty_data' => []])] + formType: ProviderSelectType::class, formOptions: ['input' => 'string'])] public array $defaultSearchProviders = []; } diff --git a/src/Settings/InfoProviderSystem/InfoProviderSettings.php b/src/Settings/InfoProviderSystem/InfoProviderSettings.php index e6e258f5..c223bd88 100644 --- a/src/Settings/InfoProviderSystem/InfoProviderSettings.php +++ b/src/Settings/InfoProviderSystem/InfoProviderSettings.php @@ -27,9 +27,8 @@ use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsTrait; -use Symfony\Component\Translation\TranslatableMessage as TM; -#[Settings(label: new TM("settings.ips"))] +#[Settings()] class InfoProviderSettings { use SettingsTrait; diff --git a/src/Settings/MiscSettings/IpnSuggestSettings.php b/src/Settings/MiscSettings/IpnSuggestSettings.php deleted file mode 100644 index 2c2cb21a..00000000 --- a/src/Settings/MiscSettings/IpnSuggestSettings.php +++ /dev/null @@ -1,109 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\Settings\MiscSettings; - -use App\Settings\SettingsIcon; -use Jbtronics\SettingsBundle\Metadata\EnvVarMode; -use Jbtronics\SettingsBundle\ParameterTypes\StringType; -use Jbtronics\SettingsBundle\Settings\Settings; -use Jbtronics\SettingsBundle\Settings\SettingsParameter; -use Jbtronics\SettingsBundle\Settings\SettingsTrait; -use Symfony\Component\Translation\StaticMessage; -use Symfony\Component\Translation\TranslatableMessage as TM; -use Symfony\Component\Validator\Constraints as Assert; - -#[Settings(label: new TM("settings.misc.ipn_suggest"))] -#[SettingsIcon("fa-arrow-up-1-9")] -class IpnSuggestSettings -{ - use SettingsTrait; - - #[SettingsParameter( - label: new TM("settings.misc.ipn_suggest.regex"), - description: new TM("settings.misc.ipn_suggest.regex.help"), - options: ['type' => StringType::class], - formOptions: ['attr' => ['placeholder' => new StaticMessage( '^[A-Za-z0-9]{3,4}(?:-[A-Za-z0-9]{3,4})*-\d{4}$')]], - envVar: "IPN_SUGGEST_REGEX", envVarMode: EnvVarMode::OVERWRITE, - )] - public ?string $regex = null; - - #[SettingsParameter( - label: new TM("settings.misc.ipn_suggest.regex_help"), - description: new TM("settings.misc.ipn_suggest.regex_help_description"), - options: ['type' => StringType::class], - formOptions: ['attr' => ['placeholder' => new TM('settings.misc.ipn_suggest.regex.help.placeholder')]], - envVar: "IPN_SUGGEST_REGEX_HELP", envVarMode: EnvVarMode::OVERWRITE, - )] - public ?string $regexHelp = null; - - #[SettingsParameter( - label: new TM("settings.misc.ipn_suggest.autoAppendSuffix"), - envVar: "bool:IPN_AUTO_APPEND_SUFFIX", envVarMode: EnvVarMode::OVERWRITE, - )] - public bool $autoAppendSuffix = false; - - #[SettingsParameter(label: new TM("settings.misc.ipn_suggest.suggestPartDigits"), - description: new TM("settings.misc.ipn_suggest.suggestPartDigits.help"), - formOptions: ['attr' => ['min' => 1, 'max' => 8]], - envVar: "int:IPN_SUGGEST_PART_DIGITS", envVarMode: EnvVarMode::OVERWRITE - )] - #[Assert\Range(min: 1, max: 8)] - public int $suggestPartDigits = 4; - - #[SettingsParameter( - label: new TM("settings.misc.ipn_suggest.useDuplicateDescription"), - description: new TM("settings.misc.ipn_suggest.useDuplicateDescription.help"), - envVar: "bool:IPN_USE_DUPLICATE_DESCRIPTION", envVarMode: EnvVarMode::OVERWRITE, - )] - public bool $useDuplicateDescription = false; - - #[SettingsParameter( - label: new TM("settings.misc.ipn_suggest.fallbackPrefix"), - description: new TM("settings.misc.ipn_suggest.fallbackPrefix.help"), - options: ['type' => StringType::class], - )] - public string $fallbackPrefix = 'N.A.'; - - #[SettingsParameter( - label: new TM("settings.misc.ipn_suggest.numberSeparator"), - description: new TM("settings.misc.ipn_suggest.numberSeparator.help"), - options: ['type' => StringType::class], - )] - public string $numberSeparator = '-'; - - #[SettingsParameter( - label: new TM("settings.misc.ipn_suggest.categorySeparator"), - description: new TM("settings.misc.ipn_suggest.categorySeparator.help"), - options: ['type' => StringType::class], - )] - public string $categorySeparator = '-'; - - #[SettingsParameter( - label: new TM("settings.misc.ipn_suggest.globalPrefix"), - description: new TM("settings.misc.ipn_suggest.globalPrefix.help"), - options: ['type' => StringType::class], - )] - public ?string $globalPrefix = null; -} diff --git a/src/Settings/MiscSettings/MiscSettings.php b/src/Settings/MiscSettings/MiscSettings.php index 050dbcbc..b8a3a73f 100644 --- a/src/Settings/MiscSettings/MiscSettings.php +++ b/src/Settings/MiscSettings/MiscSettings.php @@ -25,9 +25,8 @@ namespace App\Settings\MiscSettings; use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; use Jbtronics\SettingsBundle\Settings\Settings; -use Symfony\Component\Translation\TranslatableMessage as TM; -#[Settings(label: new TM("settings.misc"))] +#[Settings] class MiscSettings { #[EmbeddedSettings] @@ -35,7 +34,4 @@ class MiscSettings #[EmbeddedSettings] public ?ExchangeRateSettings $exchangeRate = null; - - #[EmbeddedSettings] - public ?IpnSuggestSettings $ipnSuggestSettings = null; -} +} \ No newline at end of file diff --git a/src/Settings/SynonymSettings.php b/src/Settings/SynonymSettings.php deleted file mode 100644 index 25fc87e9..00000000 --- a/src/Settings/SynonymSettings.php +++ /dev/null @@ -1,116 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Settings; - -use App\Form\Settings\TypeSynonymsCollectionType; -use App\Services\ElementTypes; -use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; -use Jbtronics\SettingsBundle\ParameterTypes\SerializeType; -use Jbtronics\SettingsBundle\Settings\Settings; -use Jbtronics\SettingsBundle\Settings\SettingsParameter; -use Jbtronics\SettingsBundle\Settings\SettingsTrait; -use Symfony\Component\Translation\TranslatableMessage as TM; -use Symfony\Component\Validator\Constraints as Assert; - -#[Settings(label: new TM("settings.synonyms"), description: "settings.synonyms.help")] -#[SettingsIcon("fa-language")] -class SynonymSettings -{ - use SettingsTrait; - - #[SettingsParameter( - ArrayType::class, - label: new TM("settings.synonyms.type_synonyms"), - description: new TM("settings.synonyms.type_synonyms.help"), - options: ['type' => SerializeType::class], - formType: TypeSynonymsCollectionType::class, - formOptions: [ - 'required' => false, - ], - )] - #[Assert\Type('array')] - #[Assert\All([new Assert\Type('array')])] - /** - * @var array> $typeSynonyms - * An array of the form: [ - * 'category' => [ - * 'en' => ['singular' => 'Category', 'plural' => 'Categories'], - * 'de' => ['singular' => 'Kategorie', 'plural' => 'Kategorien'], - * ], - * 'manufacturer' => [ - * 'en' => ['singular' => 'Manufacturer', 'plural' =>'Manufacturers'], - * ], - * ] - */ - public array $typeSynonyms = []; - - /** - * Checks if there is any synonym defined for the given type (no matter which language). - * @param ElementTypes $type - * @return bool - */ - public function isSynonymDefinedForType(ElementTypes $type): bool - { - return isset($this->typeSynonyms[$type->value]) && count($this->typeSynonyms[$type->value]) > 0; - } - - /** - * Returns the singular synonym for the given type and locale, or null if none is defined. - * @param ElementTypes $type - * @param string $locale - * @return string|null - */ - public function getSingularSynonymForType(ElementTypes $type, string $locale): ?string - { - return $this->typeSynonyms[$type->value][$locale]['singular'] ?? null; - } - - /** - * Returns the plural synonym for the given type and locale, or null if none is defined. - * @param ElementTypes $type - * @param string|null $locale - * @return string|null - */ - public function getPluralSynonymForType(ElementTypes $type, ?string $locale): ?string - { - return $this->typeSynonyms[$type->value][$locale]['plural'] - ?? $this->typeSynonyms[$type->value][$locale]['singular'] - ?? null; - } - - /** - * Sets a synonym for the given type and locale. - * @param ElementTypes $type - * @param string $locale - * @param string $singular - * @param string $plural - * @return void - */ - public function setSynonymForType(ElementTypes $type, string $locale, string $singular, string $plural): void - { - $this->typeSynonyms[$type->value][$locale] = [ - 'singular' => $singular, - 'plural' => $plural, - ]; - } -} diff --git a/src/Settings/SystemSettings/SystemSettings.php b/src/Settings/SystemSettings.php similarity index 82% rename from src/Settings/SystemSettings/SystemSettings.php rename to src/Settings/SystemSettings.php index 8cbeb560..83d00afc 100644 --- a/src/Settings/SystemSettings/SystemSettings.php +++ b/src/Settings/SystemSettings.php @@ -21,20 +21,22 @@ declare(strict_types=1); -namespace App\Settings\SystemSettings; +namespace App\Settings; +use App\Settings\SystemSettings\AttachmentsSettings; +use App\Settings\SystemSettings\CustomizationSettings; +use App\Settings\SystemSettings\HistorySettings; +use App\Settings\SystemSettings\LocalizationSettings; +use App\Settings\SystemSettings\PrivacySettings; use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; use Jbtronics\SettingsBundle\Settings\Settings; -use Symfony\Component\Translation\TranslatableMessage as TM; -#[Settings(label: new TM("settings.system"))] +#[Settings] class SystemSettings { #[EmbeddedSettings()] public ?LocalizationSettings $localization = null; - - #[EmbeddedSettings()] public ?CustomizationSettings $customization = null; @@ -46,4 +48,4 @@ class SystemSettings #[EmbeddedSettings()] public ?HistorySettings $history = null; -} +} \ No newline at end of file diff --git a/src/Settings/SystemSettings/LocalizationSettings.php b/src/Settings/SystemSettings/LocalizationSettings.php index c6780c6c..434a4e69 100644 --- a/src/Settings/SystemSettings/LocalizationSettings.php +++ b/src/Settings/SystemSettings/LocalizationSettings.php @@ -23,12 +23,9 @@ declare(strict_types=1); namespace App\Settings\SystemSettings; -use App\Form\Settings\LanguageMenuEntriesType; use App\Form\Type\LocaleSelectType; use App\Settings\SettingsIcon; use Jbtronics\SettingsBundle\Metadata\EnvVarMode; -use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; -use Jbtronics\SettingsBundle\ParameterTypes\StringType; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsTrait; @@ -63,14 +60,4 @@ class LocalizationSettings envVar: "string:BASE_CURRENCY", envVarMode: EnvVarMode::OVERWRITE )] public string $baseCurrency = 'EUR'; - - #[SettingsParameter(type: ArrayType::class, - label: new TM("settings.system.localization.language_menu_entries"), - description: new TM("settings.system.localization.language_menu_entries.description"), - options: ['type' => StringType::class], - formType: LanguageMenuEntriesType::class, - formOptions: ['multiple' => true, 'required' => false, 'ordered' => true], - )] - #[Assert\All([new Assert\Locale()])] - public array $languageMenuEntries = []; -} +} \ No newline at end of file diff --git a/src/Twig/EntityExtension.php b/src/Twig/EntityExtension.php index 427a39b5..762ebb09 100644 --- a/src/Twig/EntityExtension.php +++ b/src/Twig/EntityExtension.php @@ -24,7 +24,6 @@ namespace App\Twig; use App\Entity\Attachments\Attachment; use App\Entity\Base\AbstractDBElement; -use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; @@ -76,8 +75,6 @@ final class EntityExtension extends AbstractExtension /* Gets a human readable label for the type of the given entity */ new TwigFunction('entity_type_label', fn(object|string $entity): string => $this->nameGenerator->getLocalizedTypeLabel($entity)), - new TwigFunction('type_label', fn(object|string $entity): string => $this->nameGenerator->typeLabel($entity)), - new TwigFunction('type_label_p', fn(object|string $entity): string => $this->nameGenerator->typeLabelPlural($entity)), ]; } @@ -118,7 +115,6 @@ final class EntityExtension extends AbstractExtension Currency::class => 'currency', MeasurementUnit::class => 'measurement_unit', LabelProfile::class => 'label_profile', - PartCustomState::class => 'part_custom_state', ]; foreach ($map as $class => $type) { diff --git a/src/Twig/FormatExtension.php b/src/Twig/FormatExtension.php index 46313aaf..76628ccd 100644 --- a/src/Twig/FormatExtension.php +++ b/src/Twig/FormatExtension.php @@ -82,7 +82,7 @@ final class FormatExtension extends AbstractExtension public function formatBytes(int $bytes, int $precision = 2): string { $size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB']; - $factor = (int) floor((strlen((string) $bytes) - 1) / 3); + $factor = floor((strlen((string) $bytes) - 1) / 3); //We use the real (10 based) SI prefix here return sprintf("%.{$precision}f", $bytes / (1000 ** $factor)) . ' ' . @$size[$factor]; } diff --git a/src/Twig/Sandbox/InheritanceSecurityPolicy.php b/src/Twig/Sandbox/InheritanceSecurityPolicy.php index 06ab3a1f..93e874e9 100644 --- a/src/Twig/Sandbox/InheritanceSecurityPolicy.php +++ b/src/Twig/Sandbox/InheritanceSecurityPolicy.php @@ -34,14 +34,9 @@ use function is_array; */ final class InheritanceSecurityPolicy implements SecurityPolicyInterface { - /** - * @var array - */ private array $allowedMethods; - public function __construct(private array $allowedTags = [], private array $allowedFilters = [], array $allowedMethods = [], - /** @var array */ - private array $allowedProperties = [], private array $allowedFunctions = []) + public function __construct(private array $allowedTags = [], private array $allowedFilters = [], array $allowedMethods = [], private array $allowedProperties = [], private array $allowedFunctions = []) { $this->setAllowedMethods($allowedMethods); } diff --git a/src/Validator/Constraints/UniquePartIpnConstraint.php b/src/Validator/Constraints/UniquePartIpnConstraint.php deleted file mode 100644 index ca32f9ef..00000000 --- a/src/Validator/Constraints/UniquePartIpnConstraint.php +++ /dev/null @@ -1,22 +0,0 @@ -entityManager = $entityManager; - $this->ipnSuggestSettings = $ipnSuggestSettings; - } - - public function validate($value, Constraint $constraint): void - { - if (null === $value || '' === $value) { - return; - } - - //If the autoAppendSuffix option is enabled, the IPN becomes unique automatically later - if ($this->ipnSuggestSettings->autoAppendSuffix) { - return; - } - - if (!$constraint instanceof UniquePartIpnConstraint) { - return; - } - - /** @var Part $currentPart */ - $currentPart = $this->context->getObject(); - - if (!$currentPart instanceof Part) { - return; - } - - $repository = $this->entityManager->getRepository(Part::class); - $existingParts = $repository->findBy(['ipn' => $value]); - - foreach ($existingParts as $existingPart) { - if ($currentPart->getId() !== $existingPart->getId()) { - $this->context->buildViolation($constraint->message) - ->setParameter('{{ value }}', $value) - ->addViolation(); - } - } - } -} diff --git a/symfony.lock b/symfony.lock index 3fce7c93..7c136b4b 100644 --- a/symfony.lock +++ b/symfony.lock @@ -305,7 +305,7 @@ "repo": "github.com/symfony/recipes", "branch": "main", "version": "11.1", - "ref": "1117deb12541f35793eec9fff7494d7aa12283fc" + "ref": "c6658a60fc9d594805370eacdf542c3d6b5c0869" }, "files": [ ".env.test", @@ -488,12 +488,12 @@ ] }, "symfony/framework-bundle": { - "version": "7.4", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "7.4", - "ref": "09f6e081c763a206802674ce0cb34a022f0ffc6d" + "version": "7.3", + "ref": "5a1497d539f691b96afd45ae397ce5fe30beb4b9" }, "files": [ ".editorconfig", @@ -550,15 +550,15 @@ "version": "v4.4.2" }, "symfony/monolog-bundle": { - "version": "3.11", + "version": "3.10", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", "version": "3.7", - "ref": "1b9efb10c54cb51c713a9391c9300ff8bceda459" + "ref": "aff23899c4440dd995907613c1dd709b6f59503f" }, "files": [ - "config/packages/monolog.yaml" + "./config/packages/monolog.yaml" ] }, "symfony/options-resolver": { @@ -592,6 +592,12 @@ "symfony/polyfill-intl-normalizer": { "version": "v1.17.0" }, + "symfony/polyfill-mbstring": { + "version": "v1.10.0" + }, + "symfony/polyfill-php80": { + "version": "v1.17.0" + }, "symfony/process": { "version": "v4.2.3" }, @@ -611,12 +617,12 @@ ] }, "symfony/routing": { - "version": "7.4", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "7.4", - "ref": "bc94c4fd86f393f3ab3947c18b830ea343e51ded" + "version": "7.0", + "ref": "21b72649d5622d8f7da329ffb5afb232a023619d" }, "files": [ "config/packages/routing.yaml", @@ -627,12 +633,12 @@ "version": "v5.3.4" }, "symfony/security-bundle": { - "version": "7.4", + "version": "6.4", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "7.4", - "ref": "c42fee7802181cdd50f61b8622715829f5d2335c" + "version": "6.4", + "ref": "2ae08430db28c8eb4476605894296c82a642028f" }, "files": [ "config/packages/security.yaml", @@ -655,18 +661,18 @@ "version": "v1.1.5" }, "symfony/stimulus-bundle": { - "version": "2.31", + "version": "2.27", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "2.24", - "ref": "3357f2fa6627b93658d8e13baa416b2a94a50c5f" + "version": "2.20", + "ref": "e058471c5502e549c1404ebdd510099107bb5549" }, "files": [ + "assets/bootstrap.js", "assets/controllers.json", "assets/controllers/csrf_protection_controller.js", - "assets/controllers/hello_controller.js", - "assets/stimulus_bootstrap.js" + "assets/controllers/hello_controller.js" ] }, "symfony/stopwatch": { @@ -779,12 +785,12 @@ ] }, "symfony/webpack-encore-bundle": { - "version": "2.3", + "version": "2.2", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", "version": "2.0", - "ref": "719f6110345acb6495e496601fc1b4977d7102b3" + "ref": "9ef5412a4a2a8415aca3a3f2b4edd3866aab9a19" }, "files": [ "assets/app.js", diff --git a/templates/_turbo_control.html.twig b/templates/_turbo_control.html.twig index 90ae8d9a..4c178038 100644 --- a/templates/_turbo_control.html.twig +++ b/templates/_turbo_control.html.twig @@ -22,14 +22,9 @@
    - {% set locales = settings_instance('localization').languageMenuEntries %} - {% if locales is empty %} - {% set locales = locale_menu %} - {% endif %} - - {% for locale in locales %} + {% for locale in locale_menu %} {{ locale|language_name }} ({{ locale|upper }}) {% endfor %} -
    + \ No newline at end of file diff --git a/templates/admin/attachment_type_admin.html.twig b/templates/admin/attachment_type_admin.html.twig index 87a053af..06a8c09d 100644 --- a/templates/admin/attachment_type_admin.html.twig +++ b/templates/admin/attachment_type_admin.html.twig @@ -15,4 +15,4 @@ {% block new_title %} {% trans %}attachment_type.new{% endtrans %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/base_admin.html.twig b/templates/admin/base_admin.html.twig index e9fc0fb9..51790c3c 100644 --- a/templates/admin/base_admin.html.twig +++ b/templates/admin/base_admin.html.twig @@ -86,7 +86,7 @@ - {% if entity.parameters is defined and showParameters == true %} + {% if entity.parameters is defined %} diff --git a/templates/admin/category_admin.html.twig b/templates/admin/category_admin.html.twig index 3ddc1472..5811640b 100644 --- a/templates/admin/category_admin.html.twig +++ b/templates/admin/category_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}category.labelp{% endtrans %} {% endblock %} {% block additional_pills %} @@ -31,7 +31,6 @@
    {{ form_row(form.partname_regex) }} {{ form_row(form.partname_hint) }} - {{ form_row(form.part_ipn_prefix) }}
    {{ form_row(form.default_description) }} {{ form_row(form.default_comment) }} @@ -61,4 +60,4 @@ {{ form_row(form.eda_info.kicad_symbol) }} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/currency_admin.html.twig b/templates/admin/currency_admin.html.twig index a5d59970..fbd3822c 100644 --- a/templates/admin/currency_admin.html.twig +++ b/templates/admin/currency_admin.html.twig @@ -3,7 +3,7 @@ {% import "vars.macro.twig" as vars %} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}currency.caption{% endtrans %} {% endblock %} {% block additional_controls %} @@ -41,4 +41,4 @@ {% block new_title %} {% trans %}currency.new{% endtrans %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/footprint_admin.html.twig b/templates/admin/footprint_admin.html.twig index 1ed39e9f..a2c3e4af 100644 --- a/templates/admin/footprint_admin.html.twig +++ b/templates/admin/footprint_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}footprint.labelp{% endtrans %} {% endblock %} {% block master_picture_block %} @@ -34,4 +34,4 @@ {{ form_row(form.eda_info.kicad_footprint) }} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/group_admin.html.twig b/templates/admin/group_admin.html.twig index 831c08d5..91975524 100644 --- a/templates/admin/group_admin.html.twig +++ b/templates/admin/group_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}group.edit.caption{% endtrans %} {% endblock %} @@ -27,4 +27,4 @@ {% block new_title %} {% trans %}group.new{% endtrans %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/label_profile_admin.html.twig b/templates/admin/label_profile_admin.html.twig index 8702b18a..10c2320f 100644 --- a/templates/admin/label_profile_admin.html.twig +++ b/templates/admin/label_profile_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}label_profile.caption{% endtrans %} {% endblock %} {% block additional_pills %} @@ -58,4 +58,4 @@ {% block new_title %} {% trans %}label_profile.new{% endtrans %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/manufacturer_admin.html.twig b/templates/admin/manufacturer_admin.html.twig index 4f8f1c2b..5db892c0 100644 --- a/templates/admin/manufacturer_admin.html.twig +++ b/templates/admin/manufacturer_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_company_admin.html.twig" %} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}manufacturer.caption{% endtrans %} {% endblock %} {% block edit_title %} @@ -10,4 +10,4 @@ {% block new_title %} {% trans %}manufacturer.new{% endtrans %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/measurement_unit_admin.html.twig b/templates/admin/measurement_unit_admin.html.twig index 14df7364..31748509 100644 --- a/templates/admin/measurement_unit_admin.html.twig +++ b/templates/admin/measurement_unit_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}measurement_unit.caption{% endtrans %} {% endblock %} {% block edit_title %} diff --git a/templates/admin/part_custom_state_admin.html.twig b/templates/admin/part_custom_state_admin.html.twig deleted file mode 100644 index 9d857646..00000000 --- a/templates/admin/part_custom_state_admin.html.twig +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "admin/base_admin.html.twig" %} - -{% block card_title %} - {{ type_label_p(entity) }} -{% endblock %} - -{% block edit_title %} - {% trans %}part_custom_state.edit{% endtrans %}: {{ entity.name }} -{% endblock %} - -{% block new_title %} - {% trans %}part_custom_state.new{% endtrans %} -{% endblock %} - diff --git a/templates/admin/project_admin.html.twig b/templates/admin/project_admin.html.twig index d199b63c..1a995069 100644 --- a/templates/admin/project_admin.html.twig +++ b/templates/admin/project_admin.html.twig @@ -3,7 +3,7 @@ {# @var entity App\Entity\ProjectSystem\Project #} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}project.caption{% endtrans %} {% endblock %} {% block edit_title %} @@ -59,4 +59,4 @@ {% endif %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/storelocation_admin.html.twig b/templates/admin/storelocation_admin.html.twig index b01ecc73..c93339dc 100644 --- a/templates/admin/storelocation_admin.html.twig +++ b/templates/admin/storelocation_admin.html.twig @@ -2,7 +2,7 @@ {% import "label_system/dropdown_macro.html.twig" as dropdown %} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}storelocation.labelp{% endtrans %} {% endblock %} {% block additional_controls %} @@ -38,4 +38,4 @@ {% block new_title %} {% trans %}storelocation.new{% endtrans %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/supplier_admin.html.twig b/templates/admin/supplier_admin.html.twig index d0ca85aa..ce38a5ca 100644 --- a/templates/admin/supplier_admin.html.twig +++ b/templates/admin/supplier_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_company_admin.html.twig" %} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}supplier.caption{% endtrans %} {% endblock %} {% block additional_panes %} @@ -19,4 +19,4 @@ {% block new_title %} {% trans %}supplier.new{% endtrans %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin/user_admin.html.twig b/templates/admin/user_admin.html.twig index 9b241e56..772b42d9 100644 --- a/templates/admin/user_admin.html.twig +++ b/templates/admin/user_admin.html.twig @@ -5,7 +5,7 @@ {# @var entity \App\Entity\UserSystem\User #} {% block card_title %} - {{ type_label_p(entity) }} + {% trans %}user.edit.caption{% endtrans %} {% endblock %} {% block comment %}{% endblock %} @@ -111,4 +111,4 @@ {% block preview_picture %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/base.html.twig b/templates/base.html.twig index 2db726ee..bb9844fa 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -55,7 +55,7 @@ {{ encore_entry_link_tags('app') }} {% set table_settings = settings_instance('table') %} -